@vendure-io/ui 1.3.0 → 2.0.0-beta.1

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 +10 -5
  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/{custom → molecules}/combobox-free-text.tsx +7 -3
  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 +1 -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
@@ -41,7 +41,7 @@ function PopoverContent({
41
41
  <PopoverPrimitive.Popup
42
42
  data-slot="popover-content"
43
43
  className={cn(
44
- "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 flex w-72 origin-(--transform-origin) flex-col gap-4 rounded-md p-4 text-sm shadow-md ring-1 outline-hidden duration-100",
44
+ "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 flex w-72 origin-(--transform-origin) flex-col gap-4 rounded-md p-4 text-sm shadow-md ring-1 outline-hidden motion-reduce:animate-none data-open:duration-(--transition-duration-normal) data-open:ease-(--ease-out) data-closed:duration-(--transition-duration-fast) data-closed:ease-(--ease-in)",
45
45
  className
46
46
  )}
47
47
  {...props}
@@ -65,7 +65,7 @@ function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {
65
65
  return (
66
66
  <PopoverPrimitive.Title
67
67
  data-slot="popover-title"
68
- className={cn("font-medium", className)}
68
+ className={cn("text-style-card-title", className)}
69
69
  {...props}
70
70
  />
71
71
  )
@@ -28,7 +28,6 @@ function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
28
28
  )
29
29
  }
30
30
 
31
- // [vendure] shadow removed for flat look — restore if overwritten by shadcn upgrade
32
31
  function SelectTrigger({
33
32
  className,
34
33
  size = "default",
@@ -57,7 +56,7 @@ function SelectTrigger({
57
56
  )
58
57
  }
59
58
 
60
- // [vendure] changed w-(--anchor-width) to min-w-(--anchor-width) so dropdown expands to fit content
59
+ // min-w-(--anchor-width) rather than w-(--anchor-width) so the popup can expand beyond the trigger to fit content
61
60
  function SelectContent({
62
61
  className,
63
62
  children,
@@ -72,8 +71,27 @@ function SelectContent({
72
71
  SelectPrimitive.Positioner.Props,
73
72
  "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
74
73
  >) {
74
+ // Force the popup to remount when the set of items changes. Base UI's Select
75
+ // keeps an internal item registry that doesn't refresh when items change
76
+ // while the controlled value stays the same (e.g. a value="" fire-and-forget
77
+ // pattern used for state transitions), leaving stale items selectable.
78
+ // Re-keying on the children's values remounts the popup so items re-register.
79
+ // Only inspects top-level children — items nested in a SelectGroup or
80
+ // fragment won't contribute to the key.
81
+ const childrenKey = React.useMemo(
82
+ () =>
83
+ React.Children.toArray(children)
84
+ .map((child) =>
85
+ React.isValidElement(child)
86
+ ? String((child.props as { value?: unknown }).value ?? child.key)
87
+ : ""
88
+ )
89
+ .join("|"),
90
+ [children]
91
+ )
92
+
75
93
  return (
76
- <SelectPrimitive.Portal>
94
+ <SelectPrimitive.Portal key={childrenKey}>
77
95
  <SelectPrimitive.Positioner
78
96
  side={side}
79
97
  sideOffset={sideOffset}
@@ -85,7 +103,7 @@ function SelectContent({
85
103
  <SelectPrimitive.Popup
86
104
  data-slot="select-content"
87
105
  data-align-trigger={alignItemWithTrigger}
88
- className={cn("bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 relative isolate z-50 max-h-(--available-height) min-w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md shadow-md ring-1 duration-100 data-[align-trigger=true]:animate-none", className )}
106
+ className={cn("bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 relative isolate z-50 max-h-(--available-height) min-w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md shadow-md ring-1 motion-reduce:animate-none data-open:duration-(--transition-duration-normal) data-open:ease-(--ease-out) data-closed:duration-(--transition-duration-fast) data-closed:ease-(--ease-in) data-[align-trigger=true]:animate-none", className )}
89
107
  {...props}
90
108
  >
91
109
  <SelectScrollUpButton />
@@ -4,7 +4,7 @@ import * as React from "react"
4
4
  import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"
5
5
 
6
6
  import { cn } from "@vendure-io/ui/lib/utils"
7
- import { Button } from "@vendure-io/ui/components/ui/button"
7
+ import { Button } from "@vendure-io/ui/components/atoms/button"
8
8
  import { XIcon } from "lucide-react"
9
9
 
10
10
  function Sheet({ ...props }: SheetPrimitive.Root.Props) {
@@ -28,7 +28,7 @@ function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
28
28
  <SheetPrimitive.Backdrop
29
29
  data-slot="sheet-overlay"
30
30
  className={cn(
31
- "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/10 duration-100 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs",
31
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/10 motion-reduce:animate-none data-open:duration-(--transition-duration-normal) data-open:ease-(--ease-out) data-closed:duration-(--transition-duration-fast) data-closed:ease-(--ease-in) data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs",
32
32
  className
33
33
  )}
34
34
  {...props}
@@ -53,7 +53,7 @@ function SheetContent({
53
53
  data-slot="sheet-content"
54
54
  data-side={side}
55
55
  className={cn(
56
- "bg-background data-open:animate-in data-closed:animate-out data-[side=right]:data-closed:slide-out-to-right-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:fade-out-0 data-open:fade-in-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=bottom]:data-open:slide-in-from-bottom-10 fixed z-50 flex flex-col gap-4 bg-clip-padding text-sm shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm",
56
+ "bg-overlay data-open:animate-in data-closed:animate-out data-[side=right]:data-closed:slide-out-to-right-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:fade-out-0 data-open:fade-in-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=bottom]:data-open:slide-in-from-bottom-10 fixed z-50 flex flex-col gap-4 bg-clip-padding text-sm shadow-lg transition motion-reduce:animate-none motion-reduce:transition-none data-open:duration-(--transition-duration-normal) data-open:ease-(--ease-out) data-closed:duration-(--transition-duration-fast) data-closed:ease-(--ease-in) data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-lg data-[side=right]:sm:max-w-lg",
57
57
  className
58
58
  )}
59
59
  {...props}
@@ -104,7 +104,7 @@ function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
104
104
  return (
105
105
  <SheetPrimitive.Title
106
106
  data-slot="sheet-title"
107
- className={cn("text-foreground font-medium", className)}
107
+ className={cn("text-foreground text-style-section-title", className)}
108
108
  {...props}
109
109
  />
110
110
  )
@@ -7,22 +7,22 @@ import { cva, type VariantProps } from "class-variance-authority"
7
7
 
8
8
  import { useIsMobile } from "@vendure-io/ui/hooks/use-mobile"
9
9
  import { cn } from "@vendure-io/ui/lib/utils"
10
- import { Button } from "@vendure-io/ui/components/ui/button"
11
- import { Input } from "@vendure-io/ui/components/ui/input"
12
- import { Separator } from "@vendure-io/ui/components/ui/separator"
10
+ import { Button } from "@vendure-io/ui/components/atoms/button"
11
+ import { Input } from "@vendure-io/ui/components/atoms/input"
12
+ import { Separator } from "@vendure-io/ui/components/atoms/separator"
13
13
  import {
14
14
  Sheet,
15
15
  SheetContent,
16
16
  SheetDescription,
17
17
  SheetHeader,
18
18
  SheetTitle,
19
- } from "@vendure-io/ui/components/ui/sheet"
20
- import { Skeleton } from "@vendure-io/ui/components/ui/skeleton"
19
+ } from "@vendure-io/ui/components/atoms/sheet"
20
+ import { Skeleton } from "@vendure-io/ui/components/atoms/skeleton"
21
21
  import {
22
22
  Tooltip,
23
23
  TooltipContent,
24
24
  TooltipTrigger,
25
- } from "@vendure-io/ui/components/ui/tooltip"
25
+ } from "@vendure-io/ui/components/atoms/tooltip"
26
26
  import { PanelLeftIcon } from "lucide-react"
27
27
 
28
28
  const SIDEBAR_COOKIE_NAME = "sidebar_state"
@@ -0,0 +1,16 @@
1
+ import { cn } from "@vendure-io/ui/lib/utils"
2
+
3
+ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
4
+ return (
5
+ <div
6
+ data-slot="skeleton"
7
+ className={cn(
8
+ "animate-shimmer motion-reduce:animate-none motion-reduce:bg-none motion-reduce:bg-muted rounded-md bg-[length:200%_100%] bg-[linear-gradient(90deg,var(--color-muted)_25%,var(--color-accent)_50%,var(--color-muted)_75%)]",
9
+ className
10
+ )}
11
+ {...props}
12
+ />
13
+ )
14
+ }
15
+
16
+ export { Skeleton }
@@ -44,7 +44,6 @@ function Slider({
44
44
  className="bg-primary select-none data-horizontal:h-full data-vertical:w-full"
45
45
  />
46
46
  </SliderPrimitive.Track>
47
- {/* [vendure] shadow removed for flat look — restore if overwritten by shadcn upgrade */}
48
47
  {Array.from({ length: _values.length }, (_, index) => (
49
48
  <SliderPrimitive.Thumb
50
49
  data-slot="slider-thumb"
@@ -40,7 +40,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
40
40
  toastOptions={{
41
41
  classNames: {
42
42
  toast: "cn-toast",
43
- // [vendure] Override Sonner's hardcoded #3f3f3f description colour
43
+ // Override Sonner's hardcoded #3f3f3f description colour
44
44
  // with our semantic muted-foreground token for consistent contrast.
45
45
  // See: https://github.com/vendurehq/design/issues/5
46
46
  description: "!text-muted-foreground",
@@ -4,7 +4,6 @@ import { Switch as SwitchPrimitive } from "@base-ui/react/switch"
4
4
 
5
5
  import { cn } from "@vendure-io/ui/lib/utils"
6
6
 
7
- // [vendure] shadow removed for flat look — restore if overwritten by shadcn upgrade
8
7
  function Switch({
9
8
  className,
10
9
  size = "default",
@@ -28,7 +28,7 @@ const tabsListVariants = cva(
28
28
  {
29
29
  variants: {
30
30
  variant: {
31
- default: "bg-muted",
31
+ default: "bg-inset",
32
32
  line: "gap-1 bg-transparent",
33
33
  },
34
34
  },
@@ -53,15 +53,17 @@ function TabsList({
53
53
  )
54
54
  }
55
55
 
56
- // [vendure] shadow removed for flat look — restore if overwritten by shadcn upgrade
57
56
  function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
58
57
  return (
59
58
  <TabsPrimitive.Tab
60
59
  data-slot="tabs-trigger"
61
60
  className={cn(
62
61
  "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
63
- "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
64
- "data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground",
62
+ "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent",
63
+ // ADR 0001: the active trigger lifts to surface-raised out of the inset
64
+ // track in both themes. Dark keeps a hairline — lightness alone can't
65
+ // separate a 0.21 thumb near the black end of the ramp.
66
+ "data-active:bg-surface-raised dark:data-active:border-input dark:data-active:text-foreground data-active:text-foreground",
65
67
  "after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
66
68
  className
67
69
  )}
@@ -2,7 +2,6 @@ import * as React from "react"
2
2
 
3
3
  import { cn } from "@vendure-io/ui/lib/utils"
4
4
 
5
- // [vendure] shadow removed for flat look — restore if overwritten by shadcn upgrade
6
5
  function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
7
6
  return (
8
7
  <textarea
@@ -6,7 +6,7 @@ import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group
6
6
  import { type VariantProps } from "class-variance-authority"
7
7
 
8
8
  import { cn } from "@vendure-io/ui/lib/utils"
9
- import { toggleVariants } from "@vendure-io/ui/components/ui/toggle"
9
+ import { toggleVariants } from "@vendure-io/ui/components/atoms/toggle"
10
10
 
11
11
  const ToggleGroupContext = React.createContext<
12
12
  VariantProps<typeof toggleVariants> & {
@@ -20,7 +20,6 @@ const ToggleGroupContext = React.createContext<
20
20
  orientation: "horizontal",
21
21
  })
22
22
 
23
- // [vendure] shadow removed for flat look — restore if overwritten by shadcn upgrade
24
23
  function ToggleGroup({
25
24
  className,
26
25
  variant,
@@ -73,7 +72,8 @@ function ToggleGroupItem({
73
72
  data-size={context.size || size}
74
73
  data-spacing={context.spacing}
75
74
  className={cn(
76
- "data-[state=on]:bg-muted shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 group-data-[spacing=0]/toggle-group:shadow-none focus:z-10 focus-visible:z-10 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-md group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-md group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-md group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-md group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
75
+ // Base UI's Toggle emits data-pressed, never Radix's data-[state=on] the pressed style comes from toggleVariants via its aria-pressed rule
76
+ "shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 group-data-[spacing=0]/toggle-group:shadow-none focus:z-10 focus-visible:z-10 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-md group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-md group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-md group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-md group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
77
77
  toggleVariants({
78
78
  variant: context.variant || variant,
79
79
  size: context.size || size,
@@ -5,7 +5,6 @@ import { cva, type VariantProps } from "class-variance-authority"
5
5
 
6
6
  import { cn } from "@vendure-io/ui/lib/utils"
7
7
 
8
- // [vendure] shadow removed for flat look — restore if overwritten by shadcn upgrade
9
8
  const toggleVariants = cva(
10
9
  "hover:text-foreground aria-pressed:bg-muted focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive gap-1 rounded-md text-sm font-medium transition-[color,box-shadow] [&_svg:not([class*='size-'])]:size-4 group/toggle hover:bg-muted inline-flex items-center justify-center whitespace-nowrap outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
11
10
  {
@@ -47,10 +47,11 @@ function TooltipContent({
47
47
  sideOffset={sideOffset}
48
48
  className="isolate z-50"
49
49
  >
50
+ {/* Base UI never emits Radix-style data-state attributes — data-[state=delayed-open]:* selectors would be dead here; data-open:* covers the open animation */}
50
51
  <TooltipPrimitive.Popup
51
52
  data-slot="tooltip-content"
52
53
  className={cn(
53
- "data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 bg-foreground text-background z-50 w-fit max-w-xs origin-(--transform-origin) rounded-md px-3 py-1.5 text-xs",
54
+ "data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 bg-foreground text-background z-50 w-fit max-w-xs origin-(--transform-origin) rounded-md px-3 py-1.5 text-xs motion-reduce:animate-none data-open:duration-(--transition-duration-normal) data-open:ease-(--ease-out) data-closed:duration-(--transition-duration-fast) data-closed:ease-(--ease-in)",
54
55
  className
55
56
  )}
56
57
  {...props}
@@ -0,0 +1,170 @@
1
+ 'use client';
2
+
3
+ import {
4
+ CopyButton,
5
+ type CopyButtonProps,
6
+ } from '@vendure-io/ui/components/molecules/copyable-text';
7
+ import { chipChromeClass } from '@vendure-io/ui/components/molecules/id-chip';
8
+ import { cn } from '@vendure-io/ui/lib/utils';
9
+ import type * as React from 'react';
10
+ import { useState } from 'react';
11
+
12
+ function clampLength(value: number): number {
13
+ return Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
14
+ }
15
+
16
+ function getTokenPreview(
17
+ value: string,
18
+ prefixLength: number,
19
+ suffixLength: number,
20
+ separator: string,
21
+ ): string {
22
+ const prefix = clampLength(prefixLength);
23
+ const suffix = clampLength(suffixLength);
24
+
25
+ if (prefix === 0 && suffix === 0) return separator;
26
+
27
+ if (value.length <= prefix + suffix) {
28
+ const visibleLength = Math.max(1, Math.min(prefix || suffix, Math.floor(value.length / 2)));
29
+ return `${value.slice(0, visibleLength)}${separator}`;
30
+ }
31
+
32
+ return `${value.slice(0, prefix)}${separator}${suffix > 0 ? value.slice(-suffix) : ''}`;
33
+ }
34
+
35
+ interface AnonymizedTokenProps extends Omit<React.ComponentProps<'span'>, 'children'> {
36
+ /** Full token copied by the button. Never rendered in full by the component. */
37
+ value: string | null | undefined;
38
+ /** Remove the blur from the preview on hover/focus. @default true */
39
+ revealOnHover?: boolean;
40
+ /** Characters shown at the start of the preview. @default 8 */
41
+ previewPrefixLength?: number;
42
+ /** Characters shown at the end of the preview. @default 4 */
43
+ previewSuffixLength?: number;
44
+ /** Text placed between visible preview segments. @default "..." */
45
+ previewSeparator?: string;
46
+ /** Accessible name while the preview is blurred. @default "Partially obscured token" */
47
+ obscuredLabel?: string;
48
+ /** Accessible name while the preview is sharp. @default "Partially visible token" */
49
+ previewLabel?: string;
50
+ /** Rendered for nullish or empty values. @default "-" */
51
+ fallback?: React.ReactNode;
52
+ /** Render the copy affordance. @default true */
53
+ copyable?: boolean;
54
+ /** How long the check-mark feedback stays visible, in ms. */
55
+ timeout?: CopyButtonProps['timeout'];
56
+ /** Called after a successful copy. Wire your toast here. */
57
+ onCopied?: CopyButtonProps['onCopied'];
58
+ /** Called when the clipboard write fails. */
59
+ onCopyError?: CopyButtonProps['onCopyError'];
60
+ /** Accessible label before copying. @default "Copy token" */
61
+ copyLabel?: CopyButtonProps['copyLabel'];
62
+ /** Accessible label while the copied state is active. @default "Token copied" */
63
+ copiedLabel?: CopyButtonProps['copiedLabel'];
64
+ }
65
+
66
+ /**
67
+ * Displays a blurred, truncated token preview while copying the original value
68
+ * in full.
69
+ *
70
+ * This is presentation, not a security boundary: the full token still reaches
71
+ * the client so the explicit copy action can write it to the clipboard.
72
+ */
73
+ function AnonymizedToken({
74
+ value,
75
+ revealOnHover = true,
76
+ previewPrefixLength = 8,
77
+ previewSuffixLength = 4,
78
+ previewSeparator = '...',
79
+ obscuredLabel = 'Partially obscured token',
80
+ previewLabel = 'Partially visible token',
81
+ fallback = '-',
82
+ copyable = true,
83
+ timeout,
84
+ onCopied,
85
+ onCopyError,
86
+ copyLabel = 'Copy token',
87
+ copiedLabel = 'Token copied',
88
+ className,
89
+ onMouseEnter,
90
+ onMouseLeave,
91
+ onFocus,
92
+ onBlur,
93
+ ...props
94
+ }: AnonymizedTokenProps) {
95
+ const [previewRevealed, setPreviewRevealed] = useState(false);
96
+
97
+ if (!value) {
98
+ return (
99
+ <span
100
+ data-slot="anonymized-token"
101
+ className={cn('text-muted-foreground text-sm', className)}
102
+ {...props}
103
+ >
104
+ {fallback}
105
+ </span>
106
+ );
107
+ }
108
+
109
+ const preview = getTokenPreview(
110
+ value,
111
+ previewPrefixLength,
112
+ previewSuffixLength,
113
+ previewSeparator,
114
+ );
115
+ const showRevealed = revealOnHover && previewRevealed;
116
+
117
+ return (
118
+ <span
119
+ data-slot="anonymized-token"
120
+ role="group"
121
+ className={chipChromeClass(copyable, className)}
122
+ onMouseEnter={(event) => {
123
+ onMouseEnter?.(event);
124
+ if (revealOnHover) setPreviewRevealed(true);
125
+ }}
126
+ onMouseLeave={(event) => {
127
+ onMouseLeave?.(event);
128
+ if (revealOnHover) setPreviewRevealed(false);
129
+ }}
130
+ onFocus={(event) => {
131
+ onFocus?.(event);
132
+ if (revealOnHover) setPreviewRevealed(true);
133
+ }}
134
+ onBlur={(event) => {
135
+ onBlur?.(event);
136
+ if (revealOnHover && !event.currentTarget.contains(event.relatedTarget as Node | null)) {
137
+ setPreviewRevealed(false);
138
+ }
139
+ }}
140
+ {...props}
141
+ >
142
+ {/* Blur is the only thing that sets a token apart from a plain `IdChip`;
143
+ the pill chrome is shared. No `overflow-hidden` here — it would clip
144
+ the blur halo — and the full value is never surfaced via `title`. */}
145
+ <code
146
+ data-slot="anonymized-token-value"
147
+ aria-hidden="true"
148
+ data-revealed={showRevealed || undefined}
149
+ className="blur-[1px] transition-[filter] data-[revealed]:blur-none"
150
+ >
151
+ {preview}
152
+ </code>
153
+ <span className="sr-only">{showRevealed ? previewLabel : obscuredLabel}</span>
154
+ {copyable ? (
155
+ <CopyButton
156
+ value={value}
157
+ timeout={timeout}
158
+ onCopied={onCopied}
159
+ onCopyError={onCopyError}
160
+ copyLabel={copyLabel}
161
+ copiedLabel={copiedLabel}
162
+ className="relative size-4 after:absolute after:-inset-1"
163
+ />
164
+ ) : null}
165
+ </span>
166
+ );
167
+ }
168
+
169
+ export { AnonymizedToken };
170
+ export type { AnonymizedTokenProps };
@@ -0,0 +1,85 @@
1
+ 'use client';
2
+
3
+ import { Badge, type badgeVariants } from '@vendure-io/ui/components/atoms/badge';
4
+ import { cn } from '@vendure-io/ui/lib/utils';
5
+ import type { VariantProps } from 'class-variance-authority';
6
+ import { XIcon } from 'lucide-react';
7
+ import type * as React from 'react';
8
+
9
+ interface ChipProps extends React.ComponentProps<'span'>, VariantProps<typeof badgeVariants> {
10
+ /** Leading icon slot. */
11
+ icon?: React.ReactNode;
12
+ /**
13
+ * Presence of this handler renders the remove (×) button; the caller closes
14
+ * over its own id (`onRemove={() => remove(item.id)}`). Absent → static chip.
15
+ */
16
+ onRemove?: () => void;
17
+ /**
18
+ * Accessible label for the remove button (e.g. `Remove ${name}`). Required
19
+ * whenever `onRemove` is set — the × has no text otherwise.
20
+ */
21
+ removeLabel?: string;
22
+ /** Dims the chip and disables removal (e.g. a pending un-assign mutation). */
23
+ disabled?: boolean;
24
+ }
25
+
26
+ /**
27
+ * A compact, optionally removable tag built on the `Badge` atom. Generalizes the
28
+ * hand-rolled entity chips (facet-value / channel / customer-group) — the label
29
+ * is free `children`, and secondary text is a composition:
30
+ *
31
+ * ```tsx
32
+ * <Chip onRemove={() => remove(fv.id)} removeLabel={`Remove ${fv.name}`}>
33
+ * {fv.name} <span className="text-muted-foreground">in {facet.name}</span>
34
+ * </Chip>
35
+ * ```
36
+ */
37
+ function Chip({
38
+ className,
39
+ variant,
40
+ icon,
41
+ onRemove,
42
+ removeLabel,
43
+ disabled,
44
+ children,
45
+ ...props
46
+ }: ChipProps) {
47
+ if (process.env.NODE_ENV !== 'production' && onRemove && !removeLabel) {
48
+ console.warn(
49
+ 'Chip: `removeLabel` is required when `onRemove` is set — the remove button has no accessible name otherwise. Pass e.g. removeLabel="Remove <name>".',
50
+ );
51
+ }
52
+
53
+ return (
54
+ <Badge
55
+ data-slot="chip"
56
+ variant={variant}
57
+ data-disabled={disabled ? '' : undefined}
58
+ className={cn(
59
+ 'max-w-full gap-1',
60
+ onRemove && 'pr-1',
61
+ disabled && 'pointer-events-none opacity-60',
62
+ className,
63
+ )}
64
+ {...props}
65
+ >
66
+ {icon}
67
+ <span className="truncate">{children}</span>
68
+ {onRemove ? (
69
+ <button
70
+ type="button"
71
+ data-slot="chip-remove"
72
+ disabled={disabled}
73
+ aria-label={removeLabel}
74
+ onClick={onRemove}
75
+ className="-mr-0.5 ml-0.5 inline-flex size-4 shrink-0 items-center justify-center rounded-full text-current/70 hover:bg-black/10 hover:text-current disabled:pointer-events-none dark:hover:bg-white/10"
76
+ >
77
+ <XIcon className="size-3" />
78
+ </button>
79
+ ) : null}
80
+ </Badge>
81
+ );
82
+ }
83
+
84
+ export { Chip };
85
+ export type { ChipProps };
@@ -1,13 +1,17 @@
1
1
  'use client';
2
2
 
3
3
  import { Autocomplete } from '@base-ui/react/autocomplete';
4
- import { ComboboxContent, ComboboxItem, ComboboxList } from '@vendure-io/ui/components/ui/combobox';
4
+ import {
5
+ ComboboxContent,
6
+ ComboboxItem,
7
+ ComboboxList,
8
+ } from '@vendure-io/ui/components/atoms/combobox';
5
9
  import {
6
10
  InputGroup,
7
11
  InputGroupAddon,
8
12
  InputGroupInput,
9
- } from '@vendure-io/ui/components/ui/input-group';
10
- import { Spinner } from '@vendure-io/ui/components/ui/spinner';
13
+ } from '@vendure-io/ui/components/atoms/input-group';
14
+ import { Spinner } from '@vendure-io/ui/components/atoms/spinner';
11
15
  import { cn } from '@vendure-io/ui/lib/utils';
12
16
  import * as React from 'react';
13
17