tembro 6.0.2 → 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.
Files changed (127) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/components/calendar/calendar-range-scheduler.cjs +1 -0
  3. package/dist/components/calendar/calendar-range-scheduler.d.ts +20 -0
  4. package/dist/components/calendar/calendar-range-scheduler.js +1 -0
  5. package/dist/components/calendar/date-time-picker.cjs +1 -0
  6. package/dist/components/calendar/date-time-picker.d.ts +15 -0
  7. package/dist/components/calendar/date-time-picker.js +1 -0
  8. package/dist/components/calendar/index.d.ts +2 -0
  9. package/dist/components/charts/chart-card.cjs +1 -0
  10. package/dist/components/charts/chart-card.d.ts +24 -0
  11. package/dist/components/charts/chart-card.js +1 -0
  12. package/dist/components/charts/index.d.ts +1 -0
  13. package/dist/components/command/advanced-command-menu.cjs +1 -0
  14. package/dist/components/command/advanced-command-menu.d.ts +25 -0
  15. package/dist/components/command/advanced-command-menu.js +1 -0
  16. package/dist/components/command/index.d.ts +1 -0
  17. package/dist/components/data-table/data-grid.cjs +1 -0
  18. package/dist/components/data-table/data-grid.d.ts +15 -0
  19. package/dist/components/data-table/data-grid.js +1 -0
  20. package/dist/components/data-table/index.d.ts +1 -0
  21. package/dist/components/data-table/public.d.ts +1 -0
  22. package/dist/components/feedback/async-boundary.cjs +1 -0
  23. package/dist/components/feedback/async-boundary.d.ts +12 -0
  24. package/dist/components/feedback/async-boundary.js +1 -0
  25. package/dist/components/feedback/index.d.ts +1 -0
  26. package/dist/components/inputs/index.d.ts +1 -0
  27. package/dist/components/inputs/multi-select.cjs +1 -0
  28. package/dist/components/inputs/multi-select.d.ts +16 -0
  29. package/dist/components/inputs/multi-select.js +1 -0
  30. package/dist/components/inputs/public.d.ts +1 -0
  31. package/dist/components/modern/file-manager.cjs +1 -0
  32. package/dist/components/modern/file-manager.d.ts +20 -0
  33. package/dist/components/modern/file-manager.js +1 -0
  34. package/dist/components/modern/index.d.ts +1 -0
  35. package/dist/components/notifications/action-toast.cjs +1 -0
  36. package/dist/components/notifications/action-toast.d.ts +16 -0
  37. package/dist/components/notifications/action-toast.js +1 -0
  38. package/dist/components/notifications/index.d.ts +1 -0
  39. package/dist/components/patterns/index.d.ts +1 -0
  40. package/dist/components/patterns/permissions-matrix.cjs +1 -0
  41. package/dist/components/patterns/permissions-matrix.d.ts +19 -0
  42. package/dist/components/patterns/permissions-matrix.js +1 -0
  43. package/dist/components/patterns/public.d.ts +1 -0
  44. package/dist/src/components/calendar/calendar-range-scheduler.cjs +1 -0
  45. package/dist/src/components/calendar/calendar-range-scheduler.js +75 -0
  46. package/dist/src/components/calendar/date-time-picker.cjs +1 -0
  47. package/dist/src/components/calendar/date-time-picker.js +36 -0
  48. package/dist/src/components/calendar/index.cjs +1 -1
  49. package/dist/src/components/calendar/index.js +5 -3
  50. package/dist/src/components/charts/chart-card.cjs +1 -0
  51. package/dist/src/components/charts/chart-card.js +55 -0
  52. package/dist/src/components/charts/index.cjs +1 -1
  53. package/dist/src/components/charts/index.js +3 -2
  54. package/dist/src/components/command/advanced-command-menu.cjs +1 -0
  55. package/dist/src/components/command/advanced-command-menu.js +57 -0
  56. package/dist/src/components/command/index.cjs +1 -1
  57. package/dist/src/components/command/index.js +3 -2
  58. package/dist/src/components/data-table/data-grid.cjs +1 -0
  59. package/dist/src/components/data-table/data-grid.js +32 -0
  60. package/dist/src/components/data-table/data-table-saved-filters.cjs +1 -1
  61. package/dist/src/components/data-table/data-table-saved-filters.js +11 -11
  62. package/dist/src/components/data-table/data-table.cjs +1 -1
  63. package/dist/src/components/data-table/data-table.js +170 -170
  64. package/dist/src/components/data-table/index.cjs +1 -1
  65. package/dist/src/components/data-table/index.js +15 -14
  66. package/dist/src/components/data-table/public.cjs +1 -1
  67. package/dist/src/components/data-table/public.js +3 -2
  68. package/dist/src/components/feedback/async-boundary.cjs +1 -0
  69. package/dist/src/components/feedback/async-boundary.js +22 -0
  70. package/dist/src/components/feedback/index.cjs +1 -1
  71. package/dist/src/components/feedback/index.js +2 -1
  72. package/dist/src/components/inputs/index.cjs +1 -1
  73. package/dist/src/components/inputs/index.js +13 -12
  74. package/dist/src/components/inputs/multi-select.cjs +1 -0
  75. package/dist/src/components/inputs/multi-select.js +80 -0
  76. package/dist/src/components/inputs/public.cjs +1 -1
  77. package/dist/src/components/inputs/public.js +13 -12
  78. package/dist/src/components/modern/file-manager.cjs +1 -0
  79. package/dist/src/components/modern/file-manager.js +75 -0
  80. package/dist/src/components/modern/index.cjs +1 -1
  81. package/dist/src/components/modern/index.js +5 -4
  82. package/dist/src/components/notifications/action-toast.cjs +1 -0
  83. package/dist/src/components/notifications/action-toast.js +49 -0
  84. package/dist/src/components/notifications/index.cjs +1 -1
  85. package/dist/src/components/notifications/index.js +3 -2
  86. package/dist/src/components/patterns/index.cjs +1 -1
  87. package/dist/src/components/patterns/index.js +2 -1
  88. package/dist/src/components/patterns/permissions-matrix.cjs +1 -0
  89. package/dist/src/components/patterns/permissions-matrix.js +43 -0
  90. package/dist/src/components/patterns/public.cjs +1 -1
  91. package/dist/src/components/patterns/public.js +2 -1
  92. package/dist/src/components/ui/input/group.cjs +1 -1
  93. package/dist/src/components/ui/input/group.js +8 -8
  94. package/dist/src/index.cjs +1 -1
  95. package/dist/src/index.js +117 -107
  96. package/dist/src/public-component-surface.cjs +1 -1
  97. package/dist/src/public-component-surface.js +50 -0
  98. package/package.json +13 -13
  99. package/packages/cli/dist/index.cjs +73 -73
  100. package/packages/cli/vendor/src/components/calendar/calendar-range-scheduler.tsx +72 -0
  101. package/packages/cli/vendor/src/components/calendar/date-time-picker.tsx +53 -0
  102. package/packages/cli/vendor/src/components/calendar/index.ts +5 -3
  103. package/packages/cli/vendor/src/components/charts/chart-card.tsx +81 -0
  104. package/packages/cli/vendor/src/components/charts/index.ts +1 -0
  105. package/packages/cli/vendor/src/components/command/advanced-command-menu.tsx +88 -0
  106. package/packages/cli/vendor/src/components/command/index.ts +2 -1
  107. package/packages/cli/vendor/src/components/data-table/data-grid.tsx +59 -0
  108. package/packages/cli/vendor/src/components/data-table/index.ts +3 -2
  109. package/packages/cli/vendor/src/components/data-table/public.ts +1 -0
  110. package/packages/cli/vendor/src/components/feedback/async-boundary.tsx +36 -0
  111. package/packages/cli/vendor/src/components/feedback/index.ts +1 -0
  112. package/packages/cli/vendor/src/components/inputs/index.ts +1 -0
  113. package/packages/cli/vendor/src/components/inputs/multi-select.tsx +75 -0
  114. package/packages/cli/vendor/src/components/inputs/public.ts +1 -0
  115. package/packages/cli/vendor/src/components/modern/file-manager.tsx +79 -0
  116. package/packages/cli/vendor/src/components/modern/index.ts +1 -0
  117. package/packages/cli/vendor/src/components/notifications/action-toast.tsx +78 -0
  118. package/packages/cli/vendor/src/components/notifications/index.ts +2 -1
  119. package/packages/cli/vendor/src/components/patterns/index.ts +1 -0
  120. package/packages/cli/vendor/src/components/patterns/permissions-matrix.tsx +71 -0
  121. package/packages/cli/vendor/src/components/patterns/public.ts +1 -0
  122. package/packages/cli/vendor/src/public-component-surface.ts +10 -0
  123. package/packages/cli/vendor/templates/showcase/src/showcase/data/registry.ts +1 -1
  124. package/packages/cli/vendor/templates/showcase/src/showcase/layout/HeroSection.tsx +3 -2
  125. package/packages/cli/vendor/templates/showcase/src/showcase/layout/WorkbenchSidebar.tsx +3 -2
  126. package/packages/cli/vendor/templates/showcase/src/showcase/sections/ComponentLivePreview.tsx +1 -1
  127. package/registry.json +1726 -1256
@@ -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 }
@@ -1,2 +1,3 @@
1
1
  export * from './charts'
2
2
  export * from './progress-circle'
3
+ export * from './chart-card'
@@ -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-table-pagination'
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'
@@ -1 +1,2 @@
1
1
  export * from "./data-table"
2
+ export * from "./data-grid"
@@ -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 }
@@ -1,2 +1,3 @@
1
1
  export * from "./alert"
2
2
  export * from "./state-view"
3
+ export * from "./async-boundary"
@@ -1,4 +1,5 @@
1
1
  export * from "./async-select"
2
+ export * from "./multi-select"
2
3
  export * from "./combobox"
3
4
  export * from "./otp-input"
4
5
  export * from "./rating"
@@ -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 }
@@ -1,4 +1,5 @@
1
1
  export * from "./async-select"
2
+ export * from "./multi-select"
2
3
  export * from "./combobox"
3
4
  export * from "./otp-input"
4
5
  export * from "./rating"
@@ -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 }
@@ -3,3 +3,4 @@ export * from "./dual-list-picker"
3
3
  export * from "./resizable-panel"
4
4
  export * from "./rich-text-editor"
5
5
  export * from "./image-cropper"
6
+ export * from "./file-manager"