@spree/dashboard-ui 6.0.2 → 6.0.4

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 (84) hide show
  1. package/package.json +20 -19
  2. package/src/index.ts +5 -0
  3. package/src/lib/field-trigger.ts +10 -0
  4. package/src/lib/same-rich-text.ts +12 -3
  5. package/src/lib/utils.ts +14 -1
  6. package/src/spree/address-block.tsx +3 -3
  7. package/src/spree/address-book-row.tsx +88 -0
  8. package/src/spree/bulk-price-table.tsx +3 -3
  9. package/src/spree/claim-resolve-dialog.tsx +1 -1
  10. package/src/spree/collapsible-card.tsx +2 -2
  11. package/src/spree/color-picker.tsx +1 -1
  12. package/src/spree/currency-label.tsx +71 -0
  13. package/src/spree/data-grid/cells.tsx +2 -2
  14. package/src/spree/data-grid/data-grid.tsx +2 -2
  15. package/src/spree/delivery-form-dialog.tsx +1 -1
  16. package/src/spree/drag-handle.tsx +1 -1
  17. package/src/spree/editable-note-card.tsx +2 -2
  18. package/src/spree/form-actions.tsx +32 -10
  19. package/src/spree/fulfill-items-form.tsx +20 -22
  20. package/src/spree/fulfillment-edit-dialog.tsx +1 -1
  21. package/src/spree/fulfillment-item-list.tsx +87 -28
  22. package/src/spree/fulfillment-panel.tsx +2 -2
  23. package/src/spree/icons.ts +8 -0
  24. package/src/spree/json-preview-drawer.tsx +32 -7
  25. package/src/spree/json-value-view.tsx +14 -4
  26. package/src/spree/label-upload-dialog.tsx +1 -1
  27. package/src/spree/language-menu-items.tsx +1 -1
  28. package/src/spree/metadata/metadata-card.tsx +2 -2
  29. package/src/spree/order-cancel-dialog.tsx +3 -3
  30. package/src/spree/post-sale-create-dialogs.tsx +36 -33
  31. package/src/spree/post-sale-fields.tsx +30 -1
  32. package/src/spree/product-identity.tsx +74 -0
  33. package/src/spree/product-membership-list.tsx +79 -41
  34. package/src/spree/products/status-card.tsx +1 -1
  35. package/src/spree/quantity-tier-editor.tsx +1 -1
  36. package/src/spree/return-dialogs.tsx +2 -2
  37. package/src/spree/search-input.tsx +3 -1
  38. package/src/spree/secret-input.tsx +1 -1
  39. package/src/spree/section.tsx +55 -0
  40. package/src/spree/stat-item.tsx +27 -0
  41. package/src/spree/tax-identifiers-card.tsx +117 -76
  42. package/src/spree/theme-toggle.tsx +1 -1
  43. package/src/spree/wizard.tsx +2 -2
  44. package/src/styles.css +226 -120
  45. package/src/ui/alert.tsx +20 -17
  46. package/src/ui/attachment.tsx +1 -1
  47. package/src/ui/avatar.tsx +2 -2
  48. package/src/ui/badge.tsx +1 -1
  49. package/src/ui/breadcrumb.tsx +1 -1
  50. package/src/ui/button.tsx +14 -11
  51. package/src/ui/calendar.tsx +2 -2
  52. package/src/ui/card.tsx +14 -37
  53. package/src/ui/chart.tsx +1 -1
  54. package/src/ui/checkbox.tsx +2 -2
  55. package/src/ui/code-editor.tsx +6 -2
  56. package/src/ui/collapsible.tsx +21 -2
  57. package/src/ui/combobox.tsx +12 -16
  58. package/src/ui/command.tsx +8 -6
  59. package/src/ui/context-menu.tsx +1 -1
  60. package/src/ui/data-table.tsx +58 -15
  61. package/src/ui/date-picker.tsx +3 -3
  62. package/src/ui/date-range-panel.tsx +102 -0
  63. package/src/ui/date-range-picker.tsx +33 -72
  64. package/src/ui/dialog.tsx +3 -3
  65. package/src/ui/dropdown-menu.tsx +6 -6
  66. package/src/ui/empty.tsx +3 -3
  67. package/src/ui/field.tsx +33 -12
  68. package/src/ui/input-group.tsx +17 -6
  69. package/src/ui/input.tsx +148 -4
  70. package/src/ui/item.tsx +7 -4
  71. package/src/ui/label.tsx +1 -1
  72. package/src/ui/media-preview.tsx +40 -5
  73. package/src/ui/pagination.tsx +16 -4
  74. package/src/ui/progress.tsx +2 -2
  75. package/src/ui/radio-group.tsx +2 -2
  76. package/src/ui/rich-text-editor.tsx +3 -3
  77. package/src/ui/select.tsx +7 -16
  78. package/src/ui/sheet.tsx +13 -5
  79. package/src/ui/sidebar.tsx +5 -5
  80. package/src/ui/switch.tsx +8 -2
  81. package/src/ui/tabs.tsx +45 -9
  82. package/src/ui/textarea.tsx +1 -1
  83. package/src/ui/toast.tsx +2 -2
  84. package/src/ui/tooltip.tsx +1 -1
@@ -361,7 +361,7 @@ function SidebarInset({ className, id = 'main-content', ...props }: React.Compon
361
361
  // The inset sheet is the raised surface, so it takes the card colour —
362
362
  // pure white in light mode — rather than the page grey, which is now the
363
363
  // ground the rail sits on.
364
- 'relative z-0 flex w-full min-w-0 flex-1 flex-col bg-background md:peer-data-[variant=inset]:bg-card md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:border md:peer-data-[variant=inset]:border-sidebar-border md:peer-data-[variant=inset]:shadow-sm',
364
+ 'relative z-0 flex w-full min-w-0 flex-1 flex-col bg-background md:peer-data-[variant=inset]:bg-card md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-2xl md:peer-data-[variant=inset]:border md:peer-data-[variant=inset]:border-sidebar-border',
365
365
  className,
366
366
  )}
367
367
  {...props}
@@ -548,7 +548,7 @@ function SidebarGroupContent({ className, ...props }: React.ComponentProps<'div'
548
548
  <div
549
549
  data-slot="sidebar-group-content"
550
550
  data-sidebar="group-content"
551
- className={cn('w-full text-sm', className)}
551
+ className={cn('w-full text-base', className)}
552
552
  {...props}
553
553
  />
554
554
  )
@@ -587,7 +587,7 @@ const sidebarMenuButtonVariants = cva(
587
587
  // badge. Without it the flex line wraps and the label visibly squashes to two
588
588
  // lines inside the fixed height until the animation lands. Labels clip
589
589
  // instead — which is what the `truncate` below already intends.
590
- 'peer/menu-button group/menu-button flex gap-2 w-full items-center overflow-hidden whitespace-nowrap rounded-md p-1 text-left font-medium text-base text-sidebar-foreground/80 outline-hidden transition-[color,background-color,border-color,box-shadow] duration-100 ease-out group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-10! group-data-[collapsible=icon]:items-center group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:overflow-visible group-data-[collapsible=icon]:p-0! group-data-[collapsible=icon]:gap-0 group-data-[collapsible=icon]:[&>span:last-child]:hidden hover:bg-sidebar-accent-hover hover:text-sidebar-foreground focus-visible:shadow-[0_0_0_3px_color-mix(in_srgb,var(--ring)_15%,transparent)] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent-hover data-open:hover:text-sidebar-foreground data-active:bg-accent-strong data-active:text-foreground data-active:font-semibold data-active:hover:bg-accent-strong-hover [&>span:last-child]:truncate',
590
+ 'peer/menu-button group/menu-button flex gap-2 w-full items-center overflow-hidden whitespace-nowrap rounded-lg p-1 text-left font-normal text-base text-sidebar-foreground/80 outline-hidden transition-[color,background-color,border-color,box-shadow] duration-100 ease-out group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-10! group-data-[collapsible=icon]:items-center group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:overflow-visible group-data-[collapsible=icon]:p-0! group-data-[collapsible=icon]:gap-0 group-data-[collapsible=icon]:[&>span:last-child]:hidden hover:bg-sidebar-accent-hover hover:text-sidebar-foreground focus-visible:shadow-[0_0_0_3px_color-mix(in_srgb,var(--ring)_15%,transparent)] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent-hover data-open:hover:text-sidebar-foreground data-active:bg-sidebar-active data-active:text-foreground data-active:font-semibold data-active:hover:bg-sidebar-active-hover [&>span:last-child]:truncate',
591
591
  {
592
592
  variants: {
593
593
  variant: {
@@ -601,7 +601,7 @@ const sidebarMenuButtonVariants = cva(
601
601
  // desktop rail keeps its compact pointer-sized rows.
602
602
  default:
603
603
  'h-8 text-base in-data-[mobile=true]:h-12 in-data-[mobile=true]:px-2 in-data-[mobile=true]:[&_svg]:size-5',
604
- sm: 'h-7 text-xs in-data-[mobile=true]:h-11 in-data-[mobile=true]:text-sm',
604
+ sm: 'h-7 text-xs in-data-[mobile=true]:h-11 in-data-[mobile=true]:text-base',
605
605
  lg: 'h-12 text-base group-data-[collapsible=icon]:p-0!',
606
606
  },
607
607
  },
@@ -798,7 +798,7 @@ function SidebarMenuSubButton({
798
798
  data-size={size}
799
799
  data-active={isActive}
800
800
  className={cn(
801
- 'relative flex h-7 in-data-[mobile=true]:h-11 in-data-[mobile=true]:px-2 min-w-0 items-center gap-2 overflow-hidden rounded-lg p-1 font-medium text-sidebar-foreground/80 outline-hidden group-data-[collapsible=icon]:hidden hover:bg-sidebar-accent-hover hover:text-sidebar-foreground focus-visible:shadow-[0_0_0_3px_color-mix(in_srgb,var(--ring)_15%,transparent)] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-base data-[size=sm]:text-xs data-active:font-semibold data-active:text-foreground data-active:bg-transparent data-active:shadow-none data-active:before:absolute data-active:before:-left-4 data-active:before:top-[10%] data-active:before:h-[80%] data-active:before:w-[3px] data-active:before:rounded-sm data-active:before:bg-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
801
+ 'relative flex h-7 in-data-[mobile=true]:h-11 in-data-[mobile=true]:px-2 min-w-0 items-center gap-2 overflow-hidden rounded-lg p-1 font-normal text-sidebar-foreground/80 outline-hidden group-data-[collapsible=icon]:hidden hover:bg-sidebar-accent-hover hover:text-sidebar-foreground focus-visible:shadow-[0_0_0_3px_color-mix(in_srgb,var(--ring)_15%,transparent)] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-base data-[size=sm]:text-xs data-active:font-semibold data-active:text-foreground data-active:bg-transparent data-active:shadow-none data-active:before:absolute data-active:before:-left-4 data-active:before:top-[10%] data-active:before:h-[80%] data-active:before:w-[3px] data-active:before:rounded-sm data-active:before:bg-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
802
802
  className,
803
803
  )}
804
804
  {...props}
package/src/ui/switch.tsx CHANGED
@@ -22,14 +22,20 @@ function Switch({
22
22
  disabled={disabled}
23
23
  name={name}
24
24
  className={cn(
25
- 'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-xs transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[checked]:bg-blue-600 data-[unchecked]:bg-input',
25
+ // A flat track with a hairline edge instead of depth, and a capsule
26
+ // knob wider than it is tall, after the macOS switch.
27
+ // Base UI renders a <span>, which is never `:disabled`, so disabled
28
+ // styles key off `data-disabled`.
29
+ 'group/switch peer inline-flex h-[18px] w-[34px] shrink-0 cursor-pointer items-center rounded-full p-0.5 shadow-[inset_0_0_0_0.5px_rgb(0_0_0/0.08)] transition-[background-color,box-shadow] duration-200 ease-out outline-none focus-visible:shadow-field-focus data-[checked]:bg-blue-500 data-[unchecked]:bg-switch-off data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50',
26
30
  className,
27
31
  )}
28
32
  {...props}
29
33
  >
30
34
  <SwitchPrimitive.Thumb
31
35
  className={cn(
32
- 'pointer-events-none block h-4 w-4 rounded-full bg-white shadow-lg ring-0 transition-transform data-[checked]:translate-x-4 data-[unchecked]:translate-x-0',
36
+ // While pressed the knob turns to glass: it grows, clears to a
37
+ // frosted tint over the track, and keeps a bright rim.
38
+ 'pointer-events-none block h-3.5 w-5 rounded-full bg-white shadow-[0_0_0_0.5px_rgb(0_0_0/0.06),0_1px_2px_rgb(0_0_0/0.1)] transition-[translate,scale,background-color,box-shadow] duration-200 ease-[cubic-bezier(0.3,1.3,0.6,1)] motion-reduce:transition-none data-[checked]:translate-x-2.5 data-[unchecked]:translate-x-0 group-active/switch:scale-x-[1.3] group-active/switch:scale-y-[1.2] group-active/switch:bg-white/35 group-active/switch:shadow-[inset_0_0_0_1px_rgb(255_255_255/0.85),0_1px_4px_rgb(0_0_0/0.15)] group-active/switch:backdrop-blur-[2px] group-data-[disabled]/switch:scale-100 group-data-[disabled]/switch:bg-white',
33
39
  )}
34
40
  />
35
41
  </SwitchPrimitive.Root>
package/src/ui/tabs.tsx CHANGED
@@ -20,7 +20,25 @@ function Tabs({
20
20
  )
21
21
  }
22
22
 
23
- /** The track a set of tabs sits in, recessed so the selected tab reads as raised. */
23
+ /**
24
+ * The track a set of tabs sits in, a glass capsule in the manner of a macOS
25
+ * segmented control. The selected pill is an indicator that slides between
26
+ * tabs; until it has measured its tab, the selected tab draws the same pill
27
+ * itself.
28
+ */
29
+ // The track: the recessed surface, edged by the faint divider rule. Flat — no
30
+ // blur or sheen, which a control this small would only muddy.
31
+ const TAB_TRACK = 'bg-track-recessed shadow-[inset_0_0_0_1px_var(--border-subtle)]'
32
+
33
+ // The selected tab's surface: a flat pill set off by its fill and a card's
34
+ // edge, never a drop shadow. A card's white in the light; in the dark the
35
+ // strong accent, the step a dark surface lifts to.
36
+ const TAB_RAISED = 'bg-card shadow-[0_0_0_1px_var(--card-edge)] dark:bg-accent-strong'
37
+
38
+ // Capsules side by side; a vertical stack keeps rounded rectangles, which read
39
+ // better as a column.
40
+ const TAB_SHAPE = 'rounded-full group-data-[orientation=vertical]/tabs:rounded-lg'
41
+
24
42
  function TabsList({
25
43
  className,
26
44
  children,
@@ -30,12 +48,23 @@ function TabsList({
30
48
  <TabsPrimitive.List
31
49
  data-slot="tabs-list"
32
50
  className={cn(
33
- 'inline-flex w-fit items-center justify-center gap-1 rounded-lg bg-track-recessed p-1 text-muted-foreground',
51
+ 'group/tabs-list relative inline-flex w-fit items-center justify-center gap-0.5 rounded-full p-0.5 text-muted-foreground group-data-[orientation=vertical]/tabs:rounded-xl',
52
+ TAB_TRACK,
34
53
  'group-data-[orientation=vertical]/tabs:h-auto group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:flex-col',
35
54
  className,
36
55
  )}
37
56
  {...props}
38
57
  >
58
+ <TabsPrimitive.Indicator
59
+ data-slot="tabs-indicator"
60
+ className={cn(
61
+ TAB_RAISED,
62
+ TAB_SHAPE,
63
+ 'absolute top-(--active-tab-top) left-(--active-tab-left) h-(--active-tab-height) w-(--active-tab-width)',
64
+ // The switch's curve, so the two controls move alike.
65
+ 'transition-[left,top,width,height] duration-200 ease-[cubic-bezier(0.3,1.3,0.6,1)] motion-reduce:transition-none',
66
+ )}
67
+ />
39
68
  {children}
40
69
  </TabsPrimitive.List>
41
70
  )
@@ -58,17 +87,22 @@ function TabsTrigger({
58
87
  className={cn(
59
88
  // min-h-7 + py-1 keeps the hit area past the 24px WCAG target size at
60
89
  // every text size; the old h-[calc(100%-1px)] left it to the parent.
61
- 'inline-flex min-h-7 flex-1 cursor-pointer items-center justify-center gap-1.5 rounded-md px-2.5 py-1 text-sm font-medium whitespace-nowrap',
90
+ 'relative z-10 inline-flex min-h-7 flex-1 cursor-pointer items-center justify-center gap-1.5 px-3 py-1 text-base font-medium whitespace-nowrap',
62
91
  'transition-[color,background-color,box-shadow] duration-150 ease-out outline-none motion-reduce:transition-none',
92
+ TAB_SHAPE,
63
93
  'group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start',
64
94
  // Unselected tabs recede so the selected one reads as chosen; hover
65
95
  // promotes to full foreground rather than only shifting the surface,
66
96
  // which is the part a colour-blind or low-vision reader can see.
67
- 'text-muted-foreground hover:text-foreground',
68
- // The raised surface lives on the tab itself, as it did before the
69
- // indicator: it is the thing that makes the selection legible, and it
70
- // must not depend on a separate element rendering correctly behind it.
71
- 'data-[active]:bg-nested-raised data-[active]:text-foreground data-[active]:shadow-xs',
97
+ // An unselected tab under the pointer takes a pill one step under the
98
+ // selected one, as a sidebar item does.
99
+ 'text-muted-foreground hover:text-foreground not-data-[active]:hover:bg-accent-strong dark:not-data-[active]:hover:bg-accent',
100
+ // The selected tab draws its own pill until the indicator has
101
+ // measured it, so the selection never depends on that rendering;
102
+ // then it turns clear and the sliding indicator shows through.
103
+ cn(TAB_RAISED, 'not-data-[active]:bg-transparent not-data-[active]:shadow-none'),
104
+ 'data-[active]:text-foreground',
105
+ 'group-has-[>[data-slot=tabs-indicator]:not([hidden])]/tabs-list:bg-transparent group-has-[>[data-slot=tabs-indicator]:not([hidden])]/tabs-list:shadow-none',
72
106
  // Same focus treatment as Button, so a keyboard user sees one
73
107
  // consistent indicator across the dashboard.
74
108
  'focus-visible:shadow-[0_0_0_3px_color-mix(in_srgb,var(--ring)_15%,transparent)]',
@@ -80,7 +114,9 @@ function TabsTrigger({
80
114
  >
81
115
  {children}
82
116
  {count !== undefined && (
83
- <span className="rounded-full bg-muted px-1.5 py-0.5 text-xs tabular-nums">{count}</span>
117
+ <span className="rounded-full bg-accent-strong-hover px-1.5 py-0.5 text-xs text-foreground tabular-nums">
118
+ {count}
119
+ </span>
84
120
  )}
85
121
  </TabsPrimitive.Tab>
86
122
  )
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
7
7
  <textarea
8
8
  data-slot="textarea"
9
9
  className={cn(
10
- 'flex field-sizing-content min-h-16 w-full rounded-md border border-border shadow-control bg-card px-2.5 py-1.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',
10
+ 'flex field-sizing-content min-h-16 w-full cursor-default focus:cursor-text rounded-xl border border-field-border shadow-control bg-card px-2.5 py-1.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 not-disabled:not-read-only:not-focus:hover:bg-field-hover focus:border-blue-500 focus:shadow-field-focus disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground aria-invalid:border-destructive',
11
11
  className,
12
12
  )}
13
13
  {...props}
package/src/ui/toast.tsx CHANGED
@@ -121,10 +121,10 @@ function ToastList() {
121
121
  <ToastContent>
122
122
  <ToastIcon type={toast.type} />
123
123
  <div className="flex min-w-0 flex-1 flex-col gap-1">
124
- <ToastPrimitive.Title data-slot="toast-title" className="font-medium text-sm" />
124
+ <ToastPrimitive.Title data-slot="toast-title" className="font-medium text-base" />
125
125
  <ToastPrimitive.Description
126
126
  data-slot="toast-description"
127
- className="text-muted-foreground text-sm"
127
+ className="text-muted-foreground text-base"
128
128
  />
129
129
  </div>
130
130
  <ToastPrimitive.Action
@@ -87,7 +87,7 @@ function TooltipContent({
87
87
  className={cn(
88
88
  // `origin-(--transform-origin)` so it grows out of the element it
89
89
  // describes rather than its own centre.
90
- 'z-50 inline-flex w-fit max-w-[200px] items-center gap-1.5 rounded-lg border border-border bg-popover px-2 py-1 text-sm font-normal text-popover-foreground shadow-md origin-(--transform-origin) 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] duration-100 ease-out',
90
+ 'z-50 inline-flex w-fit max-w-[200px] items-center gap-1.5 rounded-xl border border-border bg-popover px-2 py-1 text-base font-normal text-popover-foreground shadow-md origin-(--transform-origin) 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] duration-100 ease-out',
91
91
  className,
92
92
  )}
93
93
  {...props}