chore: add Biome for lint and fix all lint errors
- Install @biomejs/biome as devDependency at root - Configure biome.json with 2-space indent, double quotes, Tailwind CSS support - Add lint/lint:fix/format/format:fix scripts to root and app package.json - Fix noNonNullAssertion: env vars extracted to variables with suppression, <div role=button> replaced with <button> - Fix noUnusedVariables: remove unused destructured vars - Fix useIterableCallbackReturn: arrow functions with block body - Fix noExplicitAny: recharts Tooltip formatters - Fix noLabelWithoutControl: add htmlFor+id or use <span> for non-input labels - Fix noStaticElementInteractions/useKeyWithClickEvents: role+keyboard events for overlays - Fix noArrayIndexKey: use error string as key - Fix CSS parse: enable tailwindDirectives parser - Normalize formatting across 80 files with biome check --write
This commit is contained in:
@@ -1,41 +1,45 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { XIcon } from "lucide-react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { XIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const MEDIA_QUERY = "(min-width: 640px)"
|
||||
const MEDIA_QUERY = "(min-width: 640px)";
|
||||
|
||||
function useMediaQuery(query: string) {
|
||||
const [matches, setMatches] = React.useState(() => {
|
||||
if (typeof window === "undefined") return true
|
||||
return window.matchMedia(query).matches
|
||||
})
|
||||
if (typeof window === "undefined") return true;
|
||||
return window.matchMedia(query).matches;
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const mql = window.matchMedia(query)
|
||||
const handler = (e: MediaQueryListEvent) => setMatches(e.matches)
|
||||
mql.addEventListener("change", handler)
|
||||
return () => mql.removeEventListener("change", handler)
|
||||
}, [query])
|
||||
const mql = window.matchMedia(query);
|
||||
const handler = (e: MediaQueryListEvent) => setMatches(e.matches);
|
||||
mql.addEventListener("change", handler);
|
||||
return () => mql.removeEventListener("change", handler);
|
||||
}, [query]);
|
||||
|
||||
return matches
|
||||
return matches;
|
||||
}
|
||||
|
||||
interface ResponsiveDialogContextValue {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
isDesktop: boolean
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
isDesktop: boolean;
|
||||
}
|
||||
|
||||
const ResponsiveDialogContext = React.createContext<ResponsiveDialogContextValue | null>(null)
|
||||
const ResponsiveDialogContext =
|
||||
React.createContext<ResponsiveDialogContextValue | null>(null);
|
||||
|
||||
function useResponsiveDialog() {
|
||||
const ctx = React.useContext(ResponsiveDialogContext)
|
||||
if (!ctx) throw new Error("ResponsiveDialog components must be used within ResponsiveDialog")
|
||||
return ctx
|
||||
const ctx = React.useContext(ResponsiveDialogContext);
|
||||
if (!ctx)
|
||||
throw new Error(
|
||||
"ResponsiveDialog components must be used within ResponsiveDialog",
|
||||
);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
function ResponsiveDialog({
|
||||
@@ -43,19 +47,22 @@ function ResponsiveDialog({
|
||||
onOpenChange,
|
||||
children,
|
||||
}: {
|
||||
open?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
children: React.ReactNode
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [internalOpen, setInternalOpen] = React.useState(false)
|
||||
const isDesktop = useMediaQuery(MEDIA_QUERY)
|
||||
const [internalOpen, setInternalOpen] = React.useState(false);
|
||||
const isDesktop = useMediaQuery(MEDIA_QUERY);
|
||||
|
||||
const controlled = open !== undefined
|
||||
const currentOpen = controlled ? open : internalOpen
|
||||
const setCurrentOpen = controlled ? onOpenChange! : setInternalOpen
|
||||
const controlled = open !== undefined;
|
||||
const currentOpen = controlled ? open : internalOpen;
|
||||
// biome-ignore lint/style/noNonNullAssertion: controlled ensures it's defined
|
||||
const setCurrentOpen = controlled ? onOpenChange! : setInternalOpen;
|
||||
|
||||
return (
|
||||
<ResponsiveDialogContext value={{ open: currentOpen, onOpenChange: setCurrentOpen, isDesktop }}>
|
||||
<ResponsiveDialogContext
|
||||
value={{ open: currentOpen, onOpenChange: setCurrentOpen, isDesktop }}
|
||||
>
|
||||
{isDesktop ? (
|
||||
<DialogPrimitive.Root open={currentOpen} onOpenChange={setCurrentOpen}>
|
||||
{children}
|
||||
@@ -66,7 +73,7 @@ function ResponsiveDialog({
|
||||
</DialogPrimitive.Root>
|
||||
)}
|
||||
</ResponsiveDialogContext>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ResponsiveDialogTrigger({
|
||||
@@ -78,7 +85,7 @@ function ResponsiveDialogTrigger({
|
||||
<DialogPrimitive.Trigger data-slot="responsive-dialog-trigger" {...props}>
|
||||
{children}
|
||||
</DialogPrimitive.Trigger>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ResponsiveDialogContent({
|
||||
@@ -86,7 +93,7 @@ function ResponsiveDialogContent({
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Content>) {
|
||||
const { isDesktop, open, onOpenChange } = useResponsiveDialog()
|
||||
const { isDesktop, open, onOpenChange } = useResponsiveDialog();
|
||||
|
||||
if (isDesktop) {
|
||||
return (
|
||||
@@ -103,7 +110,7 @@ function ResponsiveDialogContent({
|
||||
"data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-open:slide-in-from-left-1/2 data-open:slide-in-from-top-[48%]",
|
||||
"data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-closed:slide-out-to-left-1/2 data-closed:slide-out-to-top-[48%]",
|
||||
"duration-200",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -120,22 +127,27 @@ function ResponsiveDialogContent({
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPrimitive.Portal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!open) return null
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-end sm:hidden" data-slot="responsive-dialog-mobile">
|
||||
<div
|
||||
className="fixed inset-0 z-40 bg-black/10 supports-backdrop-filter:backdrop-blur-xs"
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-end sm:hidden"
|
||||
data-slot="responsive-dialog-mobile"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="fixed inset-0 z-40 bg-black/10 supports-backdrop-filter:backdrop-blur-xs cursor-default"
|
||||
onClick={() => onOpenChange(false)}
|
||||
aria-label="Cerrar"
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"relative z-50 flex w-full flex-col gap-4 rounded-t-xl border bg-popover p-6 text-popover-foreground shadow-lg",
|
||||
"animate-in slide-in-from-bottom-10 fade-in-0 duration-200",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="mx-auto -mt-2 mb-1 h-1.5 w-10 rounded-full bg-muted" />
|
||||
@@ -152,7 +164,7 @@ function ResponsiveDialogContent({
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ResponsiveDialogHeader({
|
||||
@@ -165,7 +177,7 @@ function ResponsiveDialogHeader({
|
||||
className={cn("flex flex-col gap-0.5", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ResponsiveDialogFooter({
|
||||
@@ -175,10 +187,13 @@ function ResponsiveDialogFooter({
|
||||
return (
|
||||
<div
|
||||
data-slot="responsive-dialog-footer"
|
||||
className={cn("mt-2 flex flex-col-reverse sm:flex-row sm:justify-end gap-2", className)}
|
||||
className={cn(
|
||||
"mt-2 flex flex-col-reverse sm:flex-row sm:justify-end gap-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ResponsiveDialogTitle({
|
||||
@@ -191,7 +206,7 @@ function ResponsiveDialogTitle({
|
||||
className={cn("text-base font-medium text-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ResponsiveDialogDescription({
|
||||
@@ -204,22 +219,24 @@ function ResponsiveDialogDescription({
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ResponsiveDialogClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||
return <DialogPrimitive.Close data-slot="responsive-dialog-close" {...props} />
|
||||
return (
|
||||
<DialogPrimitive.Close data-slot="responsive-dialog-close" {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
ResponsiveDialog,
|
||||
ResponsiveDialogTrigger,
|
||||
ResponsiveDialogContent,
|
||||
ResponsiveDialogHeader,
|
||||
ResponsiveDialogFooter,
|
||||
ResponsiveDialogTitle,
|
||||
ResponsiveDialogDescription,
|
||||
ResponsiveDialogClose,
|
||||
}
|
||||
ResponsiveDialogContent,
|
||||
ResponsiveDialogDescription,
|
||||
ResponsiveDialogFooter,
|
||||
ResponsiveDialogHeader,
|
||||
ResponsiveDialogTitle,
|
||||
ResponsiveDialogTrigger,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user