@hilum/ui 0.1.1

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 (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -0
  3. package/dist/icons.d.ts +1 -0
  4. package/dist/icons.js +3 -0
  5. package/dist/icons.js.map +1 -0
  6. package/dist/index.d.ts +992 -0
  7. package/dist/index.js +4433 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/tokens.css +265 -0
  10. package/dist/tokens.d.ts +189 -0
  11. package/dist/tokens.js +193 -0
  12. package/dist/tokens.js.map +1 -0
  13. package/package.json +80 -0
  14. package/src/components/__tests__/data.test.tsx +180 -0
  15. package/src/components/__tests__/disclosure.test.tsx +198 -0
  16. package/src/components/__tests__/display.test.tsx +262 -0
  17. package/src/components/__tests__/feedback.test.tsx +138 -0
  18. package/src/components/__tests__/form.test.tsx +297 -0
  19. package/src/components/__tests__/layout.test.tsx +388 -0
  20. package/src/components/__tests__/overlay.test.tsx +90 -0
  21. package/src/components/__tests__/primitives.test.tsx +248 -0
  22. package/src/components/accordion.tsx +68 -0
  23. package/src/components/action-panel.tsx +112 -0
  24. package/src/components/activity-feed.tsx +69 -0
  25. package/src/components/alert-dialog.tsx +132 -0
  26. package/src/components/alert.tsx +44 -0
  27. package/src/components/aspect-ratio.tsx +19 -0
  28. package/src/components/avatar-stack.tsx +70 -0
  29. package/src/components/avatar.tsx +103 -0
  30. package/src/components/badge.tsx +33 -0
  31. package/src/components/breadcrumb.tsx +76 -0
  32. package/src/components/button-group.tsx +45 -0
  33. package/src/components/button.tsx +60 -0
  34. package/src/components/calendar.tsx +61 -0
  35. package/src/components/card-heading.tsx +60 -0
  36. package/src/components/card.tsx +120 -0
  37. package/src/components/carousel.tsx +222 -0
  38. package/src/components/chart.tsx +77 -0
  39. package/src/components/checkbox.tsx +35 -0
  40. package/src/components/collapsible.tsx +9 -0
  41. package/src/components/color-input.tsx +90 -0
  42. package/src/components/color-picker.tsx +111 -0
  43. package/src/components/combobox.tsx +207 -0
  44. package/src/components/command-palette.tsx +137 -0
  45. package/src/components/command.tsx +248 -0
  46. package/src/components/context-menu.tsx +201 -0
  47. package/src/components/data-table.tsx +151 -0
  48. package/src/components/date-picker.tsx +56 -0
  49. package/src/components/description-list.tsx +70 -0
  50. package/src/components/dialog.tsx +97 -0
  51. package/src/components/drawer.tsx +90 -0
  52. package/src/components/dropdown-menu.tsx +202 -0
  53. package/src/components/empty-state.tsx +43 -0
  54. package/src/components/field.tsx +45 -0
  55. package/src/components/grid-list.tsx +159 -0
  56. package/src/components/hover-card.tsx +34 -0
  57. package/src/components/input-group.tsx +134 -0
  58. package/src/components/input-number.tsx +142 -0
  59. package/src/components/input-otp.tsx +68 -0
  60. package/src/components/input.tsx +23 -0
  61. package/src/components/kbd.tsx +18 -0
  62. package/src/components/label.tsx +23 -0
  63. package/src/components/media-object.tsx +49 -0
  64. package/src/components/menubar.tsx +235 -0
  65. package/src/components/native-select.tsx +44 -0
  66. package/src/components/navigation-menu.tsx +158 -0
  67. package/src/components/notification.tsx +109 -0
  68. package/src/components/page-heading.tsx +125 -0
  69. package/src/components/pagination.tsx +89 -0
  70. package/src/components/popover.tsx +35 -0
  71. package/src/components/progress.tsx +23 -0
  72. package/src/components/property-row.tsx +56 -0
  73. package/src/components/radio-card.tsx +105 -0
  74. package/src/components/radio-group.tsx +38 -0
  75. package/src/components/resizable.tsx +74 -0
  76. package/src/components/scroll-area.tsx +42 -0
  77. package/src/components/section-heading.tsx +64 -0
  78. package/src/components/select.tsx +130 -0
  79. package/src/components/separator.tsx +25 -0
  80. package/src/components/sheet.tsx +117 -0
  81. package/src/components/sidebar.tsx +636 -0
  82. package/src/components/skeleton.tsx +14 -0
  83. package/src/components/slider.tsx +24 -0
  84. package/src/components/sonner.tsx +34 -0
  85. package/src/components/spinner.tsx +37 -0
  86. package/src/components/stacked-list.tsx +64 -0
  87. package/src/components/stat-card.tsx +54 -0
  88. package/src/components/steps.tsx +179 -0
  89. package/src/components/switch.tsx +33 -0
  90. package/src/components/table.tsx +101 -0
  91. package/src/components/tabs.tsx +55 -0
  92. package/src/components/textarea.tsx +21 -0
  93. package/src/components/toggle-group.tsx +49 -0
  94. package/src/components/toggle.tsx +66 -0
  95. package/src/components/tooltip.tsx +43 -0
  96. package/src/icons.ts +158 -0
  97. package/src/index.ts +84 -0
  98. package/src/lib/utils.ts +6 -0
  99. package/src/tokens/tokens.ts +194 -0
@@ -0,0 +1,201 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { ContextMenu } from "radix-ui";
5
+ import { Check, ChevronRight, Circle } from "lucide-react";
6
+ import { cn } from "../lib/utils";
7
+
8
+ const ContextMenuRoot = ContextMenu.Root;
9
+ const ContextMenuTrigger = ContextMenu.Trigger;
10
+ const ContextMenuGroup = ContextMenu.Group;
11
+ const ContextMenuPortal = ContextMenu.Portal;
12
+ const ContextMenuSub = ContextMenu.Sub;
13
+ const ContextMenuRadioGroup = ContextMenu.RadioGroup;
14
+
15
+ const ContextMenuContent = React.forwardRef<
16
+ React.ComponentRef<typeof ContextMenu.Content>,
17
+ React.ComponentPropsWithoutRef<typeof ContextMenu.Content>
18
+ >(({ className, ...props }, ref) => (
19
+ <ContextMenu.Portal>
20
+ <ContextMenu.Content
21
+ ref={ref}
22
+ className={cn(
23
+ "z-50 min-w-[8rem] bg-white rounded-xl shadow-natural p-1",
24
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
25
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
26
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
27
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
28
+ "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
29
+ className,
30
+ )}
31
+ {...props}
32
+ />
33
+ </ContextMenu.Portal>
34
+ ));
35
+ ContextMenuContent.displayName = "ContextMenuContent";
36
+
37
+ const ContextMenuItem = React.forwardRef<
38
+ React.ComponentRef<typeof ContextMenu.Item>,
39
+ React.ComponentPropsWithoutRef<typeof ContextMenu.Item> & {
40
+ inset?: boolean;
41
+ destructive?: boolean;
42
+ }
43
+ >(({ className, inset, destructive, ...props }, ref) => (
44
+ <ContextMenu.Item
45
+ ref={ref}
46
+ className={cn(
47
+ "relative flex cursor-default select-none items-center gap-2 rounded-md px-2.5 py-1.5",
48
+ "body outline-none transition-colors",
49
+ destructive
50
+ ? "text-red-600 focus:bg-red-50 focus:text-red-700"
51
+ : "text-taupe-700 focus:bg-taupe-50 focus:text-taupe-900",
52
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
53
+ inset && "pl-8",
54
+ className,
55
+ )}
56
+ {...props}
57
+ />
58
+ ));
59
+ ContextMenuItem.displayName = "ContextMenuItem";
60
+
61
+ const ContextMenuCheckboxItem = React.forwardRef<
62
+ React.ComponentRef<typeof ContextMenu.CheckboxItem>,
63
+ React.ComponentPropsWithoutRef<typeof ContextMenu.CheckboxItem>
64
+ >(({ className, children, checked, ...props }, ref) => (
65
+ <ContextMenu.CheckboxItem
66
+ ref={ref}
67
+ className={cn(
68
+ "relative flex cursor-default select-none items-center gap-2 rounded-md pl-8 pr-2.5 py-1.5",
69
+ "body text-taupe-700 outline-none transition-colors",
70
+ "focus:bg-taupe-50 focus:text-taupe-900",
71
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
72
+ className,
73
+ )}
74
+ checked={checked}
75
+ {...props}
76
+ >
77
+ <span className="absolute left-2 flex size-3.5 items-center justify-center">
78
+ <ContextMenu.ItemIndicator>
79
+ <Check size={13} className="text-taupe-900" />
80
+ </ContextMenu.ItemIndicator>
81
+ </span>
82
+ {children}
83
+ </ContextMenu.CheckboxItem>
84
+ ));
85
+ ContextMenuCheckboxItem.displayName = "ContextMenuCheckboxItem";
86
+
87
+ const ContextMenuRadioItem = React.forwardRef<
88
+ React.ComponentRef<typeof ContextMenu.RadioItem>,
89
+ React.ComponentPropsWithoutRef<typeof ContextMenu.RadioItem>
90
+ >(({ className, children, ...props }, ref) => (
91
+ <ContextMenu.RadioItem
92
+ ref={ref}
93
+ className={cn(
94
+ "relative flex cursor-default select-none items-center gap-2 rounded-md pl-8 pr-2.5 py-1.5",
95
+ "body text-taupe-700 outline-none transition-colors",
96
+ "focus:bg-taupe-50 focus:text-taupe-900",
97
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
98
+ className,
99
+ )}
100
+ {...props}
101
+ >
102
+ <span className="absolute left-2 flex size-3.5 items-center justify-center">
103
+ <ContextMenu.ItemIndicator>
104
+ <Circle size={8} className="fill-taupe-900 text-taupe-900" />
105
+ </ContextMenu.ItemIndicator>
106
+ </span>
107
+ {children}
108
+ </ContextMenu.RadioItem>
109
+ ));
110
+ ContextMenuRadioItem.displayName = "ContextMenuRadioItem";
111
+
112
+ const ContextMenuLabel = React.forwardRef<
113
+ React.ComponentRef<typeof ContextMenu.Label>,
114
+ React.ComponentPropsWithoutRef<typeof ContextMenu.Label> & {
115
+ inset?: boolean;
116
+ }
117
+ >(({ className, inset, ...props }, ref) => (
118
+ <ContextMenu.Label
119
+ ref={ref}
120
+ className={cn("px-2.5 py-1 label text-taupe-400", inset && "pl-8", className)}
121
+ {...props}
122
+ />
123
+ ));
124
+ ContextMenuLabel.displayName = "ContextMenuLabel";
125
+
126
+ const ContextMenuSeparator = React.forwardRef<
127
+ React.ComponentRef<typeof ContextMenu.Separator>,
128
+ React.ComponentPropsWithoutRef<typeof ContextMenu.Separator>
129
+ >(({ className, ...props }, ref) => (
130
+ <ContextMenu.Separator
131
+ ref={ref}
132
+ className={cn("mx-1 my-1 h-px bg-taupe-100", className)}
133
+ {...props}
134
+ />
135
+ ));
136
+ ContextMenuSeparator.displayName = "ContextMenuSeparator";
137
+
138
+ const ContextMenuSubTrigger = React.forwardRef<
139
+ React.ComponentRef<typeof ContextMenu.SubTrigger>,
140
+ React.ComponentPropsWithoutRef<typeof ContextMenu.SubTrigger> & {
141
+ inset?: boolean;
142
+ }
143
+ >(({ className, inset, children, ...props }, ref) => (
144
+ <ContextMenu.SubTrigger
145
+ ref={ref}
146
+ className={cn(
147
+ "relative flex cursor-default select-none items-center gap-2 rounded-md px-2.5 py-1.5",
148
+ "body text-taupe-700 outline-none transition-colors",
149
+ "focus:bg-taupe-50 focus:text-taupe-900 data-[state=open]:bg-taupe-50",
150
+ inset && "pl-8",
151
+ className,
152
+ )}
153
+ {...props}
154
+ >
155
+ {children}
156
+ <ChevronRight size={14} className="ml-auto text-taupe-400" />
157
+ </ContextMenu.SubTrigger>
158
+ ));
159
+ ContextMenuSubTrigger.displayName = "ContextMenuSubTrigger";
160
+
161
+ const ContextMenuSubContent = React.forwardRef<
162
+ React.ComponentRef<typeof ContextMenu.SubContent>,
163
+ React.ComponentPropsWithoutRef<typeof ContextMenu.SubContent>
164
+ >(({ className, ...props }, ref) => (
165
+ <ContextMenu.SubContent
166
+ ref={ref}
167
+ className={cn(
168
+ "z-50 min-w-[8rem] bg-white rounded-xl border border-taupe-100 shadow-natural p-1",
169
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
170
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
171
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
172
+ "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
173
+ className,
174
+ )}
175
+ {...props}
176
+ />
177
+ ));
178
+ ContextMenuSubContent.displayName = "ContextMenuSubContent";
179
+
180
+ function ContextMenuShortcut({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) {
181
+ return <span className={cn("ml-auto caption text-taupe-400", className)} {...props} />;
182
+ }
183
+ ContextMenuShortcut.displayName = "ContextMenuShortcut";
184
+
185
+ export {
186
+ ContextMenuRoot as ContextMenu,
187
+ ContextMenuTrigger,
188
+ ContextMenuContent,
189
+ ContextMenuItem,
190
+ ContextMenuCheckboxItem,
191
+ ContextMenuRadioItem,
192
+ ContextMenuRadioGroup,
193
+ ContextMenuLabel,
194
+ ContextMenuSeparator,
195
+ ContextMenuSub,
196
+ ContextMenuSubTrigger,
197
+ ContextMenuSubContent,
198
+ ContextMenuShortcut,
199
+ ContextMenuGroup,
200
+ ContextMenuPortal,
201
+ };
@@ -0,0 +1,151 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import {
5
+ useReactTable,
6
+ getCoreRowModel,
7
+ getSortedRowModel,
8
+ getFilteredRowModel,
9
+ getPaginationRowModel,
10
+ flexRender,
11
+ type ColumnDef,
12
+ type SortingState,
13
+ type ColumnFiltersState,
14
+ } from "@tanstack/react-table";
15
+ import { ChevronUp, ChevronDown, ChevronsUpDown } from "lucide-react";
16
+ import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "./table";
17
+ import { Button } from "./button";
18
+ import { Input } from "./input";
19
+ import { cn } from "../lib/utils";
20
+
21
+ interface DataTableProps<TData> {
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ columns: ColumnDef<TData, any>[];
24
+ data: TData[];
25
+ searchKey?: string;
26
+ searchPlaceholder?: string;
27
+ pageSize?: number;
28
+ showPagination?: boolean;
29
+ }
30
+
31
+ function DataTable<TData>({
32
+ columns,
33
+ data,
34
+ searchKey,
35
+ searchPlaceholder = "Search...",
36
+ pageSize = 10,
37
+ showPagination = true,
38
+ }: DataTableProps<TData>) {
39
+ const [sorting, setSorting] = React.useState<SortingState>([]);
40
+ const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
41
+
42
+ const table = useReactTable({
43
+ data,
44
+ columns,
45
+ getCoreRowModel: getCoreRowModel(),
46
+ getSortedRowModel: getSortedRowModel(),
47
+ getFilteredRowModel: getFilteredRowModel(),
48
+ getPaginationRowModel: getPaginationRowModel(),
49
+ onSortingChange: setSorting,
50
+ onColumnFiltersChange: setColumnFilters,
51
+ state: { sorting, columnFilters },
52
+ initialState: { pagination: { pageSize } },
53
+ });
54
+
55
+ return (
56
+ <div className="flex flex-col gap-4">
57
+ {searchKey && (
58
+ <Input
59
+ placeholder={searchPlaceholder}
60
+ value={(table.getColumn(searchKey)?.getFilterValue() as string) ?? ""}
61
+ onChange={(e) => table.getColumn(searchKey)?.setFilterValue(e.target.value)}
62
+ className="max-w-sm"
63
+ />
64
+ )}
65
+ <Table>
66
+ <TableHeader>
67
+ {table.getHeaderGroups().map((headerGroup) => (
68
+ <TableRow key={headerGroup.id}>
69
+ {headerGroup.headers.map((header) => (
70
+ <TableHead key={header.id}>
71
+ {header.isPlaceholder ? null : header.column.getCanSort() ? (
72
+ <button
73
+ className={cn(
74
+ "flex items-center gap-1 transition-colors hover:text-taupe-700",
75
+ header.column.getIsSorted() && "text-taupe-900",
76
+ )}
77
+ onClick={header.column.getToggleSortingHandler()}
78
+ >
79
+ {flexRender(header.column.columnDef.header, header.getContext())}
80
+ {header.column.getIsSorted() === "asc" ? (
81
+ <ChevronUp size={12} className="text-brand-primary" />
82
+ ) : header.column.getIsSorted() === "desc" ? (
83
+ <ChevronDown size={12} className="text-brand-primary" />
84
+ ) : (
85
+ <ChevronsUpDown size={12} className="text-taupe-300" />
86
+ )}
87
+ </button>
88
+ ) : (
89
+ flexRender(header.column.columnDef.header, header.getContext())
90
+ )}
91
+ </TableHead>
92
+ ))}
93
+ </TableRow>
94
+ ))}
95
+ </TableHeader>
96
+ <TableBody>
97
+ {table.getRowModel().rows?.length ? (
98
+ table.getRowModel().rows.map((row) => (
99
+ <TableRow key={row.id}>
100
+ {row.getVisibleCells().map((cell) => (
101
+ <TableCell key={cell.id}>
102
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
103
+ </TableCell>
104
+ ))}
105
+ </TableRow>
106
+ ))
107
+ ) : (
108
+ <TableRow>
109
+ <TableCell colSpan={columns.length} className="h-24 text-center text-taupe-400">
110
+ No results.
111
+ </TableCell>
112
+ </TableRow>
113
+ )}
114
+ </TableBody>
115
+ </Table>
116
+ {showPagination && (
117
+ <div className="flex items-center justify-between">
118
+ <p className="caption text-taupe-400">
119
+ {table.getFilteredRowModel().rows.length} result
120
+ {table.getFilteredRowModel().rows.length !== 1 ? "s" : ""}
121
+ </p>
122
+ <div className="flex items-center gap-2">
123
+ <span className="caption text-taupe-400">
124
+ Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
125
+ </span>
126
+ <Button
127
+ variant="outline"
128
+ size="sm"
129
+ onClick={() => table.previousPage()}
130
+ disabled={!table.getCanPreviousPage()}
131
+ >
132
+ Previous
133
+ </Button>
134
+ <Button
135
+ variant="outline"
136
+ size="sm"
137
+ onClick={() => table.nextPage()}
138
+ disabled={!table.getCanNextPage()}
139
+ >
140
+ Next
141
+ </Button>
142
+ </div>
143
+ </div>
144
+ )}
145
+ </div>
146
+ );
147
+ }
148
+
149
+ export { DataTable };
150
+ export { createColumnHelper } from "@tanstack/react-table";
151
+ export type { ColumnDef };
@@ -0,0 +1,56 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { CalendarIcon } from "lucide-react";
5
+ import { Popover, PopoverContent, PopoverTrigger } from "./popover";
6
+ import { Calendar } from "./calendar";
7
+ import { Button } from "./button";
8
+ import { cn } from "../lib/utils";
9
+
10
+ const formatDate = (date: Date) =>
11
+ date.toLocaleDateString("en-US", {
12
+ month: "long",
13
+ day: "numeric",
14
+ year: "numeric",
15
+ });
16
+
17
+ interface DatePickerProps {
18
+ value?: Date;
19
+ onChange?: (date: Date | undefined) => void;
20
+ placeholder?: string;
21
+ disabled?: boolean;
22
+ className?: string;
23
+ }
24
+
25
+ function DatePicker({
26
+ value,
27
+ onChange,
28
+ placeholder = "Pick a date",
29
+ disabled,
30
+ className,
31
+ }: DatePickerProps) {
32
+ return (
33
+ <Popover>
34
+ <PopoverTrigger asChild>
35
+ <Button
36
+ variant="outline"
37
+ className={cn(
38
+ "w-[240px] justify-start gap-2 text-left font-normal",
39
+ !value && "text-taupe-400",
40
+ className,
41
+ )}
42
+ disabled={disabled}
43
+ >
44
+ <CalendarIcon size={14} className="text-taupe-400 shrink-0" />
45
+ {value ? formatDate(value) : <span>{placeholder}</span>}
46
+ </Button>
47
+ </PopoverTrigger>
48
+ <PopoverContent className="w-auto p-0 shadow-elevated" align="start">
49
+ <Calendar mode="single" selected={value} onSelect={onChange} autoFocus />
50
+ </PopoverContent>
51
+ </Popover>
52
+ );
53
+ }
54
+ DatePicker.displayName = "DatePicker";
55
+
56
+ export { DatePicker, formatDate };
@@ -0,0 +1,70 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ interface DescriptionItem {
5
+ term: string;
6
+ details: React.ReactNode;
7
+ action?: React.ReactNode;
8
+ }
9
+
10
+ interface DescriptionListProps {
11
+ title?: string;
12
+ description?: string;
13
+ items: DescriptionItem[];
14
+ columns?: 1 | 2;
15
+ striped?: boolean;
16
+ className?: string;
17
+ }
18
+
19
+ function DescriptionList({
20
+ title,
21
+ description,
22
+ items,
23
+ columns = 1,
24
+ striped = false,
25
+ className,
26
+ }: DescriptionListProps) {
27
+ return (
28
+ <div className={className}>
29
+ {(title || description) && (
30
+ <div className="mb-4">
31
+ {title && <p className="body font-semibold text-taupe-900">{title}</p>}
32
+ {description && <p className="mt-1 caption text-taupe-400">{description}</p>}
33
+ </div>
34
+ )}
35
+
36
+ <dl className={cn("border-t border-taupe-100", columns === 2 && "sm:grid sm:grid-cols-2")}>
37
+ {items.map((item, i) => (
38
+ <div
39
+ key={i}
40
+ className={cn(
41
+ "py-3.5",
42
+ columns === 1
43
+ ? "grid grid-cols-3 gap-4 items-baseline"
44
+ : "flex flex-col gap-1 border-b border-taupe-100",
45
+ columns === 1 && i !== items.length - 1 && "border-b border-taupe-100",
46
+ striped && i % 2 === 0 && "bg-taupe-50 -mx-4 px-4 rounded",
47
+ )}
48
+ >
49
+ <dt className="caption font-semibold text-taupe-500 uppercase tracking-wide">
50
+ {item.term}
51
+ </dt>
52
+ <dd className={cn("body text-taupe-900", columns === 1 ? "col-span-2" : "")}>
53
+ {item.action ? (
54
+ <div className="flex items-center justify-between gap-4">
55
+ <span>{item.details}</span>
56
+ {item.action}
57
+ </div>
58
+ ) : (
59
+ item.details
60
+ )}
61
+ </dd>
62
+ </div>
63
+ ))}
64
+ </dl>
65
+ </div>
66
+ );
67
+ }
68
+
69
+ export { DescriptionList };
70
+ export type { DescriptionItem };
@@ -0,0 +1,97 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Dialog } from "radix-ui";
5
+ import { X } from "lucide-react";
6
+ import { cn } from "../lib/utils";
7
+
8
+ const DialogRoot = Dialog.Root;
9
+ const DialogTrigger = Dialog.Trigger;
10
+ const DialogClose = Dialog.Close;
11
+
12
+ const DialogOverlay = React.forwardRef<
13
+ React.ComponentRef<typeof Dialog.Overlay>,
14
+ React.ComponentPropsWithoutRef<typeof Dialog.Overlay>
15
+ >(({ className, ...props }, ref) => (
16
+ <Dialog.Overlay
17
+ ref={ref}
18
+ className={cn(
19
+ "fixed inset-0 z-50 bg-black/30 backdrop-blur-sm",
20
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
21
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
22
+ className,
23
+ )}
24
+ {...props}
25
+ />
26
+ ));
27
+ DialogOverlay.displayName = "DialogOverlay";
28
+
29
+ const DialogContent = React.forwardRef<
30
+ React.ComponentRef<typeof Dialog.Content>,
31
+ React.ComponentPropsWithoutRef<typeof Dialog.Content>
32
+ >(({ className, children, ...props }, ref) => (
33
+ <Dialog.Portal>
34
+ <DialogOverlay />
35
+ <Dialog.Content
36
+ ref={ref}
37
+ className={cn(
38
+ "fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%]",
39
+ "w-full max-w-lg bg-white rounded-2xl shadow-elevated p-6",
40
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
41
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
42
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
43
+ className,
44
+ )}
45
+ {...props}
46
+ >
47
+ {children}
48
+ <Dialog.Close className="absolute right-4 top-4 rounded-md p-1 text-taupe-400 opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-taupe-900/20">
49
+ <X size={16} />
50
+ <span className="sr-only">Close</span>
51
+ </Dialog.Close>
52
+ </Dialog.Content>
53
+ </Dialog.Portal>
54
+ ));
55
+ DialogContent.displayName = "DialogContent";
56
+
57
+ function DialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
58
+ return <div className={cn("flex flex-col gap-1.5 mb-4", className)} {...props} />;
59
+ }
60
+ DialogHeader.displayName = "DialogHeader";
61
+
62
+ function DialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
63
+ return <div className={cn("flex items-center justify-end gap-2 mt-6", className)} {...props} />;
64
+ }
65
+ DialogFooter.displayName = "DialogFooter";
66
+
67
+ const DialogTitle = React.forwardRef<
68
+ React.ComponentRef<typeof Dialog.Title>,
69
+ React.ComponentPropsWithoutRef<typeof Dialog.Title>
70
+ >(({ className, ...props }, ref) => (
71
+ <Dialog.Title
72
+ ref={ref}
73
+ className={cn("body-lg font-semibold text-taupe-900", className)}
74
+ {...props}
75
+ />
76
+ ));
77
+ DialogTitle.displayName = "DialogTitle";
78
+
79
+ const DialogDescription = React.forwardRef<
80
+ React.ComponentRef<typeof Dialog.Description>,
81
+ React.ComponentPropsWithoutRef<typeof Dialog.Description>
82
+ >(({ className, ...props }, ref) => (
83
+ <Dialog.Description ref={ref} className={cn("body text-taupe-500", className)} {...props} />
84
+ ));
85
+ DialogDescription.displayName = "DialogDescription";
86
+
87
+ export {
88
+ DialogRoot as Dialog,
89
+ DialogTrigger,
90
+ DialogOverlay,
91
+ DialogContent,
92
+ DialogHeader,
93
+ DialogFooter,
94
+ DialogTitle,
95
+ DialogDescription,
96
+ DialogClose,
97
+ };
@@ -0,0 +1,90 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Drawer as DrawerPrimitive } from "vaul";
5
+ import { cn } from "../lib/utils";
6
+
7
+ const DrawerRoot = DrawerPrimitive.Root;
8
+ const DrawerTrigger = DrawerPrimitive.Trigger;
9
+ const DrawerPortal = DrawerPrimitive.Portal;
10
+ const DrawerClose = DrawerPrimitive.Close;
11
+
12
+ const DrawerOverlay = React.forwardRef<
13
+ React.ComponentRef<typeof DrawerPrimitive.Overlay>,
14
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
15
+ >(({ className, ...props }, ref) => (
16
+ <DrawerPrimitive.Overlay
17
+ ref={ref}
18
+ className={cn("fixed inset-0 z-50 bg-taupe-950/40 backdrop-blur-[1px]", className)}
19
+ {...props}
20
+ />
21
+ ));
22
+ DrawerOverlay.displayName = "DrawerOverlay";
23
+
24
+ const DrawerContent = React.forwardRef<
25
+ React.ComponentRef<typeof DrawerPrimitive.Content>,
26
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
27
+ >(({ className, children, ...props }, ref) => (
28
+ <DrawerPortal>
29
+ <DrawerOverlay />
30
+ <DrawerPrimitive.Content
31
+ ref={ref}
32
+ className={cn(
33
+ "fixed inset-x-0 bottom-0 z-50 flex flex-col rounded-t-2xl border border-taupe-100 bg-white",
34
+ className,
35
+ )}
36
+ {...props}
37
+ >
38
+ <div className="mx-auto mt-4 h-1 w-12 rounded-full bg-taupe-200" />
39
+ {children}
40
+ </DrawerPrimitive.Content>
41
+ </DrawerPortal>
42
+ ));
43
+ DrawerContent.displayName = "DrawerContent";
44
+
45
+ const DrawerHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
46
+ <div className={cn("grid gap-1 px-6 py-4 text-center sm:text-left", className)} {...props} />
47
+ );
48
+ DrawerHeader.displayName = "DrawerHeader";
49
+
50
+ const DrawerFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
51
+ <div className={cn("mt-auto flex flex-col gap-2 px-6 pb-6", className)} {...props} />
52
+ );
53
+ DrawerFooter.displayName = "DrawerFooter";
54
+
55
+ const DrawerTitle = React.forwardRef<
56
+ React.ComponentRef<typeof DrawerPrimitive.Title>,
57
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
58
+ >(({ className, ...props }, ref) => (
59
+ <DrawerPrimitive.Title
60
+ ref={ref}
61
+ className={cn("subheading text-taupe-900", className)}
62
+ {...props}
63
+ />
64
+ ));
65
+ DrawerTitle.displayName = "DrawerTitle";
66
+
67
+ const DrawerDescription = React.forwardRef<
68
+ React.ComponentRef<typeof DrawerPrimitive.Description>,
69
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
70
+ >(({ className, ...props }, ref) => (
71
+ <DrawerPrimitive.Description
72
+ ref={ref}
73
+ className={cn("body text-taupe-500", className)}
74
+ {...props}
75
+ />
76
+ ));
77
+ DrawerDescription.displayName = "DrawerDescription";
78
+
79
+ export {
80
+ DrawerRoot as Drawer,
81
+ DrawerTrigger,
82
+ DrawerPortal,
83
+ DrawerClose,
84
+ DrawerOverlay,
85
+ DrawerContent,
86
+ DrawerHeader,
87
+ DrawerFooter,
88
+ DrawerTitle,
89
+ DrawerDescription,
90
+ };