@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
@@ -5,28 +5,24 @@ import {
5
5
  Badge,
6
6
  Button,
7
7
  Card,
8
- CardAction,
9
8
  CardContent,
10
- CardHeader,
11
- CardTitle,
12
9
  Combobox,
13
10
  ComboboxContent,
14
11
  ComboboxEmpty,
15
12
  ComboboxInput,
16
13
  ComboboxItem,
17
14
  ComboboxList,
15
+ Empty,
16
+ EmptyHeader,
17
+ EmptyMedia,
18
+ EmptyTitle,
18
19
  Field,
19
20
  FieldDescription,
20
21
  FieldError,
21
22
  FieldGroup,
22
23
  FieldLabel,
23
24
  Input,
24
- Item,
25
- ItemActions,
26
- ItemContent,
27
- ItemDescription,
28
- ItemGroup,
29
- ItemTitle,
25
+ InputGroupAddon,
30
26
  RelativeTime,
31
27
  RowActions,
32
28
  Sheet,
@@ -38,7 +34,10 @@ import {
38
34
  StatusBadge,
39
35
  useConfirm,
40
36
  } from '../index'
41
- import { BadgeCheckIcon, PlusIcon } from './icons'
37
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/data-table'
38
+ import { CountryFlag } from './country-flag'
39
+ import { BadgeCheckIcon, FileTextIcon, PlusIcon } from './icons'
40
+ import { Section, SectionActions, SectionHeader, SectionTitle } from './section'
42
41
 
43
42
  /** One selectable registration kind. */
44
43
  interface KindOption {
@@ -79,6 +78,15 @@ const TAX_IDENTIFIER_DEFAULTS: TaxIdentifierFormValues = { kind: '', value: '' }
79
78
  */
80
79
  export const TAX_IDENTIFIER_KINDS = ['eu_vat', 'gb_vat', 'ch_vat', 'au_abn', 'us_ein'] as const
81
80
 
81
+ /**
82
+ * The flag of the registry a kind belongs to: the region code that opens it
83
+ * (`eu_vat` → EU, `gb_vat` → GB), which also covers a plugin's kind named the
84
+ * same way (`nz_gst`). A kind with no such prefix gets no flag.
85
+ */
86
+ export function taxIdentifierKindFlag(kind: string): string | null {
87
+ return /^([a-z]{2})_/.exec(kind)?.[1] ?? null
88
+ }
89
+
82
90
  /**
83
91
  * Mutations differ by owner (a company's registrations hang off the company,
84
92
  * a customer's off the customer) while the panel itself does not — so the
@@ -131,43 +139,64 @@ export function TaxIdentifiersCard({
131
139
  }
132
140
 
133
141
  return (
134
- <Card>
135
- <CardHeader>
136
- <CardTitle>
142
+ <Section>
143
+ <SectionHeader>
144
+ <SectionTitle>
145
+ <FileTextIcon />
137
146
  {t('admin.tax_identifiers.title')}
138
147
  {identifiers.length > 0 && <Badge variant="outline">{identifiers.length}</Badge>}
139
- </CardTitle>
148
+ </SectionTitle>
140
149
  {canEdit && (
141
- <CardAction>
150
+ <SectionActions>
142
151
  <Button size="sm" variant="outline" onClick={() => setAddOpen(true)}>
143
152
  <PlusIcon className="size-4" />
144
153
  {t('admin.tax_identifiers.add_cta')}
145
154
  </Button>
146
- </CardAction>
155
+ </SectionActions>
147
156
  )}
148
- </CardHeader>
157
+ </SectionHeader>
149
158
 
150
- {isLoading ? (
151
- <CardContent>
152
- <p className="text-muted-foreground text-sm">{t('admin.common.loading')}</p>
153
- </CardContent>
154
- ) : identifiers.length === 0 ? (
155
- <CardContent>
156
- <p className="text-muted-foreground text-sm">{t('admin.tax_identifiers.empty')}</p>
157
- </CardContent>
159
+ {isLoading || identifiers.length === 0 ? (
160
+ <Card>
161
+ <CardContent>
162
+ {isLoading ? (
163
+ <p className="text-muted-foreground text-base">{t('admin.common.loading')}</p>
164
+ ) : (
165
+ <Empty className="p-4">
166
+ <EmptyHeader>
167
+ <EmptyMedia variant="icon">
168
+ <FileTextIcon />
169
+ </EmptyMedia>
170
+ <EmptyTitle>{t('admin.tax_identifiers.empty')}</EmptyTitle>
171
+ </EmptyHeader>
172
+ </Empty>
173
+ )}
174
+ </CardContent>
175
+ </Card>
158
176
  ) : (
159
- <CardContent>
160
- <ItemGroup className="gap-3">
161
- {identifiers.map((identifier) => (
162
- <Item key={identifier.id} role="listitem" variant="outline" className="items-start">
163
- <ItemContent className="min-w-0">
164
- <ItemTitle className="flex-wrap">
165
- {identifier.value}
166
- <Badge variant="outline">
177
+ <Card>
178
+ <Table roundedTop>
179
+ <TableHeader>
180
+ <TableRow>
181
+ <TableHead>{t('admin.fields.tax_identifier.value.label')}</TableHead>
182
+ <TableHead>{t('admin.fields.tax_identifier.kind.label')}</TableHead>
183
+ <TableHead>{t('admin.fields.status.label')}</TableHead>
184
+ {canEdit && <TableHead className="w-10" />}
185
+ </TableRow>
186
+ </TableHeader>
187
+ <TableBody>
188
+ {identifiers.map((identifier) => (
189
+ <TableRow key={identifier.id}>
190
+ <TableCell className="font-medium tabular-nums">{identifier.value}</TableCell>
191
+ <TableCell>
192
+ <span className="inline-flex items-center gap-2 whitespace-nowrap">
193
+ <TaxIdentifierKindFlag kind={identifier.kind} />
167
194
  {t(`admin.tax_identifiers.kinds.${identifier.kind}`, {
168
195
  defaultValue: identifier.kind,
169
196
  })}
170
- </Badge>
197
+ </span>
198
+ </TableCell>
199
+ <TableCell>
171
200
  {identifier.validation_status ? (
172
201
  <StatusBadge
173
202
  status={identifier.validation_status}
@@ -181,47 +210,47 @@ export function TaxIdentifiersCard({
181
210
  {t('admin.tax_identifiers.validation_status.not_checked')}
182
211
  </Badge>
183
212
  )}
184
- </ItemTitle>
185
- {identifier.validated_at && (
186
- <ItemDescription className="text-xs">
187
- <RelativeTime
188
- iso={identifier.validated_at}
189
- prefix={t('admin.tax_identifiers.checked_prefix')}
213
+ {identifier.validated_at && (
214
+ <div className="text-muted-foreground text-xs">
215
+ <RelativeTime
216
+ iso={identifier.validated_at}
217
+ prefix={t('admin.tax_identifiers.checked_prefix')}
218
+ />
219
+ </div>
220
+ )}
221
+ {!identifier.validatable && (
222
+ <div className="text-muted-foreground text-xs">
223
+ {t('admin.tax_identifiers.not_validatable')}
224
+ </div>
225
+ )}
226
+ </TableCell>
227
+ {canEdit && (
228
+ <TableCell className="w-10 text-right">
229
+ <RowActions
230
+ actions={[
231
+ {
232
+ key: 'validate',
233
+ label: t('admin.tax_identifiers.validate_action'),
234
+ icon: <BadgeCheckIcon className="size-4" />,
235
+ disabled: !identifier.validatable || mutations.isValidating,
236
+ onSelect: () =>
237
+ mutations.validate(identifier.id).catch(() => undefined),
238
+ },
239
+ { key: 'edit', onSelect: () => setEditing(identifier) },
240
+ {
241
+ key: 'delete',
242
+ destructive: true,
243
+ onSelect: () => handleDelete(identifier),
244
+ },
245
+ ]}
190
246
  />
191
- </ItemDescription>
192
- )}
193
- {!identifier.validatable && (
194
- <ItemDescription className="text-xs">
195
- {t('admin.tax_identifiers.not_validatable')}
196
- </ItemDescription>
247
+ </TableCell>
197
248
  )}
198
- </ItemContent>
199
-
200
- {canEdit && (
201
- <ItemActions>
202
- <RowActions
203
- actions={[
204
- {
205
- key: 'validate',
206
- label: t('admin.tax_identifiers.validate_action'),
207
- icon: <BadgeCheckIcon className="size-4" />,
208
- disabled: !identifier.validatable || mutations.isValidating,
209
- onSelect: () => mutations.validate(identifier.id).catch(() => undefined),
210
- },
211
- { key: 'edit', onSelect: () => setEditing(identifier) },
212
- {
213
- key: 'delete',
214
- destructive: true,
215
- onSelect: () => handleDelete(identifier),
216
- },
217
- ]}
218
- />
219
- </ItemActions>
220
- )}
221
- </Item>
222
- ))}
223
- </ItemGroup>
224
- </CardContent>
249
+ </TableRow>
250
+ ))}
251
+ </TableBody>
252
+ </Table>
253
+ </Card>
225
254
  )}
226
255
 
227
256
  {addOpen && (
@@ -243,7 +272,7 @@ export function TaxIdentifiersCard({
243
272
  onSubmit={(params) => mutations.update(editing.id, params)}
244
273
  />
245
274
  )}
246
- </Card>
275
+ </Section>
247
276
  )
248
277
  }
249
278
 
@@ -311,7 +340,7 @@ function TaxIdentifierSheet({
311
340
  <div className="flex flex-1 flex-col gap-4 overflow-y-auto p-4">
312
341
  <FieldGroup>
313
342
  {errors.root?.message && !rootIsRepeated && (
314
- <p className="text-destructive text-sm" role="alert">
343
+ <p className="text-destructive text-base" role="alert">
315
344
  {errors.root.message}
316
345
  </p>
317
346
  )}
@@ -356,12 +385,19 @@ function TaxIdentifierSheet({
356
385
  <ComboboxInput
357
386
  id="tax-identifier-kind"
358
387
  placeholder={t('admin.tax_identifiers.select_kind')}
359
- />
388
+ >
389
+ {field.value && taxIdentifierKindFlag(field.value) && (
390
+ <InputGroupAddon align="inline-start">
391
+ <TaxIdentifierKindFlag kind={field.value} />
392
+ </InputGroupAddon>
393
+ )}
394
+ </ComboboxInput>
360
395
  <ComboboxContent>
361
396
  <ComboboxEmpty>{t('admin.common.no_results')}</ComboboxEmpty>
362
397
  <ComboboxList>
363
398
  {(option: KindOption) => (
364
399
  <ComboboxItem key={option.value} value={option}>
400
+ <TaxIdentifierKindFlag kind={option.value} />
365
401
  {option.label}
366
402
  </ComboboxItem>
367
403
  )}
@@ -409,3 +445,8 @@ function TaxIdentifierSheet({
409
445
  </Sheet>
410
446
  )
411
447
  }
448
+
449
+ function TaxIdentifierKindFlag({ kind }: { kind: string }) {
450
+ const iso = taxIdentifierKindFlag(kind)
451
+ return iso ? <CountryFlag iso={iso} /> : null
452
+ }
@@ -21,7 +21,7 @@ export function ThemeMenuItems() {
21
21
 
22
22
  return (
23
23
  <div className="flex items-center justify-between gap-2 px-2.5 py-1.5">
24
- <span className="text-sm text-foreground">{t('admin.components.theme_toggle.label')}</span>
24
+ <span className="text-base text-foreground">{t('admin.components.theme_toggle.label')}</span>
25
25
  <div className="flex items-center gap-0.5 rounded-lg border border-border bg-muted/40 p-0.5">
26
26
  {OPTIONS.map(({ value, icon: Icon, labelKey }) => {
27
27
  const active = mode === value
@@ -84,7 +84,7 @@ export function WizardProgress({ steps, current, onStepSelect, className }: Wiza
84
84
  tabIndex={navigable ? undefined : -1}
85
85
  aria-current={state === 'current' ? 'step' : undefined}
86
86
  className={cn(
87
- 'min-w-0 pb-7 text-left text-sm leading-none',
87
+ 'min-w-0 pb-7 text-left text-base leading-none',
88
88
  navigable && 'cursor-pointer hover:text-foreground',
89
89
  state === 'current' ? 'font-medium text-foreground' : 'text-muted-foreground',
90
90
  last && 'pb-0',
@@ -195,7 +195,7 @@ export function Wizard({
195
195
  {subtitle ? (
196
196
  <>
197
197
  <span aria-hidden className="h-4 w-px shrink-0 bg-border" />
198
- <span className="truncate text-muted-foreground text-sm">{subtitle}</span>
198
+ <span className="truncate text-muted-foreground text-base">{subtitle}</span>
199
199
  </>
200
200
  ) : null}
201
201
  </div>