@dyrected/admin 1.0.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/CHANGELOG.md +40 -0
- package/LICENSE.md +50 -0
- package/README.md +73 -0
- package/components.json +17 -0
- package/eslint.config.js +22 -0
- package/index.html +13 -0
- package/package.json +99 -0
- package/postcss.config.js +6 -0
- package/public/favicon.svg +1 -0
- package/public/icons.svg +24 -0
- package/src/App.css +184 -0
- package/src/App.tsx +25 -0
- package/src/assets/dyrected.svg +155 -0
- package/src/assets/hero.png +0 -0
- package/src/assets/react.svg +1 -0
- package/src/assets/vite.svg +1 -0
- package/src/components/auth/auth-gate.tsx +64 -0
- package/src/components/error-boundary.tsx +45 -0
- package/src/components/forms/field-renderer.tsx +111 -0
- package/src/components/forms/fields/block-builder.tsx +213 -0
- package/src/components/forms/fields/date-picker.tsx +60 -0
- package/src/components/forms/fields/json-editor.tsx +62 -0
- package/src/components/forms/fields/media-picker.tsx +286 -0
- package/src/components/forms/fields/multi-select.tsx +145 -0
- package/src/components/forms/fields/radio-field.tsx +51 -0
- package/src/components/forms/fields/relationship-picker.tsx +143 -0
- package/src/components/forms/fields/rich-text-editor.tsx +224 -0
- package/src/components/forms/fields/select-field.tsx +35 -0
- package/src/components/forms/fields/switch-field.tsx +16 -0
- package/src/components/forms/fields/text-area-field.tsx +15 -0
- package/src/components/forms/fields/text-field.tsx +24 -0
- package/src/components/forms/form-engine.tsx +87 -0
- package/src/components/forms/form-field-renderer.tsx +269 -0
- package/src/components/forms/utils.ts +97 -0
- package/src/components/layout/admin-shell.tsx +479 -0
- package/src/components/layout/branding-provider.tsx +112 -0
- package/src/components/live-preview/LivePreviewPane.tsx +128 -0
- package/src/components/media/focal-point-picker.tsx +66 -0
- package/src/components/media/media-card.tsx +44 -0
- package/src/components/media/media-grid.tsx +32 -0
- package/src/components/media/media-library-dialog.tsx +465 -0
- package/src/components/ui/aspect-ratio.tsx +7 -0
- package/src/components/ui/badge.tsx +36 -0
- package/src/components/ui/button.tsx +56 -0
- package/src/components/ui/calendar.tsx +214 -0
- package/src/components/ui/card.tsx +79 -0
- package/src/components/ui/checkbox.tsx +28 -0
- package/src/components/ui/command.tsx +151 -0
- package/src/components/ui/data-table.tsx +219 -0
- package/src/components/ui/dialog.tsx +122 -0
- package/src/components/ui/dropdown-menu.tsx +200 -0
- package/src/components/ui/form.tsx +178 -0
- package/src/components/ui/input.tsx +24 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/page-header.tsx +30 -0
- package/src/components/ui/pagination.tsx +57 -0
- package/src/components/ui/popover.tsx +29 -0
- package/src/components/ui/progress.tsx +26 -0
- package/src/components/ui/radio-group.tsx +42 -0
- package/src/components/ui/render-cell.tsx +110 -0
- package/src/components/ui/scroll-area.tsx +46 -0
- package/src/components/ui/select.tsx +160 -0
- package/src/components/ui/separator.tsx +29 -0
- package/src/components/ui/sheet.tsx +140 -0
- package/src/components/ui/sidebar.tsx +771 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/ui/sonner.tsx +27 -0
- package/src/components/ui/switch.tsx +27 -0
- package/src/components/ui/table.tsx +117 -0
- package/src/components/ui/tabs.tsx +53 -0
- package/src/components/ui/textarea.tsx +22 -0
- package/src/components/ui/toggle.tsx +43 -0
- package/src/components/ui/tooltip.tsx +28 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/hooks/use-preferences.ts +56 -0
- package/src/index.css +111 -0
- package/src/index.tsx +198 -0
- package/src/lib/utils.ts +32 -0
- package/src/main.tsx +10 -0
- package/src/pages/auth/first-user-page.tsx +115 -0
- package/src/pages/auth/login-page.tsx +91 -0
- package/src/pages/collections/edit-page.tsx +280 -0
- package/src/pages/collections/list-page.tsx +343 -0
- package/src/pages/dashboard/dashboard.tsx +150 -0
- package/src/pages/globals/editor-page.tsx +122 -0
- package/src/pages/media/media-page.tsx +564 -0
- package/src/pages/setup/setup-prompt.tsx +152 -0
- package/src/providers/dyrected-provider.tsx +122 -0
- package/src/providers/query-provider.tsx +19 -0
- package/src/types/jexl.d.ts +11 -0
- package/tailwind.config.ts +102 -0
- package/tsconfig.app.json +29 -0
- package/tsconfig.json +12 -0
- package/tsconfig.node.json +27 -0
- package/vite.config.ts +36 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import {
|
|
3
|
+
ChevronDownIcon,
|
|
4
|
+
ChevronLeftIcon,
|
|
5
|
+
ChevronRightIcon,
|
|
6
|
+
} from "lucide-react"
|
|
7
|
+
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
|
|
8
|
+
|
|
9
|
+
import { cn } from "../../lib/utils"
|
|
10
|
+
import { Button, buttonVariants } from "../../components/ui/button"
|
|
11
|
+
|
|
12
|
+
function Calendar({
|
|
13
|
+
className,
|
|
14
|
+
classNames,
|
|
15
|
+
showOutsideDays = true,
|
|
16
|
+
captionLayout = "label",
|
|
17
|
+
buttonVariant = "ghost",
|
|
18
|
+
formatters,
|
|
19
|
+
components,
|
|
20
|
+
...props
|
|
21
|
+
}: React.ComponentProps<typeof DayPicker> & {
|
|
22
|
+
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
|
|
23
|
+
}) {
|
|
24
|
+
const defaultClassNames = getDefaultClassNames()
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<DayPicker
|
|
28
|
+
showOutsideDays={showOutsideDays}
|
|
29
|
+
className={cn(
|
|
30
|
+
"bg-white group/calendar p-4 [--cell-size:2.25rem] shadow-sm rounded-xl border border-border/40",
|
|
31
|
+
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
|
32
|
+
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
|
33
|
+
className
|
|
34
|
+
)}
|
|
35
|
+
captionLayout={captionLayout}
|
|
36
|
+
formatters={{
|
|
37
|
+
formatMonthDropdown: (date) =>
|
|
38
|
+
date.toLocaleString("default", { month: "short" }),
|
|
39
|
+
...formatters,
|
|
40
|
+
}}
|
|
41
|
+
classNames={{
|
|
42
|
+
root: cn("w-fit", defaultClassNames.root),
|
|
43
|
+
months: cn(
|
|
44
|
+
"relative flex flex-col gap-4 md:flex-row",
|
|
45
|
+
defaultClassNames.months
|
|
46
|
+
),
|
|
47
|
+
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
|
|
48
|
+
nav: cn(
|
|
49
|
+
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
|
|
50
|
+
defaultClassNames.nav
|
|
51
|
+
),
|
|
52
|
+
button_previous: cn(
|
|
53
|
+
buttonVariants({ variant: buttonVariant }),
|
|
54
|
+
"h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
|
|
55
|
+
defaultClassNames.button_previous
|
|
56
|
+
),
|
|
57
|
+
button_next: cn(
|
|
58
|
+
buttonVariants({ variant: buttonVariant }),
|
|
59
|
+
"h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
|
|
60
|
+
defaultClassNames.button_next
|
|
61
|
+
),
|
|
62
|
+
month_caption: cn(
|
|
63
|
+
"flex h-[--cell-size] w-full items-center justify-center px-[--cell-size] mb-4",
|
|
64
|
+
defaultClassNames.month_caption
|
|
65
|
+
),
|
|
66
|
+
dropdowns: cn(
|
|
67
|
+
"flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium",
|
|
68
|
+
defaultClassNames.dropdowns
|
|
69
|
+
),
|
|
70
|
+
dropdown_root: cn(
|
|
71
|
+
"has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border",
|
|
72
|
+
defaultClassNames.dropdown_root
|
|
73
|
+
),
|
|
74
|
+
dropdown: cn(
|
|
75
|
+
"bg-popover absolute inset-0 opacity-0",
|
|
76
|
+
defaultClassNames.dropdown
|
|
77
|
+
),
|
|
78
|
+
caption_label: cn(
|
|
79
|
+
"select-none font-semibold text-lg tracking-tight",
|
|
80
|
+
captionLayout === "label"
|
|
81
|
+
? "text-sm"
|
|
82
|
+
: "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
|
|
83
|
+
defaultClassNames.caption_label
|
|
84
|
+
),
|
|
85
|
+
table: "w-full border-collapse",
|
|
86
|
+
weekdays: cn("grid grid-cols-7 mb-2", defaultClassNames.weekdays),
|
|
87
|
+
weekday: cn(
|
|
88
|
+
"text-muted-foreground text-center select-none text-[0.8rem] font-medium",
|
|
89
|
+
defaultClassNames.weekday
|
|
90
|
+
),
|
|
91
|
+
week: cn("grid grid-cols-7 w-full mt-1", defaultClassNames.week),
|
|
92
|
+
week_number_header: cn(
|
|
93
|
+
"w-[--cell-size] select-none",
|
|
94
|
+
defaultClassNames.week_number_header
|
|
95
|
+
),
|
|
96
|
+
week_number: cn(
|
|
97
|
+
"text-muted-foreground select-none text-[0.8rem]",
|
|
98
|
+
defaultClassNames.week_number
|
|
99
|
+
),
|
|
100
|
+
day: cn(
|
|
101
|
+
"group/day relative flex items-center justify-center h-[--cell-size] w-full select-none p-0 text-center",
|
|
102
|
+
defaultClassNames.day
|
|
103
|
+
),
|
|
104
|
+
range_start: cn(
|
|
105
|
+
"bg-primary text-primary-foreground rounded-l-md",
|
|
106
|
+
defaultClassNames.range_start
|
|
107
|
+
),
|
|
108
|
+
range_middle: cn("bg-accent text-accent-foreground rounded-none", defaultClassNames.range_middle),
|
|
109
|
+
range_end: cn("bg-primary text-primary-foreground rounded-r-md", defaultClassNames.range_end),
|
|
110
|
+
today: cn(
|
|
111
|
+
"bg-accent/50 text-accent-foreground font-bold rounded-md",
|
|
112
|
+
defaultClassNames.today
|
|
113
|
+
),
|
|
114
|
+
outside: cn(
|
|
115
|
+
"text-muted-foreground/40 aria-selected:text-muted-foreground/40 opacity-50",
|
|
116
|
+
defaultClassNames.outside
|
|
117
|
+
),
|
|
118
|
+
disabled: cn(
|
|
119
|
+
"text-muted-foreground opacity-50",
|
|
120
|
+
defaultClassNames.disabled
|
|
121
|
+
),
|
|
122
|
+
hidden: cn("invisible", defaultClassNames.hidden),
|
|
123
|
+
...classNames,
|
|
124
|
+
}}
|
|
125
|
+
components={{
|
|
126
|
+
Root: ({ className, rootRef, ...props }) => {
|
|
127
|
+
return (
|
|
128
|
+
<div
|
|
129
|
+
data-slot="calendar"
|
|
130
|
+
ref={rootRef}
|
|
131
|
+
className={cn(className)}
|
|
132
|
+
{...props}
|
|
133
|
+
/>
|
|
134
|
+
)
|
|
135
|
+
},
|
|
136
|
+
Chevron: ({ className, orientation, ...props }) => {
|
|
137
|
+
if (orientation === "left") {
|
|
138
|
+
return (
|
|
139
|
+
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (orientation === "right") {
|
|
144
|
+
return (
|
|
145
|
+
<ChevronRightIcon
|
|
146
|
+
className={cn("size-4", className)}
|
|
147
|
+
{...props}
|
|
148
|
+
/>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<ChevronDownIcon className={cn("size-4", className)} {...props} />
|
|
154
|
+
)
|
|
155
|
+
},
|
|
156
|
+
DayButton: CalendarDayButton,
|
|
157
|
+
WeekNumber: ({ children, ...props }) => {
|
|
158
|
+
return (
|
|
159
|
+
<td {...props}>
|
|
160
|
+
<div className="flex size-[--cell-size] items-center justify-center text-center">
|
|
161
|
+
{children}
|
|
162
|
+
</div>
|
|
163
|
+
</td>
|
|
164
|
+
)
|
|
165
|
+
},
|
|
166
|
+
...components,
|
|
167
|
+
}}
|
|
168
|
+
{...props}
|
|
169
|
+
/>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function CalendarDayButton({
|
|
174
|
+
className,
|
|
175
|
+
day,
|
|
176
|
+
modifiers,
|
|
177
|
+
...props
|
|
178
|
+
}: React.ComponentProps<typeof DayButton>) {
|
|
179
|
+
const ref = React.useRef<HTMLButtonElement>(null)
|
|
180
|
+
React.useEffect(() => {
|
|
181
|
+
if (modifiers.focused) ref.current?.focus()
|
|
182
|
+
}, [modifiers.focused])
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<Button
|
|
186
|
+
ref={ref}
|
|
187
|
+
variant="ghost"
|
|
188
|
+
size="icon"
|
|
189
|
+
data-day={day.date.toLocaleDateString()}
|
|
190
|
+
data-selected-single={
|
|
191
|
+
modifiers.selected &&
|
|
192
|
+
!modifiers.range_start &&
|
|
193
|
+
!modifiers.range_end &&
|
|
194
|
+
!modifiers.range_middle
|
|
195
|
+
}
|
|
196
|
+
data-range-start={modifiers.range_start}
|
|
197
|
+
data-range-end={modifiers.range_end}
|
|
198
|
+
data-range-middle={modifiers.range_middle}
|
|
199
|
+
className={cn(
|
|
200
|
+
"flex items-center justify-center h-[--cell-size] w-full min-w-[--cell-size] p-0 font-normal transition-all",
|
|
201
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
202
|
+
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[selected-single=true]:rounded-full data-[selected-single=true]:shadow-lg",
|
|
203
|
+
"data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-middle=true]:rounded-none",
|
|
204
|
+
"data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-start=true]:rounded-l-md",
|
|
205
|
+
"data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-end=true]:rounded-r-md",
|
|
206
|
+
"group-data-[focused=true]/day:ring-2 group-data-[focused=true]/day:ring-ring group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10",
|
|
207
|
+
className
|
|
208
|
+
)}
|
|
209
|
+
{...props}
|
|
210
|
+
/>
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export { Calendar, CalendarDayButton }
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
const Card = React.forwardRef<
|
|
6
|
+
HTMLDivElement,
|
|
7
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
8
|
+
>(({ className, ...props }, ref) => (
|
|
9
|
+
<div
|
|
10
|
+
ref={ref}
|
|
11
|
+
className={cn(
|
|
12
|
+
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
|
13
|
+
className
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
))
|
|
18
|
+
Card.displayName = "Card"
|
|
19
|
+
|
|
20
|
+
const CardHeader = React.forwardRef<
|
|
21
|
+
HTMLDivElement,
|
|
22
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
23
|
+
>(({ className, ...props }, ref) => (
|
|
24
|
+
<div
|
|
25
|
+
ref={ref}
|
|
26
|
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
))
|
|
30
|
+
CardHeader.displayName = "CardHeader"
|
|
31
|
+
|
|
32
|
+
const CardTitle = React.forwardRef<
|
|
33
|
+
HTMLDivElement,
|
|
34
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
35
|
+
>(({ className, ...props }, ref) => (
|
|
36
|
+
<div
|
|
37
|
+
ref={ref}
|
|
38
|
+
className={cn(
|
|
39
|
+
"text-2xl font-semibold leading-none tracking-tight",
|
|
40
|
+
className
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
))
|
|
45
|
+
CardTitle.displayName = "CardTitle"
|
|
46
|
+
|
|
47
|
+
const CardDescription = React.forwardRef<
|
|
48
|
+
HTMLDivElement,
|
|
49
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
50
|
+
>(({ className, ...props }, ref) => (
|
|
51
|
+
<div
|
|
52
|
+
ref={ref}
|
|
53
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
))
|
|
57
|
+
CardDescription.displayName = "CardDescription"
|
|
58
|
+
|
|
59
|
+
const CardContent = React.forwardRef<
|
|
60
|
+
HTMLDivElement,
|
|
61
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
62
|
+
>(({ className, ...props }, ref) => (
|
|
63
|
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
64
|
+
))
|
|
65
|
+
CardContent.displayName = "CardContent"
|
|
66
|
+
|
|
67
|
+
const CardFooter = React.forwardRef<
|
|
68
|
+
HTMLDivElement,
|
|
69
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
70
|
+
>(({ className, ...props }, ref) => (
|
|
71
|
+
<div
|
|
72
|
+
ref={ref}
|
|
73
|
+
className={cn("flex items-center p-6 pt-0", className)}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
))
|
|
77
|
+
CardFooter.displayName = "CardFooter"
|
|
78
|
+
|
|
79
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
|
3
|
+
import { Check } from "lucide-react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
const Checkbox = React.forwardRef<
|
|
8
|
+
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
|
9
|
+
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
|
10
|
+
>(({ className, ...props }, ref) => (
|
|
11
|
+
<CheckboxPrimitive.Root
|
|
12
|
+
ref={ref}
|
|
13
|
+
className={cn(
|
|
14
|
+
"grid place-content-center peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
>
|
|
19
|
+
<CheckboxPrimitive.Indicator
|
|
20
|
+
className={cn("grid place-content-center text-current")}
|
|
21
|
+
>
|
|
22
|
+
<Check className="h-4 w-4" />
|
|
23
|
+
</CheckboxPrimitive.Indicator>
|
|
24
|
+
</CheckboxPrimitive.Root>
|
|
25
|
+
))
|
|
26
|
+
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
|
27
|
+
|
|
28
|
+
export { Checkbox }
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { type DialogProps } from "@radix-ui/react-dialog"
|
|
3
|
+
import { Command as CommandPrimitive } from "cmdk"
|
|
4
|
+
import { Search } from "lucide-react"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
import { Dialog, DialogContent } from "../../components/ui/dialog"
|
|
8
|
+
|
|
9
|
+
const Command = React.forwardRef<
|
|
10
|
+
React.ElementRef<typeof CommandPrimitive>,
|
|
11
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
|
12
|
+
>(({ className, ...props }, ref) => (
|
|
13
|
+
<CommandPrimitive
|
|
14
|
+
ref={ref}
|
|
15
|
+
className={cn(
|
|
16
|
+
"flex h-full w-full flex-col overflow-hidden rounded-md bg-white text-popover-foreground",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
))
|
|
22
|
+
Command.displayName = CommandPrimitive.displayName
|
|
23
|
+
|
|
24
|
+
const CommandDialog = ({ children, ...props }: DialogProps) => {
|
|
25
|
+
return (
|
|
26
|
+
<Dialog {...props}>
|
|
27
|
+
<DialogContent className="overflow-hidden p-0 shadow-lg">
|
|
28
|
+
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
|
29
|
+
{children}
|
|
30
|
+
</Command>
|
|
31
|
+
</DialogContent>
|
|
32
|
+
</Dialog>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const CommandInput = React.forwardRef<
|
|
37
|
+
React.ElementRef<typeof CommandPrimitive.Input>,
|
|
38
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
|
39
|
+
>(({ className, ...props }, ref) => (
|
|
40
|
+
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
|
41
|
+
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
|
42
|
+
<CommandPrimitive.Input
|
|
43
|
+
ref={ref}
|
|
44
|
+
className={cn(
|
|
45
|
+
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
))
|
|
52
|
+
|
|
53
|
+
CommandInput.displayName = CommandPrimitive.Input.displayName
|
|
54
|
+
|
|
55
|
+
const CommandList = React.forwardRef<
|
|
56
|
+
React.ElementRef<typeof CommandPrimitive.List>,
|
|
57
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
|
58
|
+
>(({ className, ...props }, ref) => (
|
|
59
|
+
<CommandPrimitive.List
|
|
60
|
+
ref={ref}
|
|
61
|
+
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
))
|
|
65
|
+
|
|
66
|
+
CommandList.displayName = CommandPrimitive.List.displayName
|
|
67
|
+
|
|
68
|
+
const CommandEmpty = React.forwardRef<
|
|
69
|
+
React.ElementRef<typeof CommandPrimitive.Empty>,
|
|
70
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
|
71
|
+
>((props, ref) => (
|
|
72
|
+
<CommandPrimitive.Empty
|
|
73
|
+
ref={ref}
|
|
74
|
+
className="py-6 text-center text-sm"
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
))
|
|
78
|
+
|
|
79
|
+
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
|
80
|
+
|
|
81
|
+
const CommandGroup = React.forwardRef<
|
|
82
|
+
React.ElementRef<typeof CommandPrimitive.Group>,
|
|
83
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
|
84
|
+
>(({ className, ...props }, ref) => (
|
|
85
|
+
<CommandPrimitive.Group
|
|
86
|
+
ref={ref}
|
|
87
|
+
className={cn(
|
|
88
|
+
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
|
89
|
+
className
|
|
90
|
+
)}
|
|
91
|
+
{...props}
|
|
92
|
+
/>
|
|
93
|
+
))
|
|
94
|
+
|
|
95
|
+
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
|
96
|
+
|
|
97
|
+
const CommandSeparator = React.forwardRef<
|
|
98
|
+
React.ElementRef<typeof CommandPrimitive.Separator>,
|
|
99
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
|
100
|
+
>(({ className, ...props }, ref) => (
|
|
101
|
+
<CommandPrimitive.Separator
|
|
102
|
+
ref={ref}
|
|
103
|
+
className={cn("-mx-1 h-px bg-border", className)}
|
|
104
|
+
{...props}
|
|
105
|
+
/>
|
|
106
|
+
))
|
|
107
|
+
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
|
108
|
+
|
|
109
|
+
const CommandItem = React.forwardRef<
|
|
110
|
+
React.ElementRef<typeof CommandPrimitive.Item>,
|
|
111
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
|
112
|
+
>(({ className, ...props }, ref) => (
|
|
113
|
+
<CommandPrimitive.Item
|
|
114
|
+
ref={ref}
|
|
115
|
+
className={cn(
|
|
116
|
+
"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
117
|
+
className
|
|
118
|
+
)}
|
|
119
|
+
{...props}
|
|
120
|
+
/>
|
|
121
|
+
))
|
|
122
|
+
|
|
123
|
+
CommandItem.displayName = CommandPrimitive.Item.displayName
|
|
124
|
+
|
|
125
|
+
const CommandShortcut = ({
|
|
126
|
+
className,
|
|
127
|
+
...props
|
|
128
|
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
129
|
+
return (
|
|
130
|
+
<span
|
|
131
|
+
className={cn(
|
|
132
|
+
"ml-auto text-xs tracking-widest text-muted-foreground",
|
|
133
|
+
className
|
|
134
|
+
)}
|
|
135
|
+
{...props}
|
|
136
|
+
/>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
CommandShortcut.displayName = "CommandShortcut"
|
|
140
|
+
|
|
141
|
+
export {
|
|
142
|
+
Command,
|
|
143
|
+
CommandDialog,
|
|
144
|
+
CommandInput,
|
|
145
|
+
CommandList,
|
|
146
|
+
CommandEmpty,
|
|
147
|
+
CommandGroup,
|
|
148
|
+
CommandItem,
|
|
149
|
+
CommandShortcut,
|
|
150
|
+
CommandSeparator,
|
|
151
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import {
|
|
5
|
+
type ColumnDef,
|
|
6
|
+
type ColumnFiltersState,
|
|
7
|
+
type SortingState,
|
|
8
|
+
type VisibilityState,
|
|
9
|
+
flexRender,
|
|
10
|
+
getCoreRowModel,
|
|
11
|
+
getFilteredRowModel,
|
|
12
|
+
getPaginationRowModel,
|
|
13
|
+
getSortedRowModel,
|
|
14
|
+
useReactTable,
|
|
15
|
+
} from "@tanstack/react-table"
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
Table,
|
|
19
|
+
TableBody,
|
|
20
|
+
TableCell,
|
|
21
|
+
TableHead,
|
|
22
|
+
TableHeader,
|
|
23
|
+
TableRow,
|
|
24
|
+
} from "../../components/ui/table"
|
|
25
|
+
|
|
26
|
+
import { Button } from "../../components/ui/button"
|
|
27
|
+
import { Input } from "../../components/ui/input"
|
|
28
|
+
import {
|
|
29
|
+
DropdownMenu,
|
|
30
|
+
DropdownMenuCheckboxItem,
|
|
31
|
+
DropdownMenuContent,
|
|
32
|
+
DropdownMenuTrigger,
|
|
33
|
+
} from "../../components/ui/dropdown-menu"
|
|
34
|
+
import { ChevronLeft, ChevronRight, Settings2 } from "lucide-react"
|
|
35
|
+
import { usePreferences } from "../../hooks/use-preferences"
|
|
36
|
+
|
|
37
|
+
interface DataTableProps<TData, TValue> {
|
|
38
|
+
columns: ColumnDef<TData, TValue>[]
|
|
39
|
+
data: TData[]
|
|
40
|
+
searchKey?: string
|
|
41
|
+
rowSelection?: any
|
|
42
|
+
onRowSelectionChange?: any
|
|
43
|
+
bulkActions?: (selectedIds: string[]) => React.ReactNode
|
|
44
|
+
persistenceKey?: string
|
|
45
|
+
initialColumnVisibility?: VisibilityState
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function DataTable<TData, TValue>({
|
|
49
|
+
columns,
|
|
50
|
+
data,
|
|
51
|
+
searchKey,
|
|
52
|
+
rowSelection: externalRowSelection,
|
|
53
|
+
onRowSelectionChange,
|
|
54
|
+
bulkActions,
|
|
55
|
+
persistenceKey,
|
|
56
|
+
initialColumnVisibility = {},
|
|
57
|
+
}: DataTableProps<TData, TValue>) {
|
|
58
|
+
const [sorting, setSorting] = React.useState<SortingState>([])
|
|
59
|
+
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
|
60
|
+
[]
|
|
61
|
+
)
|
|
62
|
+
const [columnVisibility, setColumnVisibility] = usePreferences<VisibilityState>(
|
|
63
|
+
persistenceKey ? `visibility_${persistenceKey}` : "temp_visibility",
|
|
64
|
+
initialColumnVisibility
|
|
65
|
+
)
|
|
66
|
+
const [internalRowSelection, setInternalRowSelection] = React.useState({})
|
|
67
|
+
|
|
68
|
+
const rowSelection = externalRowSelection || internalRowSelection
|
|
69
|
+
const setRowSelection = onRowSelectionChange || setInternalRowSelection
|
|
70
|
+
|
|
71
|
+
const table = useReactTable({
|
|
72
|
+
data,
|
|
73
|
+
columns,
|
|
74
|
+
onSortingChange: setSorting,
|
|
75
|
+
onColumnFiltersChange: setColumnFilters,
|
|
76
|
+
getCoreRowModel: getCoreRowModel(),
|
|
77
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
78
|
+
getSortedRowModel: getSortedRowModel(),
|
|
79
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
80
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
81
|
+
onRowSelectionChange: setRowSelection,
|
|
82
|
+
state: {
|
|
83
|
+
sorting,
|
|
84
|
+
columnFilters,
|
|
85
|
+
columnVisibility,
|
|
86
|
+
rowSelection,
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="w-full space-y-4">
|
|
92
|
+
<div className="flex items-center gap-4">
|
|
93
|
+
{searchKey && (
|
|
94
|
+
<Input
|
|
95
|
+
placeholder={`Search ${searchKey}...`}
|
|
96
|
+
value={(table.getColumn(searchKey)?.getFilterValue() as string) ?? ""}
|
|
97
|
+
onChange={(event) =>
|
|
98
|
+
table.getColumn(searchKey)?.setFilterValue(event.target.value)
|
|
99
|
+
}
|
|
100
|
+
className="max-w-sm"
|
|
101
|
+
/>
|
|
102
|
+
)}
|
|
103
|
+
{bulkActions && table.getFilteredSelectedRowModel().rows.length > 0 && (
|
|
104
|
+
<div className="flex items-center gap-2 animate-in slide-in-from-left-2">
|
|
105
|
+
{bulkActions(
|
|
106
|
+
table
|
|
107
|
+
.getFilteredSelectedRowModel()
|
|
108
|
+
.rows.map((r) => (r.original as any).id)
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
<DropdownMenu>
|
|
113
|
+
<DropdownMenuTrigger asChild>
|
|
114
|
+
<Button variant="outline" size="sm" className="ml-auto flex h-8 gap-2">
|
|
115
|
+
<Settings2 className="h-4 w-4" />
|
|
116
|
+
View
|
|
117
|
+
</Button>
|
|
118
|
+
</DropdownMenuTrigger>
|
|
119
|
+
<DropdownMenuContent align="end">
|
|
120
|
+
{table
|
|
121
|
+
.getAllColumns()
|
|
122
|
+
.filter((column) => column.getCanHide())
|
|
123
|
+
.map((column) => {
|
|
124
|
+
return (
|
|
125
|
+
<DropdownMenuCheckboxItem
|
|
126
|
+
key={column.id}
|
|
127
|
+
className="capitalize"
|
|
128
|
+
checked={column.getIsVisible()}
|
|
129
|
+
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
|
130
|
+
>
|
|
131
|
+
{column.id}
|
|
132
|
+
</DropdownMenuCheckboxItem>
|
|
133
|
+
)
|
|
134
|
+
})}
|
|
135
|
+
</DropdownMenuContent>
|
|
136
|
+
</DropdownMenu>
|
|
137
|
+
</div>
|
|
138
|
+
<div className="overflow-hidden">
|
|
139
|
+
<Table>
|
|
140
|
+
<TableHeader>
|
|
141
|
+
{table.getHeaderGroups().map((headerGroup) => (
|
|
142
|
+
<TableRow key={headerGroup.id}>
|
|
143
|
+
{headerGroup.headers.map((header) => {
|
|
144
|
+
return (
|
|
145
|
+
<TableHead key={header.id}>
|
|
146
|
+
{header.isPlaceholder
|
|
147
|
+
? null
|
|
148
|
+
: flexRender(
|
|
149
|
+
header.column.columnDef.header,
|
|
150
|
+
header.getContext()
|
|
151
|
+
)}
|
|
152
|
+
</TableHead>
|
|
153
|
+
)
|
|
154
|
+
})}
|
|
155
|
+
</TableRow>
|
|
156
|
+
))}
|
|
157
|
+
</TableHeader>
|
|
158
|
+
<TableBody className="border-t border-border/40">
|
|
159
|
+
{table.getRowModel().rows?.length ? (
|
|
160
|
+
table.getRowModel().rows.map((row) => (
|
|
161
|
+
<TableRow
|
|
162
|
+
key={row.id}
|
|
163
|
+
data-state={row.getIsSelected() && "selected"}
|
|
164
|
+
className="border-none even:bg-primary/[0.03] hover:bg-primary/[0.06] transition-colors duration-200"
|
|
165
|
+
>
|
|
166
|
+
{row.getVisibleCells().map((cell) => (
|
|
167
|
+
<TableCell key={cell.id} className="py-4 px-4 border-none first:pl-4 last:pr-4">
|
|
168
|
+
{flexRender(
|
|
169
|
+
cell.column.columnDef.cell,
|
|
170
|
+
cell.getContext()
|
|
171
|
+
)}
|
|
172
|
+
</TableCell>
|
|
173
|
+
))}
|
|
174
|
+
</TableRow>
|
|
175
|
+
))
|
|
176
|
+
) : (
|
|
177
|
+
<TableRow>
|
|
178
|
+
<TableCell
|
|
179
|
+
colSpan={columns.length}
|
|
180
|
+
className="h-24 text-center"
|
|
181
|
+
>
|
|
182
|
+
No results.
|
|
183
|
+
</TableCell>
|
|
184
|
+
</TableRow>
|
|
185
|
+
)}
|
|
186
|
+
</TableBody>
|
|
187
|
+
</Table>
|
|
188
|
+
</div>
|
|
189
|
+
<div className="flex items-center justify-between px-2">
|
|
190
|
+
<div className="flex-1 text-sm text-muted-foreground">
|
|
191
|
+
{table.getFilteredSelectedRowModel().rows.length} of{" "}
|
|
192
|
+
{table.getFilteredRowModel().rows.length} row(s) selected.
|
|
193
|
+
</div>
|
|
194
|
+
<div className="flex items-center space-x-2">
|
|
195
|
+
<Button
|
|
196
|
+
variant="outline"
|
|
197
|
+
size="sm"
|
|
198
|
+
onClick={() => table.previousPage()}
|
|
199
|
+
disabled={!table.getCanPreviousPage()}
|
|
200
|
+
>
|
|
201
|
+
<ChevronLeft className="h-4 w-4" />
|
|
202
|
+
</Button>
|
|
203
|
+
<span className="text-sm font-medium">
|
|
204
|
+
Page {table.getState().pagination.pageIndex + 1} of{" "}
|
|
205
|
+
{table.getPageCount()}
|
|
206
|
+
</span>
|
|
207
|
+
<Button
|
|
208
|
+
variant="outline"
|
|
209
|
+
size="sm"
|
|
210
|
+
onClick={() => table.nextPage()}
|
|
211
|
+
disabled={!table.getCanNextPage()}
|
|
212
|
+
>
|
|
213
|
+
<ChevronRight className="h-4 w-4" />
|
|
214
|
+
</Button>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
)
|
|
219
|
+
}
|