@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.
- package/LICENSE +21 -0
- package/README.md +42 -0
- package/dist/icons.d.ts +1 -0
- package/dist/icons.js +3 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +992 -0
- package/dist/index.js +4433 -0
- package/dist/index.js.map +1 -0
- package/dist/tokens.css +265 -0
- package/dist/tokens.d.ts +189 -0
- package/dist/tokens.js +193 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +80 -0
- package/src/components/__tests__/data.test.tsx +180 -0
- package/src/components/__tests__/disclosure.test.tsx +198 -0
- package/src/components/__tests__/display.test.tsx +262 -0
- package/src/components/__tests__/feedback.test.tsx +138 -0
- package/src/components/__tests__/form.test.tsx +297 -0
- package/src/components/__tests__/layout.test.tsx +388 -0
- package/src/components/__tests__/overlay.test.tsx +90 -0
- package/src/components/__tests__/primitives.test.tsx +248 -0
- package/src/components/accordion.tsx +68 -0
- package/src/components/action-panel.tsx +112 -0
- package/src/components/activity-feed.tsx +69 -0
- package/src/components/alert-dialog.tsx +132 -0
- package/src/components/alert.tsx +44 -0
- package/src/components/aspect-ratio.tsx +19 -0
- package/src/components/avatar-stack.tsx +70 -0
- package/src/components/avatar.tsx +103 -0
- package/src/components/badge.tsx +33 -0
- package/src/components/breadcrumb.tsx +76 -0
- package/src/components/button-group.tsx +45 -0
- package/src/components/button.tsx +60 -0
- package/src/components/calendar.tsx +61 -0
- package/src/components/card-heading.tsx +60 -0
- package/src/components/card.tsx +120 -0
- package/src/components/carousel.tsx +222 -0
- package/src/components/chart.tsx +77 -0
- package/src/components/checkbox.tsx +35 -0
- package/src/components/collapsible.tsx +9 -0
- package/src/components/color-input.tsx +90 -0
- package/src/components/color-picker.tsx +111 -0
- package/src/components/combobox.tsx +207 -0
- package/src/components/command-palette.tsx +137 -0
- package/src/components/command.tsx +248 -0
- package/src/components/context-menu.tsx +201 -0
- package/src/components/data-table.tsx +151 -0
- package/src/components/date-picker.tsx +56 -0
- package/src/components/description-list.tsx +70 -0
- package/src/components/dialog.tsx +97 -0
- package/src/components/drawer.tsx +90 -0
- package/src/components/dropdown-menu.tsx +202 -0
- package/src/components/empty-state.tsx +43 -0
- package/src/components/field.tsx +45 -0
- package/src/components/grid-list.tsx +159 -0
- package/src/components/hover-card.tsx +34 -0
- package/src/components/input-group.tsx +134 -0
- package/src/components/input-number.tsx +142 -0
- package/src/components/input-otp.tsx +68 -0
- package/src/components/input.tsx +23 -0
- package/src/components/kbd.tsx +18 -0
- package/src/components/label.tsx +23 -0
- package/src/components/media-object.tsx +49 -0
- package/src/components/menubar.tsx +235 -0
- package/src/components/native-select.tsx +44 -0
- package/src/components/navigation-menu.tsx +158 -0
- package/src/components/notification.tsx +109 -0
- package/src/components/page-heading.tsx +125 -0
- package/src/components/pagination.tsx +89 -0
- package/src/components/popover.tsx +35 -0
- package/src/components/progress.tsx +23 -0
- package/src/components/property-row.tsx +56 -0
- package/src/components/radio-card.tsx +105 -0
- package/src/components/radio-group.tsx +38 -0
- package/src/components/resizable.tsx +74 -0
- package/src/components/scroll-area.tsx +42 -0
- package/src/components/section-heading.tsx +64 -0
- package/src/components/select.tsx +130 -0
- package/src/components/separator.tsx +25 -0
- package/src/components/sheet.tsx +117 -0
- package/src/components/sidebar.tsx +636 -0
- package/src/components/skeleton.tsx +14 -0
- package/src/components/slider.tsx +24 -0
- package/src/components/sonner.tsx +34 -0
- package/src/components/spinner.tsx +37 -0
- package/src/components/stacked-list.tsx +64 -0
- package/src/components/stat-card.tsx +54 -0
- package/src/components/steps.tsx +179 -0
- package/src/components/switch.tsx +33 -0
- package/src/components/table.tsx +101 -0
- package/src/components/tabs.tsx +55 -0
- package/src/components/textarea.tsx +21 -0
- package/src/components/toggle-group.tsx +49 -0
- package/src/components/toggle.tsx +66 -0
- package/src/components/tooltip.tsx +43 -0
- package/src/icons.ts +158 -0
- package/src/index.ts +84 -0
- package/src/lib/utils.ts +6 -0
- 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
|
+
};
|