@vendure-io/ui 1.2.0 → 2.0.0-beta.0

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 (112) hide show
  1. package/README.md +5 -5
  2. package/package.json +9 -4
  3. package/src/components/{ui → atoms}/alert-dialog.tsx +4 -4
  4. package/src/components/{ui → atoms}/badge.tsx +6 -3
  5. package/src/components/{ui → atoms}/button-group.tsx +1 -2
  6. package/src/components/{ui → atoms}/button.tsx +8 -2
  7. package/src/components/{ui → atoms}/calendar.tsx +1 -1
  8. package/src/components/{ui → atoms}/card.tsx +2 -3
  9. package/src/components/{ui → atoms}/carousel.tsx +1 -1
  10. package/src/components/atoms/checkbox.tsx +29 -0
  11. package/src/components/{ui → atoms}/combobox.tsx +3 -4
  12. package/src/components/{ui → atoms}/command.tsx +2 -2
  13. package/src/components/{ui → atoms}/context-menu.tsx +1 -1
  14. package/src/components/{ui → atoms}/dialog.tsx +4 -4
  15. package/src/components/{ui → atoms}/drawer.tsx +3 -3
  16. package/src/components/{ui → atoms}/dropdown-menu.tsx +3 -2
  17. package/src/components/{ui → atoms}/empty.tsx +1 -1
  18. package/src/components/{ui → atoms}/field.tsx +2 -2
  19. package/src/components/{ui → atoms}/hover-card.tsx +1 -1
  20. package/src/components/{ui → atoms}/input-group.tsx +3 -4
  21. package/src/components/{ui → atoms}/input-otp.tsx +0 -1
  22. package/src/components/{ui → atoms}/input.tsx +0 -1
  23. package/src/components/{ui → atoms}/item.tsx +2 -2
  24. package/src/components/{ui → atoms}/menubar.tsx +3 -4
  25. package/src/components/{ui → atoms}/native-select.tsx +0 -1
  26. package/src/components/{ui → atoms}/navigation-menu.tsx +7 -4
  27. package/src/components/{ui → atoms}/pagination.tsx +1 -1
  28. package/src/components/{ui → atoms}/popover.tsx +2 -2
  29. package/src/components/{ui → atoms}/select.tsx +22 -4
  30. package/src/components/{ui → atoms}/sheet.tsx +4 -4
  31. package/src/components/{ui → atoms}/sidebar.tsx +6 -6
  32. package/src/components/atoms/skeleton.tsx +16 -0
  33. package/src/components/{ui → atoms}/slider.tsx +0 -1
  34. package/src/components/{ui → atoms}/sonner.tsx +1 -1
  35. package/src/components/{ui → atoms}/switch.tsx +0 -1
  36. package/src/components/{ui → atoms}/tabs.tsx +6 -4
  37. package/src/components/{ui → atoms}/textarea.tsx +0 -1
  38. package/src/components/{ui → atoms}/toggle-group.tsx +3 -3
  39. package/src/components/{ui → atoms}/toggle.tsx +0 -1
  40. package/src/components/{ui → atoms}/tooltip.tsx +2 -1
  41. package/src/components/molecules/anonymized-token.tsx +170 -0
  42. package/src/components/molecules/chip.tsx +85 -0
  43. package/src/components/molecules/combobox-free-text.tsx +160 -0
  44. package/src/components/molecules/confirm-dialog.tsx +108 -0
  45. package/src/components/molecules/copyable-text.tsx +102 -0
  46. package/src/components/molecules/data-table/data-table-bulk-actions.tsx +69 -0
  47. package/src/components/molecules/data-table/data-table-column-header.tsx +61 -0
  48. package/src/components/molecules/data-table/data-table-filters.tsx +198 -0
  49. package/src/components/molecules/data-table/data-table-helpers.tsx +73 -0
  50. package/src/components/molecules/data-table/data-table-types.tsx +241 -0
  51. package/src/components/molecules/data-table/data-table-view-options.tsx +73 -0
  52. package/src/components/molecules/data-table/data-table.tsx +500 -0
  53. package/src/components/molecules/data-table/list-header.tsx +53 -0
  54. package/src/components/molecules/data-table/table-pagination.tsx +263 -0
  55. package/src/components/molecules/date-time.tsx +73 -0
  56. package/src/components/molecules/description-list.tsx +120 -0
  57. package/src/components/molecules/format-provider.tsx +46 -0
  58. package/src/components/molecules/id-chip.tsx +79 -0
  59. package/src/components/molecules/illustrations/access-denied.tsx +55 -0
  60. package/src/components/molecules/illustrations/empty-collection.tsx +66 -0
  61. package/src/components/molecules/illustrations/empty-database.tsx +44 -0
  62. package/src/components/molecules/illustrations/empty-media.tsx +68 -0
  63. package/src/components/molecules/illustrations/error.tsx +64 -0
  64. package/src/components/molecules/illustrations/first-run.tsx +55 -0
  65. package/src/components/molecules/illustrations/illustration-types.tsx +9 -0
  66. package/src/components/molecules/illustrations/no-activity.tsx +49 -0
  67. package/src/components/molecules/illustrations/no-deployments.tsx +49 -0
  68. package/src/components/molecules/illustrations/no-documents.tsx +67 -0
  69. package/src/components/molecules/illustrations/no-keys.tsx +50 -0
  70. package/src/components/molecules/illustrations/no-logs.tsx +53 -0
  71. package/src/components/molecules/illustrations/no-members.tsx +56 -0
  72. package/src/components/molecules/illustrations/no-notifications.tsx +52 -0
  73. package/src/components/molecules/illustrations/no-orders.tsx +48 -0
  74. package/src/components/molecules/illustrations/no-plugins.tsx +72 -0
  75. package/src/components/molecules/illustrations/no-results.tsx +69 -0
  76. package/src/components/molecules/illustrations/not-found.tsx +48 -0
  77. package/src/components/molecules/illustrations/offline.tsx +43 -0
  78. package/src/components/molecules/illustrations/pending-approval.tsx +56 -0
  79. package/src/components/molecules/illustrations/upload-dropzone.tsx +59 -0
  80. package/src/components/molecules/money.tsx +91 -0
  81. package/src/components/{custom → molecules}/multi-select.tsx +2 -2
  82. package/src/components/molecules/page-header.tsx +80 -0
  83. package/src/components/molecules/password-input.tsx +47 -0
  84. package/src/components/molecules/relative-time.tsx +81 -0
  85. package/src/components/molecules/stat-card.tsx +134 -0
  86. package/src/components/molecules/state-views/empty-state.tsx +75 -0
  87. package/src/components/molecules/state-views/error-state.tsx +91 -0
  88. package/src/components/molecules/state-views/loading-state.tsx +80 -0
  89. package/src/components/molecules/status-badge.tsx +100 -0
  90. package/src/hooks/use-copy.ts +51 -0
  91. package/src/lib/base-ui.ts +2 -1
  92. package/src/lib/format-relative.ts +41 -0
  93. package/src/lib/state-dictionary.ts +137 -0
  94. package/src/components/ui/checkbox.tsx +0 -30
  95. package/src/components/ui/skeleton.tsx +0 -13
  96. /package/src/components/{ui → atoms}/accordion.tsx +0 -0
  97. /package/src/components/{ui → atoms}/alert.tsx +0 -0
  98. /package/src/components/{ui → atoms}/aspect-ratio.tsx +0 -0
  99. /package/src/components/{ui → atoms}/avatar.tsx +0 -0
  100. /package/src/components/{ui → atoms}/breadcrumb.tsx +0 -0
  101. /package/src/components/{ui → atoms}/chart.tsx +0 -0
  102. /package/src/components/{ui → atoms}/collapsible.tsx +0 -0
  103. /package/src/components/{ui → atoms}/direction.tsx +0 -0
  104. /package/src/components/{ui → atoms}/kbd.tsx +0 -0
  105. /package/src/components/{ui → atoms}/label.tsx +0 -0
  106. /package/src/components/{ui → atoms}/progress.tsx +0 -0
  107. /package/src/components/{ui → atoms}/radio-group.tsx +0 -0
  108. /package/src/components/{ui → atoms}/resizable.tsx +0 -0
  109. /package/src/components/{ui → atoms}/scroll-area.tsx +0 -0
  110. /package/src/components/{ui → atoms}/separator.tsx +0 -0
  111. /package/src/components/{ui → atoms}/spinner.tsx +0 -0
  112. /package/src/components/{ui → atoms}/table.tsx +0 -0
@@ -0,0 +1,52 @@
1
+ import type { IllustrationProps } from '@vendure-io/ui/components/molecules/illustrations/illustration-types';
2
+ import { cn } from '@vendure-io/ui/lib/utils';
3
+
4
+ /**
5
+ * A bell at rest, its clapper still, with faint dashed arcs trailing off
6
+ * where a ring would sound — nothing new has come in. For an empty
7
+ * notifications/alerts panel where the user is caught up — pass as
8
+ * `illustration` to `EmptyState`. Not for a generic empty list with no bell
9
+ * shape (use `EmptyCollectionIllustration` instead).
10
+ */
11
+ function NoNotificationsIllustration({ className, size = 160 }: IllustrationProps) {
12
+ return (
13
+ <svg
14
+ viewBox="0 0 160 120"
15
+ width={size}
16
+ height={(size * 120) / 160}
17
+ fill="none"
18
+ strokeWidth={1.5}
19
+ strokeLinecap="round"
20
+ strokeLinejoin="round"
21
+ aria-hidden="true"
22
+ focusable="false"
23
+ className={cn('shrink-0', className)}
24
+ >
25
+ <ellipse cx="80" cy="104" rx="28" ry="5" className="fill-muted" />
26
+
27
+ {/* faint arcs where a ring would sound — dashed, trailing off */}
28
+ <path d="M48,44 Q40,52 48,60" className="stroke-border" strokeDasharray="2 5" />
29
+ <path d="M112,44 Q120,52 112,60" className="stroke-border" strokeDasharray="2 5" />
30
+
31
+ {/* hanging loop */}
32
+ <circle cx="80" cy="29" r="3" className="fill-surface stroke-muted-foreground" />
33
+
34
+ {/* bell body, dome flaring to a flat skirt */}
35
+ <path
36
+ d="M62,68 C62,48 70,34 80,34 C90,34 98,48 98,68 C101,68 103,70 103,71 L57,71 C57,70 59,68 62,68 Z"
37
+ className="fill-surface stroke-muted-foreground"
38
+ />
39
+
40
+ {/* clapper, still and centered — nothing ringing it */}
41
+ <circle cx="80" cy="72" r="2.5" className="fill-muted-foreground" />
42
+
43
+ {/* check mark — the one brand accent, caught up */}
44
+ <path
45
+ d="M95,78 L92.6,75.6 L91.9,76.4 L95,79.5 L101.6,72.9 L100.8,72.1 Z"
46
+ className="fill-brand"
47
+ />
48
+ </svg>
49
+ );
50
+ }
51
+
52
+ export { NoNotificationsIllustration };
@@ -0,0 +1,48 @@
1
+ import type { IllustrationProps } from '@vendure-io/ui/components/molecules/illustrations/illustration-types';
2
+ import { cn } from '@vendure-io/ui/lib/utils';
3
+
4
+ /**
5
+ * An empty cart standing still, with faded motion dashes behind it where
6
+ * incoming orders would trail in. For order/checkout lists with nothing in
7
+ * them yet.
8
+ */
9
+ function NoOrdersIllustration({ className, size = 160 }: IllustrationProps) {
10
+ return (
11
+ <svg
12
+ viewBox="0 0 160 120"
13
+ width={size}
14
+ height={(size * 120) / 160}
15
+ fill="none"
16
+ strokeWidth={1.5}
17
+ strokeLinecap="round"
18
+ strokeLinejoin="round"
19
+ aria-hidden="true"
20
+ focusable="false"
21
+ className={cn('shrink-0', className)}
22
+ >
23
+ <ellipse cx="78" cy="104" rx="30" ry="5" className="fill-muted" />
24
+
25
+ {/* stillness dashes, nothing rolling in */}
26
+ <line x1="10" y1="56" x2="22" y2="56" className="stroke-border" strokeDasharray="2 4" />
27
+ <line x1="6" y1="66" x2="20" y2="66" className="stroke-border" strokeDasharray="2 4" />
28
+ <line x1="10" y1="76" x2="22" y2="76" className="stroke-border" strokeDasharray="2 4" />
29
+
30
+ {/* handle */}
31
+ <path d="M42,50 L32,26 L44,26" className="stroke-muted-foreground" />
32
+
33
+ {/* basket */}
34
+ <path d="M40,50 L120,50 L108,86 L54,86 Z" className="fill-surface stroke-muted-foreground" />
35
+ <line x1="52" y1="62" x2="106" y2="62" className="stroke-border" strokeDasharray="3 3" />
36
+ <line x1="55" y1="74" x2="103" y2="74" className="stroke-border" strokeDasharray="3 3" />
37
+
38
+ {/* wheels */}
39
+ <circle cx="60" cy="94" r="6" className="fill-surface stroke-muted-foreground" />
40
+ <circle cx="100" cy="94" r="6" className="fill-surface stroke-muted-foreground" />
41
+
42
+ {/* waiting indicator — the one brand accent */}
43
+ <circle cx="112" cy="42" r="5" className="fill-brand" />
44
+ </svg>
45
+ );
46
+ }
47
+
48
+ export { NoOrdersIllustration };
@@ -0,0 +1,72 @@
1
+ import type { IllustrationProps } from '@vendure-io/ui/components/molecules/illustrations/illustration-types';
2
+ import { cn } from '@vendure-io/ui/lib/utils';
3
+
4
+ /**
5
+ * A module block hovering above a dashed socket outline shaped just like it —
6
+ * an extension point with nothing plugged in. For an empty plugin list, or a
7
+ * plugin catalog with nothing installed — pass as `illustration` to
8
+ * `EmptyState`.
9
+ *
10
+ * Not for a search/filter miss in a populated catalog — pair that with
11
+ * `NoResultsIllustration` instead.
12
+ */
13
+ function NoPluginsIllustration({ className, size = 160 }: IllustrationProps) {
14
+ return (
15
+ <svg
16
+ viewBox="0 0 160 120"
17
+ width={size}
18
+ height={(size * 120) / 160}
19
+ fill="none"
20
+ strokeWidth={1.5}
21
+ strokeLinecap="round"
22
+ strokeLinejoin="round"
23
+ aria-hidden="true"
24
+ focusable="false"
25
+ className={cn('shrink-0', className)}
26
+ >
27
+ <ellipse cx="80" cy="104" rx="28" ry="5" className="fill-muted" />
28
+
29
+ {/* module block, hovering */}
30
+ <rect
31
+ x="62"
32
+ y="20"
33
+ width="36"
34
+ height="28"
35
+ rx="4"
36
+ className="fill-surface stroke-muted-foreground"
37
+ />
38
+ <rect
39
+ x="74"
40
+ y="46"
41
+ width="12"
42
+ height="12"
43
+ rx="2"
44
+ className="fill-surface stroke-muted-foreground"
45
+ />
46
+ {/* connector pin — the one brand accent */}
47
+ <circle cx="80" cy="52" r="2.5" className="fill-brand" />
48
+
49
+ {/* empty socket, same shape, waiting below */}
50
+ <rect
51
+ x="74"
52
+ y="70"
53
+ width="12"
54
+ height="12"
55
+ rx="2"
56
+ className="stroke-border"
57
+ strokeDasharray="2 4"
58
+ />
59
+ <rect
60
+ x="62"
61
+ y="80"
62
+ width="36"
63
+ height="16"
64
+ rx="4"
65
+ className="stroke-border"
66
+ strokeDasharray="3 4"
67
+ />
68
+ </svg>
69
+ );
70
+ }
71
+
72
+ export { NoPluginsIllustration };
@@ -0,0 +1,69 @@
1
+ import type { IllustrationProps } from '@vendure-io/ui/components/molecules/illustrations/illustration-types';
2
+ import { cn } from '@vendure-io/ui/lib/utils';
3
+
4
+ /**
5
+ * A magnifying glass turning up nothing: dashed ghost rows behind the lens
6
+ * instead of results. For a search/filter that matched zero rows — pair with
7
+ * `EmptyState`. Not for "nothing exists yet" (use `EmptyCollectionIllustration`).
8
+ */
9
+ function NoResultsIllustration({ className, size = 160 }: IllustrationProps) {
10
+ return (
11
+ <svg
12
+ viewBox="0 0 160 120"
13
+ width={size}
14
+ height={(size * 120) / 160}
15
+ fill="none"
16
+ strokeWidth={1.5}
17
+ strokeLinecap="round"
18
+ strokeLinejoin="round"
19
+ aria-hidden="true"
20
+ focusable="false"
21
+ className={cn('shrink-0', className)}
22
+ >
23
+ <ellipse cx="80" cy="104" rx="32" ry="5" className="fill-muted" />
24
+
25
+ {/* ghost result rows, unmatched */}
26
+ <rect
27
+ x="30"
28
+ y="40"
29
+ width="56"
30
+ height="10"
31
+ rx="5"
32
+ className="stroke-border"
33
+ strokeDasharray="3 4"
34
+ />
35
+ <rect
36
+ x="30"
37
+ y="56"
38
+ width="44"
39
+ height="10"
40
+ rx="5"
41
+ className="stroke-border"
42
+ strokeDasharray="3 4"
43
+ />
44
+ <rect
45
+ x="30"
46
+ y="72"
47
+ width="50"
48
+ height="10"
49
+ rx="5"
50
+ className="stroke-border"
51
+ strokeDasharray="3 4"
52
+ />
53
+
54
+ {/* lens */}
55
+ <circle cx="100" cy="48" r="22" className="fill-surface stroke-muted-foreground" />
56
+ <line x1="116" y1="64" x2="134" y2="82" className="stroke-muted-foreground" />
57
+ <line x1="93" y1="41" x2="107" y2="55" className="stroke-border" />
58
+ <line x1="107" y1="41" x2="93" y2="55" className="stroke-border" />
59
+
60
+ {/* sparkle — the one brand accent */}
61
+ <polygon
62
+ points="128,20 129.5,24.5 134,26 129.5,27.5 128,32 126.5,27.5 122,26 126.5,24.5"
63
+ className="fill-brand"
64
+ />
65
+ </svg>
66
+ );
67
+ }
68
+
69
+ export { NoResultsIllustration };
@@ -0,0 +1,48 @@
1
+ import type { IllustrationProps } from '@vendure-io/ui/components/molecules/illustrations/illustration-types';
2
+ import { cn } from '@vendure-io/ui/lib/utils';
3
+
4
+ /**
5
+ * A compass whose needle points off a dashed trail that dead-ends in an "x".
6
+ * For 404s and missing-resource errors — pass as `illustration` to
7
+ * `ErrorState` alongside a "Go back" action.
8
+ */
9
+ function NotFoundIllustration({ className, size = 160 }: IllustrationProps) {
10
+ return (
11
+ <svg
12
+ viewBox="0 0 160 120"
13
+ width={size}
14
+ height={(size * 120) / 160}
15
+ fill="none"
16
+ strokeWidth={1.5}
17
+ strokeLinecap="round"
18
+ strokeLinejoin="round"
19
+ aria-hidden="true"
20
+ focusable="false"
21
+ className={cn('shrink-0', className)}
22
+ >
23
+ <ellipse cx="80" cy="104" rx="30" ry="5" className="fill-muted" />
24
+
25
+ {/* map, folded corner */}
26
+ <rect x="30" y="30" width="60" height="44" rx="2" className="fill-surface stroke-border" />
27
+ <polygon points="90,30 90,42 78,30" className="fill-muted stroke-border" />
28
+
29
+ {/* trail off the edge, lost */}
30
+ <path d="M96,70 Q120,80 148,64" className="stroke-border" strokeDasharray="3 4" />
31
+ <line x1="144" y1="60" x2="152" y2="68" className="stroke-muted-foreground" />
32
+ <line x1="152" y1="60" x2="144" y2="68" className="stroke-muted-foreground" />
33
+
34
+ {/* compass */}
35
+ <circle cx="76" cy="58" r="20" className="fill-surface stroke-muted-foreground" />
36
+ <line x1="76" y1="40" x2="76" y2="44" className="stroke-border" />
37
+ <line x1="76" y1="72" x2="76" y2="76" className="stroke-border" />
38
+ <line x1="58" y1="58" x2="62" y2="58" className="stroke-border" />
39
+ <line x1="90" y1="58" x2="94" y2="58" className="stroke-border" />
40
+ <polygon points="76,58 82,44 76,50" className="fill-muted-foreground" />
41
+ {/* needle tip — the one brand accent */}
42
+ <polygon points="76,58 70,72 76,66" className="fill-brand" />
43
+ <circle cx="76" cy="58" r="2" className="fill-surface stroke-muted-foreground" />
44
+ </svg>
45
+ );
46
+ }
47
+
48
+ export { NotFoundIllustration };
@@ -0,0 +1,43 @@
1
+ import type { IllustrationProps } from '@vendure-io/ui/components/molecules/illustrations/illustration-types';
2
+ import { cn } from '@vendure-io/ui/lib/utils';
3
+
4
+ /**
5
+ * A cloud cut by a diagonal slash, with fading signal arcs above it. For
6
+ * network/connectivity failures — pass as `illustration` to `ErrorState`.
7
+ */
8
+ function OfflineIllustration({ className, size = 160 }: IllustrationProps) {
9
+ return (
10
+ <svg
11
+ viewBox="0 0 160 120"
12
+ width={size}
13
+ height={(size * 120) / 160}
14
+ fill="none"
15
+ strokeWidth={1.5}
16
+ strokeLinecap="round"
17
+ strokeLinejoin="round"
18
+ aria-hidden="true"
19
+ focusable="false"
20
+ className={cn('shrink-0', className)}
21
+ >
22
+ <ellipse cx="80" cy="104" rx="28" ry="5" className="fill-muted" />
23
+
24
+ {/* fading signal, cut off */}
25
+ <path d="M70,20 a14,14 0 0,1 20,0" className="stroke-border" strokeDasharray="2 4" />
26
+ <path d="M65,12 a24,24 0 0,1 30,0" className="stroke-border" strokeDasharray="2 5" />
27
+
28
+ {/* cloud */}
29
+ <path
30
+ d="M46,66 C40,66 36,61 36,55 C36,49 41,44 47,44 C48,36 55,30 64,30 C72,30 79,35 81,42 C82,42 83,41 85,41 C93,41 100,48 100,56 C100,64 93,71 85,71 L52,71 C48,71 46,69 46,66 Z"
31
+ className="fill-surface stroke-muted-foreground"
32
+ />
33
+
34
+ {/* slash */}
35
+ <line x1="32" y1="32" x2="104" y2="74" className="stroke-muted-foreground" />
36
+
37
+ {/* reconnect dot — the one brand accent */}
38
+ <circle cx="104" cy="74" r="4" className="fill-brand" />
39
+ </svg>
40
+ );
41
+ }
42
+
43
+ export { OfflineIllustration };
@@ -0,0 +1,56 @@
1
+ import type { IllustrationProps } from '@vendure-io/ui/components/molecules/illustrations/illustration-types';
2
+ import { cn } from '@vendure-io/ui/lib/utils';
3
+
4
+ /**
5
+ * An hourglass mid-turn, sand piled solid at top and settling as a dashed
6
+ * pile below. For an invitation pending, an account awaiting setup or
7
+ * provisioning, or access awaiting approval. Not for a hard permission
8
+ * denial (use `AccessDeniedIllustration`) or a loading spinner state.
9
+ */
10
+ function PendingApprovalIllustration({ className, size = 160 }: IllustrationProps) {
11
+ return (
12
+ <svg
13
+ viewBox="0 0 160 120"
14
+ width={size}
15
+ height={(size * 120) / 160}
16
+ fill="none"
17
+ strokeWidth={1.5}
18
+ strokeLinecap="round"
19
+ strokeLinejoin="round"
20
+ aria-hidden="true"
21
+ focusable="false"
22
+ className={cn('shrink-0', className)}
23
+ >
24
+ <ellipse cx="80" cy="104" rx="28" ry="5" className="fill-muted" />
25
+
26
+ {/* frame */}
27
+ <line x1="62" y1="26" x2="98" y2="26" className="stroke-muted-foreground" />
28
+ <line x1="62" y1="90" x2="98" y2="90" className="stroke-muted-foreground" />
29
+
30
+ {/* glass body, pinched at the waist */}
31
+ <path
32
+ d="M66,26 L66,42 Q66,58 80,58 Q94,58 94,42 L94,26 Z"
33
+ className="fill-surface stroke-muted-foreground"
34
+ />
35
+ <path
36
+ d="M66,90 L66,74 Q66,58 80,58 Q94,58 94,74 L94,90 Z"
37
+ className="fill-surface stroke-muted-foreground"
38
+ />
39
+
40
+ {/* settled sand, still piling — awaiting completion */}
41
+ <path
42
+ d="M71,84 Q80,72 89,84 Q80,88 71,84 Z"
43
+ className="fill-muted stroke-muted-foreground"
44
+ strokeDasharray="2 5"
45
+ />
46
+
47
+ {/* remaining sand up top, already solid */}
48
+ <path d="M70,30 L90,30 L82,44 L78,44 Z" className="fill-muted" />
49
+
50
+ {/* falling stream, the one brand accent */}
51
+ <circle cx="80" cy="61" r="2" className="fill-brand" />
52
+ </svg>
53
+ );
54
+ }
55
+
56
+ export { PendingApprovalIllustration };
@@ -0,0 +1,59 @@
1
+ import type { IllustrationProps } from '@vendure-io/ui/components/molecules/illustrations/illustration-types';
2
+ import { cn } from '@vendure-io/ui/lib/utils';
3
+
4
+ /**
5
+ * A dashed-outline tray with a file card rising into it on an upward arrow.
6
+ * For a drag-and-drop upload target with nothing dropped yet ("drop files
7
+ * here to upload") — pass as `illustration` to `EmptyState`. Not for an
8
+ * already-existing media library that just happens to be empty (use
9
+ * `EmptyMediaIllustration`).
10
+ */
11
+ function UploadDropzoneIllustration({ className, size = 160 }: IllustrationProps) {
12
+ return (
13
+ <svg
14
+ viewBox="0 0 160 120"
15
+ width={size}
16
+ height={(size * 120) / 160}
17
+ fill="none"
18
+ strokeWidth={1.5}
19
+ strokeLinecap="round"
20
+ strokeLinejoin="round"
21
+ aria-hidden="true"
22
+ focusable="false"
23
+ className={cn('shrink-0', className)}
24
+ >
25
+ <ellipse cx="80" cy="104" rx="28" ry="5" className="fill-muted" />
26
+
27
+ {/* tray — the drop target, empty */}
28
+ <rect
29
+ x="32"
30
+ y="14"
31
+ width="96"
32
+ height="46"
33
+ rx="10"
34
+ className="stroke-border"
35
+ strokeDasharray="3 4"
36
+ />
37
+
38
+ {/* file card, folded corner, about to be dropped */}
39
+ <rect
40
+ x="66"
41
+ y="72"
42
+ width="28"
43
+ height="20"
44
+ rx="2"
45
+ className="fill-surface stroke-muted-foreground"
46
+ />
47
+ <polygon points="94,72 94,78 88,72" className="fill-muted stroke-border" />
48
+ <line x1="71" y1="83" x2="85" y2="83" className="stroke-border" />
49
+ <line x1="71" y1="88" x2="79" y2="88" className="stroke-border" />
50
+
51
+ {/* rising into the tray */}
52
+ <line x1="80" y1="70" x2="80" y2="40" className="stroke-muted-foreground" />
53
+ {/* arrowhead — the one brand accent */}
54
+ <polygon points="80,28 70,44 90,44" className="fill-brand" />
55
+ </svg>
56
+ );
57
+ }
58
+
59
+ export { UploadDropzoneIllustration };
@@ -0,0 +1,91 @@
1
+ 'use client';
2
+
3
+ import { useFormatSettings } from '@vendure-io/ui/components/molecules/format-provider';
4
+ import { cn } from '@vendure-io/ui/lib/utils';
5
+ import type { ComponentProps } from 'react';
6
+
7
+ // How many minor-unit digits a currency uses — 2 for USD (cents), 0 for JPY, 3
8
+ // for BHD. Derived from Intl rather than a hardcoded `/100`, which is wrong for
9
+ // zero- and three-decimal currencies (the enterprise-portal `Price` bug this
10
+ // fixes). Falls back to 2 for an unrecognised code so a typo can't throw.
11
+ function currencyFractionDigits(currency?: string): number {
12
+ if (!currency) return 2;
13
+ try {
14
+ return (
15
+ new Intl.NumberFormat(undefined, { style: 'currency', currency }).resolvedOptions()
16
+ .maximumFractionDigits ?? 2
17
+ );
18
+ } catch {
19
+ return 2;
20
+ }
21
+ }
22
+
23
+ // Dev-warn once per session, not once per render — a table of currency-less
24
+ // cells would otherwise flood the console.
25
+ let warnedMissingCurrency = false;
26
+
27
+ interface MoneyProps extends ComponentProps<'span'> {
28
+ /** Amount in integer minor units (Vendure convention), e.g. `2500` = $25.00. */
29
+ value: number;
30
+ /** ISO 4217 code. Falls back to `FormatProvider`'s `currency`. */
31
+ currency?: string;
32
+ /** BCP 47 locale. Falls back to `FormatProvider`, then the runtime default. */
33
+ locale?: string;
34
+ /**
35
+ * Minor-unit digits, used to scale minor→major and to set the displayed
36
+ * fraction digits. Resolution: prop → context `currencyPrecision` → the
37
+ * currency's own Intl fraction digits.
38
+ */
39
+ precision?: number;
40
+ currencyDisplay?: Intl.NumberFormatOptions['currencyDisplay'];
41
+ }
42
+
43
+ /**
44
+ * SSR note: output depends on locale, so server and client renders can disagree
45
+ * when it falls back to the runtime default. SSR consumers should pin `locale`
46
+ * via `FormatProvider` (or the prop) to avoid hydration mismatches.
47
+ */
48
+ function Money({
49
+ value,
50
+ currency,
51
+ locale,
52
+ precision,
53
+ currencyDisplay,
54
+ className,
55
+ ...props
56
+ }: MoneyProps) {
57
+ const settings = useFormatSettings();
58
+ const resolvedCurrency = currency ?? settings.currency;
59
+ const resolvedLocale = locale ?? settings.locale;
60
+ const resolvedPrecision =
61
+ precision ?? settings.currencyPrecision ?? currencyFractionDigits(resolvedCurrency);
62
+
63
+ if (!resolvedCurrency && !warnedMissingCurrency && process.env.NODE_ENV !== 'production') {
64
+ warnedMissingCurrency = true;
65
+ console.warn(
66
+ '<Money> was rendered without a `currency` prop or a `FormatProvider` currency; ' +
67
+ 'the amount is shown as a plain number.',
68
+ );
69
+ }
70
+
71
+ const major = value / 10 ** resolvedPrecision;
72
+ const options: Intl.NumberFormatOptions = {
73
+ minimumFractionDigits: resolvedPrecision,
74
+ maximumFractionDigits: resolvedPrecision,
75
+ };
76
+ if (resolvedCurrency) {
77
+ options.style = 'currency';
78
+ options.currency = resolvedCurrency;
79
+ if (currencyDisplay) options.currencyDisplay = currencyDisplay;
80
+ }
81
+
82
+ const formatted = new Intl.NumberFormat(resolvedLocale, options).format(major);
83
+
84
+ return (
85
+ <span data-slot="money" className={cn('tabular-nums', className)} {...props}>
86
+ {formatted}
87
+ </span>
88
+ );
89
+ }
90
+
91
+ export { Money, type MoneyProps };
@@ -7,7 +7,7 @@ import {
7
7
  SelectItem,
8
8
  SelectTrigger,
9
9
  SelectValue,
10
- } from '@vendure-io/ui/components/ui/select';
10
+ } from '@vendure-io/ui/components/atoms/select';
11
11
  import { cn } from '@vendure-io/ui/lib/utils';
12
12
  import * as React from 'react';
13
13
 
@@ -133,7 +133,7 @@ function MultiSelect<T>({
133
133
  name={name}
134
134
  required={required}
135
135
  >
136
- {/* [vendure] default to a stable, container-width trigger so it doesn't grow with the
136
+ {/* Default to a stable, container-width trigger so it doesn't grow with the
137
137
  selection — the underlying SelectTrigger is `w-fit`, which we override here. */}
138
138
  <SelectTrigger
139
139
  id={id}
@@ -0,0 +1,80 @@
1
+ import { cn } from '@vendure-io/ui/lib/utils';
2
+ import type * as React from 'react';
3
+
4
+ // Compound-only, by decision: there is no simple-props sugar and
5
+ // no dual-mode root. Callers assemble the parts, which keeps one way to build a
6
+ // header and lets variance in the wild (back links, badge rows beside the title,
7
+ // mono subtitles) live as free children rather than as an ever-growing prop set.
8
+
9
+ /** The header row: title/description on the left, actions on the right. */
10
+ function PageHeader({ className, ...props }: React.ComponentProps<'header'>) {
11
+ return (
12
+ <header
13
+ data-slot="page-header"
14
+ className={cn('flex items-start justify-between gap-4', className)}
15
+ {...props}
16
+ />
17
+ );
18
+ }
19
+
20
+ /** Left column holding the title, description, and any inline badges. */
21
+ function PageHeaderContent({ className, ...props }: React.ComponentProps<'div'>) {
22
+ return (
23
+ <div
24
+ data-slot="page-header-content"
25
+ className={cn('flex min-w-0 flex-col gap-1', className)}
26
+ {...props}
27
+ />
28
+ );
29
+ }
30
+
31
+ function PageHeaderTitle({ className, ...props }: React.ComponentProps<'h1'>) {
32
+ return (
33
+ <h1
34
+ data-slot="page-header-title"
35
+ className={cn('text-style-page-title break-words', className)}
36
+ {...props}
37
+ />
38
+ );
39
+ }
40
+
41
+ function PageHeaderDescription({ className, ...props }: React.ComponentProps<'p'>) {
42
+ return (
43
+ <p
44
+ data-slot="page-header-description"
45
+ className={cn('text-sm text-muted-foreground', className)}
46
+ {...props}
47
+ />
48
+ );
49
+ }
50
+
51
+ /** Right-hand action slot (buttons, menus). Stays put as the title wraps. */
52
+ function PageHeaderActions({ className, ...props }: React.ComponentProps<'div'>) {
53
+ return (
54
+ <div
55
+ data-slot="page-header-actions"
56
+ className={cn('flex shrink-0 items-center gap-2', className)}
57
+ {...props}
58
+ />
59
+ );
60
+ }
61
+
62
+ /** Optional row rendered above the header — typically a back link. */
63
+ function PageHeaderBackLink({ className, ...props }: React.ComponentProps<'div'>) {
64
+ return (
65
+ <div
66
+ data-slot="page-header-back-link"
67
+ className={cn('flex items-center gap-1 text-sm text-muted-foreground', className)}
68
+ {...props}
69
+ />
70
+ );
71
+ }
72
+
73
+ export {
74
+ PageHeader,
75
+ PageHeaderContent,
76
+ PageHeaderTitle,
77
+ PageHeaderDescription,
78
+ PageHeaderActions,
79
+ PageHeaderBackLink,
80
+ };