@spree/dashboard-ui 0.13.1 → 1.0.0-beta.2

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 (94) hide show
  1. package/README.md +1 -1
  2. package/package.json +13 -9
  3. package/src/hooks/use-prefers-reduced-motion.ts +26 -0
  4. package/src/hooks/use-scrolled.ts +34 -14
  5. package/src/index.ts +38 -1
  6. package/src/lib/date-range-presets.ts +79 -0
  7. package/src/lib/same-rich-text.ts +145 -0
  8. package/src/spree/address-block.tsx +5 -4
  9. package/src/spree/address-book-row.tsx +96 -0
  10. package/src/spree/back-button.tsx +14 -9
  11. package/src/spree/bulk-dialog.tsx +2 -2
  12. package/src/spree/bulk-price-table.tsx +145 -14
  13. package/src/spree/bulk-variants-table.tsx +434 -0
  14. package/src/spree/calculator-summary.tsx +5 -1
  15. package/src/spree/claim-resolve-dialog.tsx +219 -0
  16. package/src/spree/confirm-dialog.tsx +1 -1
  17. package/src/spree/copy-to-clipboard-button.tsx +1 -1
  18. package/src/spree/data-grid/cells.tsx +199 -25
  19. package/src/spree/data-grid/context.ts +6 -0
  20. package/src/spree/data-grid/data-grid.tsx +149 -7
  21. package/src/spree/data-grid/helpers.ts +6 -2
  22. package/src/spree/data-grid/index.ts +9 -1
  23. package/src/spree/data-grid/use-data-grid-keyboard.ts +4 -2
  24. package/src/spree/delivery-form-dialog.tsx +166 -0
  25. package/src/spree/drag-handle.tsx +2 -2
  26. package/src/spree/editable-note-card.tsx +103 -0
  27. package/src/spree/external-link.tsx +56 -0
  28. package/src/spree/file-type-icon.tsx +68 -0
  29. package/src/spree/form-actions.tsx +2 -3
  30. package/src/spree/form-section.tsx +46 -0
  31. package/src/spree/fulfill-items-form.tsx +282 -0
  32. package/src/spree/fulfillment-edit-dialog.tsx +211 -0
  33. package/src/spree/fulfillment-item-list.tsx +61 -0
  34. package/src/spree/fulfillment-panel.tsx +53 -0
  35. package/src/spree/icons.ts +167 -0
  36. package/src/spree/json-preview-drawer.tsx +4 -4
  37. package/src/spree/label-upload-dialog.tsx +195 -0
  38. package/src/spree/language-menu-items.tsx +37 -30
  39. package/src/spree/metadata/metadata-card.tsx +1 -1
  40. package/src/spree/order-cancel-dialog.tsx +149 -0
  41. package/src/spree/post-sale-create-dialogs.tsx +253 -0
  42. package/src/spree/post-sale-fields.tsx +133 -0
  43. package/src/spree/product-membership-list.tsx +453 -0
  44. package/src/spree/products/status-card.tsx +79 -0
  45. package/src/spree/quantity-tier-editor.tsx +162 -0
  46. package/src/spree/resource-combobox.tsx +12 -1
  47. package/src/spree/resource-name-cell.tsx +21 -4
  48. package/src/spree/return-dialogs.tsx +238 -0
  49. package/src/spree/route-error-boundary.tsx +1 -1
  50. package/src/spree/row-actions.tsx +2 -2
  51. package/src/spree/search-input.tsx +76 -0
  52. package/src/spree/secret-input.tsx +26 -40
  53. package/src/spree/tax-identifiers-card.tsx +418 -0
  54. package/src/spree/theme-toggle.tsx +36 -23
  55. package/src/spree/wizard.tsx +254 -0
  56. package/src/styles.css +750 -75
  57. package/src/ui/accordion.tsx +83 -0
  58. package/src/ui/alert.tsx +79 -0
  59. package/src/ui/attachment.tsx +1 -1
  60. package/src/ui/badge.tsx +198 -56
  61. package/src/ui/breadcrumb.tsx +1 -1
  62. package/src/ui/button.tsx +22 -10
  63. package/src/ui/calendar.tsx +1 -1
  64. package/src/ui/card.tsx +52 -6
  65. package/src/ui/checkbox.tsx +13 -5
  66. package/src/ui/combobox.tsx +125 -6
  67. package/src/ui/command.tsx +149 -23
  68. package/src/ui/context-menu.tsx +99 -0
  69. package/src/ui/data-table.tsx +448 -9
  70. package/src/ui/date-picker.tsx +18 -3
  71. package/src/ui/date-range-picker.tsx +47 -60
  72. package/src/ui/dialog.tsx +6 -6
  73. package/src/ui/dropdown-menu.tsx +18 -11
  74. package/src/ui/field.tsx +10 -2
  75. package/src/ui/input-group.tsx +1 -1
  76. package/src/ui/input.tsx +1 -1
  77. package/src/ui/media-preview.tsx +105 -0
  78. package/src/ui/pagination.tsx +19 -11
  79. package/src/ui/popover.tsx +4 -2
  80. package/src/ui/progress.tsx +5 -2
  81. package/src/ui/radio-group.tsx +4 -4
  82. package/src/ui/rich-text-editor.tsx +214 -88
  83. package/src/ui/scroll-area.tsx +65 -0
  84. package/src/ui/select.tsx +17 -10
  85. package/src/ui/separator.tsx +1 -1
  86. package/src/ui/sheet.tsx +49 -7
  87. package/src/ui/sidebar.tsx +138 -37
  88. package/src/ui/skeleton.tsx +3 -1
  89. package/src/ui/tabs.tsx +84 -0
  90. package/src/ui/textarea.tsx +1 -1
  91. package/src/ui/thumbnail.tsx +80 -0
  92. package/src/ui/toast.tsx +112 -0
  93. package/src/ui/tooltip.tsx +3 -1
  94. package/src/ui/sonner.tsx +0 -47
@@ -0,0 +1,112 @@
1
+ 'use client'
2
+
3
+ import { Toast as ToastPrimitive } from '@base-ui/react/toast'
4
+ import i18n from 'i18next'
5
+ import type * as React from 'react'
6
+ import { cn } from '../lib/utils'
7
+ import {
8
+ CircleCheckIcon,
9
+ InfoIcon,
10
+ Loader2Icon,
11
+ OctagonXIcon,
12
+ TriangleAlertIcon,
13
+ XIcon,
14
+ } from '../spree/icons'
15
+
16
+ /**
17
+ * App-wide toast manager. Created outside React so a toast can be raised from
18
+ * anywhere — a mutation hook, an event subscriber, a plain async function —
19
+ * without threading a hook through the call stack.
20
+ *
21
+ * Raise one with `toastManager.add({ type: 'success', title: '…' })`. Passing
22
+ * an `id` that already exists updates that toast in place and restarts its
23
+ * timer, which is how a long-running job reports progress through a single
24
+ * toast rather than stacking one per step.
25
+ */
26
+ export const toastManager = ToastPrimitive.createToastManager()
27
+
28
+ const ICONS: Record<string, React.ReactNode> = {
29
+ success: <CircleCheckIcon className="size-4 text-emerald-600 dark:text-emerald-400" />,
30
+ error: <OctagonXIcon className="size-4 text-destructive" />,
31
+ warning: <TriangleAlertIcon className="size-4 text-amber-600 dark:text-amber-400" />,
32
+ info: <InfoIcon className="size-4 text-muted-foreground" />,
33
+ loading: <Loader2Icon className="size-4 animate-spin text-muted-foreground" />,
34
+ }
35
+
36
+ function ToastList() {
37
+ const { toasts } = ToastPrimitive.useToastManager()
38
+
39
+ return toasts.map((toast) => (
40
+ <ToastPrimitive.Root
41
+ key={toast.id}
42
+ toast={toast}
43
+ // Base UI types a toast as `dialog`, which puts every transient
44
+ // notification in the same bucket as real modals — a `getByRole('dialog')`
45
+ // lookup, ours or a screen reader user's, matches whatever toast happens
46
+ // to be on screen. `status` is what a passive notification should expose.
47
+ role={toast.type === 'error' ? 'alert' : 'status'}
48
+ // `--toast-index` / `--toast-offset-y` drive the stacked-card effect:
49
+ // toasts behind the front one scale down and sit slightly higher.
50
+ className={cn(
51
+ // Normal flow inside a bottom-anchored column, not absolute
52
+ // positioning: a zero-height viewport gives an absolutely-positioned
53
+ // child nothing to sit on, and it renders below the fold.
54
+ 'pointer-events-auto w-full',
55
+ 'rounded-xl border border-border bg-popover p-4 text-popover-foreground shadow-lg',
56
+ 'transition-all duration-200 ease-out',
57
+ 'data-[starting-style]:translate-y-2 data-[starting-style]:opacity-0',
58
+ 'data-[ending-style]:translate-y-2 data-[ending-style]:opacity-0',
59
+ )}
60
+ >
61
+ <div className="flex items-start gap-3">
62
+ {toast.type && ICONS[toast.type] && (
63
+ <span className="mt-0.5 shrink-0">{ICONS[toast.type]}</span>
64
+ )}
65
+ <div className="flex min-w-0 flex-1 flex-col gap-1">
66
+ <ToastPrimitive.Title className="font-medium text-sm leading-snug" />
67
+ <ToastPrimitive.Description className="text-muted-foreground text-sm leading-snug" />
68
+ </div>
69
+ <ToastPrimitive.Close
70
+ // 44px tap target on touch, compact for a pointer — the same rule the
71
+ // rest of the app follows for icon-only controls.
72
+ className="-mt-1 -mr-1 inline-flex size-7 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
73
+ aria-label={i18n.t('admin.actions.close')}
74
+ >
75
+ <XIcon className="size-4" />
76
+ </ToastPrimitive.Close>
77
+ </div>
78
+ </ToastPrimitive.Root>
79
+ ))
80
+ }
81
+
82
+ /**
83
+ * Mount once at the app root, inside the provider that owns the queue.
84
+ *
85
+ * Replaces the Sonner toaster: shadcn dropped that integration in favour of
86
+ * this Base UI primitive, which is also what every other overlay in this app
87
+ * (Sheet, Dialog, Popover, Select) is already built on.
88
+ */
89
+ export function Toaster({ children }: { children?: React.ReactNode }) {
90
+ return (
91
+ <ToastPrimitive.Provider toastManager={toastManager}>
92
+ {children}
93
+ <ToastPrimitive.Portal>
94
+ <ToastPrimitive.Viewport
95
+ className={cn(
96
+ // Below the overlay layer (`z-50`) rather than above everything:
97
+ // the stack sits bottom-right, which is exactly where a side
98
+ // sheet's footer buttons are, and a toast drifting over Save is
99
+ // both unclickable and a click the merchant did not intend.
100
+ 'fixed right-4 z-40 flex w-[min(22rem,calc(100vw-2rem))] flex-col gap-2',
101
+ // `pointer-events-none` on the column so the empty space above the
102
+ // stack never blocks the page; each toast takes them back.
103
+ 'pointer-events-none',
104
+ 'bottom-[max(1rem,env(safe-area-inset-bottom))]',
105
+ )}
106
+ >
107
+ <ToastList />
108
+ </ToastPrimitive.Viewport>
109
+ </ToastPrimitive.Portal>
110
+ </ToastPrimitive.Provider>
111
+ )
112
+ }
@@ -85,7 +85,9 @@ function TooltipContent({
85
85
  <TooltipPrimitive.Popup
86
86
  data-slot="tooltip-content"
87
87
  className={cn(
88
- '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 data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 transition-[opacity,transform] duration-100',
88
+ // `origin-(--transform-origin)` so it grows out of the element it
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',
89
91
  className,
90
92
  )}
91
93
  {...props}
package/src/ui/sonner.tsx DELETED
@@ -1,47 +0,0 @@
1
- import {
2
- CircleCheckIcon,
3
- InfoIcon,
4
- Loader2Icon,
5
- OctagonXIcon,
6
- TriangleAlertIcon,
7
- } from 'lucide-react'
8
- import type { CSSProperties } from 'react'
9
- import { Toaster as Sonner, type ToasterProps } from 'sonner'
10
- import { useTheme } from '../spree/theme-provider'
11
-
12
- const Toaster = ({ ...props }: ToasterProps) => {
13
- // The shadcn-default Sonner template assumes `next-themes`. This app has its
14
- // own ThemeProvider that exposes a `mode` (the user's selection: light/dark/system)
15
- // — Sonner's `theme` prop accepts the same three values, so we forward it.
16
- const { mode } = useTheme()
17
-
18
- return (
19
- <Sonner
20
- theme={mode as ToasterProps['theme']}
21
- className="toaster group"
22
- icons={{
23
- success: <CircleCheckIcon className="size-4" />,
24
- info: <InfoIcon className="size-4" />,
25
- warning: <TriangleAlertIcon className="size-4" />,
26
- error: <OctagonXIcon className="size-4" />,
27
- loading: <Loader2Icon className="size-4 animate-spin" />,
28
- }}
29
- style={
30
- {
31
- '--normal-bg': 'var(--popover)',
32
- '--normal-text': 'var(--popover-foreground)',
33
- '--normal-border': 'var(--border)',
34
- '--border-radius': 'var(--radius)',
35
- } as CSSProperties
36
- }
37
- toastOptions={{
38
- classNames: {
39
- toast: 'cn-toast',
40
- },
41
- }}
42
- {...props}
43
- />
44
- )
45
- }
46
-
47
- export { Toaster }