@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.
- package/LICENSE +21 -0
- package/README.md +26 -0
- package/package.json +86 -0
- package/src/hooks/use-copy-to-clipboard.ts +44 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/hooks/use-scrolled.ts +30 -0
- package/src/index.ts +99 -0
- package/src/lib/date-locale.ts +29 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/validation-messages.ts +19 -0
- package/src/spree/address-block.tsx +74 -0
- package/src/spree/back-button.tsx +48 -0
- package/src/spree/bulk-dialog.tsx +71 -0
- package/src/spree/bulk-price-table.tsx +254 -0
- package/src/spree/calculator-summary.tsx +120 -0
- package/src/spree/color-picker.tsx +202 -0
- package/src/spree/confirm-dialog.tsx +82 -0
- package/src/spree/copy-to-clipboard-button.tsx +90 -0
- package/src/spree/country-flag.tsx +30 -0
- package/src/spree/data-grid/cells.tsx +415 -0
- package/src/spree/data-grid/context.ts +59 -0
- package/src/spree/data-grid/data-grid.tsx +214 -0
- package/src/spree/data-grid/fill-handle.tsx +153 -0
- package/src/spree/data-grid/helpers.ts +18 -0
- package/src/spree/data-grid/index.ts +5 -0
- package/src/spree/data-grid/types.ts +36 -0
- package/src/spree/data-grid/use-data-grid-keyboard.ts +254 -0
- package/src/spree/data-grid/use-stable-cell-registration.ts +45 -0
- package/src/spree/drag-handle.tsx +51 -0
- package/src/spree/form-actions.tsx +140 -0
- package/src/spree/json-preview-drawer.tsx +254 -0
- package/src/spree/json-value-view.tsx +149 -0
- package/src/spree/language-menu-items.tsx +54 -0
- package/src/spree/metadata/metadata-card.tsx +62 -0
- package/src/spree/relative-time.tsx +49 -0
- package/src/spree/resource-combobox.tsx +108 -0
- package/src/spree/resource-layout.tsx +31 -0
- package/src/spree/resource-multi-autocomplete.tsx +180 -0
- package/src/spree/resource-name-cell.tsx +44 -0
- package/src/spree/route-error-boundary.tsx +102 -0
- package/src/spree/row-actions.tsx +85 -0
- package/src/spree/row-click-bridge.tsx +17 -0
- package/src/spree/secret-input.tsx +137 -0
- package/src/spree/storefront-visible-switch.tsx +58 -0
- package/src/spree/tag-list.tsx +26 -0
- package/src/spree/theme-provider.tsx +83 -0
- package/src/spree/theme-toggle.tsx +35 -0
- package/src/styles.css +338 -0
- package/src/ui/attachment.tsx +187 -0
- package/src/ui/avatar.tsx +94 -0
- package/src/ui/badge.tsx +161 -0
- package/src/ui/breadcrumb.tsx +109 -0
- package/src/ui/button.tsx +60 -0
- package/src/ui/calendar.tsx +66 -0
- package/src/ui/card.tsx +72 -0
- package/src/ui/chart.tsx +94 -0
- package/src/ui/checkbox.tsx +50 -0
- package/src/ui/collapsible.tsx +18 -0
- package/src/ui/combobox.tsx +270 -0
- package/src/ui/command.tsx +172 -0
- package/src/ui/data-table.tsx +60 -0
- package/src/ui/date-picker.tsx +253 -0
- package/src/ui/date-range-picker.tsx +150 -0
- package/src/ui/dialog.tsx +214 -0
- package/src/ui/dropdown-menu.tsx +345 -0
- package/src/ui/empty.tsx +94 -0
- package/src/ui/field.tsx +221 -0
- package/src/ui/input-group.tsx +141 -0
- package/src/ui/input.tsx +19 -0
- package/src/ui/label.tsx +29 -0
- package/src/ui/pagination.tsx +123 -0
- package/src/ui/popover.tsx +76 -0
- package/src/ui/progress.tsx +73 -0
- package/src/ui/radio-group.tsx +42 -0
- package/src/ui/rich-text-editor.tsx +263 -0
- package/src/ui/select.tsx +213 -0
- package/src/ui/separator.tsx +31 -0
- package/src/ui/sheet.tsx +208 -0
- package/src/ui/sidebar.tsx +679 -0
- package/src/ui/skeleton.tsx +13 -0
- package/src/ui/slot.tsx +35 -0
- package/src/ui/sonner.tsx +47 -0
- package/src/ui/switch.tsx +39 -0
- package/src/ui/textarea.tsx +18 -0
- package/src/ui/tooltip.tsx +100 -0
package/src/ui/card.tsx
ADDED
|
@@ -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 }
|
package/src/ui/chart.tsx
ADDED
|
@@ -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
|
+
}
|