@spree/dashboard-ui 0.13.1 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +15 -9
- package/src/hooks/use-prefers-reduced-motion.ts +26 -0
- package/src/hooks/use-scrolled.ts +34 -14
- package/src/index.ts +39 -1
- package/src/lib/date-range-presets.ts +79 -0
- package/src/lib/same-rich-text.ts +145 -0
- package/src/spree/address-block.tsx +5 -4
- package/src/spree/address-book-row.tsx +96 -0
- package/src/spree/address-map.tsx +62 -0
- package/src/spree/back-button.tsx +14 -9
- package/src/spree/bulk-dialog.tsx +2 -2
- package/src/spree/bulk-price-table.tsx +145 -14
- package/src/spree/bulk-variants-table.tsx +434 -0
- package/src/spree/calculator-summary.tsx +5 -1
- package/src/spree/claim-resolve-dialog.tsx +219 -0
- package/src/spree/confirm-dialog.tsx +1 -1
- package/src/spree/copy-to-clipboard-button.tsx +1 -1
- package/src/spree/data-grid/cells.tsx +199 -25
- package/src/spree/data-grid/context.ts +6 -0
- package/src/spree/data-grid/data-grid.tsx +149 -7
- package/src/spree/data-grid/helpers.ts +6 -2
- package/src/spree/data-grid/index.ts +9 -1
- package/src/spree/data-grid/use-data-grid-keyboard.ts +4 -2
- package/src/spree/delivery-form-dialog.tsx +166 -0
- package/src/spree/drag-handle.tsx +2 -2
- package/src/spree/editable-note-card.tsx +103 -0
- package/src/spree/external-link.tsx +56 -0
- package/src/spree/file-type-icon.tsx +68 -0
- package/src/spree/form-actions.tsx +2 -3
- package/src/spree/form-section.tsx +46 -0
- package/src/spree/fulfill-items-form.tsx +282 -0
- package/src/spree/fulfillment-edit-dialog.tsx +211 -0
- package/src/spree/fulfillment-item-list.tsx +61 -0
- package/src/spree/fulfillment-panel.tsx +53 -0
- package/src/spree/icons.ts +167 -0
- package/src/spree/json-preview-drawer.tsx +4 -4
- package/src/spree/label-upload-dialog.tsx +195 -0
- package/src/spree/language-menu-items.tsx +37 -30
- package/src/spree/metadata/metadata-card.tsx +1 -1
- package/src/spree/order-cancel-dialog.tsx +149 -0
- package/src/spree/post-sale-create-dialogs.tsx +253 -0
- package/src/spree/post-sale-fields.tsx +133 -0
- package/src/spree/product-membership-list.tsx +453 -0
- package/src/spree/products/status-card.tsx +79 -0
- package/src/spree/quantity-tier-editor.tsx +162 -0
- package/src/spree/resource-combobox.tsx +12 -1
- package/src/spree/resource-name-cell.tsx +21 -4
- package/src/spree/return-dialogs.tsx +238 -0
- package/src/spree/route-error-boundary.tsx +1 -1
- package/src/spree/row-actions.tsx +2 -2
- package/src/spree/search-input.tsx +76 -0
- package/src/spree/secret-input.tsx +26 -40
- package/src/spree/tax-identifiers-card.tsx +418 -0
- package/src/spree/theme-toggle.tsx +36 -23
- package/src/spree/wizard.tsx +254 -0
- package/src/styles.css +750 -75
- package/src/ui/accordion.tsx +83 -0
- package/src/ui/alert.tsx +79 -0
- package/src/ui/attachment.tsx +1 -1
- package/src/ui/badge.tsx +198 -56
- package/src/ui/breadcrumb.tsx +1 -1
- package/src/ui/button.tsx +22 -10
- package/src/ui/calendar.tsx +1 -1
- package/src/ui/card.tsx +52 -6
- package/src/ui/checkbox.tsx +13 -5
- package/src/ui/combobox.tsx +125 -6
- package/src/ui/command.tsx +149 -23
- package/src/ui/context-menu.tsx +99 -0
- package/src/ui/data-table.tsx +448 -9
- package/src/ui/date-picker.tsx +18 -3
- package/src/ui/date-range-picker.tsx +47 -60
- package/src/ui/dialog.tsx +6 -6
- package/src/ui/dropdown-menu.tsx +18 -11
- package/src/ui/field.tsx +10 -2
- package/src/ui/input-group.tsx +1 -1
- package/src/ui/input.tsx +1 -1
- package/src/ui/map.tsx +2110 -0
- package/src/ui/media-preview.tsx +105 -0
- package/src/ui/pagination.tsx +19 -11
- package/src/ui/popover.tsx +4 -2
- package/src/ui/progress.tsx +5 -2
- package/src/ui/radio-group.tsx +4 -4
- package/src/ui/rich-text-editor.tsx +214 -88
- package/src/ui/scroll-area.tsx +65 -0
- package/src/ui/select.tsx +17 -10
- package/src/ui/separator.tsx +1 -1
- package/src/ui/sheet.tsx +49 -7
- package/src/ui/sidebar.tsx +138 -37
- package/src/ui/skeleton.tsx +3 -1
- package/src/ui/tabs.tsx +84 -0
- package/src/ui/textarea.tsx +1 -1
- package/src/ui/thumbnail.tsx +80 -0
- package/src/ui/toast.tsx +112 -0
- package/src/ui/tooltip.tsx +3 -1
- package/src/ui/sonner.tsx +0 -47
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import { format
|
|
2
|
-
import {
|
|
3
|
-
import { useState } from 'react'
|
|
1
|
+
import { format } from 'date-fns'
|
|
2
|
+
import { useMemo, useState } from 'react'
|
|
4
3
|
import type { DateRange as DayPickerDateRange } from 'react-day-picker'
|
|
5
4
|
import { useTranslation } from 'react-i18next'
|
|
6
5
|
import { activeDateLocale } from '../lib/date-locale'
|
|
6
|
+
import {
|
|
7
|
+
DATE_RANGE_PRESET_KEYS,
|
|
8
|
+
type DateRangePresetKey,
|
|
9
|
+
matchDateRangePreset,
|
|
10
|
+
resolveDateRangePreset,
|
|
11
|
+
} from '../lib/date-range-presets'
|
|
7
12
|
import { cn } from '../lib/utils'
|
|
13
|
+
import { CalendarIcon, ChevronDownIcon } from '../spree/icons'
|
|
8
14
|
import { Button } from './button'
|
|
9
15
|
import { Calendar } from './calendar'
|
|
10
16
|
import { Popover, PopoverContent, PopoverTrigger } from './popover'
|
|
@@ -14,79 +20,58 @@ export interface DateRange {
|
|
|
14
20
|
to: Date
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
const PRESET_LABEL_KEYS: Record<Exclude<DateRangePresetKey, 'custom'>, string> = {
|
|
24
|
+
'7d': 'admin.components.date_range_picker.presets.last_7_days',
|
|
25
|
+
'14d': 'admin.components.date_range_picker.presets.last_14_days',
|
|
26
|
+
'30d': 'admin.components.date_range_picker.presets.last_30_days',
|
|
27
|
+
'90d': 'admin.components.date_range_picker.presets.last_90_days',
|
|
28
|
+
this_month: 'admin.components.date_range_picker.presets.this_month',
|
|
29
|
+
ytd: 'admin.components.date_range_picker.presets.year_to_date',
|
|
23
30
|
}
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
key: '7d',
|
|
28
|
-
labelKey: 'admin.components.date_range_picker.presets.last_7_days',
|
|
29
|
-
value: () => ({ from: subDays(new Date(), 7), to: new Date() }),
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
key: '14d',
|
|
33
|
-
labelKey: 'admin.components.date_range_picker.presets.last_14_days',
|
|
34
|
-
value: () => ({ from: subDays(new Date(), 14), to: new Date() }),
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
key: '30d',
|
|
38
|
-
labelKey: 'admin.components.date_range_picker.presets.last_30_days',
|
|
39
|
-
value: () => ({ from: subDays(new Date(), 30), to: new Date() }),
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
key: '90d',
|
|
43
|
-
labelKey: 'admin.components.date_range_picker.presets.last_90_days',
|
|
44
|
-
value: () => ({ from: subDays(new Date(), 90), to: new Date() }),
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
key: 'this_month',
|
|
48
|
-
labelKey: 'admin.components.date_range_picker.presets.this_month',
|
|
49
|
-
value: () => ({ from: startOfMonth(new Date()), to: new Date() }),
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
key: 'ytd',
|
|
53
|
-
labelKey: 'admin.components.date_range_picker.presets.year_to_date',
|
|
54
|
-
value: () => ({ from: startOfYear(new Date()), to: new Date() }),
|
|
55
|
-
},
|
|
56
|
-
]
|
|
57
|
-
|
|
58
|
-
interface DateRangePickerProps {
|
|
32
|
+
export interface DateRangePickerProps {
|
|
59
33
|
value: DateRange
|
|
60
34
|
onChange: (range: DateRange) => void
|
|
35
|
+
/**
|
|
36
|
+
* IANA timezone used to resolve relative presets ("last 30 days").
|
|
37
|
+
* Defaults to the browser zone. Pass the store timezone on admin
|
|
38
|
+
* surfaces so every merchant sees the same span.
|
|
39
|
+
*/
|
|
40
|
+
timezone?: string
|
|
61
41
|
}
|
|
62
42
|
|
|
63
|
-
export function DateRangePicker({ value, onChange }: DateRangePickerProps) {
|
|
43
|
+
export function DateRangePicker({ value, onChange, timezone }: DateRangePickerProps) {
|
|
64
44
|
const { t } = useTranslation()
|
|
65
45
|
const [open, setOpen] = useState(false)
|
|
66
|
-
const [activePreset, setActivePreset] = useState<PresetKey>('30d')
|
|
67
46
|
const [calendarRange, setCalendarRange] = useState<DayPickerDateRange | undefined>()
|
|
47
|
+
const [month, setMonth] = useState<Date>(value.from)
|
|
68
48
|
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
49
|
+
const zone = useMemo(
|
|
50
|
+
() => timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone ?? 'UTC',
|
|
51
|
+
[timezone],
|
|
52
|
+
)
|
|
53
|
+
// Derived from `value` so a remount — or a range written by the parent —
|
|
54
|
+
// keeps the trigger and the highlighted preset in sync. A second piece of
|
|
55
|
+
// preset state is what made "Last 90 days" revert to "Last 30 days"
|
|
56
|
+
// after the dashboard swapped the picker for a skeleton.
|
|
57
|
+
const activePreset = matchDateRangePreset(value, zone)
|
|
72
58
|
|
|
73
59
|
const dateLocale = activeDateLocale()
|
|
74
60
|
const triggerLabel =
|
|
75
61
|
activePreset === 'custom'
|
|
76
62
|
? `${format(value.from, 'MMM d', { locale: dateLocale })} – ${format(value.to, 'MMM d', { locale: dateLocale })}`
|
|
77
|
-
: t(
|
|
63
|
+
: t(PRESET_LABEL_KEYS[activePreset])
|
|
78
64
|
|
|
79
|
-
function selectPreset(
|
|
80
|
-
const range =
|
|
81
|
-
setActivePreset(preset.key)
|
|
65
|
+
function selectPreset(key: Exclude<DateRangePresetKey, 'custom'>) {
|
|
66
|
+
const range = resolveDateRangePreset(key, zone)
|
|
82
67
|
setCalendarRange({ from: range.from, to: range.to })
|
|
68
|
+
setMonth(range.from)
|
|
83
69
|
onChange(range)
|
|
84
70
|
setOpen(false)
|
|
85
71
|
}
|
|
86
72
|
|
|
87
73
|
function applyCustomRange() {
|
|
88
74
|
if (calendarRange?.from && calendarRange?.to) {
|
|
89
|
-
setActivePreset('custom')
|
|
90
75
|
onChange({ from: calendarRange.from, to: calendarRange.to })
|
|
91
76
|
setOpen(false)
|
|
92
77
|
}
|
|
@@ -101,6 +86,7 @@ export function DateRangePicker({ value, onChange }: DateRangePickerProps) {
|
|
|
101
86
|
setOpen(nextOpen)
|
|
102
87
|
if (nextOpen) {
|
|
103
88
|
setCalendarRange({ from: value.from, to: value.to })
|
|
89
|
+
setMonth(value.from)
|
|
104
90
|
}
|
|
105
91
|
}}
|
|
106
92
|
>
|
|
@@ -114,25 +100,26 @@ export function DateRangePicker({ value, onChange }: DateRangePickerProps) {
|
|
|
114
100
|
<PopoverContent align="end" className="w-auto p-0">
|
|
115
101
|
<div className="flex">
|
|
116
102
|
<div className="flex flex-col border-r py-2">
|
|
117
|
-
{
|
|
103
|
+
{DATE_RANGE_PRESET_KEYS.map((key) => (
|
|
118
104
|
<button
|
|
119
|
-
key={
|
|
105
|
+
key={key}
|
|
120
106
|
type="button"
|
|
121
|
-
onClick={() => selectPreset(
|
|
107
|
+
onClick={() => selectPreset(key)}
|
|
122
108
|
className={cn(
|
|
123
109
|
'whitespace-nowrap px-4 py-1.5 text-left text-sm transition-colors hover:bg-accent',
|
|
124
|
-
activePreset ===
|
|
125
|
-
activePreset !==
|
|
110
|
+
activePreset === key && 'font-medium text-foreground',
|
|
111
|
+
activePreset !== key && 'text-muted-foreground',
|
|
126
112
|
)}
|
|
127
113
|
>
|
|
128
|
-
{t(
|
|
114
|
+
{t(PRESET_LABEL_KEYS[key])}
|
|
129
115
|
</button>
|
|
130
116
|
))}
|
|
131
117
|
</div>
|
|
132
118
|
<div className="flex flex-col p-3">
|
|
133
119
|
<Calendar
|
|
134
120
|
mode="range"
|
|
135
|
-
|
|
121
|
+
month={month}
|
|
122
|
+
onMonthChange={setMonth}
|
|
136
123
|
selected={calendarRange}
|
|
137
124
|
onSelect={setCalendarRange}
|
|
138
125
|
numberOfMonths={2}
|
package/src/ui/dialog.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog'
|
|
2
2
|
import i18n from 'i18next'
|
|
3
|
-
import { XIcon } from 'lucide-react'
|
|
4
3
|
import * as React from 'react'
|
|
5
4
|
import { cn } from '../lib/utils'
|
|
5
|
+
import { XIcon } from '../spree/icons'
|
|
6
6
|
import { Button } from './button'
|
|
7
7
|
|
|
8
8
|
function Dialog({
|
|
@@ -83,7 +83,7 @@ function DialogOverlay({
|
|
|
83
83
|
<DialogPrimitive.Backdrop
|
|
84
84
|
data-slot="dialog-overlay"
|
|
85
85
|
className={cn(
|
|
86
|
-
'fixed inset-0 isolate z-50 bg-
|
|
86
|
+
'fixed inset-0 isolate z-50 bg-background/60 dark:bg-black/60 duration-100 data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 transition-opacity',
|
|
87
87
|
className,
|
|
88
88
|
)}
|
|
89
89
|
{...props}
|
|
@@ -105,7 +105,7 @@ function DialogContent({
|
|
|
105
105
|
<DialogPrimitive.Popup
|
|
106
106
|
data-slot="dialog-content"
|
|
107
107
|
className={cn(
|
|
108
|
-
'fixed top-1/2 left-1/2 z-50 flex w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-xl border border-border bg-card text-foreground text-sm shadow-lg duration-100 outline-none sm:max-w-[500px] data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 transition-[opacity,transform]',
|
|
108
|
+
'fixed top-1/2 left-1/2 z-50 flex w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-xl border border-border bg-card text-foreground text-sm shadow-lg duration-100 outline-none sm:max-w-[min(500px,calc(100%-2rem))] data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 transition-[opacity,transform,translate,scale]',
|
|
109
109
|
className,
|
|
110
110
|
)}
|
|
111
111
|
style={{ maxHeight: '90vh' }}
|
|
@@ -132,7 +132,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
|
132
132
|
return (
|
|
133
133
|
<div
|
|
134
134
|
data-slot="dialog-header"
|
|
135
|
-
className={cn('flex flex-col gap-1.5 border-b p-4', className)}
|
|
135
|
+
className={cn('flex flex-col gap-1.5 border-b border-border-subtle p-4', className)}
|
|
136
136
|
{...props}
|
|
137
137
|
/>
|
|
138
138
|
)
|
|
@@ -160,7 +160,7 @@ function DialogFooter({
|
|
|
160
160
|
<div
|
|
161
161
|
data-slot="dialog-footer"
|
|
162
162
|
className={cn(
|
|
163
|
-
'flex flex-col-reverse gap-2 border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',
|
|
163
|
+
'flex flex-col-reverse gap-2 border-t border-border-subtle bg-muted/50 p-4 sm:flex-row sm:justify-end',
|
|
164
164
|
className,
|
|
165
165
|
)}
|
|
166
166
|
{...props}
|
|
@@ -177,7 +177,7 @@ function DialogTitle({ className, ...props }: React.ComponentProps<typeof Dialog
|
|
|
177
177
|
return (
|
|
178
178
|
<DialogPrimitive.Title
|
|
179
179
|
data-slot="dialog-title"
|
|
180
|
-
className={cn('text-
|
|
180
|
+
className={cn('text-lg font-medium', className)}
|
|
181
181
|
{...props}
|
|
182
182
|
/>
|
|
183
183
|
)
|
package/src/ui/dropdown-menu.tsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { Menu as MenuPrimitive } from '@base-ui/react/menu'
|
|
4
|
-
import { CheckIcon, ChevronRightIcon } from 'lucide-react'
|
|
5
4
|
import * as React from 'react'
|
|
6
5
|
import { cn } from '../lib/utils'
|
|
6
|
+
import { CheckIcon, ChevronRightIcon } from '../spree/icons'
|
|
7
7
|
|
|
8
8
|
function DropdownMenu({
|
|
9
9
|
children,
|
|
@@ -81,7 +81,9 @@ function DropdownMenuContent({
|
|
|
81
81
|
<MenuPrimitive.Popup
|
|
82
82
|
data-slot="dropdown-menu-content"
|
|
83
83
|
className={cn(
|
|
84
|
-
|
|
84
|
+
// `origin-(--transform-origin)` so the menu grows out of its
|
|
85
|
+
// trigger rather than its own centre.
|
|
86
|
+
'z-50 max-h-[var(--available-height)] min-w-40 max-w-80 overflow-x-hidden overflow-y-auto rounded-xl border border-border bg-popover p-1 text-popover-foreground shadow-lg origin-(--transform-origin) duration-100 ease-out data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 transition-[opacity,transform,translate,scale]',
|
|
85
87
|
className,
|
|
86
88
|
)}
|
|
87
89
|
{...props}
|
|
@@ -117,7 +119,7 @@ function DropdownMenuItem({
|
|
|
117
119
|
data-variant={variant}
|
|
118
120
|
render={children}
|
|
119
121
|
className={cn(
|
|
120
|
-
"group/dropdown-menu-item relative flex cursor-
|
|
122
|
+
"group/dropdown-menu-item relative flex cursor-pointer items-center gap-3 rounded-lg px-2 py-1.5 text-sm outline-hidden select-none transition-colors duration-75 focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
121
123
|
className,
|
|
122
124
|
)}
|
|
123
125
|
{...props}
|
|
@@ -131,7 +133,7 @@ function DropdownMenuItem({
|
|
|
131
133
|
data-inset={inset}
|
|
132
134
|
data-variant={variant}
|
|
133
135
|
className={cn(
|
|
134
|
-
"group/dropdown-menu-item relative flex cursor-
|
|
136
|
+
"group/dropdown-menu-item relative flex cursor-pointer items-center gap-3 rounded-lg px-2 py-1.5 text-sm outline-hidden select-none transition-colors duration-75 focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
135
137
|
className,
|
|
136
138
|
)}
|
|
137
139
|
{...props}
|
|
@@ -158,7 +160,7 @@ function DropdownMenuCheckboxItem({
|
|
|
158
160
|
data-slot="dropdown-menu-checkbox-item"
|
|
159
161
|
data-inset={inset}
|
|
160
162
|
className={cn(
|
|
161
|
-
"relative flex cursor-
|
|
163
|
+
"relative flex cursor-pointer items-center gap-3 rounded-lg py-1.5 pr-8 pl-2.5 text-sm outline-hidden select-none transition-colors duration-75 focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
162
164
|
className,
|
|
163
165
|
)}
|
|
164
166
|
checked={checked}
|
|
@@ -197,7 +199,7 @@ function DropdownMenuRadioItem({
|
|
|
197
199
|
data-slot="dropdown-menu-radio-item"
|
|
198
200
|
data-inset={inset}
|
|
199
201
|
className={cn(
|
|
200
|
-
"relative flex cursor-
|
|
202
|
+
"relative flex cursor-pointer items-center gap-3 rounded-lg py-1.5 pr-8 pl-2.5 text-sm outline-hidden select-none transition-colors duration-75 focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
201
203
|
className,
|
|
202
204
|
)}
|
|
203
205
|
{...props}
|
|
@@ -228,7 +230,7 @@ function DropdownMenuLabel({
|
|
|
228
230
|
data-slot="dropdown-menu-label"
|
|
229
231
|
data-inset={inset}
|
|
230
232
|
className={cn(
|
|
231
|
-
'px-2
|
|
233
|
+
'px-2 py-1.5 text-sm font-normal text-muted-foreground data-inset:pl-7',
|
|
232
234
|
className,
|
|
233
235
|
)}
|
|
234
236
|
{...props}
|
|
@@ -244,7 +246,7 @@ function DropdownMenuSeparator({
|
|
|
244
246
|
return (
|
|
245
247
|
<MenuPrimitive.Separator
|
|
246
248
|
data-slot="dropdown-menu-separator"
|
|
247
|
-
className={cn('
|
|
249
|
+
className={cn('mx-1 my-1 h-px bg-border-subtle', className)}
|
|
248
250
|
{...props}
|
|
249
251
|
/>
|
|
250
252
|
)
|
|
@@ -280,22 +282,25 @@ function DropdownMenuSubTrigger({
|
|
|
280
282
|
className,
|
|
281
283
|
inset,
|
|
282
284
|
children,
|
|
285
|
+
hideChevron,
|
|
283
286
|
...props
|
|
284
287
|
}: React.ComponentProps<typeof MenuPrimitive.SubmenuTrigger> & {
|
|
285
288
|
inset?: boolean
|
|
289
|
+
/** Suppress the default trailing chevron when the trigger renders its own affordance. */
|
|
290
|
+
hideChevron?: boolean
|
|
286
291
|
}) {
|
|
287
292
|
return (
|
|
288
293
|
<MenuPrimitive.SubmenuTrigger
|
|
289
294
|
data-slot="dropdown-menu-sub-trigger"
|
|
290
295
|
data-inset={inset}
|
|
291
296
|
className={cn(
|
|
292
|
-
"flex cursor-
|
|
297
|
+
"flex cursor-pointer items-center gap-3 rounded-lg px-2 py-1.5 text-sm outline-hidden select-none transition-colors duration-75 focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-[popup-open]:bg-accent data-[popup-open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
293
298
|
className,
|
|
294
299
|
)}
|
|
295
300
|
{...props}
|
|
296
301
|
>
|
|
297
302
|
{children}
|
|
298
|
-
<ChevronRightIcon className="ml-auto" />
|
|
303
|
+
{!hideChevron && <ChevronRightIcon className="ml-auto" />}
|
|
299
304
|
</MenuPrimitive.SubmenuTrigger>
|
|
300
305
|
)
|
|
301
306
|
}
|
|
@@ -314,7 +319,9 @@ function DropdownMenuSubContent({
|
|
|
314
319
|
<MenuPrimitive.Popup
|
|
315
320
|
data-slot="dropdown-menu-sub-content"
|
|
316
321
|
className={cn(
|
|
317
|
-
|
|
322
|
+
// `origin-(--transform-origin)` so the submenu grows out of its
|
|
323
|
+
// parent item rather than its own centre.
|
|
324
|
+
'z-50 min-w-[96px] overflow-hidden rounded-xl border border-border bg-popover p-1 text-popover-foreground shadow-lg origin-(--transform-origin) duration-100 ease-out data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 transition-[opacity,transform,translate,scale]',
|
|
318
325
|
className,
|
|
319
326
|
)}
|
|
320
327
|
{...props}
|
package/src/ui/field.tsx
CHANGED
|
@@ -9,7 +9,7 @@ function FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {
|
|
|
9
9
|
<fieldset
|
|
10
10
|
data-slot="field-set"
|
|
11
11
|
className={cn(
|
|
12
|
-
'flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',
|
|
12
|
+
'flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 rounded-md p-3 border border-border-subtle shadow-xs bg-card',
|
|
13
13
|
className,
|
|
14
14
|
)}
|
|
15
15
|
{...props}
|
|
@@ -27,7 +27,7 @@ function FieldLegend({
|
|
|
27
27
|
data-slot="field-legend"
|
|
28
28
|
data-variant={variant}
|
|
29
29
|
className={cn(
|
|
30
|
-
'mb-1.5 font-
|
|
30
|
+
'mb-1.5 font-semibold data-[variant=label]:text-sm data-[variant=legend]:text-base',
|
|
31
31
|
className,
|
|
32
32
|
)}
|
|
33
33
|
{...props}
|
|
@@ -95,6 +95,14 @@ function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>)
|
|
|
95
95
|
data-slot="field-label"
|
|
96
96
|
className={cn(
|
|
97
97
|
'group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-blue-500/30 has-data-checked:bg-blue-500/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border *:data-[slot=field]:p-2.5 dark:has-data-checked:border-blue-500/20 dark:has-data-checked:bg-blue-500/10',
|
|
98
|
+
// Card-shaped labels (the ones wrapping a whole `Field`) are clickable
|
|
99
|
+
// targets, so they need the affordance a bare text label doesn't:
|
|
100
|
+
// a pointer and a hover tint. Scoped by `has-[>[data-slot=field]]` so
|
|
101
|
+
// plain field labels keep their default cursor, and excluded on the
|
|
102
|
+
// checked card by `not-has-data-checked` — a competing hover rule of
|
|
103
|
+
// equal specificity would just race the selected tint and win.
|
|
104
|
+
'has-[>[data-slot=field]]:cursor-pointer has-[>[data-slot=field]]:transition-colors',
|
|
105
|
+
'has-[>[data-slot=field]]:not-has-data-checked:hover:border-border has-[>[data-slot=field]]:not-has-data-checked:hover:bg-accent/60',
|
|
98
106
|
'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col',
|
|
99
107
|
className,
|
|
100
108
|
)}
|
package/src/ui/input-group.tsx
CHANGED
|
@@ -11,7 +11,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
|
|
|
11
11
|
data-slot="input-group"
|
|
12
12
|
role="group"
|
|
13
13
|
className={cn(
|
|
14
|
-
'group/input-group relative flex w-full min-w-0 items-center rounded-
|
|
14
|
+
'group/input-group relative flex w-full min-w-0 items-center rounded-md border border-border bg-card text-foreground transition-[color,background-color,border-color,box-shadow] duration-100 ease-out outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:shadow-none has-disabled:bg-muted has-disabled:cursor-not-allowed has-disabled:text-muted-foreground has-[[data-slot=input-group-control]:focus-visible]:border-blue-500 has-[[data-slot=input-group-control]:focus-visible]:shadow-[0_0_0_3px_color-mix(in_srgb,var(--ring)_15%,transparent)] has-[[data-slot][aria-invalid=true]]:border-destructive has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',
|
|
15
15
|
className,
|
|
16
16
|
)}
|
|
17
17
|
{...props}
|
package/src/ui/input.tsx
CHANGED
|
@@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
|
|
|
8
8
|
type={type}
|
|
9
9
|
data-slot="input"
|
|
10
10
|
className={cn(
|
|
11
|
-
'block w-full min-w-0 min-h-8 rounded-
|
|
11
|
+
'block w-full min-w-0 min-h-8 rounded-md border border-border bg-card py-1.5 px-2.5 text-base font-normal leading-normal text-foreground transition-[color,background-color,border-color,box-shadow] duration-100 ease-out outline-none placeholder:text-muted-foreground focus:border-blue-500 focus:shadow-[0_0_0_3px_color-mix(in_srgb,var(--ring)_15%,transparent)] disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground aria-invalid:border-destructive file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground',
|
|
12
12
|
className,
|
|
13
13
|
)}
|
|
14
14
|
{...props}
|