frontend indentation set to 2

This commit is contained in:
Oliver Bryan
2026-01-21 17:47:04 +00:00
parent 70504b3056
commit 5a5e40659c
117 changed files with 7548 additions and 7785 deletions

View File

@@ -5,73 +5,73 @@ import { Link } from "react-router-dom";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline: "bg-transparent border dark:hover:bg-muted/40",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
dummy: "",
},
size: {
none: "",
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
"cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline: "bg-transparent border dark:hover:bg-muted/40",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
dummy: "",
},
size: {
none: "",
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
function Button({
className,
variant = "default",
size = "default",
asChild = false,
linkTo,
...props
className,
variant = "default",
size = "default",
asChild = false,
linkTo,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
linkTo?: string;
}) {
const Comp = asChild ? Slot : "button";
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
linkTo?: string;
}) {
const Comp = asChild ? Slot : "button";
return (
<>
{linkTo ? (
<Link to={linkTo}>
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
</Link>
) : (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)}
</>
);
return (
<>
{linkTo ? (
<Link to={linkTo}>
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
</Link>
) : (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)}
</>
);
}
export { Button, buttonVariants };

View File

@@ -6,213 +6,202 @@ import Icon from "@/components/ui/icon";
import { cn } from "@/lib/utils";
function Calendar({
className,
classNames,
showOutsideDays = true,
captionLayout = "label",
buttonVariant = "ghost",
formatters,
components,
sprints,
isEnd,
...props
className,
classNames,
showOutsideDays = true,
captionLayout = "label",
buttonVariant = "ghost",
formatters,
components,
sprints,
isEnd,
...props
}: React.ComponentProps<typeof DayPicker> & {
buttonVariant?: React.ComponentProps<typeof Button>["variant"];
sprints?: SprintRecord[];
isEnd?: boolean;
buttonVariant?: React.ComponentProps<typeof Button>["variant"];
sprints?: SprintRecord[];
isEnd?: boolean;
}) {
const defaultClassNames = getDefaultClassNames();
const defaultClassNames = getDefaultClassNames();
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn(
"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className,
)}
captionLayout={captionLayout}
formatters={{
formatMonthDropdown: (date) => date.toLocaleString("default", { month: "short" }),
...formatters,
}}
classNames={{
root: cn("w-fit", defaultClassNames.root),
months: cn("flex gap-4 flex-col md:flex-row relative", defaultClassNames.months),
month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
nav: cn(
"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
defaultClassNames.nav,
),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
defaultClassNames.button_previous,
),
button_next: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
defaultClassNames.button_next,
),
month_caption: cn(
"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
defaultClassNames.month_caption,
),
dropdowns: cn(
"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
defaultClassNames.dropdowns,
),
dropdown_root: cn(
"relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px]",
defaultClassNames.dropdown_root,
),
dropdown: cn("absolute bg-popover inset-0 opacity-0", defaultClassNames.dropdown),
caption_label: cn(
"select-none font-medium",
captionLayout === "label"
? "text-sm"
: "pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
defaultClassNames.caption_label,
),
table: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn(
"text-muted-foreground flex-1 font-normal text-[0.8rem] select-none",
defaultClassNames.weekday,
),
week: cn("flex w-full mt-2", defaultClassNames.week),
week_number_header: cn("select-none w-(--cell-size)", defaultClassNames.week_number_header),
week_number: cn(
"text-[0.8rem] select-none text-muted-foreground",
defaultClassNames.week_number,
),
day: cn(
"relative w-full h-full p-0 text-center group/day aspect-square select-none",
defaultClassNames.day,
),
range_start: cn("bg-accent", defaultClassNames.range_start),
range_middle: cn(defaultClassNames.range_middle),
range_end: cn("bg-accent", defaultClassNames.range_end),
today: cn("border border-dashed -m-px", defaultClassNames.today),
outside: cn(
"text-muted-foreground aria-selected:text-muted-foreground",
defaultClassNames.outside,
),
disabled: cn("text-muted-foreground opacity-50", defaultClassNames.disabled),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames,
}}
components={{
Root: ({ className, rootRef, ...props }) => {
return <div data-slot="calendar" ref={rootRef} className={cn(className)} {...props} />;
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") {
return <Icon icon="chevronLeftIcon" className={cn("size-4", className)} {...props} />;
}
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn(
"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className,
)}
captionLayout={captionLayout}
formatters={{
formatMonthDropdown: (date) => date.toLocaleString("default", { month: "short" }),
...formatters,
}}
classNames={{
root: cn("w-fit", defaultClassNames.root),
months: cn("flex gap-4 flex-col md:flex-row relative", defaultClassNames.months),
month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
nav: cn(
"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
defaultClassNames.nav,
),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
defaultClassNames.button_previous,
),
button_next: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
defaultClassNames.button_next,
),
month_caption: cn(
"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
defaultClassNames.month_caption,
),
dropdowns: cn(
"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
defaultClassNames.dropdowns,
),
dropdown_root: cn(
"relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px]",
defaultClassNames.dropdown_root,
),
dropdown: cn("absolute bg-popover inset-0 opacity-0", defaultClassNames.dropdown),
caption_label: cn(
"select-none font-medium",
captionLayout === "label"
? "text-sm"
: "pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
defaultClassNames.caption_label,
),
table: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn(
"text-muted-foreground flex-1 font-normal text-[0.8rem] select-none",
defaultClassNames.weekday,
),
week: cn("flex w-full mt-2", defaultClassNames.week),
week_number_header: cn("select-none w-(--cell-size)", defaultClassNames.week_number_header),
week_number: cn("text-[0.8rem] select-none text-muted-foreground", defaultClassNames.week_number),
day: cn(
"relative w-full h-full p-0 text-center group/day aspect-square select-none",
defaultClassNames.day,
),
range_start: cn("bg-accent", defaultClassNames.range_start),
range_middle: cn(defaultClassNames.range_middle),
range_end: cn("bg-accent", defaultClassNames.range_end),
today: cn("border border-dashed -m-px", defaultClassNames.today),
outside: cn("text-muted-foreground aria-selected:text-muted-foreground", defaultClassNames.outside),
disabled: cn("text-muted-foreground opacity-50", defaultClassNames.disabled),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames,
}}
components={{
Root: ({ className, rootRef, ...props }) => {
return <div data-slot="calendar" ref={rootRef} className={cn(className)} {...props} />;
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") {
return <Icon icon="chevronLeftIcon" className={cn("size-4", className)} {...props} />;
}
if (orientation === "right") {
return (
<Icon icon="chevronRightIcon" className={cn("size-4", className)} {...props} />
);
}
if (orientation === "right") {
return <Icon icon="chevronRightIcon" className={cn("size-4", className)} {...props} />;
}
return <Icon icon="chevronDownIcon" className={cn("size-4", className)} {...props} />;
},
DayButton: (props) => <CalendarDayButton {...props} sprints={sprints} isEnd={isEnd} />,
WeekNumber: ({ children, ...props }) => {
return (
<td {...props}>
<div className="flex size-(--cell-size) items-center justify-center text-center">
{children}
</div>
</td>
);
},
...components,
}}
{...props}
/>
);
return <Icon icon="chevronDownIcon" className={cn("size-4", className)} {...props} />;
},
DayButton: (props) => <CalendarDayButton {...props} sprints={sprints} isEnd={isEnd} />,
WeekNumber: ({ children, ...props }) => {
return (
<td {...props}>
<div className="flex size-(--cell-size) items-center justify-center text-center">
{children}
</div>
</td>
);
},
...components,
}}
{...props}
/>
);
}
function CalendarDayButton({
className,
day,
modifiers,
sprints,
style,
disabled,
isEnd,
...props
className,
day,
modifiers,
sprints,
style,
disabled,
isEnd,
...props
}: React.ComponentProps<typeof DayButton> & { sprints?: SprintRecord[]; isEnd?: boolean }) {
const defaultClassNames = getDefaultClassNames();
const defaultClassNames = getDefaultClassNames();
const ref = React.useRef<HTMLButtonElement>(null);
React.useEffect(() => {
if (modifiers.focused) ref.current?.focus();
}, [modifiers.focused]);
const ref = React.useRef<HTMLButtonElement>(null);
React.useEffect(() => {
if (modifiers.focused) ref.current?.focus();
}, [modifiers.focused]);
let isDisabled = false;
let sprint: SprintRecord | null = null;
let isDisabled = false;
let sprint: SprintRecord | null = null;
for (const entry of sprints || []) {
if (day.date >= new Date(entry.startDate) && day.date <= new Date(entry.endDate)) {
isDisabled = true;
sprint = entry;
}
for (const entry of sprints || []) {
if (day.date >= new Date(entry.startDate) && day.date <= new Date(entry.endDate)) {
isDisabled = true;
sprint = entry;
}
}
return (
<Button
ref={ref}
variant="ghost"
size="icon"
data-day={day.date.toLocaleDateString()}
data-selected-single={
modifiers.selected &&
!modifiers.range_start &&
!modifiers.range_end &&
!modifiers.range_middle
}
data-range-start={modifiers.range_start}
data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle}
className={cn(
"flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal",
"[&>span]:text-xs [&>span]:opacity-70",
!sprint?.color && "hover:bg-primary/90 hover:text-foreground",
"data-[selected-single=true]:!bg-foreground data-[selected-single=true]:!text-background data-[selected-single=true]:hover:!bg-foreground/90",
"data-[range-start=true]:!bg-foreground data-[range-start=true]:!text-background",
"data-[range-middle=true]:!bg-foreground data-[range-middle=true]:!text-background",
"data-[range-end=true]:!bg-foreground data-[range-end=true]:!text-background",
sprint?.color && "border-t border-b !border-(--sprint-color) !bg-(--sprint-color)/5",
defaultClassNames.day,
className,
)}
style={
{
...style,
"--sprint-color": sprint?.color ? sprint.color : null,
borderLeft:
sprint && day.date.getUTCDate() === new Date(sprint.startDate).getUTCDate()
? `1px solid ${sprint?.color}`
: day.date.getDay() === 0 // sunday (left side)
? `1px dashed ${sprint?.color}`
: `0px`,
borderRight:
sprint && day.date.getUTCDate() === new Date(sprint.endDate).getUTCDate()
? `1px solid ${sprint?.color}`
: day.date.getDay() === 6 // saturday (right side)
? `1px dashed ${sprint?.color}`
: `0px`,
} as React.CSSProperties
}
disabled={isDisabled || disabled}
{...props}
/>
);
return (
<Button
ref={ref}
variant="ghost"
size="icon"
data-day={day.date.toLocaleDateString()}
data-selected-single={
modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle
}
data-range-start={modifiers.range_start}
data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle}
className={cn(
"flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal",
"[&>span]:text-xs [&>span]:opacity-70",
!sprint?.color && "hover:bg-primary/90 hover:text-foreground",
"data-[selected-single=true]:!bg-foreground data-[selected-single=true]:!text-background data-[selected-single=true]:hover:!bg-foreground/90",
"data-[range-start=true]:!bg-foreground data-[range-start=true]:!text-background",
"data-[range-middle=true]:!bg-foreground data-[range-middle=true]:!text-background",
"data-[range-end=true]:!bg-foreground data-[range-end=true]:!text-background",
sprint?.color && "border-t border-b !border-(--sprint-color) !bg-(--sprint-color)/5",
defaultClassNames.day,
className,
)}
style={
{
...style,
"--sprint-color": sprint?.color ? sprint.color : null,
borderLeft:
sprint && day.date.getUTCDate() === new Date(sprint.startDate).getUTCDate()
? `1px solid ${sprint?.color}`
: day.date.getDay() === 0 // sunday (left side)
? `1px dashed ${sprint?.color}`
: `0px`,
borderRight:
sprint && day.date.getUTCDate() === new Date(sprint.endDate).getUTCDate()
? `1px solid ${sprint?.color}`
: day.date.getDay() === 6 // saturday (right side)
? `1px dashed ${sprint?.color}`
: `0px`,
} as React.CSSProperties
}
disabled={isDisabled || disabled}
{...props}
/>
);
}
export { Calendar, CalendarDayButton };

View File

@@ -5,39 +5,35 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
import { cn } from "@/lib/utils";
export default function ColourPicker({
colour,
onChange,
asChild = true,
className,
colour,
onChange,
asChild = true,
className,
}: {
colour: string;
onChange: (value: string) => void;
asChild?: boolean;
className?: string;
colour: string;
onChange: (value: string) => void;
asChild?: boolean;
className?: string;
}) {
return (
<Popover>
<PopoverTrigger asChild={asChild}>
<Button
type="button"
className={cn("w-8 h-8", className)}
style={{ backgroundColor: colour }}
/>
</PopoverTrigger>
<PopoverContent className="w-fit grid gap-2 p-2" align="start" side={"top"}>
<HexColorPicker color={colour} onChange={onChange} className="p-0 m-0" />
<div className="border w-[92px] inline-flex items-center">
<Input
value={colour.slice(1).toUpperCase()}
onChange={(e) => onChange(`#${e.target.value}`)}
spellCheck={false}
className="flex-1 border-transparent h-fit pl-0 mx-0"
maxLength={6}
showCounter={false}
showHashPrefix
/>
</div>
</PopoverContent>
</Popover>
);
return (
<Popover>
<PopoverTrigger asChild={asChild}>
<Button type="button" className={cn("w-8 h-8", className)} style={{ backgroundColor: colour }} />
</PopoverTrigger>
<PopoverContent className="w-fit grid gap-2 p-2" align="start" side={"top"}>
<HexColorPicker color={colour} onChange={onChange} className="p-0 m-0" />
<div className="border w-[92px] inline-flex items-center">
<Input
value={colour.slice(1).toUpperCase()}
onChange={(e) => onChange(`#${e.target.value}`)}
spellCheck={false}
className="flex-1 border-transparent h-fit pl-0 mx-0"
maxLength={6}
showCounter={false}
showHashPrefix
/>
</div>
</PopoverContent>
</Popover>
);
}

View File

@@ -3,55 +3,55 @@ import { Button } from "@/components/ui/button";
import { Dialog, DialogClose, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
export function ConfirmDialog({
open,
onOpenChange,
onConfirm,
title,
message,
processingText = "Processing...",
confirmText = "Confirm",
cancelText = "Cancel",
variant = "default",
open,
onOpenChange,
onConfirm,
title,
message,
processingText = "Processing...",
confirmText = "Confirm",
cancelText = "Cancel",
variant = "default",
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
onConfirm: () => void;
title: string;
message: string;
processingText?: string;
confirmText?: string;
cancelText?: string;
variant?: "default" | "destructive";
open: boolean;
onOpenChange: (open: boolean) => void;
onConfirm: () => void;
title: string;
message: string;
processingText?: string;
confirmText?: string;
cancelText?: string;
variant?: "default" | "destructive";
}) {
const [submitting, setSubmitting] = useState(false);
const [submitting, setSubmitting] = useState(false);
const handleConfirm = async () => {
setSubmitting(true);
try {
await onConfirm();
} finally {
setSubmitting(false);
}
};
const handleConfirm = async () => {
setSubmitting(true);
try {
await onConfirm();
} finally {
setSubmitting(false);
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
</DialogHeader>
<p className="text-sm text-muted-foreground">{message}</p>
<div className="flex gap-2 justify-end mt-4">
<DialogClose asChild>
<Button variant="outline" type="button">
{cancelText}
</Button>
</DialogClose>
<Button variant={variant} onClick={handleConfirm} disabled={submitting}>
{submitting ? processingText : confirmText}
</Button>
</div>
</DialogContent>
</Dialog>
);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
</DialogHeader>
<p className="text-sm text-muted-foreground">{message}</p>
<div className="flex gap-2 justify-end mt-4">
<DialogClose asChild>
<Button variant="outline" type="button">
{cancelText}
</Button>
</DialogClose>
<Button variant={variant} onClick={handleConfirm} disabled={submitting}>
{submitting ? processingText : confirmText}
</Button>
</div>
</DialogContent>
</Dialog>
);
}

View File

@@ -5,133 +5,133 @@ import Icon from "@/components/ui/icon";
import { cn } from "@/lib/utils";
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
}
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
}
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
}
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
}
function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn("fixed inset-0 z-50 bg-black/50", className)}
{...props}
/>
);
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn("fixed inset-0 z-50 bg-black/50", className)}
{...props}
/>
);
}
function DialogContent({
className,
children,
showCloseButton = true,
closePos = "top-right",
...props
className,
children,
showCloseButton = true,
closePos = "top-right",
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean;
closePos?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
showCloseButton?: boolean;
closePos?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
}) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"bg-background data-[state=closed]:zoom-out-95",
"data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%]",
"z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%]",
"gap-4 border p-4 shadow-lg duration-200 outline-none w-sm",
className,
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className={cn(
"cursor-pointer ring-offset-background focus:ring-ring",
"data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",
"absolute opacity-70",
closePos === "top-left" && "top-4 left-4",
closePos === "top-right" && "top-4 right-4",
closePos === "bottom-left" && "bottom-4 left-4",
closePos === "bottom-right" && "bottom-4 right-4",
"hover:opacity-100 focus:ring-2 focus:ring-offset-2 ",
"ocus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none",
"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
)}
>
<Icon icon="x" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
);
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"bg-background data-[state=closed]:zoom-out-95",
"data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%]",
"z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%]",
"gap-4 border p-4 shadow-lg duration-200 outline-none w-sm",
className,
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className={cn(
"cursor-pointer ring-offset-background focus:ring-ring",
"data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",
"absolute opacity-70",
closePos === "top-left" && "top-4 left-4",
closePos === "top-right" && "top-4 right-4",
closePos === "bottom-left" && "bottom-4 left-4",
closePos === "bottom-right" && "bottom-4 right-4",
"hover:opacity-100 focus:ring-2 focus:ring-offset-2 ",
"ocus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none",
"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
)}
>
<Icon icon="x" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
);
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
);
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
);
}
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-footer"
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
{...props}
/>
);
return (
<div
data-slot="dialog-footer"
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
{...props}
/>
);
}
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn("text-lg leading-none font-semibold", className)}
{...props}
/>
);
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn("text-lg leading-none font-semibold", className)}
{...props}
/>
);
}
function DialogDescription({
className,
...props
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
);
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
);
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
};

View File

@@ -4,286 +4,286 @@ import Icon from "@/components/ui/icon";
import { cn } from "@/lib/utils";
function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
}
function DropdownMenuPortal({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
}
function DropdownMenuTrigger({
className,
size = "default",
noStyle = false,
...props
className,
size = "default",
noStyle = false,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger> & {
size?: "sm" | "default";
noStyle?: boolean;
size?: "sm" | "default";
noStyle?: boolean;
}) {
return (
<DropdownMenuPrimitive.Trigger
data-slot="dropdown-menu-trigger"
data-size={size}
className={
noStyle
? cn(className)
: cn(
"cursor-pointer border data-[placeholder]:text-muted-foreground",
"[&_svg:not([class*='text-'])]:text-foreground",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
"aria-invalid:border-destructive dark:hover:bg-muted/40",
"flex w-fit items-center justify-between gap-2 border",
"bg-transparent px-3 py-2 text-sm whitespace-nowrap",
"shadow-xs outline-none disabled:cursor-not-allowed",
"disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8",
"*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex",
"*:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2",
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)
}
{...props}
/>
);
return (
<DropdownMenuPrimitive.Trigger
data-slot="dropdown-menu-trigger"
data-size={size}
className={
noStyle
? cn(className)
: cn(
"cursor-pointer border data-[placeholder]:text-muted-foreground",
"[&_svg:not([class*='text-'])]:text-foreground",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
"aria-invalid:border-destructive dark:hover:bg-muted/40",
"flex w-fit items-center justify-between gap-2 border",
"bg-transparent px-3 py-2 text-sm whitespace-nowrap",
"shadow-xs outline-none disabled:cursor-not-allowed",
"disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8",
"*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex",
"*:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2",
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)
}
{...props}
/>
);
}
function DropdownMenuContent({
className,
sideOffset = 0,
...props
className,
sideOffset = 0,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground",
"data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95",
"data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2",
"data-[side=left]:slide-in-from-right-2",
"data-[side=right]:slide-in-from-left-2",
"data-[side=top]:slide-in-from-bottom-2",
"z-50 max-h-(--radix-dropdown-menu-content-available-height)",
"min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin)",
"overflow-x-hidden overflow-y-auto border p-1 shadow-md",
"data-[side=bottom]:translate-y-1",
className,
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
);
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground",
"data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95",
"data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2",
"data-[side=left]:slide-in-from-right-2",
"data-[side=right]:slide-in-from-left-2",
"data-[side=top]:slide-in-from-bottom-2",
"z-50 max-h-(--radix-dropdown-menu-content-available-height)",
"min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin)",
"overflow-x-hidden overflow-y-auto border p-1 shadow-md",
"data-[side=bottom]:translate-y-1",
className,
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
);
}
function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
}
function DropdownMenuItem({
className,
inset,
variant = "default",
...props
className,
inset,
variant = "default",
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
variant?: "default" | "destructive";
inset?: boolean;
variant?: "default" | "destructive";
}) {
return (
<DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent/40 focus:text-accent-foreground",
"data-[variant=destructive]:text-destructive",
"data-[variant=destructive]:focus:bg-destructive/10",
"dark:data-[variant=destructive]:focus:bg-destructive/20",
"data-[variant=destructive]:focus:text-destructive",
"data-[variant=destructive]:*:[svg]:!text-destructive",
"[&_svg:not([class*='text-'])]:text-foreground relative",
"flex w-full cursor-pointer items-center gap-2",
"px-2 py-1 text-sm outline-hidden select-none",
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"data-[inset]:pl-8 [&_svg]:pointer-events-none",
"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
);
return (
<DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent/40 focus:text-accent-foreground",
"data-[variant=destructive]:text-destructive",
"data-[variant=destructive]:focus:bg-destructive/10",
"dark:data-[variant=destructive]:focus:bg-destructive/20",
"data-[variant=destructive]:focus:text-destructive",
"data-[variant=destructive]:*:[svg]:!text-destructive",
"[&_svg:not([class*='text-'])]:text-foreground relative",
"flex w-full cursor-pointer items-center gap-2",
"px-2 py-1 text-sm outline-hidden select-none",
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"data-[inset]:pl-8 [&_svg]:pointer-events-none",
"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
);
}
function DropdownMenuCheckboxItem({
className,
children,
checked,
...props
className,
children,
checked,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
return (
<DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative",
"flex cursor-default items-center gap-2 py-1.5 pr-2 pl-8",
"text-sm outline-hidden select-none data-[disabled]:pointer-events-none",
"data-[disabled]:opacity-50 [&_svg]:pointer-events-none",
"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Icon icon="checkIcon" className="size-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
);
return (
<DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative",
"flex cursor-default items-center gap-2 py-1.5 pr-2 pl-8",
"text-sm outline-hidden select-none data-[disabled]:pointer-events-none",
"data-[disabled]:opacity-50 [&_svg]:pointer-events-none",
"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Icon icon="checkIcon" className="size-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
);
}
function DropdownMenuRadioGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
}
function DropdownMenuRadioItem({
className,
children,
...props
className,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
return (
<DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative",
"flex cursor-default items-center gap-2 py-1.5 pr-2 pl-8",
"text-sm outline-hidden select-none data-[disabled]:pointer-events-none",
"data-[disabled]:opacity-50 [&_svg]:pointer-events-none",
"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Icon icon="circleIcon" className="size-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
);
return (
<DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative",
"flex cursor-default items-center gap-2 py-1.5 pr-2 pl-8",
"text-sm outline-hidden select-none data-[disabled]:pointer-events-none",
"data-[disabled]:opacity-50 [&_svg]:pointer-events-none",
"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Icon icon="circleIcon" className="size-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
);
}
function DropdownMenuLabel({
className,
inset,
...props
className,
inset,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
inset?: boolean;
}) {
return (
<DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn("px-2 py-0 text-sm font-medium data-[inset]:pl-8", className)}
{...props}
/>
);
return (
<DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn("px-2 py-0 text-sm font-medium data-[inset]:pl-8", className)}
{...props}
/>
);
}
function DropdownMenuSeparator({
className,
...props
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
);
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
);
}
function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
{...props}
/>
);
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
{...props}
/>
);
}
function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
}
function DropdownMenuSubTrigger({
className,
inset,
children,
...props
className,
inset,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
inset?: boolean;
}) {
return (
<DropdownMenuPrimitive.SubTrigger
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent",
"data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-foreground",
"flex cursor-default items-center gap-2 px-2 py-1.5 text-sm outline-hidden select-none",
"data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
{children}
<Icon icon="chevronRightIcon" className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
);
return (
<DropdownMenuPrimitive.SubTrigger
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent",
"data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-foreground",
"flex cursor-default items-center gap-2 px-2 py-1.5 text-sm outline-hidden select-none",
"data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
{children}
<Icon icon="chevronRightIcon" className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
);
}
function DropdownMenuSubContent({
className,
...props
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
return (
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in",
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
"data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95",
"data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2",
"data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
"data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem]",
"origin-(--radix-dropdown-menu-content-transform-origin)",
"overflow-hidden border p-1 shadow-lg",
className,
)}
{...props}
/>
);
return (
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in",
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
"data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95",
"data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2",
"data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
"data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem]",
"origin-(--radix-dropdown-menu-content-transform-origin)",
"overflow-hidden border p-1 shadow-lg",
className,
)}
{...props}
/>
);
}
export {
DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
};

View File

@@ -3,73 +3,73 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export function Field({
label,
value = "",
onChange = () => {},
validate,
hidden = false,
submitAttempted,
placeholder,
error,
tabIndex,
spellcheck,
maxLength,
showCounter = true,
label,
value = "",
onChange = () => {},
validate,
hidden = false,
submitAttempted,
placeholder,
error,
tabIndex,
spellcheck,
maxLength,
showCounter = true,
}: {
label: string;
value?: string;
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
validate?: (value: string) => string | undefined;
hidden?: boolean;
submitAttempted?: boolean;
placeholder?: string;
error?: string;
tabIndex?: number;
spellcheck?: boolean;
maxLength?: number;
showCounter?: boolean;
label: string;
value?: string;
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
validate?: (value: string) => string | undefined;
hidden?: boolean;
submitAttempted?: boolean;
placeholder?: string;
error?: string;
tabIndex?: number;
spellcheck?: boolean;
maxLength?: number;
showCounter?: boolean;
}) {
const [internalTouched, setInternalTouched] = useState(false);
const isTouched = submitAttempted || internalTouched;
const [internalTouched, setInternalTouched] = useState(false);
const isTouched = submitAttempted || internalTouched;
const invalidMessage = useMemo(() => {
if (!isTouched && value === "") {
return "";
}
return validate?.(value) ?? "";
}, [isTouched, validate, value]);
const invalidMessage = useMemo(() => {
if (!isTouched && value === "") {
return "";
}
return validate?.(value) ?? "";
}, [isTouched, validate, value]);
return (
<div className="flex flex-col gap-1 w-full">
<div className="flex items-end justify-between w-full">
<Label htmlFor={label} className="flex items-center text-sm">
{label}
</Label>
</div>
<Input
id={label}
placeholder={placeholder ?? label}
value={value}
onChange={(e) => {
onChange(e);
setInternalTouched(true);
}}
onBlur={() => setInternalTouched(true)}
name={label}
aria-invalid={error !== undefined || invalidMessage !== ""}
type={hidden ? "password" : "text"}
tabIndex={tabIndex}
spellCheck={spellcheck}
maxLength={maxLength}
showCounter={showCounter}
/>
<div className="flex items-end justify-end w-full text-xs mb-0 -mt-1">
{error || invalidMessage !== "" ? (
<Label className="text-destructive text-sm">{error ?? invalidMessage}</Label>
) : (
<Label className="opacity-0 text-sm">a</Label>
)}
</div>
</div>
);
return (
<div className="flex flex-col gap-1 w-full">
<div className="flex items-end justify-between w-full">
<Label htmlFor={label} className="flex items-center text-sm">
{label}
</Label>
</div>
<Input
id={label}
placeholder={placeholder ?? label}
value={value}
onChange={(e) => {
onChange(e);
setInternalTouched(true);
}}
onBlur={() => setInternalTouched(true)}
name={label}
aria-invalid={error !== undefined || invalidMessage !== ""}
type={hidden ? "password" : "text"}
tabIndex={tabIndex}
spellCheck={spellcheck}
maxLength={maxLength}
showCounter={showCounter}
/>
<div className="flex items-end justify-end w-full text-xs mb-0 -mt-1">
{error || invalidMessage !== "" ? (
<Label className="text-destructive text-sm">{error ?? invalidMessage}</Label>
) : (
<Label className="opacity-0 text-sm">a</Label>
)}
</div>
</div>
);
}

View File

@@ -3,42 +3,40 @@ import type * as React from "react";
import { cn } from "@/lib/utils";
const iconButtonVariants = cva(
"cursor-pointer inline-flex items-center justify-center [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none disabled:pointer-events-none disabled:opacity-50 focus-visible:ring-ring/50 focus-visible:ring-[3px]",
{
variants: {
variant: {
default: "hover:text-foreground/70",
destructive: "text-destructive hover:text-destructive/70",
yellow: "text-yellow-500 hover:text-yellow-500/70",
green: "text-green-500 hover:text-green-500/70",
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
outline: "border bg-transparent dark:hover:bg-muted/40",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
},
size: {
default: "w-6 h-6",
sm: "w-5 h-5",
md: "w-9 h-9",
lg: "w-10 h-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
"cursor-pointer inline-flex items-center justify-center [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none disabled:pointer-events-none disabled:opacity-50 focus-visible:ring-ring/50 focus-visible:ring-[3px]",
{
variants: {
variant: {
default: "hover:text-foreground/70",
destructive: "text-destructive hover:text-destructive/70",
yellow: "text-yellow-500 hover:text-yellow-500/70",
green: "text-green-500 hover:text-green-500/70",
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
outline: "border bg-transparent dark:hover:bg-muted/40",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
},
size: {
default: "w-6 h-6",
sm: "w-5 h-5",
md: "w-9 h-9",
lg: "w-10 h-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
function IconButton({
className,
variant,
size,
...props
className,
variant,
size,
...props
}: React.ComponentProps<"button"> & VariantProps<typeof iconButtonVariants>) {
return (
<button type="button" className={cn(iconButtonVariants({ variant, size, className }))} {...props} />
);
return <button type="button" className={cn(iconButtonVariants({ variant, size, className }))} {...props} />;
}
export { IconButton, iconButtonVariants };

View File

@@ -1,143 +1,143 @@
import {
Alert as PixelAlert,
Check as PixelCheck,
ChevronDown as PixelChevronDown,
ChevronLeft as PixelChevronLeft,
ChevronRight as PixelChevronRight,
ChevronUp as PixelChevronUp,
Circle as PixelCircle,
Clock as PixelClock,
Close as PixelClose,
Edit as PixelEdit,
Home as PixelHome,
InfoBox as PixelInfo,
Link as PixelLink,
Loader as PixelLoader,
Logout as PixelLogout,
Moon as PixelMoon,
MoreVertical as PixelMoreVertical,
NoteDelete as PixelNoteDelete,
Plus as PixelPlus,
Server as PixelServer,
Sun as PixelSun,
Trash as PixelTrash,
Undo as PixelUndo,
User as PixelUser,
ViewportWide as PixelViewportWide,
Alert as PixelAlert,
Check as PixelCheck,
ChevronDown as PixelChevronDown,
ChevronLeft as PixelChevronLeft,
ChevronRight as PixelChevronRight,
ChevronUp as PixelChevronUp,
Circle as PixelCircle,
Clock as PixelClock,
Close as PixelClose,
Edit as PixelEdit,
Home as PixelHome,
InfoBox as PixelInfo,
Link as PixelLink,
Loader as PixelLoader,
Logout as PixelLogout,
Moon as PixelMoon,
MoreVertical as PixelMoreVertical,
NoteDelete as PixelNoteDelete,
Plus as PixelPlus,
Server as PixelServer,
Sun as PixelSun,
Trash as PixelTrash,
Undo as PixelUndo,
User as PixelUser,
ViewportWide as PixelViewportWide,
} from "@nsmr/pixelart-react";
import {
CheckIcon as PhosphorCheck,
CheckCircleIcon as PhosphorCheckCircle,
CaretDownIcon as PhosphorChevronDown,
CaretLeftIcon as PhosphorChevronLeft,
CaretRightIcon as PhosphorChevronRight,
CaretUpIcon as PhosphorChevronUp,
CircleIcon as PhosphorCircle,
ClockIcon as PhosphorClock,
DotsSixVerticalIcon as PhosphorDotsSixVertical,
DotsThreeVerticalIcon as PhosphorDotsThreeVertical,
PencilSimpleIcon as PhosphorEdit,
HashIcon as PhosphorHash,
HashStraightIcon as PhosphorHashStraight,
HouseIcon as PhosphorHome,
InfoIcon as PhosphorInfo,
LinkIcon as PhosphorLink,
SpinnerGapIcon as PhosphorLoader,
SignOutIcon as PhosphorLogOut,
MoonIcon as PhosphorMoon,
OctagonIcon as PhosphorOctagon,
PlusIcon as PhosphorPlus,
QuestionIcon as PhosphorQuestion,
HardDrivesIcon as PhosphorServer,
SunIcon as PhosphorSun,
TrashIcon as PhosphorTrash,
ArrowCounterClockwiseIcon as PhosphorUndo,
UserIcon as PhosphorUser,
WarningIcon as PhosphorWarning,
XIcon as PhosphorX,
CheckIcon as PhosphorCheck,
CheckCircleIcon as PhosphorCheckCircle,
CaretDownIcon as PhosphorChevronDown,
CaretLeftIcon as PhosphorChevronLeft,
CaretRightIcon as PhosphorChevronRight,
CaretUpIcon as PhosphorChevronUp,
CircleIcon as PhosphorCircle,
ClockIcon as PhosphorClock,
DotsSixVerticalIcon as PhosphorDotsSixVertical,
DotsThreeVerticalIcon as PhosphorDotsThreeVertical,
PencilSimpleIcon as PhosphorEdit,
HashIcon as PhosphorHash,
HashStraightIcon as PhosphorHashStraight,
HouseIcon as PhosphorHome,
InfoIcon as PhosphorInfo,
LinkIcon as PhosphorLink,
SpinnerGapIcon as PhosphorLoader,
SignOutIcon as PhosphorLogOut,
MoonIcon as PhosphorMoon,
OctagonIcon as PhosphorOctagon,
PlusIcon as PhosphorPlus,
QuestionIcon as PhosphorQuestion,
HardDrivesIcon as PhosphorServer,
SunIcon as PhosphorSun,
TrashIcon as PhosphorTrash,
ArrowCounterClockwiseIcon as PhosphorUndo,
UserIcon as PhosphorUser,
WarningIcon as PhosphorWarning,
XIcon as PhosphorX,
} from "@phosphor-icons/react";
import type { IconStyle } from "@sprint/shared";
import {
AlertTriangle,
Check,
CheckIcon,
ChevronDown,
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
ChevronUp,
ChevronUpIcon,
CircleCheckIcon,
CircleIcon,
CircleQuestionMark,
Edit,
EllipsisVertical,
GripVerticalIcon,
Hash,
InfoIcon,
Link,
Loader,
Loader2Icon,
LogOut,
Home as LucideHome,
Moon,
OctagonXIcon,
Plus,
ServerIcon,
Sun,
Timer,
Trash,
TriangleAlertIcon,
Undo,
Undo2,
UserRound,
X,
AlertTriangle,
Check,
CheckIcon,
ChevronDown,
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
ChevronUp,
ChevronUpIcon,
CircleCheckIcon,
CircleIcon,
CircleQuestionMark,
Edit,
EllipsisVertical,
GripVerticalIcon,
Hash,
InfoIcon,
Link,
Loader,
Loader2Icon,
LogOut,
Home as LucideHome,
Moon,
OctagonXIcon,
Plus,
ServerIcon,
Sun,
Timer,
Trash,
TriangleAlertIcon,
Undo,
Undo2,
UserRound,
X,
} from "lucide-react";
import { useSessionSafe } from "@/components/session-provider";
const icons = {
alertTriangle: { lucide: AlertTriangle, pixel: PixelAlert, phosphor: PhosphorWarning },
check: { lucide: Check, pixel: PixelCheck, phosphor: PhosphorCheck },
checkIcon: { lucide: CheckIcon, pixel: PixelCheck, phosphor: PhosphorCheck },
chevronDown: { lucide: ChevronDown, pixel: PixelChevronDown, phosphor: PhosphorChevronDown },
chevronDownIcon: { lucide: ChevronDownIcon, pixel: PixelChevronDown, phosphor: PhosphorChevronDown },
chevronLeftIcon: { lucide: ChevronLeftIcon, pixel: PixelChevronLeft, phosphor: PhosphorChevronLeft },
chevronRightIcon: { lucide: ChevronRightIcon, pixel: PixelChevronRight, phosphor: PhosphorChevronRight },
chevronUp: { lucide: ChevronUp, pixel: PixelChevronUp, phosphor: PhosphorChevronUp },
chevronUpIcon: { lucide: ChevronUpIcon, pixel: PixelChevronUp, phosphor: PhosphorChevronUp },
circleCheckIcon: { lucide: CircleCheckIcon, pixel: PixelCheck, phosphor: PhosphorCheckCircle },
circleIcon: { lucide: CircleIcon, pixel: PixelCircle, phosphor: PhosphorCircle },
circleQuestionMark: { lucide: CircleQuestionMark, pixel: PixelNoteDelete, phosphor: PhosphorQuestion },
edit: { lucide: Edit, pixel: PixelEdit, phosphor: PhosphorEdit },
ellipsisVertical: {
lucide: EllipsisVertical,
pixel: PixelMoreVertical,
phosphor: PhosphorDotsThreeVertical,
},
gripVerticalIcon: {
lucide: GripVerticalIcon,
pixel: PixelViewportWide,
phosphor: PhosphorDotsSixVertical,
},
hash: { lucide: Hash, pixel: PhosphorHashStraight, phosphor: PhosphorHash },
home: { lucide: LucideHome, pixel: PixelHome, phosphor: PhosphorHome },
infoIcon: { lucide: InfoIcon, pixel: PixelInfo, phosphor: PhosphorInfo },
link: { lucide: Link, pixel: PixelLink, phosphor: PhosphorLink },
loader: { lucide: Loader, pixel: PixelLoader, phosphor: PhosphorLoader },
loader2Icon: { lucide: Loader2Icon, pixel: PixelLoader, phosphor: PhosphorLoader },
logOut: { lucide: LogOut, pixel: PixelLogout, phosphor: PhosphorLogOut },
moon: { lucide: Moon, pixel: PixelMoon, phosphor: PhosphorMoon },
octagonXIcon: { lucide: OctagonXIcon, pixel: PixelClose, phosphor: PhosphorOctagon },
plus: { lucide: Plus, pixel: PixelPlus, phosphor: PhosphorPlus },
serverIcon: { lucide: ServerIcon, pixel: PixelServer, phosphor: PhosphorServer },
sun: { lucide: Sun, pixel: PixelSun, phosphor: PhosphorSun },
timer: { lucide: Timer, pixel: PixelClock, phosphor: PhosphorClock },
trash: { lucide: Trash, pixel: PixelTrash, phosphor: PhosphorTrash },
triangleAlertIcon: { lucide: TriangleAlertIcon, pixel: PixelAlert, phosphor: PhosphorWarning },
undo: { lucide: Undo, pixel: PixelUndo, phosphor: PhosphorUndo },
undo2: { lucide: Undo2, pixel: PixelUndo, phosphor: PhosphorUndo },
userRound: { lucide: UserRound, pixel: PixelUser, phosphor: PhosphorUser },
x: { lucide: X, pixel: PixelClose, phosphor: PhosphorX },
alertTriangle: { lucide: AlertTriangle, pixel: PixelAlert, phosphor: PhosphorWarning },
check: { lucide: Check, pixel: PixelCheck, phosphor: PhosphorCheck },
checkIcon: { lucide: CheckIcon, pixel: PixelCheck, phosphor: PhosphorCheck },
chevronDown: { lucide: ChevronDown, pixel: PixelChevronDown, phosphor: PhosphorChevronDown },
chevronDownIcon: { lucide: ChevronDownIcon, pixel: PixelChevronDown, phosphor: PhosphorChevronDown },
chevronLeftIcon: { lucide: ChevronLeftIcon, pixel: PixelChevronLeft, phosphor: PhosphorChevronLeft },
chevronRightIcon: { lucide: ChevronRightIcon, pixel: PixelChevronRight, phosphor: PhosphorChevronRight },
chevronUp: { lucide: ChevronUp, pixel: PixelChevronUp, phosphor: PhosphorChevronUp },
chevronUpIcon: { lucide: ChevronUpIcon, pixel: PixelChevronUp, phosphor: PhosphorChevronUp },
circleCheckIcon: { lucide: CircleCheckIcon, pixel: PixelCheck, phosphor: PhosphorCheckCircle },
circleIcon: { lucide: CircleIcon, pixel: PixelCircle, phosphor: PhosphorCircle },
circleQuestionMark: { lucide: CircleQuestionMark, pixel: PixelNoteDelete, phosphor: PhosphorQuestion },
edit: { lucide: Edit, pixel: PixelEdit, phosphor: PhosphorEdit },
ellipsisVertical: {
lucide: EllipsisVertical,
pixel: PixelMoreVertical,
phosphor: PhosphorDotsThreeVertical,
},
gripVerticalIcon: {
lucide: GripVerticalIcon,
pixel: PixelViewportWide,
phosphor: PhosphorDotsSixVertical,
},
hash: { lucide: Hash, pixel: PhosphorHashStraight, phosphor: PhosphorHash },
home: { lucide: LucideHome, pixel: PixelHome, phosphor: PhosphorHome },
infoIcon: { lucide: InfoIcon, pixel: PixelInfo, phosphor: PhosphorInfo },
link: { lucide: Link, pixel: PixelLink, phosphor: PhosphorLink },
loader: { lucide: Loader, pixel: PixelLoader, phosphor: PhosphorLoader },
loader2Icon: { lucide: Loader2Icon, pixel: PixelLoader, phosphor: PhosphorLoader },
logOut: { lucide: LogOut, pixel: PixelLogout, phosphor: PhosphorLogOut },
moon: { lucide: Moon, pixel: PixelMoon, phosphor: PhosphorMoon },
octagonXIcon: { lucide: OctagonXIcon, pixel: PixelClose, phosphor: PhosphorOctagon },
plus: { lucide: Plus, pixel: PixelPlus, phosphor: PhosphorPlus },
serverIcon: { lucide: ServerIcon, pixel: PixelServer, phosphor: PhosphorServer },
sun: { lucide: Sun, pixel: PixelSun, phosphor: PhosphorSun },
timer: { lucide: Timer, pixel: PixelClock, phosphor: PhosphorClock },
trash: { lucide: Trash, pixel: PixelTrash, phosphor: PhosphorTrash },
triangleAlertIcon: { lucide: TriangleAlertIcon, pixel: PixelAlert, phosphor: PhosphorWarning },
undo: { lucide: Undo, pixel: PixelUndo, phosphor: PhosphorUndo },
undo2: { lucide: Undo2, pixel: PixelUndo, phosphor: PhosphorUndo },
userRound: { lucide: UserRound, pixel: PixelUser, phosphor: PhosphorUser },
x: { lucide: X, pixel: PixelClose, phosphor: PhosphorX },
};
export type IconName = keyof typeof icons;
@@ -146,41 +146,41 @@ export const iconStyles = ["lucide", "pixel", "phosphor"] as const;
export type { IconStyle };
export default function Icon({
icon,
iconStyle,
size = 24,
...props
icon,
iconStyle,
size = 24,
...props
}: {
icon: IconName;
iconStyle?: IconStyle;
size?: number | string;
color?: string;
icon: IconName;
iconStyle?: IconStyle;
size?: number | string;
color?: string;
} & React.ComponentProps<"svg">) {
const session = useSessionSafe();
const resolvedStyle = (iconStyle ??
session?.user?.iconPreference ??
localStorage.getItem("iconPreference") ??
"lucide") as IconStyle;
const IconComponent = icons[icon]?.[resolvedStyle];
const session = useSessionSafe();
const resolvedStyle = (iconStyle ??
session?.user?.iconPreference ??
localStorage.getItem("iconPreference") ??
"lucide") as IconStyle;
const IconComponent = icons[icon]?.[resolvedStyle];
if (localStorage.getItem("iconPreference") !== resolvedStyle)
localStorage.setItem("iconPreference", resolvedStyle);
if (localStorage.getItem("iconPreference") !== resolvedStyle)
localStorage.setItem("iconPreference", resolvedStyle);
if (!IconComponent) {
return null;
}
if (!IconComponent) {
return null;
}
return (
<IconComponent
size={size}
fill={
(resolvedStyle === "pixel" && icon === "moon") ||
(resolvedStyle === "pixel" && icon === "hash") ||
resolvedStyle === "phosphor"
? "var(--foreground)"
: "transparent"
}
{...props}
/>
);
return (
<IconComponent
size={size}
fill={
(resolvedStyle === "pixel" && icon === "moon") ||
(resolvedStyle === "pixel" && icon === "hash") ||
resolvedStyle === "phosphor"
? "var(--foreground)"
: "transparent"
}
{...props}
/>
);
}

View File

@@ -3,65 +3,65 @@ import Icon from "@/components/ui/icon";
import { cn } from "@/lib/utils";
function Input({
className,
type,
showCounter = true,
showHashPrefix = false,
inputClassName,
...props
className,
type,
showCounter = true,
showHashPrefix = false,
inputClassName,
...props
}: React.ComponentProps<"input"> & {
showCounter?: boolean;
showHashPrefix?: boolean;
inputClassName?: string;
showCounter?: boolean;
showHashPrefix?: boolean;
inputClassName?: string;
}) {
const maxLength = typeof props.maxLength === "number" ? props.maxLength : undefined;
const currentLength = typeof props.value === "string" ? props.value.length : undefined;
const maxLength = typeof props.maxLength === "number" ? props.maxLength : undefined;
const currentLength = typeof props.value === "string" ? props.value.length : undefined;
return (
<div
className={cn(
"border-input dark:bg-input/30 flex h-9 w-full min-w-0 items-center border bg-transparent",
"transition-[color,box-shadow]",
"has-[:focus-visible]:border-ring",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
"aria-invalid:border-destructive",
className,
)}
return (
<div
className={cn(
"border-input dark:bg-input/30 flex h-9 w-full min-w-0 items-center border bg-transparent",
"transition-[color,box-shadow]",
"has-[:focus-visible]:border-ring",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
"aria-invalid:border-destructive",
className,
)}
>
{showHashPrefix && (
<span className="border-r px-1 py-1 text-muted-foreground">
<Icon icon="hash" className="size-3.5" />
</span>
)}
<input
type={type}
data-slot="input"
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground",
"h-full flex-1 min-w-0 bg-transparent px-3 py-1 pr-1 text-base outline-none",
"file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium",
"disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
showHashPrefix ? "pl-2 py-0" : "pl-3",
inputClassName,
)}
{...props}
/>
{showCounter && currentLength !== undefined && maxLength !== undefined && (
<span
className={cn(
"border-l px-2 h-full flex w-fit items-center justify-center text-[11px] tabular-nums",
currentLength / maxLength >= 1
? "text-destructive"
: currentLength / maxLength >= 0.75
? "text-amber-500"
: "text-muted-foreground",
)}
>
{showHashPrefix && (
<span className="border-r px-1 py-1 text-muted-foreground">
<Icon icon="hash" className="size-3.5" />
</span>
)}
<input
type={type}
data-slot="input"
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground",
"h-full flex-1 min-w-0 bg-transparent px-3 py-1 pr-1 text-base outline-none",
"file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium",
"disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
showHashPrefix ? "pl-2 py-0" : "pl-3",
inputClassName,
)}
{...props}
/>
{showCounter && currentLength !== undefined && maxLength !== undefined && (
<span
className={cn(
"border-l px-2 h-full flex w-fit items-center justify-center text-[11px] tabular-nums",
currentLength / maxLength >= 1
? "text-destructive"
: currentLength / maxLength >= 0.75
? "text-amber-500"
: "text-muted-foreground",
)}
>
{String(currentLength).padStart(String(maxLength).length, "0")}/{maxLength}
</span>
)}
</div>
);
{String(currentLength).padStart(String(maxLength).length, "0")}/{maxLength}
</span>
)}
</div>
);
}
export { Input };

View File

@@ -4,16 +4,16 @@ import type * as React from "react";
import { cn } from "@/lib/utils";
function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className,
)}
{...props}
/>
);
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className,
)}
{...props}
/>
);
}
export { Label };

View File

@@ -4,39 +4,39 @@ import type * as React from "react";
import { cn } from "@/lib/utils";
function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
}
function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
}
function PopoverContent({
className,
align = "center",
sideOffset = 4,
...props
className,
align = "center",
sideOffset = 4,
...props
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
return (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
data-slot="popover-content"
align={align}
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground z-50 w-72",
"origin-(--radix-popover-content-transform-origin) border p-4",
"shadow-md outline-hidden",
className,
)}
{...props}
/>
</PopoverPrimitive.Portal>
);
return (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
data-slot="popover-content"
align={align}
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground z-50 w-72",
"origin-(--radix-popover-content-transform-origin) border p-4",
"shadow-md outline-hidden",
className,
)}
{...props}
/>
</PopoverPrimitive.Portal>
);
}
function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
}
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };

View File

@@ -7,53 +7,53 @@ import Icon from "@/components/ui/icon";
import { cn } from "@/lib/utils";
function ResizablePanelGroup({ className, ...props }: React.ComponentProps<typeof Group>) {
return (
<Group
data-slot="resizable-panel-group"
className={cn("flex h-full w-full data-[panel-group-direction=vertical]:flex-col", className)}
{...props}
/>
);
return (
<Group
data-slot="resizable-panel-group"
className={cn("flex h-full w-full data-[panel-group-direction=vertical]:flex-col", className)}
{...props}
/>
);
}
function ResizablePanel({ ...props }: React.ComponentProps<typeof Panel>) {
return <Panel data-slot="resizable-panel" {...props} />;
return <Panel data-slot="resizable-panel" {...props} />;
}
function ResizableSeparator({
withHandle,
className,
...props
withHandle,
className,
...props
}: React.ComponentProps<typeof Separator> & {
withHandle?: boolean;
withHandle?: boolean;
}) {
return (
<Separator
data-slot="resizable-handle"
className={cn(
"relative flex w-1 items-center justify-center",
"after:absolute after:inset-y-0 after:left-1/2",
"after:w-1 after:-translate-x-1/2 focus-visible:ring-0",
"focus-visible:ring-offset-0 focus-visible:outline-hidden",
"data-[panel-group-direction=vertical]:h-px",
"data-[panel-group-direction=vertical]:w-full",
"data-[panel-group-direction=vertical]:after:left-0",
"data-[panel-group-direction=vertical]:after:h-1",
"data-[panel-group-direction=vertical]:after:w-full",
"data-[panel-group-direction=vertical]:after:translate-x-0",
"data-[panel-group-direction=vertical]:after:-translate-y-1/2",
"[&[data-panel-group-direction=vertical]>div]:rotate-90",
className,
)}
{...props}
>
{withHandle && (
<div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
<Icon icon="gripVerticalIcon" className="size-2.5" />
</div>
)}
</Separator>
);
return (
<Separator
data-slot="resizable-handle"
className={cn(
"relative flex w-1 items-center justify-center",
"after:absolute after:inset-y-0 after:left-1/2",
"after:w-1 after:-translate-x-1/2 focus-visible:ring-0",
"focus-visible:ring-offset-0 focus-visible:outline-hidden",
"data-[panel-group-direction=vertical]:h-px",
"data-[panel-group-direction=vertical]:w-full",
"data-[panel-group-direction=vertical]:after:left-0",
"data-[panel-group-direction=vertical]:after:h-1",
"data-[panel-group-direction=vertical]:after:w-full",
"data-[panel-group-direction=vertical]:after:translate-x-0",
"data-[panel-group-direction=vertical]:after:-translate-y-1/2",
"[&[data-panel-group-direction=vertical]>div]:rotate-90",
className,
)}
{...props}
>
{withHandle && (
<div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
<Icon icon="gripVerticalIcon" className="size-2.5" />
</div>
)}
</Separator>
);
}
export { ResizablePanelGroup, ResizablePanel, ResizableSeparator };

View File

@@ -5,228 +5,228 @@ import Icon from "@/components/ui/icon";
import { cn } from "@/lib/utils";
function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />;
return <SelectPrimitive.Root data-slot="select" {...props} />;
}
function SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
}
function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
}
function SelectTrigger({
className,
size = "default",
variant = "default",
children,
isOpen,
label,
hasValue,
labelPosition = "top",
chevronClassName,
...props
className,
size = "default",
variant = "default",
children,
isOpen,
label,
hasValue,
labelPosition = "top",
chevronClassName,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
isOpen?: boolean;
size?: "sm" | "default";
variant?: "default" | "unstyled";
label?: string;
hasValue?: boolean;
labelPosition?: "top" | "bottom";
chevronClassName?: string;
isOpen?: boolean;
size?: "sm" | "default";
variant?: "default" | "unstyled";
label?: string;
hasValue?: boolean;
labelPosition?: "top" | "bottom";
chevronClassName?: string;
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
variant === "unstyled"
? "cursor-pointer bg-transparent shadow-none outline-none"
: [
"cursor-pointer border data-[placeholder]:text-muted-foreground",
"[&_svg:not([class*='text-'])]:text-muted-foreground",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
"aria-invalid:border-destructive dark:hover:bg-muted/40",
"relative flex w-fit items-center justify-between gap-2 border",
"bg-transparent px-3 py-2 text-sm whitespace-nowrap",
"shadow-xs outline-none disabled:cursor-not-allowed",
"disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8",
"*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex",
"*:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2",
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
],
className,
)}
{...props}
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
variant === "unstyled"
? "cursor-pointer bg-transparent shadow-none outline-none"
: [
"cursor-pointer border data-[placeholder]:text-muted-foreground",
"[&_svg:not([class*='text-'])]:text-muted-foreground",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
"aria-invalid:border-destructive dark:hover:bg-muted/40",
"relative flex w-fit items-center justify-between gap-2 border",
"bg-transparent px-3 py-2 text-sm whitespace-nowrap",
"shadow-xs outline-none disabled:cursor-not-allowed",
"disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8",
"*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex",
"*:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2",
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
],
className,
)}
{...props}
>
{label && hasValue && (
<span
className={cn(
"text-muted-foreground bg-background absolute left-1 text-[12px] leading-none font-700",
labelPosition === "top" ? "-top-1" : "-bottom-1",
)}
>
{label && hasValue && (
<span
className={cn(
"text-muted-foreground bg-background absolute left-1 text-[12px] leading-none font-700",
labelPosition === "top" ? "-top-1" : "-bottom-1",
)}
>
{label}
</span>
)}
{children}
<SelectPrimitive.Icon asChild>
<Icon
icon="chevronDownIcon"
className={cn("size-4.5 opacity-50", chevronClassName)}
style={{ rotate: isOpen ? "180deg" : "0deg" }}
/>
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
);
{label}
</span>
)}
{children}
<SelectPrimitive.Icon asChild>
<Icon
icon="chevronDownIcon"
className={cn("size-4.5 opacity-50", chevronClassName)}
style={{ rotate: isOpen ? "180deg" : "0deg" }}
/>
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
);
}
function SelectContent({
className,
children,
position = "item-aligned",
align = "center",
...props
className,
children,
position = "item-aligned",
align = "center",
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
data-align={align}
className={cn(
"bg-popover text-popover-foreground",
"data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95",
"data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2",
"data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
"data-[side=top]:slide-in-from-bottom-2 relative z-50",
"max-h-(--radix-select-content-available-height) min-w-[8rem]",
"origin-(--radix-select-content-transform-origin) overflow-x-hidden",
"overflow-y-auto border shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=bottom]:-translate-x-1.5 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1 data-[side=top]:-translate-x-0.5",
position === "popper" && align === "start" && "data-[side=bottom]:-translate-x-0",
className,
)}
position={position}
align={align}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
);
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
data-align={align}
className={cn(
"bg-popover text-popover-foreground",
"data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95",
"data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2",
"data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
"data-[side=top]:slide-in-from-bottom-2 relative z-50",
"max-h-(--radix-select-content-available-height) min-w-[8rem]",
"origin-(--radix-select-content-transform-origin) overflow-x-hidden",
"overflow-y-auto border shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=bottom]:-translate-x-1.5 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1 data-[side=top]:-translate-x-0.5",
position === "popper" && align === "start" && "data-[side=bottom]:-translate-x-0",
className,
)}
position={position}
align={align}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
);
}
function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("text-muted-foreground px-2 py-1.5 text-xs font-700", className)}
{...props}
/>
);
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("text-muted-foreground px-2 py-1.5 text-xs font-700", className)}
{...props}
/>
);
}
function SelectItem({
className,
textClassName,
children,
...props
className,
textClassName,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item> & {
textClassName?: string;
textClassName?: string;
}) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent/40 focus:text-accent-foreground",
"[&_svg:not([class*='text-'])]:text-muted-foreground relative",
"flex w-full cursor-pointer items-center gap-2 py-1.5 pr-8 pl-2",
"text-sm outline-hidden select-none data-[disabled]:pointer-events-none",
"data-[disabled]:opacity-50 [&_svg]:pointer-events-none",
"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"*:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className,
)}
{...props}
>
<span
data-slot="select-item-indicator"
className="absolute right-2 flex size-3.5 items-center justify-center"
>
<SelectPrimitive.ItemIndicator>
<Icon icon="checkIcon" className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
{textClassName ? (
<span className={cn(textClassName)}>{children}</span>
) : (
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
)}
</SelectPrimitive.Item>
);
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent/40 focus:text-accent-foreground",
"[&_svg:not([class*='text-'])]:text-muted-foreground relative",
"flex w-full cursor-pointer items-center gap-2 py-1.5 pr-8 pl-2",
"text-sm outline-hidden select-none data-[disabled]:pointer-events-none",
"data-[disabled]:opacity-50 [&_svg]:pointer-events-none",
"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"*:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className,
)}
{...props}
>
<span
data-slot="select-item-indicator"
className="absolute right-2 flex size-3.5 items-center justify-center"
>
<SelectPrimitive.ItemIndicator>
<Icon icon="checkIcon" className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
{textClassName ? (
<span className={cn(textClassName)}>{children}</span>
) : (
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
)}
</SelectPrimitive.Item>
);
}
function SelectSeparator({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
{...props}
/>
);
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
{...props}
/>
);
}
function SelectScrollUpButton({
className,
...props
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<Icon icon="chevronUpIcon" className="size-4" />
</SelectPrimitive.ScrollUpButton>
);
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<Icon icon="chevronUpIcon" className="size-4" />
</SelectPrimitive.ScrollUpButton>
);
}
function SelectScrollDownButton({
className,
...props
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<Icon icon="chevronDownIcon" className="size-4" />
</SelectPrimitive.ScrollDownButton>
);
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<Icon icon="chevronDownIcon" className="size-4" />
</SelectPrimitive.ScrollDownButton>
);
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
};

View File

@@ -3,30 +3,30 @@ import { Toaster as Sonner, type ToasterProps } from "sonner";
import Icon from "@/components/ui/icon";
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme();
const { theme = "system" } = useTheme();
return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
icons={{
success: <Icon icon="circleCheckIcon" className="size-4" />,
info: <Icon icon="infoIcon" className="size-4" />,
warning: <Icon icon="triangleAlertIcon" className="size-4" />,
error: <Icon icon="octagonXIcon" className="size-4" />,
loading: <Icon icon="loader2Icon" className="size-4 animate-spin" />,
}}
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
"--border-radius": "0",
} as React.CSSProperties
}
{...props}
/>
);
return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
icons={{
success: <Icon icon="circleCheckIcon" className="size-4" />,
info: <Icon icon="infoIcon" className="size-4" />,
warning: <Icon icon="triangleAlertIcon" className="size-4" />,
error: <Icon icon="octagonXIcon" className="size-4" />,
loading: <Icon icon="loader2Icon" className="size-4 animate-spin" />,
}}
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
"--border-radius": "0",
} as React.CSSProperties
}
{...props}
/>
);
};
export { Toaster };

View File

@@ -2,15 +2,15 @@ import Icon from "@/components/ui/icon";
import { cn } from "@/lib/utils";
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
return (
<Icon
icon="loader"
role="status"
aria-label="Loading"
className={cn("size-4 animate-spin", className)}
{...props}
/>
);
return (
<Icon
icon="loader"
role="status"
aria-label="Loading"
className={cn("size-4 animate-spin", className)}
{...props}
/>
);
}
export { Spinner };

View File

@@ -3,86 +3,84 @@ import type * as React from "react";
import { cn } from "@/lib/utils";
function Table({ className, ...props }: React.ComponentProps<"table">) {
return (
<div data-slot="table-container" className="relative w-full overflow-hidden">
<table data-slot="table" className={cn("w-full caption-bottom text-sm", className)} {...props} />
</div>
);
return (
<div data-slot="table-container" className="relative w-full overflow-hidden">
<table data-slot="table" className={cn("w-full caption-bottom text-sm", className)} {...props} />
</div>
);
}
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
return <thead data-slot="table-header" className={cn("[&_tr]:border-b", className)} {...props} />;
return <thead data-slot="table-header" className={cn("[&_tr]:border-b", className)} {...props} />;
}
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
return (
<tbody data-slot="table-body" className={cn("[&_tr:last-child]:border-0", className)} {...props} />
);
return <tbody data-slot="table-body" className={cn("[&_tr:last-child]:border-0", className)} {...props} />;
}
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
return (
<tfoot
data-slot="table-footer"
className={cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className)}
{...props}
/>
);
return (
<tfoot
data-slot="table-footer"
className={cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className)}
{...props}
/>
);
}
function TableRow({
className,
hoverEffect = true,
...props
className,
hoverEffect = true,
...props
}: React.ComponentProps<"tr"> & { hoverEffect?: boolean }) {
return (
<tr
data-slot="table-row"
className={cn(
"data-[state=selected]:bg-muted h-[25px] border-b",
hoverEffect && "hover:bg-muted/40",
className,
)}
{...props}
/>
);
return (
<tr
data-slot="table-row"
className={cn(
"data-[state=selected]:bg-muted h-[25px] border-b",
hoverEffect && "hover:bg-muted/40",
className,
)}
{...props}
/>
);
}
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
return (
<th
data-slot="table-head"
className={cn(
"text-foreground px-2 h-[25px] text-left text-sm align-middle font-medium",
"whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className,
)}
{...props}
/>
);
return (
<th
data-slot="table-head"
className={cn(
"text-foreground px-2 h-[25px] text-left text-sm align-middle font-medium",
"whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className,
)}
{...props}
/>
);
}
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
return (
<td
data-slot="table-cell"
className={cn(
"px-2 py-1 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className,
)}
{...props}
/>
);
return (
<td
data-slot="table-cell"
className={cn(
"px-2 py-1 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className,
)}
{...props}
/>
);
}
function TableCaption({ className, ...props }: React.ComponentProps<"caption">) {
return (
<caption
data-slot="table-caption"
className={cn("text-muted-foreground mt-4 text-sm", className)}
{...props}
/>
);
return (
<caption
data-slot="table-caption"
className={cn("text-muted-foreground mt-4 text-sm", className)}
{...props}
/>
);
}
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };

View File

@@ -4,53 +4,51 @@ import type * as React from "react";
import { cn } from "@/lib/utils";
function Tabs({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root data-slot="tabs" className={cn("flex flex-col gap-2", className)} {...props} />
);
return <TabsPrimitive.Root data-slot="tabs" className={cn("flex flex-col gap-2", className)} {...props} />;
}
function TabsList({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.List>) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"border text-muted-foreground inline-flex h-9 w-fit items-center justify-center p-[3px]",
className,
)}
{...props}
/>
);
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"border text-muted-foreground inline-flex h-9 w-fit items-center justify-center p-[3px]",
className,
)}
{...props}
/>
);
}
function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring",
"dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30",
"text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)]",
"flex-1 items-center justify-center gap-1.5 border border-transparent px-2 py-1",
"text-sm font-medium whitespace-nowrap transition-[color,box-shadow]",
"focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none",
"disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none",
"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 cursor-pointer",
className,
)}
{...props}
/>
);
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring",
"dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30",
"text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)]",
"flex-1 items-center justify-center gap-1.5 border border-transparent px-2 py-1",
"text-sm font-medium whitespace-nowrap transition-[color,box-shadow]",
"focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none",
"disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none",
"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 cursor-pointer",
className,
)}
{...props}
/>
);
}
function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn("flex-1 outline-none", className)}
{...props}
/>
);
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn("flex-1 outline-none", className)}
{...props}
/>
);
}
export { Tabs, TabsList, TabsTrigger, TabsContent };

View File

@@ -3,23 +3,23 @@ import type * as React from "react";
import { cn } from "@/lib/utils";
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
<textarea
data-slot="textarea"
className={cn(
"border-input dark:bg-input/30 w-full min-w-0 border bg-transparent",
"transition-[color,box-shadow]",
"focus-visible:border-ring",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
"field-sizing-content min-h-2 px-3 py-2 text-base md:text-sm resize-none",
"placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground",
"disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
"outline-none",
className,
)}
{...props}
/>
);
return (
<textarea
data-slot="textarea"
className={cn(
"border-input dark:bg-input/30 w-full min-w-0 border bg-transparent",
"transition-[color,box-shadow]",
"focus-visible:border-ring",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
"field-sizing-content min-h-2 px-3 py-2 text-base md:text-sm resize-none",
"placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground",
"disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
"outline-none",
className,
)}
{...props}
/>
);
}
export { Textarea };