@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
@@ -8,8 +8,8 @@ import { Checkbox } from '../ui/checkbox'
8
8
  import { Field, FieldLabel } from '../ui/field'
9
9
  import { Input } from '../ui/input'
10
10
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'
11
- import { Thumbnail } from '../ui/thumbnail'
12
- import { PackageIcon, TriangleAlertIcon } from './icons'
11
+ import { TriangleAlertIcon } from './icons'
12
+ import { ProductIdentity } from './product-identity'
13
13
 
14
14
  /** A pickable row: what it looks like and how many units are on offer. */
15
15
  export type FulfillableRowData = {
@@ -48,10 +48,7 @@ function ItemQuantityRow({
48
48
 
49
49
  return (
50
50
  <div
51
- className={cn(
52
- 'my-2 flex items-center gap-3 rounded-lg py-2 pl-2',
53
- selected && 'bg-accent-strong',
54
- )}
51
+ className={cn('my-2 flex items-center gap-3 rounded-lg p-2', selected && 'bg-accent-strong')}
55
52
  >
56
53
  <Controller
57
54
  control={form.control}
@@ -74,14 +71,12 @@ function ItemQuantityRow({
74
71
  )}
75
72
  />
76
73
 
77
- <Thumbnail src={row.thumbnailUrl} fallback={<PackageIcon />} />
78
-
79
- <div className="min-w-0 flex-1">
80
- <div className="truncate font-medium text-sm">{row.name}</div>
81
- {row.optionsText && (
82
- <div className="truncate text-muted-foreground text-xs">{row.optionsText}</div>
83
- )}
84
- </div>
74
+ <ProductIdentity
75
+ name={row.name}
76
+ subtitle={row.optionsText}
77
+ thumbnailUrl={row.thumbnailUrl}
78
+ className="flex-1"
79
+ />
85
80
 
86
81
  <Controller
87
82
  control={form.control}
@@ -108,7 +103,7 @@ function ItemQuantityRow({
108
103
  }}
109
104
  onBlur={field.onBlur}
110
105
  />
111
- <span className="w-16 whitespace-nowrap text-muted-foreground text-sm">
106
+ <span className="w-16 whitespace-nowrap text-muted-foreground text-base">
112
107
  {t('admin.orders.fulfill.of_units', { count: row.quantity })}
113
108
  </span>
114
109
  </div>
@@ -172,7 +167,7 @@ export function FulfillItemsForm({
172
167
  return (
173
168
  <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col">
174
169
  {errors.root?.message && (
175
- <p className="text-destructive text-sm" role="alert">
170
+ <p className="px-4 pt-3 text-destructive text-base" role="alert">
176
171
  {errors.root.message}
177
172
  </p>
178
173
  )}
@@ -180,7 +175,10 @@ export function FulfillItemsForm({
180
175
  {rows.length === 0 ? (
181
176
  <p className="py-6 text-center text-muted-foreground">{t('admin.orders.fulfill.empty')}</p>
182
177
  ) : (
183
- <div className="divide-y">
178
+ // Rows are inset 8px so a selected row's fill sits inside the card,
179
+ // and their own 8px padding lines the content up with the 16px card
180
+ // content below.
181
+ <div className="divide-y px-2">
184
182
  {rows.map((row, index) => (
185
183
  <ItemQuantityRow
186
184
  key={row.key}
@@ -194,7 +192,7 @@ export function FulfillItemsForm({
194
192
  </div>
195
193
  )}
196
194
 
197
- <div className="grid grid-cols-1 gap-4 border-border-subtle border-t py-3 sm:grid-cols-2">
195
+ <div className="mx-4 grid grid-cols-1 gap-4 border-border-subtle border-t py-3 sm:grid-cols-2">
198
196
  <Field>
199
197
  <FieldLabel htmlFor={`${idPrefix}-tracking`}>
200
198
  {t('admin.orders.fulfill.tracking_label')}
@@ -241,7 +239,7 @@ export function FulfillItemsForm({
241
239
  render={({ field }) => (
242
240
  <label
243
241
  htmlFor={`${idPrefix}-notify`}
244
- className="flex cursor-pointer items-center gap-2 py-3 text-sm"
242
+ className="flex cursor-pointer items-center gap-2 px-4 py-3 text-base"
245
243
  >
246
244
  <Checkbox
247
245
  id={`${idPrefix}-notify`}
@@ -254,7 +252,7 @@ export function FulfillItemsForm({
254
252
  />
255
253
 
256
254
  {!shipsEverything && totalSelected > 0 && (
257
- <div className="px-3 pb-3">
255
+ <div className="px-4 pb-3">
258
256
  <Alert variant="warning">
259
257
  <TriangleAlertIcon />
260
258
  <AlertDescription>{t('admin.orders.fulfill.partial_hint')}</AlertDescription>
@@ -262,8 +260,8 @@ export function FulfillItemsForm({
262
260
  </div>
263
261
  )}
264
262
 
265
- <div className="flex items-center justify-end gap-2 border-border-subtle border-t py-3">
266
- <span className="mr-auto text-muted-foreground text-sm">
263
+ <div className="mx-4 flex items-center justify-end gap-2 border-border-subtle border-t py-3">
264
+ <span className="mr-auto text-muted-foreground text-base">
267
265
  {t('admin.orders.fulfill.units_selected')}: {totalSelected} / {totalAvailable}
268
266
  </span>
269
267
  <Button type="button" size="sm" variant="outline" onClick={onCancel}>
@@ -125,7 +125,7 @@ export function FulfillmentEditDialog({
125
125
 
126
126
  <DialogBody className="flex flex-col gap-4">
127
127
  {errorMessage && (
128
- <p className="text-sm text-destructive" role="alert">
128
+ <p className="text-base text-destructive" role="alert">
129
129
  {errorMessage}
130
130
  </p>
131
131
  )}
@@ -1,7 +1,18 @@
1
- import type { ReactNode } from 'react'
2
- import { Item, ItemActions, ItemContent, ItemDescription, ItemTitle } from '../ui/item'
3
- import { Thumbnail } from '../ui/thumbnail'
4
- import { PackageIcon } from './icons'
1
+ import type { ReactElement, ReactNode } from 'react'
2
+ import { Item, ItemActions } from '../ui/item'
3
+ import { CornerDownRightIcon } from './icons'
4
+ import { ProductIdentity } from './product-identity'
5
+
6
+ /** What an exchanged unit was swapped for. */
7
+ export type FulfillmentItemReplacement = {
8
+ name: string
9
+ optionsText?: string | null
10
+ thumbnailUrl?: string | null
11
+ productId?: string | null
12
+ displayPrice?: string | null
13
+ /** Read out before the replacement, since the arrow says it only by sight. */
14
+ label?: string
15
+ }
5
16
 
6
17
  /**
7
18
  * One row of a fulfillment's item list, already joined to its line item by
@@ -18,50 +29,98 @@ export type FulfillmentItemRowData = {
18
29
  thumbnailUrl?: string | null
19
30
  displayPrice?: string | null
20
31
  priceSource?: ReactNode
32
+ /** Beside the name, for state the row's own surface tracks (a return's receipt). */
33
+ badges?: ReactNode
34
+ productId?: string | null
35
+ /** An exchange's new item, shown under the one going back. */
36
+ replacement?: FulfillmentItemReplacement
21
37
  quantity: number
22
38
  }
23
39
 
40
+ /** Builds the link a product opens; routing belongs to the app. */
41
+ export type ProductLinkRender = (productId: string) => ReactElement
42
+
24
43
  /**
25
44
  * What a fulfillment holds: what it looks like, what it costs and how many
26
45
  * units travel together.
27
46
  *
28
47
  * Read-only — changing what an order contains after placement belongs to the
29
48
  * order edit screen, not here. Shared by the operator's order page and the
30
- * seller's so a parcel's contents read the same on both.
49
+ * seller's so a parcel's contents read the same on both, and by the return,
50
+ * exchange and claim cards so a unit looks the same wherever it goes.
31
51
  */
32
- function ItemRow({ row }: { row: FulfillmentItemRowData }) {
52
+ function ItemRow({
53
+ row,
54
+ productLink,
55
+ }: {
56
+ row: FulfillmentItemRowData
57
+ productLink?: ProductLinkRender
58
+ }) {
59
+ const linkFor = (productId?: string | null) =>
60
+ productId && productLink ? productLink(productId) : undefined
61
+ const replacement = row.replacement
62
+
33
63
  return (
34
- <Item className="flex-nowrap gap-3 rounded-none border-0 p-3">
35
- <Thumbnail src={row.thumbnailUrl} fallback={<PackageIcon />} />
64
+ <div className="py-3">
65
+ <Item className="flex-nowrap gap-3 rounded-none border-0 p-0">
66
+ <ProductIdentity
67
+ name={row.name}
68
+ subtitle={row.optionsText}
69
+ thumbnailUrl={row.thumbnailUrl}
70
+ badges={row.badges}
71
+ render={linkFor(row.productId)}
72
+ className="flex-1"
73
+ />
36
74
 
37
- <ItemContent className="min-w-0 gap-0">
38
- <ItemTitle className="block truncate">{row.name}</ItemTitle>
39
- {row.optionsText && (
40
- <ItemDescription className="line-clamp-1 text-xs">{row.optionsText}</ItemDescription>
41
- )}
42
- </ItemContent>
75
+ <ItemActions className="min-w-0 shrink flex-col items-end gap-0 text-right text-muted-foreground text-base">
76
+ {row.displayPrice ? (
77
+ <span className="whitespace-nowrap">
78
+ {row.displayPrice} × {row.quantity}
79
+ </span>
80
+ ) : (
81
+ <span className="whitespace-nowrap">× {row.quantity}</span>
82
+ )}
83
+ {row.priceSource && <div className="max-w-full truncate text-xs">{row.priceSource}</div>}
84
+ </ItemActions>
85
+ </Item>
43
86
 
44
- <ItemActions className="min-w-0 shrink flex-col items-end gap-0 text-right text-muted-foreground text-sm">
45
- {row.displayPrice ? (
46
- <span className="whitespace-nowrap">
47
- {row.displayPrice} × {row.quantity}
48
- </span>
49
- ) : (
50
- <span className="whitespace-nowrap">× {row.quantity}</span>
51
- )}
52
- {row.priceSource && <div className="max-w-full truncate text-xs">{row.priceSource}</div>}
53
- </ItemActions>
54
- </Item>
87
+ {replacement && (
88
+ <Item className="mt-4 flex-nowrap gap-3 rounded-none border-0 p-0 pl-3">
89
+ <CornerDownRightIcon aria-hidden className="size-4 shrink-0 text-muted-foreground" />
90
+ {replacement.label && <span className="sr-only">{replacement.label}</span>}
91
+ <ProductIdentity
92
+ name={replacement.name}
93
+ subtitle={replacement.optionsText}
94
+ thumbnailUrl={replacement.thumbnailUrl}
95
+ render={linkFor(replacement.productId)}
96
+ className="flex-1"
97
+ />
98
+ {replacement.displayPrice && (
99
+ <ItemActions className="whitespace-nowrap text-muted-foreground text-base">
100
+ {replacement.displayPrice} × {row.quantity}
101
+ </ItemActions>
102
+ )}
103
+ </Item>
104
+ )}
105
+ </div>
55
106
  )
56
107
  }
57
108
 
58
- export function FulfillmentItemList({ rows }: { rows: FulfillmentItemRowData[] }) {
109
+ export function FulfillmentItemList({
110
+ rows,
111
+ productLink,
112
+ }: {
113
+ rows: FulfillmentItemRowData[]
114
+ /** Makes each product a link to its page. */
115
+ productLink?: ProductLinkRender
116
+ }) {
59
117
  if (rows.length === 0) return null
60
118
 
61
119
  return (
62
- <div className="divide-y">
120
+ // Inset by margin so the rules between rows stop short of the card's edges.
121
+ <div className="mx-4 divide-y">
63
122
  {rows.map((row) => (
64
- <ItemRow key={row.key} row={row} />
123
+ <ItemRow key={row.key} row={row} productLink={productLink} />
65
124
  ))}
66
125
  </div>
67
126
  )
@@ -31,9 +31,9 @@ export function FulfillmentPanel({
31
31
  children: ReactNode
32
32
  }) {
33
33
  return (
34
- <Card variant="nested">
34
+ <Card>
35
35
  <CardHeader>
36
- <CardTitle className="min-w-0 font-normal text-sm">
36
+ <CardTitle className="min-w-0 font-normal text-base">
37
37
  <StatusBadge status={status} label={statusLabel} />
38
38
  {location && (
39
39
  <div className="flex min-w-0 items-center gap-1.5 text-muted-foreground text-xs">
@@ -24,6 +24,7 @@ export {
24
24
  BotIcon,
25
25
  BoxesIcon,
26
26
  BracesIcon,
27
+ BugIcon,
27
28
  Building2Icon,
28
29
  CalculatorIcon,
29
30
  CalendarIcon,
@@ -38,6 +39,7 @@ export {
38
39
  ChevronsLeftIcon,
39
40
  ChevronsRightIcon,
40
41
  ChevronsUpDownIcon,
42
+ ChevronUpIcon,
41
43
  CircleAlertIcon,
42
44
  CircleCheckIcon,
43
45
  CircleDotIcon,
@@ -48,6 +50,7 @@ export {
48
50
  CommandIcon,
49
51
  CopyIcon,
50
52
  CornerDownLeftIcon,
53
+ CornerDownRightIcon,
51
54
  CreditCardIcon,
52
55
  DownloadIcon,
53
56
  EllipsisVerticalIcon,
@@ -73,6 +76,7 @@ export {
73
76
  GlobeLockIcon,
74
77
  GripVerticalIcon,
75
78
  HandCoinsIcon,
79
+ HashIcon,
76
80
  HistoryIcon,
77
81
  HomeIcon,
78
82
  ImageIcon,
@@ -83,6 +87,7 @@ export {
83
87
  KeyRoundIcon,
84
88
  LanguagesIcon,
85
89
  LayersIcon,
90
+ LayoutGridIcon,
86
91
  LibraryIcon,
87
92
  LinkIcon,
88
93
  ListChecksIcon,
@@ -100,6 +105,7 @@ export {
100
105
  Maximize,
101
106
  MenuIcon,
102
107
  MessageCircleIcon,
108
+ MessageSquareIcon,
103
109
  Minus,
104
110
  MinusIcon,
105
111
  MonitorIcon,
@@ -111,6 +117,7 @@ export {
111
117
  PackageCheckIcon,
112
118
  PackageIcon,
113
119
  PackageXIcon,
120
+ PaletteIcon,
114
121
  PanelLeftIcon,
115
122
  PaperclipIcon,
116
123
  PauseIcon,
@@ -166,6 +173,7 @@ export {
166
173
  UploadIcon,
167
174
  UserIcon,
168
175
  UserMinusIcon,
176
+ UserPenIcon,
169
177
  UserPlusIcon,
170
178
  UsersIcon,
171
179
  UsersRoundIcon,
@@ -19,6 +19,7 @@ import {
19
19
  ExternalLinkIcon,
20
20
  Loader2Icon,
21
21
  RotateCcwIcon,
22
+ TerminalIcon,
22
23
  XIcon,
23
24
  } from './icons'
24
25
  import { type JsonValueLinkResolver, JsonValueView } from './json-value-view'
@@ -51,6 +52,12 @@ export interface JsonPreviewDrawerProps {
51
52
  * convention.
52
53
  */
53
54
  resolveLink?: JsonValueLinkResolver
55
+ /**
56
+ * A ready-made shell command that repeats the request. When given, the
57
+ * toolbar offers "Copy as curl". Build it with a placeholder in place of
58
+ * any credential — the drawer copies it verbatim.
59
+ */
60
+ curl?: string
54
61
  }
55
62
 
56
63
  export function JsonPreviewDrawer({
@@ -60,6 +67,7 @@ export function JsonPreviewDrawer({
60
67
  fetch,
61
68
  endpoint,
62
69
  resolveLink,
70
+ curl,
63
71
  }: JsonPreviewDrawerProps) {
64
72
  const { t } = useTranslation()
65
73
  const resolvedTitle = title ?? t('admin.components.json_preview_drawer.title')
@@ -111,7 +119,8 @@ export function JsonPreviewDrawer({
111
119
  }
112
120
 
113
121
  const [collapsed, setCollapsed] = useState<boolean | number>(2)
114
- const { copied: copiedAll, copy } = useCopyToClipboard()
122
+ const { copied: copiedJson, copy: copyJson } = useCopyToClipboard()
123
+ const { copied: copiedCurl, copy: copyCurl } = useCopyToClipboard()
115
124
 
116
125
  const formatted = useMemo(() => (data ? JSON.stringify(data, null, 2) : ''), [data])
117
126
 
@@ -122,7 +131,7 @@ export function JsonPreviewDrawer({
122
131
  showCloseButton={false}
123
132
  className="w-full sm:max-w-2xl bg-zinc-950 text-zinc-100 border-zinc-800 overflow-hidden"
124
133
  >
125
- <SheetHeader className="border-b border-zinc-800 bg-zinc-950 text-zinc-100 rounded-t-xl">
134
+ <SheetHeader className="border-b border-zinc-800 bg-zinc-950 text-zinc-100 rounded-t-2xl">
126
135
  <SheetTitle className="text-zinc-100 font-mono text-base">{resolvedTitle}</SheetTitle>
127
136
  {endpoint && (
128
137
  <SheetDescription className="text-zinc-400 font-mono text-xs">
@@ -159,12 +168,28 @@ export function JsonPreviewDrawer({
159
168
  <DepthControl value={collapsed} onChange={setCollapsed} />
160
169
 
161
170
  <span className="ml-auto inline-flex items-center gap-1">
162
- <ToolbarButton size="xs" onClick={() => formatted && copy(formatted)} disabled={!data}>
163
- {copiedAll ? <CheckIcon className="size-3.5" /> : <CopyIcon className="size-3.5" />}
164
- {copiedAll
171
+ <ToolbarButton
172
+ size="xs"
173
+ onClick={() => formatted && copyJson(formatted)}
174
+ disabled={!data}
175
+ >
176
+ {copiedJson ? <CheckIcon className="size-3.5" /> : <CopyIcon className="size-3.5" />}
177
+ {copiedJson
165
178
  ? t('admin.components.json_preview_drawer.copied')
166
- : t('admin.components.json_preview_drawer.copy_all')}
179
+ : t('admin.components.json_preview_drawer.copy_json')}
167
180
  </ToolbarButton>
181
+ {curl && (
182
+ <ToolbarButton size="xs" onClick={() => copyCurl(curl)}>
183
+ {copiedCurl ? (
184
+ <CheckIcon className="size-3.5" />
185
+ ) : (
186
+ <TerminalIcon className="size-3.5" />
187
+ )}
188
+ {copiedCurl
189
+ ? t('admin.components.json_preview_drawer.copied')
190
+ : t('admin.components.json_preview_drawer.copy_curl')}
191
+ </ToolbarButton>
192
+ )}
168
193
  <ToolbarButton
169
194
  size="icon-xs"
170
195
  onClick={() => refetch()}
@@ -193,7 +218,7 @@ export function JsonPreviewDrawer({
193
218
  </span>
194
219
  </div>
195
220
 
196
- <div className="themed-scrollbar flex-1 overflow-auto p-4 font-mono text-sm">
221
+ <div className="themed-scrollbar flex-1 overflow-auto p-4 font-mono text-base">
197
222
  {isLoading ? (
198
223
  <p className="text-zinc-500">{t('admin.common.loading')}</p>
199
224
  ) : error ? (
@@ -71,7 +71,7 @@ export function JsonValueView({
71
71
  return (
72
72
  <div
73
73
  className={cn(
74
- 'font-mono text-sm',
74
+ 'font-mono text-base',
75
75
  // The vscode theme is dark — when the parent doesn't already paint a
76
76
  // dark background, paint one here so the syntax colors stay legible.
77
77
  !bare && 'bg-zinc-950 text-zinc-100 rounded-md p-3',
@@ -87,9 +87,19 @@ export function JsonValueView({
87
87
  enableClipboard
88
88
  shortenTextAfterLength={60}
89
89
  components={{
90
- value: ({ value: v, type, ...rest }) => (
91
- <ValueRenderer value={v} type={type} resolveLink={resolveLink} {...rest} />
92
- ),
90
+ // The viewer's own props, which React would warn about on a <span>.
91
+ value: ({
92
+ value: v,
93
+ type,
94
+ parentValue: _parentValue,
95
+ data: _data,
96
+ visible: _visible,
97
+ quotes: _quotes,
98
+ namespace: _namespace,
99
+ setValue: _setValue,
100
+ keyName: _keyName,
101
+ ...rest
102
+ }) => <ValueRenderer value={v} type={type} resolveLink={resolveLink} {...rest} />,
93
103
  }}
94
104
  />
95
105
  </div>
@@ -90,7 +90,7 @@ export function LabelUploadDialog({
90
90
 
91
91
  <DialogBody className="flex flex-col gap-4">
92
92
  {errors.root?.message && (
93
- <p className="text-destructive text-sm" role="alert">
93
+ <p className="text-destructive text-base" role="alert">
94
94
  {errors.root.message}
95
95
  </p>
96
96
  )}
@@ -34,7 +34,7 @@ export function LanguageMenuItems({ label, locales, value, onSelect }: LanguageM
34
34
  // Not a DropdownMenuItem: the row is a container for the select, and menu
35
35
  // items steal the pointer and close the menu on click.
36
36
  <div className="flex items-center justify-between gap-2 rounded-lg px-2 py-1.5">
37
- <span className="text-sm text-foreground">{label}</span>
37
+ <span className="text-base text-foreground">{label}</span>
38
38
  {/* The select is transparent and stretched over the pill, so the hit area
39
39
  tracks the pill's width whatever the locale name is. `focus-within`
40
40
  carries the focus ring, since `opacity-0` hides the native one. */}
@@ -72,7 +72,7 @@ export function MetadataList({
72
72
  {externalReferencesTitle && (
73
73
  <h4 className="text-xs font-medium text-muted-foreground">{externalReferencesTitle}</h4>
74
74
  )}
75
- <dl className="grid grid-cols-[minmax(160px,1fr)_2fr] items-center gap-x-4 gap-y-1 text-sm">
75
+ <dl className="grid grid-cols-[minmax(160px,1fr)_2fr] items-center gap-x-4 gap-y-1 text-base">
76
76
  {Object.entries(externalReferences).map(([system, key]) => (
77
77
  <Fragment key={system}>
78
78
  <dt>
@@ -94,7 +94,7 @@ export function MetadataList({
94
94
  {both && metadataTitle && (
95
95
  <h4 className="text-xs font-medium text-muted-foreground">{metadataTitle}</h4>
96
96
  )}
97
- <dl className="grid grid-cols-[minmax(160px,1fr)_2fr] gap-x-4 gap-y-2 text-sm">
97
+ <dl className="grid grid-cols-[minmax(160px,1fr)_2fr] gap-x-4 gap-y-2 text-base">
98
98
  {Object.entries(metadata).map(([key, value]) => (
99
99
  <Fragment key={key}>
100
100
  <dt>
@@ -67,7 +67,7 @@ export function OrderCancelDialog({
67
67
 
68
68
  <DialogBody>
69
69
  {errors.root?.message && (
70
- <p className="text-destructive text-sm" role="alert">
70
+ <p className="text-destructive text-base" role="alert">
71
71
  {errors.root.message}
72
72
  </p>
73
73
  )}
@@ -94,7 +94,7 @@ export function OrderCancelDialog({
94
94
  render={({ field }) => (
95
95
  <label
96
96
  htmlFor="order-cancel-refund"
97
- className="flex cursor-pointer items-start gap-2 text-sm"
97
+ className="flex cursor-pointer items-start gap-2 text-base"
98
98
  >
99
99
  <Checkbox
100
100
  id="order-cancel-refund"
@@ -119,7 +119,7 @@ export function OrderCancelDialog({
119
119
  render={({ field }) => (
120
120
  <label
121
121
  htmlFor="order-cancel-notify"
122
- className="flex cursor-pointer items-center gap-2 text-sm"
122
+ className="flex cursor-pointer items-center gap-2 text-base"
123
123
  >
124
124
  <Checkbox
125
125
  id="order-cancel-notify"
@@ -12,10 +12,12 @@ import {
12
12
  import { Field, FieldLabel } from '../ui/field'
13
13
  import { Input } from '../ui/input'
14
14
  import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from '../ui/input-group'
15
+ import { Item, ItemActions, ItemContent, ItemFooter } from '../ui/item'
15
16
  import { Textarea } from '../ui/textarea'
16
17
  import {
17
18
  type PostSaleSelection,
18
19
  type PostSaleUnit,
20
+ PostSaleUnitLabel,
19
21
  QuantityPicker,
20
22
  selectedUnits,
21
23
  } from './post-sale-fields'
@@ -158,12 +160,11 @@ export function CreateClaimDialog({
158
160
  const chosenQuantity = selection[line.id] ?? 0
159
161
 
160
162
  return (
161
- <div
162
- key={line.id}
163
- className="flex flex-col gap-2 rounded-lg border border-border-subtle p-3"
164
- >
165
- <div className="flex items-center justify-between gap-4">
166
- <span className="min-w-0 truncate text-sm">{line.label}</span>
163
+ <Item variant="outline" key={line.id}>
164
+ <ItemContent className="min-w-0">
165
+ <PostSaleUnitLabel unit={line} />
166
+ </ItemContent>
167
+ <ItemActions>
167
168
  <Input
168
169
  type="number"
169
170
  min={0}
@@ -187,35 +188,37 @@ export function CreateClaimDialog({
187
188
  }
188
189
  }}
189
190
  />
190
- </div>
191
+ </ItemActions>
191
192
  {chosenQuantity > 0 && (
192
- <Field>
193
- <FieldLabel htmlFor={`claim-amount-${line.id}`}>
194
- {t('admin.pages.orders.detail.claims.refund_amount')}
195
- </FieldLabel>
196
- <InputGroup>
197
- <InputGroupAddon>
198
- <InputGroupText>{currencySymbol}</InputGroupText>
199
- </InputGroupAddon>
200
- <InputGroupInput
201
- id={`claim-amount-${line.id}`}
202
- type="number"
203
- step="any"
204
- min="0"
205
- value={amounts[line.id] ?? ''}
206
- onChange={(event) => {
207
- setAmounts({ ...amounts, [line.id]: event.target.value })
208
- // Clearing the field hands it back to the quantity.
209
- setTypedAmounts({
210
- ...typedAmounts,
211
- [line.id]: event.target.value !== '',
212
- })
213
- }}
214
- />
215
- </InputGroup>
216
- </Field>
193
+ <ItemFooter className="flex-col items-stretch">
194
+ <Field>
195
+ <FieldLabel htmlFor={`claim-amount-${line.id}`}>
196
+ {t('admin.pages.orders.detail.claims.refund_amount')}
197
+ </FieldLabel>
198
+ <InputGroup>
199
+ <InputGroupAddon>
200
+ <InputGroupText>{currencySymbol}</InputGroupText>
201
+ </InputGroupAddon>
202
+ <InputGroupInput
203
+ id={`claim-amount-${line.id}`}
204
+ type="number"
205
+ step="any"
206
+ min="0"
207
+ value={amounts[line.id] ?? ''}
208
+ onChange={(event) => {
209
+ setAmounts({ ...amounts, [line.id]: event.target.value })
210
+ // Clearing the field hands it back to the quantity.
211
+ setTypedAmounts({
212
+ ...typedAmounts,
213
+ [line.id]: event.target.value !== '',
214
+ })
215
+ }}
216
+ />
217
+ </InputGroup>
218
+ </Field>
219
+ </ItemFooter>
217
220
  )}
218
- </div>
221
+ </Item>
219
222
  )
220
223
  })}
221
224
  </div>