@kinetixui/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +1176 -0
  3. package/dist/index.js +3939 -0
  4. package/package.json +106 -0
  5. package/src/components/accordion.tsx +52 -0
  6. package/src/components/alert-dialog.tsx +115 -0
  7. package/src/components/alert.tsx +47 -0
  8. package/src/components/aspect-ratio.tsx +7 -0
  9. package/src/components/audio-player.tsx +150 -0
  10. package/src/components/avatar.tsx +76 -0
  11. package/src/components/badge.tsx +40 -0
  12. package/src/components/breadcrumb.tsx +77 -0
  13. package/src/components/button.tsx +119 -0
  14. package/src/components/calendar.tsx +60 -0
  15. package/src/components/card.tsx +50 -0
  16. package/src/components/carousel.tsx +181 -0
  17. package/src/components/chart.tsx +177 -0
  18. package/src/components/checkbox.tsx +38 -0
  19. package/src/components/circular-progress.tsx +67 -0
  20. package/src/components/code-block.tsx +96 -0
  21. package/src/components/collapsible.tsx +9 -0
  22. package/src/components/command.tsx +123 -0
  23. package/src/components/context-menu.tsx +132 -0
  24. package/src/components/data-table.tsx +92 -0
  25. package/src/components/date-picker.tsx +68 -0
  26. package/src/components/dialog.tsx +101 -0
  27. package/src/components/drawer.tsx +82 -0
  28. package/src/components/dropdown-menu.tsx +132 -0
  29. package/src/components/fab.tsx +58 -0
  30. package/src/components/field.tsx +128 -0
  31. package/src/components/file-upload.tsx +183 -0
  32. package/src/components/footer.tsx +62 -0
  33. package/src/components/form.tsx +130 -0
  34. package/src/components/hover-card.tsx +28 -0
  35. package/src/components/image.tsx +87 -0
  36. package/src/components/inform.tsx +82 -0
  37. package/src/components/input-group.tsx +96 -0
  38. package/src/components/input-otp.tsx +63 -0
  39. package/src/components/input.tsx +72 -0
  40. package/src/components/label.tsx +20 -0
  41. package/src/components/list.tsx +69 -0
  42. package/src/components/menubar.tsx +168 -0
  43. package/src/components/metric.tsx +51 -0
  44. package/src/components/modal.tsx +126 -0
  45. package/src/components/navigation-bar.tsx +49 -0
  46. package/src/components/navigation-menu.tsx +117 -0
  47. package/src/components/number-input.tsx +86 -0
  48. package/src/components/pagination.tsx +77 -0
  49. package/src/components/password-input.tsx +36 -0
  50. package/src/components/popover.tsx +32 -0
  51. package/src/components/progress.tsx +24 -0
  52. package/src/components/quote.tsx +34 -0
  53. package/src/components/radio-group.tsx +44 -0
  54. package/src/components/rating.tsx +88 -0
  55. package/src/components/resizable.tsx +40 -0
  56. package/src/components/scroll-area.tsx +39 -0
  57. package/src/components/select.tsx +124 -0
  58. package/src/components/separator.tsx +25 -0
  59. package/src/components/sheet.tsx +104 -0
  60. package/src/components/sidebar.tsx +390 -0
  61. package/src/components/skeleton.tsx +9 -0
  62. package/src/components/slider.tsx +24 -0
  63. package/src/components/sonner.tsx +30 -0
  64. package/src/components/spinner.tsx +43 -0
  65. package/src/components/stepper.tsx +75 -0
  66. package/src/components/switch.tsx +37 -0
  67. package/src/components/tab-bar.tsx +58 -0
  68. package/src/components/table-of-contents.tsx +46 -0
  69. package/src/components/table.tsx +70 -0
  70. package/src/components/tabs.tsx +54 -0
  71. package/src/components/tag.tsx +56 -0
  72. package/src/components/textarea.tsx +56 -0
  73. package/src/components/toggle-group.tsx +41 -0
  74. package/src/components/toggle.tsx +34 -0
  75. package/src/components/tooltip.tsx +31 -0
  76. package/src/index.ts +305 -0
  77. package/src/lib/utils.ts +7 -0
  78. package/src/stories/Button.stories.tsx +155 -0
  79. package/src/stories/Input.stories.tsx +80 -0
  80. package/src/stories/Textarea.stories.tsx +69 -0
  81. package/tailwind.config.ts +107 -0
@@ -0,0 +1,77 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
5
+ import { cn } from "../lib/utils";
6
+ import { buttonVariants } from "./button";
7
+
8
+ const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
9
+ <nav
10
+ role="navigation"
11
+ aria-label="pagination"
12
+ className={cn("mx-auto flex w-full justify-center font-sans", className)}
13
+ {...props}
14
+ />
15
+ );
16
+ Pagination.displayName = "Pagination";
17
+
18
+ const PaginationContent = React.forwardRef<HTMLUListElement, React.ComponentProps<"ul">>(
19
+ ({ className, ...props }, ref) => (
20
+ <ul ref={ref} className={cn("flex flex-row items-center gap-1", className)} {...props} />
21
+ ),
22
+ );
23
+ PaginationContent.displayName = "PaginationContent";
24
+
25
+ const PaginationItem = React.forwardRef<HTMLLIElement, React.ComponentProps<"li">>(
26
+ ({ className, ...props }, ref) => <li ref={ref} className={cn("", className)} {...props} />,
27
+ );
28
+ PaginationItem.displayName = "PaginationItem";
29
+
30
+ type PaginationLinkProps = { isActive?: boolean } & React.ComponentProps<"a">;
31
+
32
+ const PaginationLink = ({ className, isActive, ...props }: PaginationLinkProps) => (
33
+ <a
34
+ aria-current={isActive ? "page" : undefined}
35
+ className={cn(
36
+ buttonVariants({ variant: isActive ? "Outline" : "Ghost", size: "icon" }),
37
+ "size-9",
38
+ className,
39
+ )}
40
+ {...props}
41
+ />
42
+ );
43
+ PaginationLink.displayName = "PaginationLink";
44
+
45
+ const PaginationPrevious = ({ className, ...props }: React.ComponentProps<typeof PaginationLink>) => (
46
+ <PaginationLink aria-label="Go to previous page" className={cn("w-auto gap-1 px-2.5", className)} {...props}>
47
+ <ChevronLeft className="size-4" />
48
+ <span>Previous</span>
49
+ </PaginationLink>
50
+ );
51
+ PaginationPrevious.displayName = "PaginationPrevious";
52
+
53
+ const PaginationNext = ({ className, ...props }: React.ComponentProps<typeof PaginationLink>) => (
54
+ <PaginationLink aria-label="Go to next page" className={cn("w-auto gap-1 px-2.5", className)} {...props}>
55
+ <span>Next</span>
56
+ <ChevronRight className="size-4" />
57
+ </PaginationLink>
58
+ );
59
+ PaginationNext.displayName = "PaginationNext";
60
+
61
+ const PaginationEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
62
+ <span aria-hidden className={cn("flex size-9 items-center justify-center", className)} {...props}>
63
+ <MoreHorizontal className="size-4" />
64
+ <span className="sr-only">More pages</span>
65
+ </span>
66
+ );
67
+ PaginationEllipsis.displayName = "PaginationEllipsis";
68
+
69
+ export {
70
+ Pagination,
71
+ PaginationContent,
72
+ PaginationItem,
73
+ PaginationLink,
74
+ PaginationPrevious,
75
+ PaginationNext,
76
+ PaginationEllipsis,
77
+ };
@@ -0,0 +1,36 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Eye, EyeOff } from "lucide-react";
5
+ import { cn } from "../lib/utils";
6
+ import { InputGroup, InputGroupAddon, InputGroupInput } from "./input-group";
7
+
8
+ /**
9
+ * PasswordInput — an InputGroup recipe: a password field with a show / hide
10
+ * toggle. Forwards its ref to the underlying <input>.
11
+ */
12
+ const PasswordInput = React.forwardRef<
13
+ HTMLInputElement,
14
+ Omit<React.InputHTMLAttributes<HTMLInputElement>, "type">
15
+ >(({ className, ...props }, ref) => {
16
+ const [visible, setVisible] = React.useState(false);
17
+ return (
18
+ <InputGroup className={className}>
19
+ <InputGroupInput ref={ref} type={visible ? "text" : "password"} {...props} />
20
+ <InputGroupAddon align="end">
21
+ <button
22
+ type="button"
23
+ aria-label={visible ? "Hide password" : "Show password"}
24
+ aria-pressed={visible}
25
+ onClick={() => setVisible((v) => !v)}
26
+ className={cn("rounded-[2px] outline-none transition-colors hover:text-foreground focus-visible:text-foreground")}
27
+ >
28
+ {visible ? <EyeOff /> : <Eye />}
29
+ </button>
30
+ </InputGroupAddon>
31
+ </InputGroup>
32
+ );
33
+ });
34
+ PasswordInput.displayName = "PasswordInput";
35
+
36
+ export { PasswordInput };
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as PopoverPrimitive from "@radix-ui/react-popover";
5
+ import { cn } from "../lib/utils";
6
+
7
+ const Popover = PopoverPrimitive.Root;
8
+ const PopoverTrigger = PopoverPrimitive.Trigger;
9
+ const PopoverAnchor = PopoverPrimitive.Anchor;
10
+
11
+ const PopoverContent = React.forwardRef<
12
+ React.ElementRef<typeof PopoverPrimitive.Content>,
13
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
14
+ >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
15
+ <PopoverPrimitive.Portal>
16
+ <PopoverPrimitive.Content
17
+ ref={ref}
18
+ align={align}
19
+ sideOffset={sideOffset}
20
+ className={cn(
21
+ "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none font-sans",
22
+ "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",
23
+ "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",
24
+ className,
25
+ )}
26
+ {...props}
27
+ />
28
+ </PopoverPrimitive.Portal>
29
+ ));
30
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName;
31
+
32
+ export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
@@ -0,0 +1,24 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as ProgressPrimitive from "@radix-ui/react-progress";
5
+ import { cn } from "../lib/utils";
6
+
7
+ const Progress = React.forwardRef<
8
+ React.ElementRef<typeof ProgressPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
10
+ >(({ className, value, ...props }, ref) => (
11
+ <ProgressPrimitive.Root
12
+ ref={ref}
13
+ className={cn("relative h-2 w-full overflow-hidden rounded-full bg-muted", className)}
14
+ {...props}
15
+ >
16
+ <ProgressPrimitive.Indicator
17
+ className="size-full flex-1 bg-primary transition-transform"
18
+ style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
19
+ />
20
+ </ProgressPrimitive.Root>
21
+ ));
22
+ Progress.displayName = ProgressPrimitive.Root.displayName;
23
+
24
+ export { Progress };
@@ -0,0 +1,34 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cn } from "../lib/utils";
5
+
6
+ /**
7
+ * Quote — a blockquote with an optional attributed author (name, title,
8
+ * avatar slot).
9
+ */
10
+ export interface QuoteProps extends React.HTMLAttributes<HTMLElement> {
11
+ author?: React.ReactNode;
12
+ authorTitle?: React.ReactNode;
13
+ avatar?: React.ReactNode;
14
+ }
15
+
16
+ const Quote = React.forwardRef<HTMLElement, QuoteProps>(
17
+ ({ className, author, authorTitle, avatar, children, ...props }, ref) => (
18
+ <figure ref={ref} className={cn("flex flex-col gap-4 font-sans", className)} {...props}>
19
+ <blockquote className="text-title-md font-medium text-foreground">&ldquo;{children}&rdquo;</blockquote>
20
+ {(author || authorTitle) && (
21
+ <figcaption className="flex items-center gap-3">
22
+ {avatar}
23
+ <div>
24
+ {author && <p className="text-body-md font-medium text-foreground">{author}</p>}
25
+ {authorTitle && <p className="text-body-sm text-muted-foreground">{authorTitle}</p>}
26
+ </div>
27
+ </figcaption>
28
+ )}
29
+ </figure>
30
+ ),
31
+ );
32
+ Quote.displayName = "Quote";
33
+
34
+ export { Quote };
@@ -0,0 +1,44 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
5
+ import { Circle } from "lucide-react";
6
+ import { cn } from "../lib/utils";
7
+
8
+ /**
9
+ * RadioGroup — reconciled 1:1 with the KinetixUI design source, node 54863:536.
10
+ * 18px circle, 2px border. Unchecked border = `--input`; checked border + dot =
11
+ * `--primary`. Focus = 2px `--ring` offset. `aria-invalid` → `--destructive`.
12
+ */
13
+ const RadioGroup = React.forwardRef<
14
+ React.ElementRef<typeof RadioGroupPrimitive.Root>,
15
+ React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
16
+ >(({ className, ...props }, ref) => (
17
+ <RadioGroupPrimitive.Root ref={ref} className={cn("grid gap-3", className)} {...props} />
18
+ ));
19
+ RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
20
+
21
+ const RadioGroupItem = React.forwardRef<
22
+ React.ElementRef<typeof RadioGroupPrimitive.Item>,
23
+ React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
24
+ >(({ className, ...props }, ref) => (
25
+ <RadioGroupPrimitive.Item
26
+ ref={ref}
27
+ className={cn(
28
+ "aspect-square size-[18px] rounded-full border-2 border-input text-primary outline-none transition-colors",
29
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
30
+ "disabled:cursor-not-allowed disabled:opacity-50",
31
+ "data-[state=checked]:border-primary",
32
+ "aria-invalid:border-destructive",
33
+ className,
34
+ )}
35
+ {...props}
36
+ >
37
+ <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
38
+ <Circle className="size-2.5 fill-primary text-primary" />
39
+ </RadioGroupPrimitive.Indicator>
40
+ </RadioGroupPrimitive.Item>
41
+ ));
42
+ RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
43
+
44
+ export { RadioGroup, RadioGroupItem };
@@ -0,0 +1,88 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Star } from "lucide-react";
5
+ import { cva, type VariantProps } from "class-variance-authority";
6
+ import { cn } from "../lib/utils";
7
+
8
+ /**
9
+ * Rating — a star rating input / display. Controlled (`value`) or
10
+ * uncontrolled (`defaultValue`); `readOnly` renders a static display, e.g.
11
+ * inside a Card.
12
+ */
13
+ const starVariants = cva("transition-colors", {
14
+ variants: {
15
+ size: {
16
+ sm: "size-4",
17
+ md: "size-5",
18
+ lg: "size-6",
19
+ },
20
+ },
21
+ defaultVariants: { size: "md" },
22
+ });
23
+
24
+ export interface RatingProps
25
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange">,
26
+ VariantProps<typeof starVariants> {
27
+ value?: number;
28
+ defaultValue?: number;
29
+ max?: number;
30
+ readOnly?: boolean;
31
+ onChange?: (value: number) => void;
32
+ }
33
+
34
+ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
35
+ ({ className, value, defaultValue = 0, max = 5, size, readOnly = false, onChange, ...props }, ref) => {
36
+ const [internal, setInternal] = React.useState(defaultValue);
37
+ const [hovered, setHovered] = React.useState<number | null>(null);
38
+ const current = value ?? internal;
39
+ const display = hovered ?? current;
40
+
41
+ const set = (n: number) => {
42
+ if (readOnly) return;
43
+ setInternal(n);
44
+ onChange?.(n);
45
+ };
46
+
47
+ return (
48
+ <div
49
+ ref={ref}
50
+ role={readOnly ? "img" : "radiogroup"}
51
+ aria-label={readOnly ? `Rated ${current} out of ${max}` : "Rating"}
52
+ className={cn("inline-flex items-center gap-0.5", className)}
53
+ onMouseLeave={() => setHovered(null)}
54
+ {...props}
55
+ >
56
+ {Array.from({ length: max }, (_, i) => {
57
+ const n = i + 1;
58
+ const filled = n <= display;
59
+ return (
60
+ <button
61
+ key={n}
62
+ type="button"
63
+ disabled={readOnly}
64
+ aria-label={`${n} star${n > 1 ? "s" : ""}`}
65
+ aria-pressed={n <= current}
66
+ onMouseEnter={() => !readOnly && setHovered(n)}
67
+ onClick={() => set(n)}
68
+ className={cn(
69
+ "rounded-[2px] outline-none disabled:cursor-default",
70
+ !readOnly && "cursor-pointer focus-visible:ring-2 focus-visible:ring-ring",
71
+ )}
72
+ >
73
+ <Star
74
+ className={cn(
75
+ starVariants({ size }),
76
+ filled ? "fill-warning text-warning" : "fill-transparent text-muted-foreground",
77
+ )}
78
+ />
79
+ </button>
80
+ );
81
+ })}
82
+ </div>
83
+ );
84
+ },
85
+ );
86
+ Rating.displayName = "Rating";
87
+
88
+ export { Rating };
@@ -0,0 +1,40 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { GripVertical } from "lucide-react";
5
+ import * as ResizablePrimitive from "react-resizable-panels";
6
+ import { cn } from "../lib/utils";
7
+
8
+ const ResizablePanelGroup = ({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
12
+ <ResizablePrimitive.PanelGroup
13
+ className={cn("flex h-full w-full data-[panel-group-direction=vertical]:flex-col", className)}
14
+ {...props}
15
+ />
16
+ );
17
+
18
+ const ResizablePanel = ResizablePrimitive.Panel;
19
+
20
+ const ResizableHandle = ({
21
+ withHandle,
22
+ className,
23
+ ...props
24
+ }: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & { withHandle?: boolean }) => (
25
+ <ResizablePrimitive.PanelResizeHandle
26
+ className={cn(
27
+ "relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 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-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
28
+ className,
29
+ )}
30
+ {...props}
31
+ >
32
+ {withHandle && (
33
+ <div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
34
+ <GripVertical className="size-2.5" />
35
+ </div>
36
+ )}
37
+ </ResizablePrimitive.PanelResizeHandle>
38
+ );
39
+
40
+ export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
@@ -0,0 +1,39 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
5
+ import { cn } from "../lib/utils";
6
+
7
+ const ScrollArea = React.forwardRef<
8
+ React.ElementRef<typeof ScrollAreaPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
10
+ >(({ className, children, ...props }, ref) => (
11
+ <ScrollAreaPrimitive.Root ref={ref} className={cn("relative overflow-hidden", className)} {...props}>
12
+ <ScrollAreaPrimitive.Viewport className="size-full rounded-[inherit]">{children}</ScrollAreaPrimitive.Viewport>
13
+ <ScrollBar />
14
+ <ScrollAreaPrimitive.Corner />
15
+ </ScrollAreaPrimitive.Root>
16
+ ));
17
+ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
18
+
19
+ const ScrollBar = React.forwardRef<
20
+ React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
21
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
22
+ >(({ className, orientation = "vertical", ...props }, ref) => (
23
+ <ScrollAreaPrimitive.ScrollAreaScrollbar
24
+ ref={ref}
25
+ orientation={orientation}
26
+ className={cn(
27
+ "flex touch-none select-none transition-colors",
28
+ orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-px",
29
+ orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-px",
30
+ className,
31
+ )}
32
+ {...props}
33
+ >
34
+ <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
35
+ </ScrollAreaPrimitive.ScrollAreaScrollbar>
36
+ ));
37
+ ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
38
+
39
+ export { ScrollArea, ScrollBar };
@@ -0,0 +1,124 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SelectPrimitive from "@radix-ui/react-select";
5
+ import { Check, ChevronDown, ChevronUp } from "lucide-react";
6
+ import { cn } from "../lib/utils";
7
+
8
+ /**
9
+ * Select — reconciled 1:1 with the KinetixUI design source, node 54855:13882.
10
+ * The trigger mirrors Input: `rounded-sm`, `border-input`, `--spacing-3` pad,
11
+ * Body Medium text, a soft `--shadow-focus` glow, `--destructive` on aria-invalid.
12
+ */
13
+ const Select = SelectPrimitive.Root;
14
+ const SelectGroup = SelectPrimitive.Group;
15
+ const SelectValue = SelectPrimitive.Value;
16
+
17
+ const SelectTrigger = React.forwardRef<
18
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
19
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
20
+ >(({ className, children, ...props }, ref) => (
21
+ <SelectPrimitive.Trigger
22
+ ref={ref}
23
+ className={cn(
24
+ "flex min-h-[44px] w-full items-center justify-between rounded-sm border border-input bg-background px-3 py-3",
25
+ "font-sans text-[14px] leading-5 tracking-[0.25px] text-foreground",
26
+ "data-[placeholder]:text-muted-foreground [&>span]:line-clamp-1",
27
+ "outline-none transition-colors",
28
+ "focus:border-primary focus:shadow-focus",
29
+ "data-[state=open]:border-primary",
30
+ "aria-[invalid=true]:border-destructive aria-[invalid=true]:focus:border-destructive aria-[invalid=true]:focus:shadow-focus-destructive",
31
+ "disabled:cursor-not-allowed disabled:opacity-50",
32
+ className,
33
+ )}
34
+ {...props}
35
+ >
36
+ {children}
37
+ <SelectPrimitive.Icon asChild>
38
+ <ChevronDown className="size-4 shrink-0 opacity-60" />
39
+ </SelectPrimitive.Icon>
40
+ </SelectPrimitive.Trigger>
41
+ ));
42
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
43
+
44
+ const SelectContent = React.forwardRef<
45
+ React.ElementRef<typeof SelectPrimitive.Content>,
46
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
47
+ >(({ className, children, position = "popper", ...props }, ref) => (
48
+ <SelectPrimitive.Portal>
49
+ <SelectPrimitive.Content
50
+ ref={ref}
51
+ position={position}
52
+ className={cn(
53
+ "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md font-sans",
54
+ "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",
55
+ position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1",
56
+ className,
57
+ )}
58
+ {...props}
59
+ >
60
+ <SelectPrimitive.ScrollUpButton className="flex h-6 items-center justify-center">
61
+ <ChevronUp className="size-4" />
62
+ </SelectPrimitive.ScrollUpButton>
63
+ <SelectPrimitive.Viewport
64
+ className={cn("p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]")}
65
+ >
66
+ {children}
67
+ </SelectPrimitive.Viewport>
68
+ <SelectPrimitive.ScrollDownButton className="flex h-6 items-center justify-center">
69
+ <ChevronDown className="size-4" />
70
+ </SelectPrimitive.ScrollDownButton>
71
+ </SelectPrimitive.Content>
72
+ </SelectPrimitive.Portal>
73
+ ));
74
+ SelectContent.displayName = SelectPrimitive.Content.displayName;
75
+
76
+ const SelectLabel = React.forwardRef<
77
+ React.ElementRef<typeof SelectPrimitive.Label>,
78
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
79
+ >(({ className, ...props }, ref) => (
80
+ <SelectPrimitive.Label ref={ref} className={cn("px-2 py-1.5 text-sm font-semibold", className)} {...props} />
81
+ ));
82
+ SelectLabel.displayName = SelectPrimitive.Label.displayName;
83
+
84
+ const SelectItem = React.forwardRef<
85
+ React.ElementRef<typeof SelectPrimitive.Item>,
86
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
87
+ >(({ className, children, ...props }, ref) => (
88
+ <SelectPrimitive.Item
89
+ ref={ref}
90
+ className={cn(
91
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none",
92
+ "focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
93
+ className,
94
+ )}
95
+ {...props}
96
+ >
97
+ <span className="absolute left-2 flex size-3.5 items-center justify-center">
98
+ <SelectPrimitive.ItemIndicator>
99
+ <Check className="size-4" />
100
+ </SelectPrimitive.ItemIndicator>
101
+ </span>
102
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
103
+ </SelectPrimitive.Item>
104
+ ));
105
+ SelectItem.displayName = SelectPrimitive.Item.displayName;
106
+
107
+ const SelectSeparator = React.forwardRef<
108
+ React.ElementRef<typeof SelectPrimitive.Separator>,
109
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
110
+ >(({ className, ...props }, ref) => (
111
+ <SelectPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
112
+ ));
113
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
114
+
115
+ export {
116
+ Select,
117
+ SelectGroup,
118
+ SelectValue,
119
+ SelectTrigger,
120
+ SelectContent,
121
+ SelectLabel,
122
+ SelectItem,
123
+ SelectSeparator,
124
+ };
@@ -0,0 +1,25 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SeparatorPrimitive from "@radix-ui/react-separator";
5
+ import { cn } from "../lib/utils";
6
+
7
+ const Separator = React.forwardRef<
8
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
10
+ >(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
11
+ <SeparatorPrimitive.Root
12
+ ref={ref}
13
+ decorative={decorative}
14
+ orientation={orientation}
15
+ className={cn(
16
+ "shrink-0 bg-border",
17
+ orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
18
+ className,
19
+ )}
20
+ {...props}
21
+ />
22
+ ));
23
+ Separator.displayName = SeparatorPrimitive.Root.displayName;
24
+
25
+ export { Separator };
@@ -0,0 +1,104 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SheetPrimitive from "@radix-ui/react-dialog";
5
+ import { cva, type VariantProps } from "class-variance-authority";
6
+ import { X } from "lucide-react";
7
+ import { cn } from "../lib/utils";
8
+
9
+ const Sheet = SheetPrimitive.Root;
10
+ const SheetTrigger = SheetPrimitive.Trigger;
11
+ const SheetClose = SheetPrimitive.Close;
12
+ const SheetPortal = SheetPrimitive.Portal;
13
+
14
+ const SheetOverlay = React.forwardRef<
15
+ React.ElementRef<typeof SheetPrimitive.Overlay>,
16
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
17
+ >(({ className, ...props }, ref) => (
18
+ <SheetPrimitive.Overlay
19
+ ref={ref}
20
+ className={cn(
21
+ "fixed inset-0 z-50 bg-foreground/40 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
22
+ className,
23
+ )}
24
+ {...props}
25
+ />
26
+ ));
27
+ SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
28
+
29
+ const sheetVariants = cva(
30
+ "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 font-sans",
31
+ {
32
+ variants: {
33
+ side: {
34
+ top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
35
+ bottom:
36
+ "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
37
+ left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
38
+ right:
39
+ "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
40
+ },
41
+ },
42
+ defaultVariants: { side: "right" },
43
+ },
44
+ );
45
+
46
+ interface SheetContentProps
47
+ extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
48
+ VariantProps<typeof sheetVariants> {}
49
+
50
+ const SheetContent = React.forwardRef<
51
+ React.ElementRef<typeof SheetPrimitive.Content>,
52
+ SheetContentProps
53
+ >(({ side = "right", className, children, ...props }, ref) => (
54
+ <SheetPortal>
55
+ <SheetOverlay />
56
+ <SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
57
+ {children}
58
+ <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring">
59
+ <X className="size-4" />
60
+ <span className="sr-only">Close</span>
61
+ </SheetPrimitive.Close>
62
+ </SheetPrimitive.Content>
63
+ </SheetPortal>
64
+ ));
65
+ SheetContent.displayName = SheetPrimitive.Content.displayName;
66
+
67
+ const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
68
+ <div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
69
+ );
70
+ SheetHeader.displayName = "SheetHeader";
71
+
72
+ const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
73
+ <div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
74
+ );
75
+ SheetFooter.displayName = "SheetFooter";
76
+
77
+ const SheetTitle = React.forwardRef<
78
+ React.ElementRef<typeof SheetPrimitive.Title>,
79
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
80
+ >(({ className, ...props }, ref) => (
81
+ <SheetPrimitive.Title ref={ref} className={cn("text-lg font-semibold text-foreground", className)} {...props} />
82
+ ));
83
+ SheetTitle.displayName = SheetPrimitive.Title.displayName;
84
+
85
+ const SheetDescription = React.forwardRef<
86
+ React.ElementRef<typeof SheetPrimitive.Description>,
87
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
88
+ >(({ className, ...props }, ref) => (
89
+ <SheetPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
90
+ ));
91
+ SheetDescription.displayName = SheetPrimitive.Description.displayName;
92
+
93
+ export {
94
+ Sheet,
95
+ SheetPortal,
96
+ SheetOverlay,
97
+ SheetTrigger,
98
+ SheetClose,
99
+ SheetContent,
100
+ SheetHeader,
101
+ SheetFooter,
102
+ SheetTitle,
103
+ SheetDescription,
104
+ };