tembro 6.0.3 → 6.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.
- package/CHANGELOG.md +13 -0
- package/dist/components/calendar/calendar-range-scheduler.cjs +1 -0
- package/dist/components/calendar/calendar-range-scheduler.d.ts +20 -0
- package/dist/components/calendar/calendar-range-scheduler.js +1 -0
- package/dist/components/calendar/date-time-picker.cjs +1 -0
- package/dist/components/calendar/date-time-picker.d.ts +15 -0
- package/dist/components/calendar/date-time-picker.js +1 -0
- package/dist/components/calendar/index.d.ts +2 -0
- package/dist/components/charts/chart-card.cjs +1 -0
- package/dist/components/charts/chart-card.d.ts +24 -0
- package/dist/components/charts/chart-card.js +1 -0
- package/dist/components/charts/index.d.ts +1 -0
- package/dist/components/command/advanced-command-menu.cjs +1 -0
- package/dist/components/command/advanced-command-menu.d.ts +25 -0
- package/dist/components/command/advanced-command-menu.js +1 -0
- package/dist/components/command/index.d.ts +1 -0
- package/dist/components/data-table/data-grid.cjs +1 -0
- package/dist/components/data-table/data-grid.d.ts +15 -0
- package/dist/components/data-table/data-grid.js +1 -0
- package/dist/components/data-table/index.d.ts +1 -0
- package/dist/components/data-table/public.d.ts +1 -0
- package/dist/components/feedback/async-boundary.cjs +1 -0
- package/dist/components/feedback/async-boundary.d.ts +12 -0
- package/dist/components/feedback/async-boundary.js +1 -0
- package/dist/components/feedback/index.d.ts +1 -0
- package/dist/components/inputs/index.d.ts +1 -0
- package/dist/components/inputs/multi-select.cjs +1 -0
- package/dist/components/inputs/multi-select.d.ts +16 -0
- package/dist/components/inputs/multi-select.js +1 -0
- package/dist/components/inputs/public.d.ts +1 -0
- package/dist/components/modern/file-manager.cjs +1 -0
- package/dist/components/modern/file-manager.d.ts +20 -0
- package/dist/components/modern/file-manager.js +1 -0
- package/dist/components/modern/index.d.ts +1 -0
- package/dist/components/notifications/action-toast.cjs +1 -0
- package/dist/components/notifications/action-toast.d.ts +16 -0
- package/dist/components/notifications/action-toast.js +1 -0
- package/dist/components/notifications/index.d.ts +1 -0
- package/dist/components/patterns/index.d.ts +1 -0
- package/dist/components/patterns/permissions-matrix.cjs +1 -0
- package/dist/components/patterns/permissions-matrix.d.ts +19 -0
- package/dist/components/patterns/permissions-matrix.js +1 -0
- package/dist/components/patterns/public.d.ts +1 -0
- package/dist/src/components/calendar/calendar-range-scheduler.cjs +1 -0
- package/dist/src/components/calendar/calendar-range-scheduler.js +75 -0
- package/dist/src/components/calendar/date-time-picker.cjs +1 -0
- package/dist/src/components/calendar/date-time-picker.js +36 -0
- package/dist/src/components/calendar/index.cjs +1 -1
- package/dist/src/components/calendar/index.js +5 -3
- package/dist/src/components/charts/chart-card.cjs +1 -0
- package/dist/src/components/charts/chart-card.js +55 -0
- package/dist/src/components/charts/index.cjs +1 -1
- package/dist/src/components/charts/index.js +3 -2
- package/dist/src/components/command/advanced-command-menu.cjs +1 -0
- package/dist/src/components/command/advanced-command-menu.js +57 -0
- package/dist/src/components/command/index.cjs +1 -1
- package/dist/src/components/command/index.js +3 -2
- package/dist/src/components/data-table/data-grid.cjs +1 -0
- package/dist/src/components/data-table/data-grid.js +32 -0
- package/dist/src/components/data-table/data-table-saved-filters.cjs +1 -1
- package/dist/src/components/data-table/data-table-saved-filters.js +11 -11
- package/dist/src/components/data-table/data-table.cjs +1 -1
- package/dist/src/components/data-table/data-table.js +170 -170
- package/dist/src/components/data-table/index.cjs +1 -1
- package/dist/src/components/data-table/index.js +15 -14
- package/dist/src/components/data-table/public.cjs +1 -1
- package/dist/src/components/data-table/public.js +3 -2
- package/dist/src/components/feedback/async-boundary.cjs +1 -0
- package/dist/src/components/feedback/async-boundary.js +22 -0
- package/dist/src/components/feedback/index.cjs +1 -1
- package/dist/src/components/feedback/index.js +2 -1
- package/dist/src/components/inputs/index.cjs +1 -1
- package/dist/src/components/inputs/index.js +13 -12
- package/dist/src/components/inputs/multi-select.cjs +1 -0
- package/dist/src/components/inputs/multi-select.js +80 -0
- package/dist/src/components/inputs/public.cjs +1 -1
- package/dist/src/components/inputs/public.js +13 -12
- package/dist/src/components/modern/file-manager.cjs +1 -0
- package/dist/src/components/modern/file-manager.js +75 -0
- package/dist/src/components/modern/index.cjs +1 -1
- package/dist/src/components/modern/index.js +5 -4
- package/dist/src/components/notifications/action-toast.cjs +1 -0
- package/dist/src/components/notifications/action-toast.js +49 -0
- package/dist/src/components/notifications/index.cjs +1 -1
- package/dist/src/components/notifications/index.js +3 -2
- package/dist/src/components/patterns/index.cjs +1 -1
- package/dist/src/components/patterns/index.js +2 -1
- package/dist/src/components/patterns/permissions-matrix.cjs +1 -0
- package/dist/src/components/patterns/permissions-matrix.js +43 -0
- package/dist/src/components/patterns/public.cjs +1 -1
- package/dist/src/components/patterns/public.js +2 -1
- package/dist/src/components/ui/input/group.cjs +1 -1
- package/dist/src/components/ui/input/group.js +8 -8
- package/dist/src/index.cjs +1 -1
- package/dist/src/index.js +117 -107
- package/dist/src/public-component-surface.cjs +1 -1
- package/dist/src/public-component-surface.js +50 -0
- package/package.json +1 -1
- package/packages/cli/dist/index.cjs +73 -73
- package/packages/cli/vendor/src/components/calendar/calendar-range-scheduler.tsx +72 -0
- package/packages/cli/vendor/src/components/calendar/date-time-picker.tsx +53 -0
- package/packages/cli/vendor/src/components/calendar/index.ts +5 -3
- package/packages/cli/vendor/src/components/charts/chart-card.tsx +81 -0
- package/packages/cli/vendor/src/components/charts/index.ts +1 -0
- package/packages/cli/vendor/src/components/command/advanced-command-menu.tsx +88 -0
- package/packages/cli/vendor/src/components/command/index.ts +2 -1
- package/packages/cli/vendor/src/components/data-table/data-grid.tsx +59 -0
- package/packages/cli/vendor/src/components/data-table/index.ts +3 -2
- package/packages/cli/vendor/src/components/data-table/public.ts +1 -0
- package/packages/cli/vendor/src/components/feedback/async-boundary.tsx +36 -0
- package/packages/cli/vendor/src/components/feedback/index.ts +1 -0
- package/packages/cli/vendor/src/components/inputs/index.ts +1 -0
- package/packages/cli/vendor/src/components/inputs/multi-select.tsx +75 -0
- package/packages/cli/vendor/src/components/inputs/public.ts +1 -0
- package/packages/cli/vendor/src/components/modern/file-manager.tsx +79 -0
- package/packages/cli/vendor/src/components/modern/index.ts +1 -0
- package/packages/cli/vendor/src/components/notifications/action-toast.tsx +78 -0
- package/packages/cli/vendor/src/components/notifications/index.ts +2 -1
- package/packages/cli/vendor/src/components/patterns/index.ts +1 -0
- package/packages/cli/vendor/src/components/patterns/permissions-matrix.tsx +71 -0
- package/packages/cli/vendor/src/components/patterns/public.ts +1 -0
- package/packages/cli/vendor/src/public-component-surface.ts +10 -0
- package/packages/cli/vendor/templates/showcase/src/showcase/layout/HeroSection.tsx +2 -2
- package/packages/cli/vendor/templates/showcase/src/showcase/layout/WorkbenchSidebar.tsx +1 -1
- package/registry.json +3264 -2794
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { CalendarDaysIcon } from "lucide-react"
|
|
5
|
+
|
|
6
|
+
import { DateRangePicker, type DateRangePickerValue } from "@/components/calendar/date-range-picker"
|
|
7
|
+
import { Badge } from "@/components/ui/badge"
|
|
8
|
+
import { Button } from "@/components/ui/button"
|
|
9
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
10
|
+
import { cn } from "@/lib/utils"
|
|
11
|
+
|
|
12
|
+
export type CalendarRangeSchedulerEvent = {
|
|
13
|
+
id: string
|
|
14
|
+
title: React.ReactNode
|
|
15
|
+
range: DateRangePickerValue
|
|
16
|
+
tone?: "default" | "info" | "success" | "warning" | "danger"
|
|
17
|
+
meta?: React.ReactNode
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type CalendarRangeSchedulerProps = Omit<React.ComponentProps<typeof Card>, "onSelect"> & {
|
|
21
|
+
value?: DateRangePickerValue
|
|
22
|
+
onValueChange?: (range: DateRangePickerValue) => void
|
|
23
|
+
events?: CalendarRangeSchedulerEvent[]
|
|
24
|
+
onEventSelect?: (event: CalendarRangeSchedulerEvent) => void
|
|
25
|
+
title?: React.ReactNode
|
|
26
|
+
action?: React.ReactNode
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function CalendarRangeScheduler({
|
|
30
|
+
value,
|
|
31
|
+
onValueChange,
|
|
32
|
+
events = [],
|
|
33
|
+
onEventSelect,
|
|
34
|
+
title = "Schedule range",
|
|
35
|
+
action,
|
|
36
|
+
className,
|
|
37
|
+
...props
|
|
38
|
+
}: CalendarRangeSchedulerProps) {
|
|
39
|
+
return (
|
|
40
|
+
<Card data-slot="calendar-range-scheduler" className={cn("min-w-0", className)} {...props}>
|
|
41
|
+
<CardHeader className="flex-row items-center justify-between gap-3">
|
|
42
|
+
<CardTitle className="flex items-center gap-2"><CalendarDaysIcon className="size-4" />{title}</CardTitle>
|
|
43
|
+
{action}
|
|
44
|
+
</CardHeader>
|
|
45
|
+
<CardContent className="grid gap-4 lg:grid-cols-[320px_minmax(0,1fr)]">
|
|
46
|
+
<DateRangePicker value={value} onValueChange={onValueChange} />
|
|
47
|
+
<div className="grid content-start gap-2">
|
|
48
|
+
{events.length ? events.map((event) => (
|
|
49
|
+
<button
|
|
50
|
+
key={event.id}
|
|
51
|
+
type="button"
|
|
52
|
+
className="grid gap-1 rounded-lg border bg-background p-3 text-left transition hover:border-primary/45 hover:bg-muted/40"
|
|
53
|
+
onClick={() => onEventSelect?.(event)}
|
|
54
|
+
>
|
|
55
|
+
<span className="flex items-center justify-between gap-2">
|
|
56
|
+
<span className="font-medium">{event.title}</span>
|
|
57
|
+
<Badge label={event.tone ?? "scheduled"} status={event.tone === "danger" ? "danger" : event.tone === "warning" ? "warning" : event.tone === "success" ? "success" : "info"} variant="soft" />
|
|
58
|
+
</span>
|
|
59
|
+
<span className="text-xs text-muted-foreground">{event.range.from || "Start"} - {event.range.to || "End"}</span>
|
|
60
|
+
{event.meta ? <span className="text-xs text-muted-foreground">{event.meta}</span> : null}
|
|
61
|
+
</button>
|
|
62
|
+
)) : (
|
|
63
|
+
<div className="rounded-lg border border-dashed p-6 text-sm text-muted-foreground">No scheduled ranges.</div>
|
|
64
|
+
)}
|
|
65
|
+
<Button type="button" variant="outline" onClick={() => onValueChange?.({ from: "", to: "" })}>Clear range</Button>
|
|
66
|
+
</div>
|
|
67
|
+
</CardContent>
|
|
68
|
+
</Card>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { CalendarRangeScheduler }
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { DatePicker, type DatePickerProps } from "@/components/calendar/date-picker"
|
|
6
|
+
import { TimePicker } from "@/components/inputs/time-picker"
|
|
7
|
+
import { cn } from "@/lib/utils"
|
|
8
|
+
|
|
9
|
+
export type DateTimeValue = {
|
|
10
|
+
date?: string
|
|
11
|
+
time?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type CalendarDateTimePickerProps = Omit<React.ComponentProps<"div">, "value" | "onChange"> & {
|
|
15
|
+
value?: DateTimeValue
|
|
16
|
+
onValueChange?: (value: DateTimeValue) => void
|
|
17
|
+
datePickerProps?: Omit<DatePickerProps, "value" | "onValueChange">
|
|
18
|
+
timeLabel?: React.ReactNode
|
|
19
|
+
disabled?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function CalendarDateTimePicker({
|
|
23
|
+
value,
|
|
24
|
+
onValueChange,
|
|
25
|
+
datePickerProps,
|
|
26
|
+
timeLabel = "Time",
|
|
27
|
+
disabled,
|
|
28
|
+
className,
|
|
29
|
+
...props
|
|
30
|
+
}: CalendarDateTimePickerProps) {
|
|
31
|
+
const current = value ?? {}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div data-slot="date-time-picker" className={cn("grid gap-3 sm:grid-cols-2", className)} {...props}>
|
|
35
|
+
<DatePicker
|
|
36
|
+
{...datePickerProps}
|
|
37
|
+
value={current.date}
|
|
38
|
+
disabled={disabled || datePickerProps?.disabled}
|
|
39
|
+
onValueChange={(date) => onValueChange?.({ ...current, date })}
|
|
40
|
+
/>
|
|
41
|
+
<label className="grid gap-1.5 text-sm font-medium">
|
|
42
|
+
<span>{timeLabel}</span>
|
|
43
|
+
<TimePicker
|
|
44
|
+
value={current.time ?? ""}
|
|
45
|
+
onChange={(event) => onValueChange?.({ ...current, time: event.currentTarget.value })}
|
|
46
|
+
disabled={disabled}
|
|
47
|
+
/>
|
|
48
|
+
</label>
|
|
49
|
+
</div>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { CalendarDateTimePicker }
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from "./date-utils"
|
|
2
|
-
export * from "./calendar"
|
|
3
|
-
export * from "./date-picker"
|
|
4
|
-
export * from "./date-range-picker"
|
|
2
|
+
export * from "./calendar"
|
|
3
|
+
export * from "./date-picker"
|
|
4
|
+
export * from "./date-range-picker"
|
|
5
|
+
export * from "./date-time-picker"
|
|
6
|
+
export * from "./calendar-range-scheduler"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { BarChart, LineChart, DonutChart, type ChartDatum, type ChartSeries, type ChartState } from "@/components/charts/charts"
|
|
6
|
+
import { Badge } from "@/components/ui/badge"
|
|
7
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
8
|
+
import { Select } from "@/components/ui/select"
|
|
9
|
+
import { cn } from "@/lib/utils"
|
|
10
|
+
|
|
11
|
+
export type ChartCardType = "bar" | "line" | "donut"
|
|
12
|
+
export type ChartCardFilter = {
|
|
13
|
+
label: string
|
|
14
|
+
value: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type ChartCardProps = Omit<React.ComponentProps<typeof Card>, "type"> & {
|
|
18
|
+
title?: React.ReactNode
|
|
19
|
+
description?: React.ReactNode
|
|
20
|
+
type?: ChartCardType
|
|
21
|
+
data?: ChartDatum[]
|
|
22
|
+
series?: ChartSeries[]
|
|
23
|
+
labels?: React.ReactNode[]
|
|
24
|
+
state?: ChartState
|
|
25
|
+
metric?: React.ReactNode
|
|
26
|
+
badge?: React.ReactNode
|
|
27
|
+
filters?: ChartCardFilter[]
|
|
28
|
+
filterValue?: string
|
|
29
|
+
onFilterChange?: (value: string) => void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function ChartCard({
|
|
33
|
+
title,
|
|
34
|
+
description,
|
|
35
|
+
type = "bar",
|
|
36
|
+
data = [],
|
|
37
|
+
series,
|
|
38
|
+
labels,
|
|
39
|
+
state,
|
|
40
|
+
metric,
|
|
41
|
+
badge,
|
|
42
|
+
filters,
|
|
43
|
+
filterValue,
|
|
44
|
+
onFilterChange,
|
|
45
|
+
className,
|
|
46
|
+
...props
|
|
47
|
+
}: ChartCardProps) {
|
|
48
|
+
return (
|
|
49
|
+
<Card data-slot="chart-card" className={cn("min-w-0", className)} {...props}>
|
|
50
|
+
<CardHeader className="flex-row items-start justify-between gap-3">
|
|
51
|
+
<div className="grid gap-1">
|
|
52
|
+
{title ? <CardTitle>{title}</CardTitle> : null}
|
|
53
|
+
{description ? <CardDescription>{description}</CardDescription> : null}
|
|
54
|
+
{metric ? <div className="text-2xl font-semibold tracking-tight">{metric}</div> : null}
|
|
55
|
+
</div>
|
|
56
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
57
|
+
{badge ?? <Badge label={type} variant="secondary" />}
|
|
58
|
+
{filters?.length ? (
|
|
59
|
+
<Select
|
|
60
|
+
value={filterValue}
|
|
61
|
+
onValueChange={(value) => value && onFilterChange?.(value)}
|
|
62
|
+
options={filters}
|
|
63
|
+
triggerClassName="h-9 w-36"
|
|
64
|
+
/>
|
|
65
|
+
) : null}
|
|
66
|
+
</div>
|
|
67
|
+
</CardHeader>
|
|
68
|
+
<CardContent>
|
|
69
|
+
{type === "line" ? (
|
|
70
|
+
<LineChart series={series} labels={labels} values={data.map((item) => item.value)} state={state} />
|
|
71
|
+
) : type === "donut" ? (
|
|
72
|
+
<DonutChart data={data} state={state} />
|
|
73
|
+
) : (
|
|
74
|
+
<BarChart data={data} series={series} state={state} />
|
|
75
|
+
)}
|
|
76
|
+
</CardContent>
|
|
77
|
+
</Card>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { ChartCard }
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { SearchIcon } from "lucide-react"
|
|
5
|
+
|
|
6
|
+
import { Button } from "@/components/ui/button"
|
|
7
|
+
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandShortcut } from "@/components/ui/command"
|
|
8
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
|
9
|
+
|
|
10
|
+
export type AdvancedCommandMenuItem = {
|
|
11
|
+
id: string
|
|
12
|
+
label: React.ReactNode
|
|
13
|
+
description?: React.ReactNode
|
|
14
|
+
icon?: React.ReactNode
|
|
15
|
+
shortcut?: React.ReactNode
|
|
16
|
+
keywords?: string[]
|
|
17
|
+
onSelect?: () => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type AdvancedCommandMenuGroup = {
|
|
21
|
+
id: string
|
|
22
|
+
label: React.ReactNode
|
|
23
|
+
items: AdvancedCommandMenuItem[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type AdvancedCommandMenuProps = {
|
|
27
|
+
open?: boolean
|
|
28
|
+
onOpenChange?: (open: boolean) => void
|
|
29
|
+
groups: AdvancedCommandMenuGroup[]
|
|
30
|
+
title?: React.ReactNode
|
|
31
|
+
triggerLabel?: React.ReactNode
|
|
32
|
+
placeholder?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function AdvancedCommandMenu({
|
|
36
|
+
open,
|
|
37
|
+
onOpenChange,
|
|
38
|
+
groups,
|
|
39
|
+
title = "Command menu",
|
|
40
|
+
triggerLabel = "Command",
|
|
41
|
+
placeholder = "Search commands...",
|
|
42
|
+
}: AdvancedCommandMenuProps) {
|
|
43
|
+
const [internalOpen, setInternalOpen] = React.useState(false)
|
|
44
|
+
const resolvedOpen = open ?? internalOpen
|
|
45
|
+
const setOpen = onOpenChange ?? setInternalOpen
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
<Button type="button" variant="outline" leftIcon={<SearchIcon className="size-4" />} onClick={() => setOpen(true)}>{triggerLabel}</Button>
|
|
50
|
+
<Dialog open={resolvedOpen} onOpenChange={setOpen}>
|
|
51
|
+
<DialogContent className="overflow-hidden p-0">
|
|
52
|
+
<DialogHeader className="border-b px-4 py-3">
|
|
53
|
+
<DialogTitle>{title}</DialogTitle>
|
|
54
|
+
</DialogHeader>
|
|
55
|
+
<Command>
|
|
56
|
+
<CommandInput placeholder={placeholder} />
|
|
57
|
+
<CommandList>
|
|
58
|
+
<CommandEmpty>No command found.</CommandEmpty>
|
|
59
|
+
{groups.map((group) => (
|
|
60
|
+
<CommandGroup key={group.id} heading={group.label}>
|
|
61
|
+
{group.items.map((item) => (
|
|
62
|
+
<CommandItem
|
|
63
|
+
key={item.id}
|
|
64
|
+
value={[item.id, item.label, item.description, ...(item.keywords ?? [])].filter(Boolean).join(" ")}
|
|
65
|
+
onSelect={() => {
|
|
66
|
+
item.onSelect?.()
|
|
67
|
+
setOpen(false)
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
{item.icon}
|
|
71
|
+
<span className="grid min-w-0 flex-1">
|
|
72
|
+
<span className="truncate">{item.label}</span>
|
|
73
|
+
{item.description ? <span className="truncate text-xs text-muted-foreground">{item.description}</span> : null}
|
|
74
|
+
</span>
|
|
75
|
+
{item.shortcut ? <CommandShortcut>{item.shortcut}</CommandShortcut> : null}
|
|
76
|
+
</CommandItem>
|
|
77
|
+
))}
|
|
78
|
+
</CommandGroup>
|
|
79
|
+
))}
|
|
80
|
+
</CommandList>
|
|
81
|
+
</Command>
|
|
82
|
+
</DialogContent>
|
|
83
|
+
</Dialog>
|
|
84
|
+
</>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { AdvancedCommandMenu }
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export * from "./command-palette"
|
|
1
|
+
export * from "./command-palette"
|
|
2
|
+
export * from "./advanced-command-menu"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { Input } from "@/components/ui/input"
|
|
6
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
|
7
|
+
import { cn } from "@/lib/utils"
|
|
8
|
+
|
|
9
|
+
export type DataGridColumn<TRow extends Record<string, unknown>> = {
|
|
10
|
+
key: keyof TRow & string
|
|
11
|
+
header: React.ReactNode
|
|
12
|
+
editable?: boolean
|
|
13
|
+
width?: React.CSSProperties["width"]
|
|
14
|
+
render?: (value: TRow[keyof TRow], row: TRow, rowIndex: number) => React.ReactNode
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type DataGridProps<TRow extends Record<string, unknown>> = React.ComponentProps<"div"> & {
|
|
18
|
+
columns: DataGridColumn<TRow>[]
|
|
19
|
+
rows: TRow[]
|
|
20
|
+
onRowsChange?: (rows: TRow[]) => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function DataGrid<TRow extends Record<string, unknown>>({ columns, rows, onRowsChange, className, ...props }: DataGridProps<TRow>) {
|
|
24
|
+
const updateCell = (rowIndex: number, key: keyof TRow & string, value: string) => {
|
|
25
|
+
onRowsChange?.(rows.map((row, index) => index === rowIndex ? { ...row, [key]: value } : row))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div data-slot="data-grid" className={cn("overflow-hidden rounded-lg border", className)} {...props}>
|
|
30
|
+
<Table>
|
|
31
|
+
<TableHeader>
|
|
32
|
+
<TableRow>{columns.map((column) => <TableHead key={column.key} style={{ width: column.width }}>{column.header}</TableHead>)}</TableRow>
|
|
33
|
+
</TableHeader>
|
|
34
|
+
<TableBody>
|
|
35
|
+
{rows.map((row, rowIndex) => (
|
|
36
|
+
<TableRow key={rowIndex}>
|
|
37
|
+
{columns.map((column) => {
|
|
38
|
+
const value = row[column.key]
|
|
39
|
+
return (
|
|
40
|
+
<TableCell key={column.key}>
|
|
41
|
+
{column.editable ? (
|
|
42
|
+
<Input value={String(value ?? "")} onValueChange={(nextValue) => updateCell(rowIndex, column.key, nextValue)} />
|
|
43
|
+
) : column.render ? (
|
|
44
|
+
column.render(value, row, rowIndex)
|
|
45
|
+
) : (
|
|
46
|
+
String(value ?? "")
|
|
47
|
+
)}
|
|
48
|
+
</TableCell>
|
|
49
|
+
)
|
|
50
|
+
})}
|
|
51
|
+
</TableRow>
|
|
52
|
+
))}
|
|
53
|
+
</TableBody>
|
|
54
|
+
</Table>
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { DataGrid }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export * from './data-table'
|
|
2
|
-
export * from './data-
|
|
1
|
+
export * from './data-table'
|
|
2
|
+
export * from './data-grid'
|
|
3
|
+
export * from './data-table-pagination'
|
|
3
4
|
export * from './data-table-toolbar'
|
|
4
5
|
export * from './data-table-column-visibility-menu'
|
|
5
6
|
export * from './data-table-select-column'
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { StateView } from "@/components/feedback/state-view"
|
|
6
|
+
|
|
7
|
+
export type AsyncBoundaryProps = React.PropsWithChildren<{
|
|
8
|
+
loading?: boolean
|
|
9
|
+
error?: unknown
|
|
10
|
+
empty?: boolean
|
|
11
|
+
loadingTitle?: React.ReactNode
|
|
12
|
+
errorTitle?: React.ReactNode
|
|
13
|
+
emptyTitle?: React.ReactNode
|
|
14
|
+
onRetry?: () => void
|
|
15
|
+
}>
|
|
16
|
+
|
|
17
|
+
function AsyncBoundary({
|
|
18
|
+
loading,
|
|
19
|
+
error,
|
|
20
|
+
empty,
|
|
21
|
+
loadingTitle = "Loading",
|
|
22
|
+
errorTitle = "Unable to load",
|
|
23
|
+
emptyTitle = "No data",
|
|
24
|
+
onRetry,
|
|
25
|
+
children,
|
|
26
|
+
}: AsyncBoundaryProps) {
|
|
27
|
+
if (loading) return <StateView status="loading" title={loadingTitle} loadingVariant="skeleton" />
|
|
28
|
+
if (error) {
|
|
29
|
+
const description = error instanceof Error ? error.message : "Try again or check the request."
|
|
30
|
+
return <StateView status="error" title={errorTitle} description={description} onRetry={onRetry} />
|
|
31
|
+
}
|
|
32
|
+
if (empty) return <StateView status="empty" title={emptyTitle} />
|
|
33
|
+
return <>{children}</>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { AsyncBoundary }
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { CheckIcon, ChevronDownIcon, XIcon } from "lucide-react"
|
|
5
|
+
|
|
6
|
+
import { Badge } from "@/components/ui/badge"
|
|
7
|
+
import { Button } from "@/components/ui/button"
|
|
8
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
|
9
|
+
import { cn } from "@/lib/utils"
|
|
10
|
+
|
|
11
|
+
export type MultiSelectOption = {
|
|
12
|
+
value: string
|
|
13
|
+
label: React.ReactNode
|
|
14
|
+
description?: React.ReactNode
|
|
15
|
+
disabled?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type MultiSelectProps = React.ComponentProps<"div"> & {
|
|
19
|
+
options: MultiSelectOption[]
|
|
20
|
+
value?: string[]
|
|
21
|
+
onValueChange?: (value: string[]) => void
|
|
22
|
+
placeholder?: React.ReactNode
|
|
23
|
+
maxVisibleTags?: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function MultiSelect({ options, value = [], onValueChange, placeholder = "Select options", maxVisibleTags = 3, className, ...props }: MultiSelectProps) {
|
|
27
|
+
const [open, setOpen] = React.useState(false)
|
|
28
|
+
const selected = options.filter((option) => value.includes(option.value))
|
|
29
|
+
const hiddenCount = Math.max(0, selected.length - maxVisibleTags)
|
|
30
|
+
|
|
31
|
+
const toggle = (optionValue: string) => {
|
|
32
|
+
onValueChange?.(value.includes(optionValue) ? value.filter((item) => item !== optionValue) : [...value, optionValue])
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div data-slot="multi-select" className={cn("w-full", className)} {...props}>
|
|
37
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
38
|
+
<PopoverTrigger render={<Button type="button" variant="outline" className="min-h-10 w-full justify-between" />}>
|
|
39
|
+
<span className="flex min-w-0 flex-1 flex-wrap gap-1">
|
|
40
|
+
{selected.length ? selected.slice(0, maxVisibleTags).map((option) => (
|
|
41
|
+
<Badge key={option.value} label={option.label} variant="secondary" />
|
|
42
|
+
)) : <span className="text-muted-foreground">{placeholder}</span>}
|
|
43
|
+
{hiddenCount ? <Badge label={`+${hiddenCount}`} variant="secondary" /> : null}
|
|
44
|
+
</span>
|
|
45
|
+
<ChevronDownIcon className="size-4 opacity-70" />
|
|
46
|
+
</PopoverTrigger>
|
|
47
|
+
<PopoverContent align="start" className="w-[var(--anchor-width)] p-1">
|
|
48
|
+
<div className="grid gap-1">
|
|
49
|
+
{options.map((option) => {
|
|
50
|
+
const checked = value.includes(option.value)
|
|
51
|
+
return (
|
|
52
|
+
<button
|
|
53
|
+
key={option.value}
|
|
54
|
+
type="button"
|
|
55
|
+
disabled={option.disabled}
|
|
56
|
+
className="flex items-start gap-2 rounded-md px-2 py-2 text-left text-sm hover:bg-muted disabled:pointer-events-none disabled:opacity-50"
|
|
57
|
+
onClick={() => toggle(option.value)}
|
|
58
|
+
>
|
|
59
|
+
<span className="mt-0.5 grid size-4 place-items-center rounded border">{checked ? <CheckIcon className="size-3" /> : null}</span>
|
|
60
|
+
<span className="grid min-w-0 flex-1">
|
|
61
|
+
<span className="truncate font-medium">{option.label}</span>
|
|
62
|
+
{option.description ? <span className="truncate text-xs text-muted-foreground">{option.description}</span> : null}
|
|
63
|
+
</span>
|
|
64
|
+
</button>
|
|
65
|
+
)
|
|
66
|
+
})}
|
|
67
|
+
{value.length ? <Button type="button" size="sm" variant="ghost" leftIcon={<XIcon className="size-3.5" />} onClick={() => onValueChange?.([])}>Clear</Button> : null}
|
|
68
|
+
</div>
|
|
69
|
+
</PopoverContent>
|
|
70
|
+
</Popover>
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { MultiSelect }
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { FileIcon, FolderIcon, MoreHorizontalIcon } from "lucide-react"
|
|
5
|
+
|
|
6
|
+
import { Badge } from "@/components/ui/badge"
|
|
7
|
+
import { Button } from "@/components/ui/button"
|
|
8
|
+
import { Input } from "@/components/ui/input"
|
|
9
|
+
import { ScrollBox } from "@/components/ui/scroll-box"
|
|
10
|
+
import { cn } from "@/lib/utils"
|
|
11
|
+
|
|
12
|
+
export type FileManagerItem = {
|
|
13
|
+
id: string
|
|
14
|
+
name: string
|
|
15
|
+
type: "file" | "folder"
|
|
16
|
+
size?: React.ReactNode
|
|
17
|
+
owner?: React.ReactNode
|
|
18
|
+
status?: React.ReactNode
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type FileManagerProps = React.ComponentProps<"div"> & {
|
|
22
|
+
items: FileManagerItem[]
|
|
23
|
+
selectedId?: string
|
|
24
|
+
onSelect?: (item: FileManagerItem) => void
|
|
25
|
+
onOpen?: (item: FileManagerItem) => void
|
|
26
|
+
onSearchChange?: (value: string) => void
|
|
27
|
+
searchValue?: string
|
|
28
|
+
actions?: React.ReactNode
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function FileManager({ items, selectedId, onSelect, onOpen, searchValue, onSearchChange, actions, className, ...props }: FileManagerProps) {
|
|
32
|
+
const [internalSearch, setInternalSearch] = React.useState("")
|
|
33
|
+
const query = searchValue ?? internalSearch
|
|
34
|
+
const filteredItems = items.filter((item) => item.name.toLowerCase().includes(query.toLowerCase()))
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div data-slot="file-manager" className={cn("grid min-w-0 gap-3 rounded-lg border bg-card p-3", className)} {...props}>
|
|
38
|
+
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
39
|
+
<Input
|
|
40
|
+
kind="search"
|
|
41
|
+
placeholder="Search files..."
|
|
42
|
+
value={query}
|
|
43
|
+
onValueChange={(value) => {
|
|
44
|
+
setInternalSearch(value)
|
|
45
|
+
onSearchChange?.(value)
|
|
46
|
+
}}
|
|
47
|
+
className="max-w-sm"
|
|
48
|
+
/>
|
|
49
|
+
{actions}
|
|
50
|
+
</div>
|
|
51
|
+
<ScrollBox className="max-h-[420px]">
|
|
52
|
+
<div className="grid gap-1">
|
|
53
|
+
{filteredItems.map((item) => (
|
|
54
|
+
<button
|
|
55
|
+
key={item.id}
|
|
56
|
+
type="button"
|
|
57
|
+
data-selected={selectedId === item.id || undefined}
|
|
58
|
+
className="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 rounded-md px-3 py-2 text-left transition hover:bg-muted/60 data-[selected=true]:bg-muted"
|
|
59
|
+
onClick={() => onSelect?.(item)}
|
|
60
|
+
onDoubleClick={() => onOpen?.(item)}
|
|
61
|
+
>
|
|
62
|
+
{item.type === "folder" ? <FolderIcon className="size-4 text-primary" /> : <FileIcon className="size-4 text-muted-foreground" />}
|
|
63
|
+
<span className="min-w-0">
|
|
64
|
+
<span className="block truncate text-sm font-medium">{item.name}</span>
|
|
65
|
+
<span className="text-xs text-muted-foreground">{item.owner ?? "Shared"} {item.size ? `- ${item.size}` : ""}</span>
|
|
66
|
+
</span>
|
|
67
|
+
<span className="flex items-center gap-2">
|
|
68
|
+
{item.status ? <Badge label={item.status} variant="soft" status="info" /> : null}
|
|
69
|
+
<Button type="button" variant="ghost" size="icon-xs" aria-label="File actions"><MoreHorizontalIcon /></Button>
|
|
70
|
+
</span>
|
|
71
|
+
</button>
|
|
72
|
+
))}
|
|
73
|
+
</div>
|
|
74
|
+
</ScrollBox>
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { FileManager }
|