@spree/dashboard-ui 0.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 (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -0
  3. package/package.json +86 -0
  4. package/src/hooks/use-copy-to-clipboard.ts +44 -0
  5. package/src/hooks/use-mobile.ts +19 -0
  6. package/src/hooks/use-scrolled.ts +30 -0
  7. package/src/index.ts +99 -0
  8. package/src/lib/date-locale.ts +29 -0
  9. package/src/lib/utils.ts +6 -0
  10. package/src/lib/validation-messages.ts +19 -0
  11. package/src/spree/address-block.tsx +74 -0
  12. package/src/spree/back-button.tsx +48 -0
  13. package/src/spree/bulk-dialog.tsx +71 -0
  14. package/src/spree/bulk-price-table.tsx +254 -0
  15. package/src/spree/calculator-summary.tsx +120 -0
  16. package/src/spree/color-picker.tsx +202 -0
  17. package/src/spree/confirm-dialog.tsx +82 -0
  18. package/src/spree/copy-to-clipboard-button.tsx +90 -0
  19. package/src/spree/country-flag.tsx +30 -0
  20. package/src/spree/data-grid/cells.tsx +415 -0
  21. package/src/spree/data-grid/context.ts +59 -0
  22. package/src/spree/data-grid/data-grid.tsx +214 -0
  23. package/src/spree/data-grid/fill-handle.tsx +153 -0
  24. package/src/spree/data-grid/helpers.ts +18 -0
  25. package/src/spree/data-grid/index.ts +5 -0
  26. package/src/spree/data-grid/types.ts +36 -0
  27. package/src/spree/data-grid/use-data-grid-keyboard.ts +254 -0
  28. package/src/spree/data-grid/use-stable-cell-registration.ts +45 -0
  29. package/src/spree/drag-handle.tsx +51 -0
  30. package/src/spree/form-actions.tsx +140 -0
  31. package/src/spree/json-preview-drawer.tsx +254 -0
  32. package/src/spree/json-value-view.tsx +149 -0
  33. package/src/spree/language-menu-items.tsx +54 -0
  34. package/src/spree/metadata/metadata-card.tsx +62 -0
  35. package/src/spree/relative-time.tsx +49 -0
  36. package/src/spree/resource-combobox.tsx +108 -0
  37. package/src/spree/resource-layout.tsx +31 -0
  38. package/src/spree/resource-multi-autocomplete.tsx +180 -0
  39. package/src/spree/resource-name-cell.tsx +44 -0
  40. package/src/spree/route-error-boundary.tsx +102 -0
  41. package/src/spree/row-actions.tsx +85 -0
  42. package/src/spree/row-click-bridge.tsx +17 -0
  43. package/src/spree/secret-input.tsx +137 -0
  44. package/src/spree/storefront-visible-switch.tsx +58 -0
  45. package/src/spree/tag-list.tsx +26 -0
  46. package/src/spree/theme-provider.tsx +83 -0
  47. package/src/spree/theme-toggle.tsx +35 -0
  48. package/src/styles.css +338 -0
  49. package/src/ui/attachment.tsx +187 -0
  50. package/src/ui/avatar.tsx +94 -0
  51. package/src/ui/badge.tsx +161 -0
  52. package/src/ui/breadcrumb.tsx +109 -0
  53. package/src/ui/button.tsx +60 -0
  54. package/src/ui/calendar.tsx +66 -0
  55. package/src/ui/card.tsx +72 -0
  56. package/src/ui/chart.tsx +94 -0
  57. package/src/ui/checkbox.tsx +50 -0
  58. package/src/ui/collapsible.tsx +18 -0
  59. package/src/ui/combobox.tsx +270 -0
  60. package/src/ui/command.tsx +172 -0
  61. package/src/ui/data-table.tsx +60 -0
  62. package/src/ui/date-picker.tsx +253 -0
  63. package/src/ui/date-range-picker.tsx +150 -0
  64. package/src/ui/dialog.tsx +214 -0
  65. package/src/ui/dropdown-menu.tsx +345 -0
  66. package/src/ui/empty.tsx +94 -0
  67. package/src/ui/field.tsx +221 -0
  68. package/src/ui/input-group.tsx +141 -0
  69. package/src/ui/input.tsx +19 -0
  70. package/src/ui/label.tsx +29 -0
  71. package/src/ui/pagination.tsx +123 -0
  72. package/src/ui/popover.tsx +76 -0
  73. package/src/ui/progress.tsx +73 -0
  74. package/src/ui/radio-group.tsx +42 -0
  75. package/src/ui/rich-text-editor.tsx +263 -0
  76. package/src/ui/select.tsx +213 -0
  77. package/src/ui/separator.tsx +31 -0
  78. package/src/ui/sheet.tsx +208 -0
  79. package/src/ui/sidebar.tsx +679 -0
  80. package/src/ui/skeleton.tsx +13 -0
  81. package/src/ui/slot.tsx +35 -0
  82. package/src/ui/sonner.tsx +47 -0
  83. package/src/ui/switch.tsx +39 -0
  84. package/src/ui/textarea.tsx +18 -0
  85. package/src/ui/tooltip.tsx +100 -0
@@ -0,0 +1,72 @@
1
+ import type * as React from 'react'
2
+
3
+ import { cn } from '../lib/utils'
4
+
5
+ function Card({ className, ...props }: React.ComponentProps<'div'>) {
6
+ return (
7
+ <div
8
+ data-slot="card"
9
+ className={cn(
10
+ 'group/card flex flex-col min-w-0 break-words bg-card text-card-foreground border border-border rounded-xl shadow-sm',
11
+ className,
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
19
+ return (
20
+ <div
21
+ data-slot="card-header"
22
+ className={cn(
23
+ 'group/card-header @container/card-header grid auto-rows-min items-start gap-1 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] px-3 py-2.5 border-b border-border rounded-t-[calc(var(--radius-2xl)-1px)]',
24
+ className,
25
+ )}
26
+ {...props}
27
+ />
28
+ )
29
+ }
30
+
31
+ function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
32
+ return (
33
+ <div
34
+ data-slot="card-title"
35
+ className={cn('text-lg font-medium flex items-center gap-2', className)}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
42
+ return (
43
+ <div
44
+ data-slot="card-description"
45
+ className={cn('text-sm text-muted-foreground text-left', className)}
46
+ {...props}
47
+ />
48
+ )
49
+ }
50
+
51
+ function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
52
+ return <div data-slot="card-action" className={cn('ml-auto', className)} {...props} />
53
+ }
54
+
55
+ function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
56
+ return <div data-slot="card-content" className={cn('p-3', className)} {...props} />
57
+ }
58
+
59
+ function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
60
+ return (
61
+ <div
62
+ data-slot="card-footer"
63
+ className={cn(
64
+ 'flex items-center p-3 bg-transparent border-t border-border rounded-b-[calc(var(--radius-xl)-1px)]',
65
+ className,
66
+ )}
67
+ {...props}
68
+ />
69
+ )
70
+ }
71
+
72
+ export { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
@@ -0,0 +1,94 @@
1
+ import * as React from 'react'
2
+ import { ResponsiveContainer, Tooltip } from 'recharts'
3
+ import { cn } from '../lib/utils'
4
+
5
+ export type ChartConfig = Record<string, { label: string; color?: string }>
6
+
7
+ interface ChartContextValue {
8
+ config: ChartConfig
9
+ }
10
+
11
+ const ChartContext = React.createContext<ChartContextValue | null>(null)
12
+
13
+ export function useChart() {
14
+ const ctx = React.useContext(ChartContext)
15
+ if (!ctx) throw new Error('useChart must be used within ChartContainer')
16
+ return ctx
17
+ }
18
+
19
+ export function ChartContainer({
20
+ config,
21
+ className,
22
+ children,
23
+ ...props
24
+ }: React.ComponentProps<'div'> & { config: ChartConfig }) {
25
+ const cssVars = Object.entries(config).reduce(
26
+ (acc, [key, value]) => {
27
+ if (value.color) acc[`--color-${key}` as string] = value.color
28
+ return acc
29
+ },
30
+ {} as Record<string, string>,
31
+ )
32
+
33
+ return (
34
+ <ChartContext.Provider value={{ config }}>
35
+ <div
36
+ data-slot="chart"
37
+ className={cn(
38
+ "flex justify-center text-xs [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden",
39
+ className,
40
+ )}
41
+ style={cssVars}
42
+ {...props}
43
+ >
44
+ <ResponsiveContainer>{children}</ResponsiveContainer>
45
+ </div>
46
+ </ChartContext.Provider>
47
+ )
48
+ }
49
+
50
+ export const ChartTooltip = Tooltip
51
+
52
+ export function ChartTooltipContent({
53
+ active,
54
+ payload,
55
+ label,
56
+ className,
57
+ labelFormatter,
58
+ }: {
59
+ active?: boolean
60
+ payload?: Array<{ dataKey?: string; name?: string; value?: number | string; color?: string }>
61
+ label?: string
62
+ className?: string
63
+ nameKey?: string
64
+ labelFormatter?: (label: string) => string
65
+ }) {
66
+ const { config } = useChart()
67
+ if (!active || !payload?.length) return null
68
+
69
+ const formattedLabel = labelFormatter ? labelFormatter(label ?? '') : label
70
+
71
+ return (
72
+ <div className={cn('rounded-lg border bg-background px-3 py-2 text-sm shadow-md', className)}>
73
+ <div className="mb-1 font-medium">{formattedLabel}</div>
74
+ <div className="flex flex-col gap-0.5">
75
+ {payload.map((item, i) => {
76
+ const key = item.dataKey ?? item.name ?? ''
77
+ const conf = config[key]
78
+ return (
79
+ <div key={i} className="flex items-center gap-2">
80
+ <div
81
+ className="size-2.5 rounded-[2px]"
82
+ style={{ background: item.color || conf?.color }}
83
+ />
84
+ <span className="text-muted-foreground">{conf?.label || item.name}:</span>
85
+ <span className="font-medium">
86
+ {typeof item.value === 'number' ? item.value.toLocaleString() : item.value}
87
+ </span>
88
+ </div>
89
+ )
90
+ })}
91
+ </div>
92
+ </div>
93
+ )
94
+ }
@@ -0,0 +1,50 @@
1
+ import { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox'
2
+ import { CheckIcon, MinusIcon } from 'lucide-react'
3
+ import type * as React from 'react'
4
+ import { cn } from '../lib/utils'
5
+
6
+ function Checkbox({
7
+ className,
8
+ checked,
9
+ defaultChecked,
10
+ onCheckedChange,
11
+ indeterminate,
12
+ disabled,
13
+ name,
14
+ ...props
15
+ }: Omit<React.ComponentProps<typeof CheckboxPrimitive.Root>, 'children' | 'onCheckedChange'> & {
16
+ onCheckedChange?: (checked: boolean) => void
17
+ }) {
18
+ return (
19
+ <CheckboxPrimitive.Root
20
+ data-slot="checkbox"
21
+ checked={checked}
22
+ defaultChecked={defaultChecked}
23
+ onCheckedChange={onCheckedChange ? (next) => onCheckedChange(next) : undefined}
24
+ indeterminate={indeterminate}
25
+ disabled={disabled}
26
+ name={name}
27
+ className={cn(
28
+ 'peer relative inline-block size-4 shrink-0 rounded-[4px] border border-input align-[-3px] shadow-xs outline-none transition-shadow',
29
+ 'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
30
+ 'disabled:cursor-not-allowed disabled:opacity-50',
31
+ 'aria-invalid:border-destructive aria-invalid:ring-destructive/20',
32
+ 'data-[checked]:border-blue-500 data-[checked]:bg-blue-500 data-[checked]:text-white',
33
+ 'data-[indeterminate]:border-blue-500 data-[indeterminate]:bg-blue-500 data-[indeterminate]:text-white',
34
+ 'dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:data-[checked]:bg-blue-500',
35
+ className,
36
+ )}
37
+ {...props}
38
+ >
39
+ <CheckboxPrimitive.Indicator
40
+ keepMounted
41
+ data-slot="checkbox-indicator"
42
+ className="absolute inset-0 flex items-center justify-center text-current transition-none data-[unchecked]:hidden"
43
+ >
44
+ {indeterminate ? <MinusIcon className="size-3.5" /> : <CheckIcon className="size-3.5" />}
45
+ </CheckboxPrimitive.Indicator>
46
+ </CheckboxPrimitive.Root>
47
+ )
48
+ }
49
+
50
+ export { Checkbox }
@@ -0,0 +1,18 @@
1
+ import { Collapsible as CollapsiblePrimitive } from '@base-ui/react/collapsible'
2
+ import type * as React from 'react'
3
+
4
+ function Collapsible({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
5
+ return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
6
+ }
7
+
8
+ function CollapsibleTrigger({
9
+ ...props
10
+ }: React.ComponentProps<typeof CollapsiblePrimitive.Trigger>) {
11
+ return <CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />
12
+ }
13
+
14
+ function CollapsibleContent({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.Panel>) {
15
+ return <CollapsiblePrimitive.Panel data-slot="collapsible-content" {...props} />
16
+ }
17
+
18
+ export { Collapsible, CollapsibleContent, CollapsibleTrigger }
@@ -0,0 +1,270 @@
1
+ 'use client'
2
+
3
+ import { Combobox as ComboboxPrimitive } from '@base-ui/react'
4
+ import { CheckIcon, ChevronDownIcon, XIcon } from 'lucide-react'
5
+ import * as React from 'react'
6
+ import { cn } from '../lib/utils'
7
+ import { Button } from './button'
8
+ import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from './input-group'
9
+
10
+ const Combobox = ComboboxPrimitive.Root
11
+
12
+ function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {
13
+ return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />
14
+ }
15
+
16
+ function ComboboxTrigger({ className, children, ...props }: ComboboxPrimitive.Trigger.Props) {
17
+ return (
18
+ <ComboboxPrimitive.Trigger
19
+ data-slot="combobox-trigger"
20
+ className={cn("[&_svg:not([class*='size-'])]:size-4", className)}
21
+ {...props}
22
+ >
23
+ {children}
24
+ <ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
25
+ </ComboboxPrimitive.Trigger>
26
+ )
27
+ }
28
+
29
+ function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {
30
+ return (
31
+ <ComboboxPrimitive.Clear
32
+ data-slot="combobox-clear"
33
+ render={<InputGroupButton variant="ghost" size="icon-xs" />}
34
+ className={cn(className)}
35
+ {...props}
36
+ >
37
+ <XIcon className="pointer-events-none" />
38
+ </ComboboxPrimitive.Clear>
39
+ )
40
+ }
41
+
42
+ function ComboboxInput({
43
+ className,
44
+ children,
45
+ disabled = false,
46
+ showTrigger = true,
47
+ showClear = false,
48
+ ...props
49
+ }: ComboboxPrimitive.Input.Props & {
50
+ showTrigger?: boolean
51
+ showClear?: boolean
52
+ }) {
53
+ return (
54
+ <InputGroup className={cn('w-auto', className)}>
55
+ <ComboboxPrimitive.Input render={<InputGroupInput disabled={disabled} />} {...props} />
56
+ <InputGroupAddon align="inline-end">
57
+ {showTrigger && (
58
+ <InputGroupButton
59
+ size="icon-xs"
60
+ variant="ghost"
61
+ asChild
62
+ data-slot="input-group-button"
63
+ className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
64
+ disabled={disabled}
65
+ >
66
+ <ComboboxTrigger />
67
+ </InputGroupButton>
68
+ )}
69
+ {showClear && <ComboboxClear disabled={disabled} />}
70
+ </InputGroupAddon>
71
+ {children}
72
+ </InputGroup>
73
+ )
74
+ }
75
+
76
+ function ComboboxContent({
77
+ className,
78
+ side = 'bottom',
79
+ sideOffset = 6,
80
+ align = 'start',
81
+ alignOffset = 0,
82
+ anchor,
83
+ ...props
84
+ }: ComboboxPrimitive.Popup.Props &
85
+ Pick<
86
+ ComboboxPrimitive.Positioner.Props,
87
+ 'side' | 'align' | 'sideOffset' | 'alignOffset' | 'anchor'
88
+ >) {
89
+ return (
90
+ <ComboboxPrimitive.Portal>
91
+ <ComboboxPrimitive.Positioner
92
+ side={side}
93
+ sideOffset={sideOffset}
94
+ align={align}
95
+ alignOffset={alignOffset}
96
+ anchor={anchor}
97
+ className="isolate z-50"
98
+ >
99
+ <ComboboxPrimitive.Popup
100
+ data-slot="combobox-content"
101
+ data-chips={!!anchor}
102
+ className={cn(
103
+ 'group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
104
+ className,
105
+ )}
106
+ {...props}
107
+ />
108
+ </ComboboxPrimitive.Positioner>
109
+ </ComboboxPrimitive.Portal>
110
+ )
111
+ }
112
+
113
+ function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
114
+ return (
115
+ <ComboboxPrimitive.List
116
+ data-slot="combobox-list"
117
+ className={cn(
118
+ 'no-scrollbar max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto overscroll-contain p-1 data-empty:p-0',
119
+ className,
120
+ )}
121
+ {...props}
122
+ />
123
+ )
124
+ }
125
+
126
+ function ComboboxItem({ className, children, ...props }: ComboboxPrimitive.Item.Props) {
127
+ return (
128
+ <ComboboxPrimitive.Item
129
+ data-slot="combobox-item"
130
+ className={cn(
131
+ // Padding + font sized to match `<Input>` (`py-1.5 px-2.5 text-base
132
+ // leading-normal`) so the dropdown rows feel like a continuation of
133
+ // the trigger field. `pr-8` keeps room for the check indicator.
134
+ "relative flex w-full cursor-default items-center gap-2 rounded-md py-1.5 pr-8 pl-2.5 text-base font-normal leading-normal outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
135
+ className,
136
+ )}
137
+ {...props}
138
+ >
139
+ {children}
140
+ <ComboboxPrimitive.ItemIndicator
141
+ render={
142
+ <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
143
+ }
144
+ >
145
+ <CheckIcon className="pointer-events-none" />
146
+ </ComboboxPrimitive.ItemIndicator>
147
+ </ComboboxPrimitive.Item>
148
+ )
149
+ }
150
+
151
+ function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {
152
+ return <ComboboxPrimitive.Group data-slot="combobox-group" className={cn(className)} {...props} />
153
+ }
154
+
155
+ function ComboboxLabel({ className, ...props }: ComboboxPrimitive.GroupLabel.Props) {
156
+ return (
157
+ <ComboboxPrimitive.GroupLabel
158
+ data-slot="combobox-label"
159
+ className={cn('px-2 py-1.5 text-xs text-muted-foreground', className)}
160
+ {...props}
161
+ />
162
+ )
163
+ }
164
+
165
+ function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) {
166
+ return <ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} />
167
+ }
168
+
169
+ function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
170
+ return (
171
+ <ComboboxPrimitive.Empty
172
+ data-slot="combobox-empty"
173
+ className={cn(
174
+ 'hidden w-full justify-center py-2 text-center text-sm text-muted-foreground group-data-empty/combobox-content:flex',
175
+ className,
176
+ )}
177
+ {...props}
178
+ />
179
+ )
180
+ }
181
+
182
+ function ComboboxSeparator({ className, ...props }: ComboboxPrimitive.Separator.Props) {
183
+ return (
184
+ <ComboboxPrimitive.Separator
185
+ data-slot="combobox-separator"
186
+ className={cn('-mx-1 my-1 h-px bg-border', className)}
187
+ {...props}
188
+ />
189
+ )
190
+ }
191
+
192
+ function ComboboxChips({
193
+ className,
194
+ ...props
195
+ }: React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> & ComboboxPrimitive.Chips.Props) {
196
+ return (
197
+ <ComboboxPrimitive.Chips
198
+ data-slot="combobox-chips"
199
+ className={cn(
200
+ 'flex flex-wrap items-center gap-1 rounded-lg border border-border bg-card text-foreground shadow-xs px-2.5 py-1.5 text-base transition-all duration-100 ease-in-out focus-within:border-blue-500 focus-within:shadow-[0_0_0_3px_rgba(59,130,246,0.15)] has-aria-invalid:border-destructive has-data-[slot=combobox-chip]:px-1',
201
+ className,
202
+ )}
203
+ {...props}
204
+ />
205
+ )
206
+ }
207
+
208
+ function ComboboxChip({
209
+ className,
210
+ children,
211
+ showRemove = true,
212
+ ...props
213
+ }: ComboboxPrimitive.Chip.Props & {
214
+ showRemove?: boolean
215
+ }) {
216
+ return (
217
+ <ComboboxPrimitive.Chip
218
+ data-slot="combobox-chip"
219
+ className={cn(
220
+ 'flex h-[calc(--spacing(5.25))] w-fit max-w-full items-center justify-center gap-1 rounded-sm bg-muted px-1.5 text-xs font-medium text-foreground has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0',
221
+ className,
222
+ )}
223
+ {...props}
224
+ >
225
+ <span className="min-w-0 truncate">{children}</span>
226
+ {showRemove && (
227
+ <ComboboxPrimitive.ChipRemove
228
+ render={<Button variant="ghost" size="icon-xs" />}
229
+ className="opacity-50 hover:opacity-100"
230
+ data-slot="combobox-chip-remove"
231
+ >
232
+ <XIcon className="pointer-events-none" />
233
+ </ComboboxPrimitive.ChipRemove>
234
+ )}
235
+ </ComboboxPrimitive.Chip>
236
+ )
237
+ }
238
+
239
+ function ComboboxChipsInput({ className, ...props }: ComboboxPrimitive.Input.Props) {
240
+ return (
241
+ <ComboboxPrimitive.Input
242
+ data-slot="combobox-chip-input"
243
+ className={cn('min-w-16 flex-1 outline-none', className)}
244
+ {...props}
245
+ />
246
+ )
247
+ }
248
+
249
+ function useComboboxAnchor() {
250
+ return React.useRef<HTMLDivElement | null>(null)
251
+ }
252
+
253
+ export {
254
+ Combobox,
255
+ ComboboxChip,
256
+ ComboboxChips,
257
+ ComboboxChipsInput,
258
+ ComboboxCollection,
259
+ ComboboxContent,
260
+ ComboboxEmpty,
261
+ ComboboxGroup,
262
+ ComboboxInput,
263
+ ComboboxItem,
264
+ ComboboxLabel,
265
+ ComboboxList,
266
+ ComboboxSeparator,
267
+ ComboboxTrigger,
268
+ ComboboxValue,
269
+ useComboboxAnchor,
270
+ }
@@ -0,0 +1,172 @@
1
+ import { Command as CommandPrimitive } from 'cmdk'
2
+ import i18n from 'i18next'
3
+ import { CheckIcon, SearchIcon } from 'lucide-react'
4
+ import * as React from 'react'
5
+ import { cn } from '../lib/utils'
6
+ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from './dialog'
7
+ import { InputGroup, InputGroupAddon } from './input-group'
8
+
9
+ function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
10
+ return (
11
+ <CommandPrimitive
12
+ data-slot="command"
13
+ className={cn(
14
+ 'flex size-full flex-col overflow-hidden rounded-xl! bg-popover p-1 text-popover-foreground',
15
+ className,
16
+ )}
17
+ {...props}
18
+ />
19
+ )
20
+ }
21
+
22
+ function CommandDialog({
23
+ title = i18n.t('admin.components.command_palette.title'),
24
+ description = i18n.t('admin.components.command_palette.description'),
25
+ children,
26
+ className,
27
+ showCloseButton = false,
28
+ ...props
29
+ }: React.ComponentProps<typeof Dialog> & {
30
+ title?: string
31
+ description?: string
32
+ className?: string
33
+ showCloseButton?: boolean
34
+ }) {
35
+ return (
36
+ <Dialog {...props}>
37
+ <DialogHeader className="sr-only">
38
+ <DialogTitle>{title}</DialogTitle>
39
+ <DialogDescription>{description}</DialogDescription>
40
+ </DialogHeader>
41
+ <DialogContent
42
+ className={cn('top-1/3 translate-y-0 overflow-hidden rounded-xl! p-0', className)}
43
+ showCloseButton={showCloseButton}
44
+ >
45
+ {children}
46
+ </DialogContent>
47
+ </Dialog>
48
+ )
49
+ }
50
+
51
+ function CommandInput({
52
+ className,
53
+ ...props
54
+ }: React.ComponentProps<typeof CommandPrimitive.Input>) {
55
+ return (
56
+ <div data-slot="command-input-wrapper" className="p-1 pb-0">
57
+ <InputGroup className="h-8! rounded-lg! border-input/30 bg-input/30 shadow-none! *:data-[slot=input-group-addon]:pl-2!">
58
+ <CommandPrimitive.Input
59
+ data-slot="command-input"
60
+ className={cn(
61
+ 'w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50',
62
+ className,
63
+ )}
64
+ {...props}
65
+ />
66
+ <InputGroupAddon>
67
+ <SearchIcon className="size-4 shrink-0 opacity-50" />
68
+ </InputGroupAddon>
69
+ </InputGroup>
70
+ </div>
71
+ )
72
+ }
73
+
74
+ function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {
75
+ return (
76
+ <CommandPrimitive.List
77
+ data-slot="command-list"
78
+ className={cn(
79
+ 'no-scrollbar max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto outline-none',
80
+ className,
81
+ )}
82
+ {...props}
83
+ />
84
+ )
85
+ }
86
+
87
+ function CommandEmpty({
88
+ className,
89
+ ...props
90
+ }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
91
+ return (
92
+ <CommandPrimitive.Empty
93
+ data-slot="command-empty"
94
+ className={cn('py-6 text-center text-sm', className)}
95
+ {...props}
96
+ />
97
+ )
98
+ }
99
+
100
+ function CommandGroup({
101
+ className,
102
+ ...props
103
+ }: React.ComponentProps<typeof CommandPrimitive.Group>) {
104
+ return (
105
+ <CommandPrimitive.Group
106
+ data-slot="command-group"
107
+ className={cn(
108
+ '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',
109
+ className,
110
+ )}
111
+ {...props}
112
+ />
113
+ )
114
+ }
115
+
116
+ function CommandSeparator({
117
+ className,
118
+ ...props
119
+ }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
120
+ return (
121
+ <CommandPrimitive.Separator
122
+ data-slot="command-separator"
123
+ className={cn('-mx-1 h-px bg-border', className)}
124
+ {...props}
125
+ />
126
+ )
127
+ }
128
+
129
+ function CommandItem({
130
+ className,
131
+ children,
132
+ ...props
133
+ }: React.ComponentProps<typeof CommandPrimitive.Item>) {
134
+ return (
135
+ <CommandPrimitive.Item
136
+ data-slot="command-item"
137
+ className={cn(
138
+ "group/command-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none in-data-[slot=dialog-content]:rounded-lg! data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-selected:bg-muted data-selected:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-selected:*:[svg]:text-foreground",
139
+ className,
140
+ )}
141
+ {...props}
142
+ >
143
+ {children}
144
+ <CheckIcon className="ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" />
145
+ </CommandPrimitive.Item>
146
+ )
147
+ }
148
+
149
+ function CommandShortcut({ className, ...props }: React.ComponentProps<'span'>) {
150
+ return (
151
+ <span
152
+ data-slot="command-shortcut"
153
+ className={cn(
154
+ 'ml-auto text-xs tracking-widest text-muted-foreground group-data-selected/command-item:text-foreground',
155
+ className,
156
+ )}
157
+ {...props}
158
+ />
159
+ )
160
+ }
161
+
162
+ export {
163
+ Command,
164
+ CommandDialog,
165
+ CommandEmpty,
166
+ CommandGroup,
167
+ CommandInput,
168
+ CommandItem,
169
+ CommandList,
170
+ CommandSeparator,
171
+ CommandShortcut,
172
+ }