@open-mercato/ui 0.6.4-develop.4178.1.aad9ddaa95 → 0.6.4-develop.4210.1.d412061cfe

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 (92) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +14 -0
  3. package/dist/backend/DataTable.js +28 -62
  4. package/dist/backend/DataTable.js.map +2 -2
  5. package/dist/backend/detail/AttachmentMetadataDialog.js +1 -1
  6. package/dist/backend/detail/AttachmentMetadataDialog.js.map +2 -2
  7. package/dist/primitives/activity-feed.js +196 -0
  8. package/dist/primitives/activity-feed.js.map +7 -0
  9. package/dist/primitives/avatar.js +140 -14
  10. package/dist/primitives/avatar.js.map +3 -3
  11. package/dist/primitives/badge.js +94 -5
  12. package/dist/primitives/badge.js.map +2 -2
  13. package/dist/primitives/button-group.js +52 -0
  14. package/dist/primitives/button-group.js.map +7 -0
  15. package/dist/primitives/color-picker.js +520 -0
  16. package/dist/primitives/color-picker.js.map +7 -0
  17. package/dist/primitives/command-menu.js +355 -0
  18. package/dist/primitives/command-menu.js.map +7 -0
  19. package/dist/primitives/dialog.js +210 -35
  20. package/dist/primitives/dialog.js.map +2 -2
  21. package/dist/primitives/drawer.js +219 -0
  22. package/dist/primitives/drawer.js.map +7 -0
  23. package/dist/primitives/notification-feed.js +213 -0
  24. package/dist/primitives/notification-feed.js.map +7 -0
  25. package/dist/primitives/notification.js +23 -2
  26. package/dist/primitives/notification.js.map +2 -2
  27. package/dist/primitives/pagination.js +245 -0
  28. package/dist/primitives/pagination.js.map +7 -0
  29. package/dist/primitives/progress.js +178 -5
  30. package/dist/primitives/progress.js.map +2 -2
  31. package/dist/primitives/rating.js +174 -0
  32. package/dist/primitives/rating.js.map +7 -0
  33. package/dist/primitives/scroll-area.js +82 -0
  34. package/dist/primitives/scroll-area.js.map +7 -0
  35. package/dist/primitives/segmented-control.js +101 -0
  36. package/dist/primitives/segmented-control.js.map +7 -0
  37. package/dist/primitives/separator.js +103 -4
  38. package/dist/primitives/separator.js.map +2 -2
  39. package/dist/primitives/slider.js +115 -0
  40. package/dist/primitives/slider.js.map +7 -0
  41. package/dist/primitives/step-indicator.js +187 -0
  42. package/dist/primitives/step-indicator.js.map +7 -0
  43. package/dist/primitives/table.js +102 -10
  44. package/dist/primitives/table.js.map +2 -2
  45. package/dist/primitives/tabs.js +122 -14
  46. package/dist/primitives/tabs.js.map +2 -2
  47. package/dist/primitives/time-picker.js +9 -11
  48. package/dist/primitives/time-picker.js.map +2 -2
  49. package/package.json +8 -4
  50. package/src/backend/DataTable.tsx +43 -60
  51. package/src/backend/detail/AttachmentMetadataDialog.tsx +1 -1
  52. package/src/primitives/__tests__/activity-feed.test.tsx +216 -0
  53. package/src/primitives/__tests__/avatar.test.tsx +131 -0
  54. package/src/primitives/__tests__/badge.test.tsx +189 -0
  55. package/src/primitives/__tests__/button-group.test.tsx +117 -0
  56. package/src/primitives/__tests__/color-picker.test.tsx +589 -0
  57. package/src/primitives/__tests__/command-menu.test.tsx +231 -0
  58. package/src/primitives/__tests__/dialog.test.tsx +290 -0
  59. package/src/primitives/__tests__/drawer.test.tsx +264 -0
  60. package/src/primitives/__tests__/notification-feed.test.tsx +303 -0
  61. package/src/primitives/__tests__/notification.test.tsx +102 -2
  62. package/src/primitives/__tests__/pagination.test.tsx +268 -0
  63. package/src/primitives/__tests__/progress.test.tsx +222 -0
  64. package/src/primitives/__tests__/rating.test.tsx +164 -0
  65. package/src/primitives/__tests__/scroll-area.test.tsx +109 -0
  66. package/src/primitives/__tests__/segmented-control.test.tsx +129 -0
  67. package/src/primitives/__tests__/separator.test.tsx +143 -0
  68. package/src/primitives/__tests__/slider.test.tsx +86 -0
  69. package/src/primitives/__tests__/step-indicator.test.tsx +205 -0
  70. package/src/primitives/__tests__/table.test.tsx +199 -0
  71. package/src/primitives/__tests__/tabs.test.tsx +139 -0
  72. package/src/primitives/activity-feed.tsx +326 -0
  73. package/src/primitives/avatar.tsx +225 -22
  74. package/src/primitives/badge.tsx +141 -6
  75. package/src/primitives/button-group.tsx +100 -0
  76. package/src/primitives/color-picker.tsx +693 -0
  77. package/src/primitives/command-menu.tsx +503 -0
  78. package/src/primitives/dialog.tsx +315 -51
  79. package/src/primitives/drawer.tsx +338 -0
  80. package/src/primitives/notification-feed.tsx +358 -0
  81. package/src/primitives/notification.tsx +42 -2
  82. package/src/primitives/pagination.tsx +376 -0
  83. package/src/primitives/progress.tsx +267 -6
  84. package/src/primitives/rating.tsx +245 -0
  85. package/src/primitives/scroll-area.tsx +169 -0
  86. package/src/primitives/segmented-control.tsx +162 -0
  87. package/src/primitives/separator.tsx +163 -3
  88. package/src/primitives/slider.tsx +160 -0
  89. package/src/primitives/step-indicator.tsx +287 -0
  90. package/src/primitives/table.tsx +142 -11
  91. package/src/primitives/tabs.tsx +174 -12
  92. package/src/primitives/time-picker.tsx +9 -11
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/primitives/segmented-control.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport * as RadioGroupPrimitive from '@radix-ui/react-radio-group'\nimport { cva, type VariantProps } from 'class-variance-authority'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\n\n/**\n * iOS-style segmented control per Figma `Switch / Chart / Cryptocurrency`\n * (component set id `199963:1442` in DS Open Mercato). Renders a single\n * track with N items where exactly one is selected at a time. Selecting\n * a new item fires `onValueChange`.\n *\n * Use for **mutually-exclusive view state** \u2014 list page filters like\n * \"All / Active / Archived\", chart period selectors, layout toggles\n * (List / Grid). For *related actions* (each does something different),\n * reach for `ButtonGroup` instead.\n *\n * Built on Radix `RadioGroup` so we inherit the radio-group ARIA contract\n * (`role=\"radiogroup\"`, `role=\"radio\"` on items, arrow-key navigation,\n * roving tabindex) for free. No new dependency \u2014 Radix RadioGroup is\n * already installed via the `Radio` primitive.\n *\n * ```tsx\n * const [view, setView] = React.useState('all')\n * <SegmentedControl value={view} onValueChange={setView} aria-label=\"View filter\">\n * <SegmentedControlItem value=\"all\">All</SegmentedControlItem>\n * <SegmentedControlItem value=\"active\">Active</SegmentedControlItem>\n * <SegmentedControlItem value=\"archived\">Archived</SegmentedControlItem>\n * </SegmentedControl>\n * ```\n *\n * Sizes:\n * - `default` (h-8 / 32px) \u2014 standard toolbar density.\n * - `sm` (h-7 / 28px) \u2014 tighter; pair with `text-xs`.\n */\n\ntype SegmentedControlContextValue = {\n size: 'sm' | 'default'\n disabled?: boolean\n}\n\nconst SegmentedControlContext = React.createContext<SegmentedControlContextValue>({\n size: 'default',\n disabled: false,\n})\n\nconst trackVariants = cva(\n // Pill-shaped track with subtle inner padding so selected items render\n // a smaller inner pill (the iOS slide-thumb effect). Track is muted \u2014\n // selected item raises with bg-background + shadow-sm. We use full\n // `bg-muted` (not /40) so the contrast between track and a selected\n // bg-background item stays visible in the light theme; in dark mode\n // the token already darkens further so the contrast holds.\n //\n // Height math (box-border on every element):\n // default \u2192 track h-8 (32px) \u2212 2px border \u2212 2px padding (p-px \u00D72) = 28px \u2192 matches item h-7\n // sm \u2192 track h-7 (28px) \u2212 2px border \u2212 2px padding (p-px \u00D72) = 24px \u2192 matches item h-6\n // Using `p-0.5` (2px each side = 4px total) instead breaks both sizes by\n // 2px so the selected pill clips top and bottom against the track border.\n 'inline-flex w-fit gap-0 rounded-full border border-input bg-muted p-px transition-colors',\n {\n variants: {\n size: {\n sm: 'h-7',\n default: 'h-8',\n },\n disabled: {\n true: 'cursor-not-allowed opacity-60',\n false: '',\n },\n },\n defaultVariants: {\n size: 'default',\n disabled: false,\n },\n },\n)\n\nconst itemVariants = cva(\n // Items are pills that fill the track minus its 2px inner padding.\n // Selected state lifts via bg-background + shadow-sm + font-semibold\n // (the font-weight bump gives a secondary visual cue beyond color +\n // shadow, important when the surrounding theme is high-key and the\n // shadow alone is hard to read). Unselected text is muted; hover\n // only nudges color (no bg change \u2014 keeps the track flat).\n 'inline-flex items-center justify-center rounded-full font-medium ' +\n 'transition-all outline-none focus-visible:shadow-focus ' +\n 'disabled:cursor-not-allowed disabled:opacity-50 ' +\n 'data-[state=checked]:bg-background data-[state=checked]:text-foreground data-[state=checked]:font-semibold data-[state=checked]:shadow-sm ' +\n 'data-[state=unchecked]:bg-transparent data-[state=unchecked]:text-muted-foreground data-[state=unchecked]:hover:text-foreground',\n {\n variants: {\n size: {\n sm: 'h-6 px-2.5 text-xs',\n default: 'h-7 px-3 text-sm',\n },\n },\n defaultVariants: {\n size: 'default',\n },\n },\n)\n\nexport type SegmentedControlProps = Omit<\n React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>,\n 'orientation'\n> &\n VariantProps<typeof trackVariants> & {\n /** Optional screen-reader label for the radio group. */\n 'aria-label'?: string\n }\n\nexport const SegmentedControl = React.forwardRef<\n React.ElementRef<typeof RadioGroupPrimitive.Root>,\n SegmentedControlProps\n>(({ className, size, disabled, children, ...props }, ref) => {\n const ctx = React.useMemo<SegmentedControlContextValue>(\n () => ({ size: size ?? 'default', disabled: disabled ?? false }),\n [size, disabled],\n )\n return (\n <SegmentedControlContext.Provider value={ctx}>\n <RadioGroupPrimitive.Root\n ref={ref}\n orientation=\"horizontal\"\n disabled={disabled ?? undefined}\n data-slot=\"segmented-control\"\n className={cn(trackVariants({ size, disabled }), className)}\n {...props}\n >\n {children}\n </RadioGroupPrimitive.Root>\n </SegmentedControlContext.Provider>\n )\n})\nSegmentedControl.displayName = 'SegmentedControl'\n\nexport type SegmentedControlItemProps = React.ComponentPropsWithoutRef<\n typeof RadioGroupPrimitive.Item\n>\n\nexport const SegmentedControlItem = React.forwardRef<\n React.ElementRef<typeof RadioGroupPrimitive.Item>,\n SegmentedControlItemProps\n>(({ className, children, ...props }, ref) => {\n const { size } = React.useContext(SegmentedControlContext)\n return (\n <RadioGroupPrimitive.Item\n ref={ref}\n data-slot=\"segmented-control-item\"\n className={cn(itemVariants({ size }), className)}\n {...props}\n >\n {children}\n </RadioGroupPrimitive.Item>\n )\n})\nSegmentedControlItem.displayName = 'SegmentedControlItem'\n\nexport { trackVariants as segmentedControlTrackVariants, itemVariants as segmentedControlItemVariants }\n"],
5
+ "mappings": ";AA4HM;AA1HN,YAAY,WAAW;AACvB,YAAY,yBAAyB;AACrC,SAAS,WAA8B;AAEvC,SAAS,UAAU;AAqCnB,MAAM,0BAA0B,MAAM,cAA4C;AAAA,EAChF,MAAM;AAAA,EACN,UAAU;AACZ,CAAC;AAED,MAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAapB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,EACF;AACF;AAEA,MAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOnB;AAAA,EAKA;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,SAAS;AAAA,MACX;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAWO,MAAM,mBAAmB,MAAM,WAGpC,CAAC,EAAE,WAAW,MAAM,UAAU,UAAU,GAAG,MAAM,GAAG,QAAQ;AAC5D,QAAM,MAAM,MAAM;AAAA,IAChB,OAAO,EAAE,MAAM,QAAQ,WAAW,UAAU,YAAY,MAAM;AAAA,IAC9D,CAAC,MAAM,QAAQ;AAAA,EACjB;AACA,SACE,oBAAC,wBAAwB,UAAxB,EAAiC,OAAO,KACvC;AAAA,IAAC,oBAAoB;AAAA,IAApB;AAAA,MACC;AAAA,MACA,aAAY;AAAA,MACZ,UAAU,YAAY;AAAA,MACtB,aAAU;AAAA,MACV,WAAW,GAAG,cAAc,EAAE,MAAM,SAAS,CAAC,GAAG,SAAS;AAAA,MACzD,GAAG;AAAA,MAEH;AAAA;AAAA,EACH,GACF;AAEJ,CAAC;AACD,iBAAiB,cAAc;AAMxB,MAAM,uBAAuB,MAAM,WAGxC,CAAC,EAAE,WAAW,UAAU,GAAG,MAAM,GAAG,QAAQ;AAC5C,QAAM,EAAE,KAAK,IAAI,MAAM,WAAW,uBAAuB;AACzD,SACE;AAAA,IAAC,oBAAoB;AAAA,IAApB;AAAA,MACC;AAAA,MACA,aAAU;AAAA,MACV,WAAW,GAAG,aAAa,EAAE,KAAK,CAAC,GAAG,SAAS;AAAA,MAC9C,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ,CAAC;AACD,qBAAqB,cAAc;",
6
+ "names": []
7
+ }
@@ -1,7 +1,106 @@
1
- import { jsx } from "react/jsx-runtime";
2
- function Separator({ className = "", orientation = "horizontal" }) {
3
- const base = orientation === "vertical" ? "w-px h-full" : "h-px w-full";
4
- return /* @__PURE__ */ jsx("div", { role: "separator", "aria-orientation": orientation, className: `${base} bg-border ${className}` });
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { cn } from "@open-mercato/shared/lib/utils";
4
+ const HORIZONTAL_RULE_CLASSES = {
5
+ solid: "h-px bg-border",
6
+ dashed: "h-0 border-t border-dashed border-border"
7
+ };
8
+ const HORIZONTAL_LABELED_SEGMENT = {
9
+ solid: "h-px bg-border",
10
+ dashed: "h-0 border-t border-dashed border-border"
11
+ };
12
+ const VERTICAL_RULE_CLASSES = {
13
+ solid: "w-px bg-border",
14
+ dashed: "w-0 border-l border-dashed border-border"
15
+ };
16
+ function Separator({
17
+ className = "",
18
+ orientation = "horizontal",
19
+ variant = "solid",
20
+ label,
21
+ labelAlign = "center",
22
+ section = false,
23
+ children
24
+ }) {
25
+ if (section) {
26
+ return /* @__PURE__ */ jsx(
27
+ "div",
28
+ {
29
+ role: "separator",
30
+ "data-slot": "separator",
31
+ "data-variant": "section",
32
+ "aria-orientation": "horizontal",
33
+ className: cn(
34
+ "w-full bg-muted px-3 py-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground",
35
+ className
36
+ ),
37
+ children: label ?? children
38
+ }
39
+ );
40
+ }
41
+ if (orientation === "vertical") {
42
+ return /* @__PURE__ */ jsx(
43
+ "div",
44
+ {
45
+ role: "separator",
46
+ "aria-orientation": "vertical",
47
+ "data-slot": "separator",
48
+ "data-variant": variant,
49
+ className: cn("h-full", VERTICAL_RULE_CLASSES[variant], className)
50
+ }
51
+ );
52
+ }
53
+ if (label !== void 0) {
54
+ const startSpan = labelAlign === "start" ? "w-6" : "flex-1";
55
+ const endSpan = labelAlign === "end" ? "w-6" : "flex-1";
56
+ return /* @__PURE__ */ jsxs(
57
+ "div",
58
+ {
59
+ role: "separator",
60
+ "aria-orientation": "horizontal",
61
+ "data-slot": "separator",
62
+ "data-variant": variant,
63
+ "data-label-align": labelAlign,
64
+ className: cn("flex w-full items-center gap-3", className),
65
+ children: [
66
+ /* @__PURE__ */ jsx(
67
+ "span",
68
+ {
69
+ "aria-hidden": "true",
70
+ "data-slot": "separator-rule",
71
+ className: cn("shrink-0", HORIZONTAL_LABELED_SEGMENT[variant], startSpan)
72
+ }
73
+ ),
74
+ /* @__PURE__ */ jsx(
75
+ "span",
76
+ {
77
+ "data-slot": "separator-label",
78
+ className: "shrink-0 text-xs font-medium uppercase tracking-wide text-muted-foreground",
79
+ children: label
80
+ }
81
+ ),
82
+ /* @__PURE__ */ jsx(
83
+ "span",
84
+ {
85
+ "aria-hidden": "true",
86
+ "data-slot": "separator-rule",
87
+ className: cn("shrink-0", HORIZONTAL_LABELED_SEGMENT[variant], endSpan)
88
+ }
89
+ )
90
+ ]
91
+ }
92
+ );
93
+ }
94
+ return /* @__PURE__ */ jsx(
95
+ "div",
96
+ {
97
+ role: "separator",
98
+ "aria-orientation": "horizontal",
99
+ "data-slot": "separator",
100
+ "data-variant": variant,
101
+ className: cn("w-full", HORIZONTAL_RULE_CLASSES[variant], className)
102
+ }
103
+ );
5
104
  }
6
105
  export {
7
106
  Separator
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/primitives/separator.tsx"],
4
- "sourcesContent": ["import * as React from 'react'\n\nexport function Separator({ className = '', orientation = 'horizontal' }: { className?: string; orientation?: 'horizontal' | 'vertical' }) {\n const base = orientation === 'vertical' ? 'w-px h-full' : 'h-px w-full'\n return <div role=\"separator\" aria-orientation={orientation} className={`${base} bg-border ${className}`} />\n}\n\n"],
5
- "mappings": "AAIS;AAFF,SAAS,UAAU,EAAE,YAAY,IAAI,cAAc,aAAa,GAAoE;AACzI,QAAM,OAAO,gBAAgB,aAAa,gBAAgB;AAC1D,SAAO,oBAAC,SAAI,MAAK,aAAY,oBAAkB,aAAa,WAAW,GAAG,IAAI,cAAc,SAAS,IAAI;AAC3G;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\n\n/**\n * Visual divider \u2014 horizontal or vertical. Phase B.3 rewrite (Figma\n * `Content Divider` page, componentSet `414:4401`) keeps the original\n * bare-rule API and adds the labeled / dashed / section variants.\n *\n * ## Backward compatibility (5 import sites)\n *\n * All five existing consumers pass `<Separator />` or `<Separator\n * className=\"my-4\" />` and keep working verbatim:\n *\n * customers/.../companies/[id]/page.tsx\n * workflows/.../tasks/[id]/page.tsx\n * workflows/components/EdgeEditDialog.tsx\n * workflows/components/mobile/MobileTaskForm.tsx\n * data_sync/.../data-sync/page.tsx\n *\n * ## Variants per Figma `Content Divider [1.1]`\n *\n * plain \u2014 `<Separator />`\n * labeled \u2014 `<Separator label=\"OR\" />`\n * labeled start \u2014 `<Separator label=\"OR\" labelAlign=\"start\" />`\n * section header \u2014 `<Separator section>AMOUNT & ACCOUNT</Separator>`\n * dashed \u2014 `<Separator variant=\"dashed\" />`\n *\n * For the \"rule + center button overlay\" examples (Figma variants\n * 6-9 \u2014 `+` button, navigation, \"Add\" button, button row) the\n * primitive ships only the rule. Wrap your buttons in a parent\n * positioned over the separator at the consumer level \u2014 adding an\n * `overlay` slot to the primitive would multiply layout failure\n * modes (button width, button height, button spacing) far more\n * than the small win of saving the consumer wrap.\n */\n\nexport type SeparatorProps = {\n className?: string\n orientation?: 'horizontal' | 'vertical'\n /** Visual style of the rule. */\n variant?: 'solid' | 'dashed'\n /** Optional inline label. When set, the primitive renders as two\n * rule halves with the label between them (horizontal only).\n * Ignored for vertical orientation \u2014 pass plain children inside a\n * vertical flex container instead. */\n label?: React.ReactNode\n /** Alignment of the label between the two rule halves.\n * @default 'center' */\n labelAlign?: 'center' | 'start' | 'end'\n /** Section-header style \u2014 full-width bg-muted strip + uppercase\n * label (Figma variant 5: \"AMOUNT & ACCOUNT\" content header). When\n * `section` is true the `label` prop is rendered inside the strip;\n * or pass content via `children`. Mutually exclusive with\n * `label`/`labelAlign` (those use the inline-rule style). */\n section?: boolean\n /** Children \u2014 only consumed when `section` is true (the strip\n * label can be JSX). Ignored for non-section variants. */\n children?: React.ReactNode\n}\n\n// `solid` keeps the legacy `bg-border` painted-line approach for\n// max backward compat with any consumers relying on box-model sizing.\n// `dashed` requires `border-style` so it falls back to `border-t`\n// with the painted line dropped via `h-0`.\nconst HORIZONTAL_RULE_CLASSES: Record<'solid' | 'dashed', string> = {\n solid: 'h-px bg-border',\n dashed: 'h-0 border-t border-dashed border-border',\n}\n\nconst HORIZONTAL_LABELED_SEGMENT: Record<'solid' | 'dashed', string> = {\n solid: 'h-px bg-border',\n dashed: 'h-0 border-t border-dashed border-border',\n}\n\nconst VERTICAL_RULE_CLASSES: Record<'solid' | 'dashed', string> = {\n solid: 'w-px bg-border',\n dashed: 'w-0 border-l border-dashed border-border',\n}\n\nexport function Separator({\n className = '',\n orientation = 'horizontal',\n variant = 'solid',\n label,\n labelAlign = 'center',\n section = false,\n children,\n}: SeparatorProps) {\n // ---- Section header variant (Figma 5: AMOUNT & ACCOUNT) ----------\n if (section) {\n return (\n <div\n role=\"separator\"\n data-slot=\"separator\"\n data-variant=\"section\"\n aria-orientation=\"horizontal\"\n className={cn(\n 'w-full bg-muted px-3 py-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground',\n className,\n )}\n >\n {label ?? children}\n </div>\n )\n }\n\n // ---- Vertical rule (no label support \u2014 Figma doesn't ship that\n // pattern) -----------------------------------------------------------\n if (orientation === 'vertical') {\n return (\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n data-slot=\"separator\"\n data-variant={variant}\n className={cn('h-full', VERTICAL_RULE_CLASSES[variant], className)}\n />\n )\n }\n\n // ---- Horizontal rule with optional inline label -------------------\n if (label !== undefined) {\n const startSpan = labelAlign === 'start' ? 'w-6' : 'flex-1'\n const endSpan = labelAlign === 'end' ? 'w-6' : 'flex-1'\n return (\n <div\n role=\"separator\"\n aria-orientation=\"horizontal\"\n data-slot=\"separator\"\n data-variant={variant}\n data-label-align={labelAlign}\n className={cn('flex w-full items-center gap-3', className)}\n >\n <span\n aria-hidden=\"true\"\n data-slot=\"separator-rule\"\n className={cn('shrink-0', HORIZONTAL_LABELED_SEGMENT[variant], startSpan)}\n />\n <span\n data-slot=\"separator-label\"\n className=\"shrink-0 text-xs font-medium uppercase tracking-wide text-muted-foreground\"\n >\n {label}\n </span>\n <span\n aria-hidden=\"true\"\n data-slot=\"separator-rule\"\n className={cn('shrink-0', HORIZONTAL_LABELED_SEGMENT[variant], endSpan)}\n />\n </div>\n )\n }\n\n // ---- Plain horizontal rule (original bare-line look) -------------\n return (\n <div\n role=\"separator\"\n aria-orientation=\"horizontal\"\n data-slot=\"separator\"\n data-variant={variant}\n className={cn('w-full', HORIZONTAL_RULE_CLASSES[variant], className)}\n />\n )\n}\n"],
5
+ "mappings": ";AA8FM,cAkCA,YAlCA;AA1FN,SAAS,UAAU;AA+DnB,MAAM,0BAA8D;AAAA,EAClE,OAAO;AAAA,EACP,QAAQ;AACV;AAEA,MAAM,6BAAiE;AAAA,EACrE,OAAO;AAAA,EACP,QAAQ;AACV;AAEA,MAAM,wBAA4D;AAAA,EAChE,OAAO;AAAA,EACP,QAAQ;AACV;AAEO,SAAS,UAAU;AAAA,EACxB,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,UAAU;AAAA,EACV;AAAA,EACA,aAAa;AAAA,EACb,UAAU;AAAA,EACV;AACF,GAAmB;AAEjB,MAAI,SAAS;AACX,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,aAAU;AAAA,QACV,gBAAa;AAAA,QACb,oBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA;AAAA,QACF;AAAA,QAEC,mBAAS;AAAA;AAAA,IACZ;AAAA,EAEJ;AAIA,MAAI,gBAAgB,YAAY;AAC9B,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,oBAAiB;AAAA,QACjB,aAAU;AAAA,QACV,gBAAc;AAAA,QACd,WAAW,GAAG,UAAU,sBAAsB,OAAO,GAAG,SAAS;AAAA;AAAA,IACnE;AAAA,EAEJ;AAGA,MAAI,UAAU,QAAW;AACvB,UAAM,YAAY,eAAe,UAAU,QAAQ;AACnD,UAAM,UAAU,eAAe,QAAQ,QAAQ;AAC/C,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,oBAAiB;AAAA,QACjB,aAAU;AAAA,QACV,gBAAc;AAAA,QACd,oBAAkB;AAAA,QAClB,WAAW,GAAG,kCAAkC,SAAS;AAAA,QAEzD;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,eAAY;AAAA,cACZ,aAAU;AAAA,cACV,WAAW,GAAG,YAAY,2BAA2B,OAAO,GAAG,SAAS;AAAA;AAAA,UAC1E;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,aAAU;AAAA,cACV,WAAU;AAAA,cAET;AAAA;AAAA,UACH;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,eAAY;AAAA,cACZ,aAAU;AAAA,cACV,WAAW,GAAG,YAAY,2BAA2B,OAAO,GAAG,OAAO;AAAA;AAAA,UACxE;AAAA;AAAA;AAAA,IACF;AAAA,EAEJ;AAGA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,oBAAiB;AAAA,MACjB,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,WAAW,GAAG,UAAU,wBAAwB,OAAO,GAAG,SAAS;AAAA;AAAA,EACrE;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,115 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import * as SliderPrimitive from "@radix-ui/react-slider";
5
+ import { cva } from "class-variance-authority";
6
+ import { cn } from "@open-mercato/shared/lib/utils";
7
+ const sliderRootVariants = cva(
8
+ // Disabled cursor + greyed body via explicit disabled colors on the
9
+ // range and thumb (NOT a blanket opacity — opacity over indigo would
10
+ // bleed a faded indigo through the thumb, which the Figma disabled
11
+ // state explicitly avoids).
12
+ "relative flex w-full touch-none select-none items-center data-[disabled]:cursor-not-allowed",
13
+ {
14
+ variants: {
15
+ orientation: {
16
+ horizontal: "h-5",
17
+ vertical: "h-full w-5 flex-col"
18
+ }
19
+ },
20
+ defaultVariants: { orientation: "horizontal" }
21
+ }
22
+ );
23
+ const sliderTrackVariants = cva(
24
+ "relative grow overflow-hidden rounded-full bg-muted",
25
+ {
26
+ variants: {
27
+ orientation: {
28
+ horizontal: "h-1.5 w-full",
29
+ vertical: "w-1.5 h-full"
30
+ }
31
+ },
32
+ defaultVariants: { orientation: "horizontal" }
33
+ }
34
+ );
35
+ const sliderRangeVariants = cva(
36
+ // Enabled: indigo brand color (Figma `#6366F1`). Disabled: collapse to
37
+ // a neutral mid-grey so the range visually merges with the track (the
38
+ // Figma disabled state shows no indigo bleed at all).
39
+ "absolute rounded-full bg-accent-indigo data-[disabled]:bg-muted-foreground/25",
40
+ {
41
+ variants: {
42
+ orientation: {
43
+ horizontal: "h-full",
44
+ vertical: "w-full"
45
+ }
46
+ },
47
+ defaultVariants: { orientation: "horizontal" }
48
+ }
49
+ );
50
+ const sliderThumbVariants = cva(
51
+ // Figma `Slider [1.1]` (componentSet 2617:1169):
52
+ //
53
+ // Enabled state — 16×16 white circle with NO outer stroke and a
54
+ // centered 6×6 indigo dot. Separation from the track comes from a
55
+ // drop shadow, not a border. Outer = `bg-background` + `shadow-sm`,
56
+ // inner dot via `::after` so the thumb stays a single DOM node.
57
+ //
58
+ // Disabled state — same 16×16 white circle but now with a thin grey
59
+ // ring (the shadow is dropped entirely) and a grey inner dot. The
60
+ // border is what makes the thumb still legible against the track
61
+ // when shadow + indigo are both gone.
62
+ "relative block size-4 rounded-full bg-background shadow-sm transition-shadow after:content-[''] after:absolute after:left-1/2 after:top-1/2 after:size-1.5 after:-translate-x-1/2 after:-translate-y-1/2 after:rounded-full after:bg-accent-indigo hover:shadow-md focus-visible:outline-none focus-visible:shadow-focus data-[disabled]:pointer-events-none data-[disabled]:shadow-none data-[disabled]:border data-[disabled]:border-muted-foreground/30 data-[disabled]:after:bg-muted-foreground/40"
63
+ );
64
+ const Slider = React.forwardRef(({ className, orientation, value, defaultValue, ...props }, ref) => {
65
+ const thumbCount = (() => {
66
+ const v = value ?? defaultValue;
67
+ if (Array.isArray(v) && v.length > 0) return v.length;
68
+ return 1;
69
+ })();
70
+ return /* @__PURE__ */ jsxs(
71
+ SliderPrimitive.Root,
72
+ {
73
+ ref,
74
+ orientation: orientation ?? "horizontal",
75
+ value,
76
+ defaultValue,
77
+ "data-slot": "slider-root",
78
+ className: cn(sliderRootVariants({ orientation }), className),
79
+ ...props,
80
+ children: [
81
+ /* @__PURE__ */ jsx(
82
+ SliderPrimitive.Track,
83
+ {
84
+ "data-slot": "slider-track",
85
+ className: cn(sliderTrackVariants({ orientation })),
86
+ children: /* @__PURE__ */ jsx(
87
+ SliderPrimitive.Range,
88
+ {
89
+ "data-slot": "slider-range",
90
+ className: cn(sliderRangeVariants({ orientation }))
91
+ }
92
+ )
93
+ }
94
+ ),
95
+ Array.from({ length: thumbCount }).map((_, index) => /* @__PURE__ */ jsx(
96
+ SliderPrimitive.Thumb,
97
+ {
98
+ "data-slot": "slider-thumb",
99
+ className: cn(sliderThumbVariants())
100
+ },
101
+ index
102
+ ))
103
+ ]
104
+ }
105
+ );
106
+ });
107
+ Slider.displayName = "Slider";
108
+ export {
109
+ Slider,
110
+ sliderRangeVariants,
111
+ sliderRootVariants,
112
+ sliderThumbVariants,
113
+ sliderTrackVariants
114
+ };
115
+ //# sourceMappingURL=slider.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/primitives/slider.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport * as SliderPrimitive from '@radix-ui/react-slider'\nimport { cva, type VariantProps } from 'class-variance-authority'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\n\n/**\n * Numeric value selector \u2014 single value or two-thumb range. Used for price\n * ranges, quantity selectors, opacity / brightness controls, threshold\n * configuration. Built on Radix `@radix-ui/react-slider` so we inherit\n * the slider ARIA contract (`role=\"slider\"`, `aria-valuemin/max/now`,\n * keyboard arrow / home / end navigation, RTL flip).\n *\n * No dedicated Figma node in the DS Open Mercato library at the time\n * this primitive was authored \u2014 `Level Slider` in the file is an emoji\n * icon, not a track-thumb component. Styling inferred from DS tokens\n * (primary brand on the selected portion, muted track, contrasting\n * thumb with focus ring) \u2014 see R4 in the v5 spec for the\n * inferred-design protocol.\n *\n * Single thumb:\n *\n * ```tsx\n * const [v, setV] = React.useState([20])\n * <Slider value={v} onValueChange={setV} min={0} max={100} step={5} />\n * ```\n *\n * Range (two thumbs):\n *\n * ```tsx\n * const [range, setRange] = React.useState([10, 60])\n * <Slider value={range} onValueChange={setRange} aria-label=\"Price range\" />\n * ```\n *\n * Optional `aria-label` should be provided when the slider's purpose\n * is not obvious from surrounding context.\n */\n\nconst sliderRootVariants = cva(\n // Disabled cursor + greyed body via explicit disabled colors on the\n // range and thumb (NOT a blanket opacity \u2014 opacity over indigo would\n // bleed a faded indigo through the thumb, which the Figma disabled\n // state explicitly avoids).\n 'relative flex w-full touch-none select-none items-center data-[disabled]:cursor-not-allowed',\n {\n variants: {\n orientation: {\n horizontal: 'h-5',\n vertical: 'h-full w-5 flex-col',\n },\n },\n defaultVariants: { orientation: 'horizontal' },\n },\n)\n\nconst sliderTrackVariants = cva(\n 'relative grow overflow-hidden rounded-full bg-muted',\n {\n variants: {\n orientation: {\n horizontal: 'h-1.5 w-full',\n vertical: 'w-1.5 h-full',\n },\n },\n defaultVariants: { orientation: 'horizontal' },\n },\n)\n\nconst sliderRangeVariants = cva(\n // Enabled: indigo brand color (Figma `#6366F1`). Disabled: collapse to\n // a neutral mid-grey so the range visually merges with the track (the\n // Figma disabled state shows no indigo bleed at all).\n 'absolute rounded-full bg-accent-indigo data-[disabled]:bg-muted-foreground/25',\n {\n variants: {\n orientation: {\n horizontal: 'h-full',\n vertical: 'w-full',\n },\n },\n defaultVariants: { orientation: 'horizontal' },\n },\n)\n\nconst sliderThumbVariants = cva(\n // Figma `Slider [1.1]` (componentSet 2617:1169):\n //\n // Enabled state \u2014 16\u00D716 white circle with NO outer stroke and a\n // centered 6\u00D76 indigo dot. Separation from the track comes from a\n // drop shadow, not a border. Outer = `bg-background` + `shadow-sm`,\n // inner dot via `::after` so the thumb stays a single DOM node.\n //\n // Disabled state \u2014 same 16\u00D716 white circle but now with a thin grey\n // ring (the shadow is dropped entirely) and a grey inner dot. The\n // border is what makes the thumb still legible against the track\n // when shadow + indigo are both gone.\n 'relative block size-4 rounded-full bg-background shadow-sm transition-shadow ' +\n \"after:content-[''] after:absolute after:left-1/2 after:top-1/2 after:size-1.5 after:-translate-x-1/2 after:-translate-y-1/2 after:rounded-full after:bg-accent-indigo \" +\n 'hover:shadow-md ' +\n 'focus-visible:outline-none focus-visible:shadow-focus ' +\n 'data-[disabled]:pointer-events-none data-[disabled]:shadow-none data-[disabled]:border data-[disabled]:border-muted-foreground/30 ' +\n 'data-[disabled]:after:bg-muted-foreground/40',\n)\n\nexport type SliderProps = React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> &\n VariantProps<typeof sliderRootVariants>\n\nexport const Slider = React.forwardRef<\n React.ElementRef<typeof SliderPrimitive.Root>,\n SliderProps\n>(({ className, orientation, value, defaultValue, ...props }, ref) => {\n // Derive the number of thumbs from the value/defaultValue length so we\n // render the right number of `Thumb` nodes. Radix supports both\n // single-thumb and range; the dom-level pattern is identical apart\n // from the count of thumb children.\n const thumbCount = (() => {\n const v = (value ?? defaultValue) as number[] | undefined\n if (Array.isArray(v) && v.length > 0) return v.length\n return 1\n })()\n\n return (\n <SliderPrimitive.Root\n ref={ref}\n orientation={orientation ?? 'horizontal'}\n value={value}\n defaultValue={defaultValue}\n data-slot=\"slider-root\"\n className={cn(sliderRootVariants({ orientation }), className)}\n {...props}\n >\n <SliderPrimitive.Track\n data-slot=\"slider-track\"\n className={cn(sliderTrackVariants({ orientation }))}\n >\n <SliderPrimitive.Range\n data-slot=\"slider-range\"\n className={cn(sliderRangeVariants({ orientation }))}\n />\n </SliderPrimitive.Track>\n {Array.from({ length: thumbCount }).map((_, index) => (\n <SliderPrimitive.Thumb\n key={index}\n data-slot=\"slider-thumb\"\n className={cn(sliderThumbVariants())}\n />\n ))}\n </SliderPrimitive.Root>\n )\n})\nSlider.displayName = 'Slider'\n\nexport {\n sliderRootVariants,\n sliderTrackVariants,\n sliderRangeVariants,\n sliderThumbVariants,\n}\n"],
5
+ "mappings": ";AA4HI,SAaI,KAbJ;AA1HJ,YAAY,WAAW;AACvB,YAAY,qBAAqB;AACjC,SAAS,WAA8B;AAEvC,SAAS,UAAU;AAkCnB,MAAM,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKzB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,aAAa;AAAA,QACX,YAAY;AAAA,QACZ,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,aAAa,aAAa;AAAA,EAC/C;AACF;AAEA,MAAM,sBAAsB;AAAA,EAC1B;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,aAAa;AAAA,QACX,YAAY;AAAA,QACZ,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,aAAa,aAAa;AAAA,EAC/C;AACF;AAEA,MAAM,sBAAsB;AAAA;AAAA;AAAA;AAAA,EAI1B;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,aAAa;AAAA,QACX,YAAY;AAAA,QACZ,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,aAAa,aAAa;AAAA,EAC/C;AACF;AAEA,MAAM,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAY1B;AAMF;AAKO,MAAM,SAAS,MAAM,WAG1B,CAAC,EAAE,WAAW,aAAa,OAAO,cAAc,GAAG,MAAM,GAAG,QAAQ;AAKpE,QAAM,cAAc,MAAM;AACxB,UAAM,IAAK,SAAS;AACpB,QAAI,MAAM,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAG,QAAO,EAAE;AAC/C,WAAO;AAAA,EACT,GAAG;AAEH,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC;AAAA,MACA,aAAa,eAAe;AAAA,MAC5B;AAAA,MACA;AAAA,MACA,aAAU;AAAA,MACV,WAAW,GAAG,mBAAmB,EAAE,YAAY,CAAC,GAAG,SAAS;AAAA,MAC3D,GAAG;AAAA,MAEJ;AAAA;AAAA,UAAC,gBAAgB;AAAA,UAAhB;AAAA,YACC,aAAU;AAAA,YACV,WAAW,GAAG,oBAAoB,EAAE,YAAY,CAAC,CAAC;AAAA,YAElD;AAAA,cAAC,gBAAgB;AAAA,cAAhB;AAAA,gBACC,aAAU;AAAA,gBACV,WAAW,GAAG,oBAAoB,EAAE,YAAY,CAAC,CAAC;AAAA;AAAA,YACpD;AAAA;AAAA,QACF;AAAA,QACC,MAAM,KAAK,EAAE,QAAQ,WAAW,CAAC,EAAE,IAAI,CAAC,GAAG,UAC1C;AAAA,UAAC,gBAAgB;AAAA,UAAhB;AAAA,YAEC,aAAU;AAAA,YACV,WAAW,GAAG,oBAAoB,CAAC;AAAA;AAAA,UAF9B;AAAA,QAGP,CACD;AAAA;AAAA;AAAA,EACH;AAEJ,CAAC;AACD,OAAO,cAAc;",
6
+ "names": []
7
+ }
@@ -0,0 +1,187 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Check, ChevronRight, X } from "lucide-react";
5
+ import { cva } from "class-variance-authority";
6
+ import { cn } from "@open-mercato/shared/lib/utils";
7
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
8
+ const rootVariants = cva("flex", {
9
+ variants: {
10
+ orientation: {
11
+ horizontal: "flex-row items-center gap-4",
12
+ vertical: "flex-col items-stretch gap-2"
13
+ }
14
+ },
15
+ defaultVariants: { orientation: "horizontal" }
16
+ });
17
+ const horizontalItemVariants = cva("inline-flex items-center gap-2", {
18
+ variants: {
19
+ status: {
20
+ pending: "",
21
+ current: "",
22
+ complete: "",
23
+ error: ""
24
+ }
25
+ },
26
+ defaultVariants: { status: "pending" }
27
+ });
28
+ const verticalItemVariants = cva(
29
+ "inline-flex items-center gap-2.5 rounded-lg px-3 py-2 transition-colors w-full",
30
+ {
31
+ variants: {
32
+ status: {
33
+ pending: "bg-muted/40",
34
+ current: "bg-background ring-1 ring-border",
35
+ complete: "bg-muted/40",
36
+ error: "bg-status-error-bg ring-1 ring-status-error-border"
37
+ }
38
+ },
39
+ defaultVariants: { status: "pending" }
40
+ }
41
+ );
42
+ const dotVariants = cva(
43
+ "relative z-10 inline-flex shrink-0 items-center justify-center rounded-full transition-colors",
44
+ {
45
+ variants: {
46
+ size: {
47
+ sm: "size-4",
48
+ default: "size-5"
49
+ },
50
+ status: {
51
+ pending: "border border-muted-foreground/30 bg-background",
52
+ current: "bg-accent-indigo text-accent-indigo-foreground",
53
+ complete: "bg-status-success-icon text-status-success-bg",
54
+ error: "bg-status-error-icon text-status-error-bg"
55
+ }
56
+ },
57
+ defaultVariants: { size: "default", status: "pending" }
58
+ }
59
+ );
60
+ const labelVariants = cva("text-sm leading-tight", {
61
+ variants: {
62
+ status: {
63
+ pending: "text-muted-foreground",
64
+ current: "text-foreground font-medium",
65
+ complete: "text-muted-foreground",
66
+ error: "text-status-error-text font-medium"
67
+ }
68
+ },
69
+ defaultVariants: { status: "pending" }
70
+ });
71
+ const DEFAULT_CLICKABLE = ["complete", "current"];
72
+ function renderDotContent(status) {
73
+ if (status === "complete") return /* @__PURE__ */ jsx(Check, { "aria-hidden": "true", className: "size-3" });
74
+ if (status === "error") return /* @__PURE__ */ jsx(X, { "aria-hidden": "true", className: "size-3" });
75
+ return null;
76
+ }
77
+ const StepIndicator = React.forwardRef(
78
+ ({
79
+ className,
80
+ steps,
81
+ orientation,
82
+ size,
83
+ onStepClick,
84
+ clickableStatuses = DEFAULT_CLICKABLE,
85
+ ...props
86
+ }, ref) => {
87
+ const t = useT();
88
+ const isVertical = orientation === "vertical";
89
+ const interactive = typeof onStepClick === "function";
90
+ return /* @__PURE__ */ jsx(
91
+ "ol",
92
+ {
93
+ ref,
94
+ "data-slot": "step-indicator",
95
+ "aria-orientation": isVertical ? "vertical" : "horizontal",
96
+ className: cn(rootVariants({ orientation }), className),
97
+ ...props,
98
+ children: steps.map((step, index) => {
99
+ const isLast = index === steps.length - 1;
100
+ const canClick = interactive && clickableStatuses.includes(step.status);
101
+ const dotNode = /* @__PURE__ */ jsx(
102
+ "span",
103
+ {
104
+ "data-slot": "step-indicator-dot",
105
+ "data-status": step.status,
106
+ className: cn(dotVariants({ size, status: step.status })),
107
+ "aria-current": step.status === "current" ? "step" : void 0,
108
+ children: renderDotContent(step.status)
109
+ }
110
+ );
111
+ const labelNode = /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
112
+ /* @__PURE__ */ jsx("span", { className: cn(labelVariants({ status: step.status })), children: step.label }),
113
+ step.description && isVertical ? /* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-xs text-muted-foreground leading-tight", children: step.description }) : null
114
+ ] });
115
+ const verticalTrailingChevron = isVertical && step.status === "current" ? /* @__PURE__ */ jsx(
116
+ ChevronRight,
117
+ {
118
+ "aria-hidden": "true",
119
+ className: "ml-auto size-4 text-muted-foreground"
120
+ }
121
+ ) : null;
122
+ const itemBody = isVertical ? /* @__PURE__ */ jsxs(
123
+ "span",
124
+ {
125
+ className: cn(
126
+ verticalItemVariants({ status: step.status }),
127
+ canClick && "cursor-pointer"
128
+ ),
129
+ children: [
130
+ dotNode,
131
+ labelNode,
132
+ verticalTrailingChevron
133
+ ]
134
+ }
135
+ ) : /* @__PURE__ */ jsxs("span", { className: cn(horizontalItemVariants({ status: step.status })), children: [
136
+ dotNode,
137
+ labelNode
138
+ ] });
139
+ return /* @__PURE__ */ jsxs(
140
+ "li",
141
+ {
142
+ "data-slot": "step-indicator-item",
143
+ "data-status": step.status,
144
+ className: cn(
145
+ isVertical ? "list-none" : "flex items-center gap-4 list-none"
146
+ ),
147
+ children: [
148
+ canClick ? /* @__PURE__ */ jsx(
149
+ "button",
150
+ {
151
+ type: "button",
152
+ onClick: () => onStepClick?.(step.id),
153
+ "aria-label": t("ui.stepIndicator.goToStep.ariaLabel", "Go to step: {label}", { label: step.label }),
154
+ className: cn(
155
+ "inline-flex items-center gap-2",
156
+ isVertical && "w-full",
157
+ "rounded-md outline-none focus-visible:shadow-focus text-left"
158
+ ),
159
+ children: itemBody
160
+ }
161
+ ) : itemBody,
162
+ !isVertical && !isLast ? /* @__PURE__ */ jsx(
163
+ ChevronRight,
164
+ {
165
+ "aria-hidden": "true",
166
+ "data-slot": "step-indicator-connector",
167
+ className: "size-4 shrink-0 text-muted-foreground/50"
168
+ }
169
+ ) : null
170
+ ]
171
+ },
172
+ step.id
173
+ );
174
+ })
175
+ }
176
+ );
177
+ }
178
+ );
179
+ StepIndicator.displayName = "StepIndicator";
180
+ export {
181
+ StepIndicator,
182
+ dotVariants as stepIndicatorDotVariants,
183
+ horizontalItemVariants as stepIndicatorHorizontalItemVariants,
184
+ rootVariants as stepIndicatorRootVariants,
185
+ verticalItemVariants as stepIndicatorVerticalItemVariants
186
+ };
187
+ //# sourceMappingURL=step-indicator.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/primitives/step-indicator.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Check, ChevronRight, X } from 'lucide-react'\nimport { cva, type VariantProps } from 'class-variance-authority'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\n/**\n * Multi-step progress indicator for wizards, onboarding flows, checkout\n * funnels.\n *\n * Anchored on Figma:\n * - `Step Indicator Horizontal [1.1]` (DS OM componentSet `3507:28`) \u2014\n * horizontal flow: each item followed by a chevron-right icon\n * connector except the last item, which has no trailing connector\n * - `Step Indicator Horizontal Items [1.1]` (componentSet `3505:3498`) \u2014\n * horizontal item with 3 states (Default / Active / Completed)\n * - `Step Indicator Vertical [1.1]` (componentSet `3507:227`) \u2014 vertical\n * pill list (each item is its own bg+rounded container)\n * - `Step Indicator Vertical Items [1.1]` (componentSet `3507:190`) \u2014\n * vertical item with 3 states\n *\n * Distinct from `Progress` (continuous %) \u2014 StepIndicator is discrete\n * (Step 2 of 5) with named labels per step.\n *\n * The four `status` values supported by this primitive are:\n *\n * - `'pending'` \u2014 Figma \"Default\" (white dot, muted label)\n * - `'current'` \u2014 Figma \"Active\" (accent-indigo dot, foreground label)\n * - `'complete'` \u2014 Figma \"Completed\" (status-success dot with check)\n * - `'error'` \u2014 extension beyond the Figma source. Figma does not\n * model an error state, but real product surfaces (failed checkout\n * step, rejected workflow step) need one. Rendered with\n * `status-error-icon` + an X glyph, paralleling the success branch.\n *\n * ```tsx\n * const steps: StepIndicatorStep[] = [\n * { id: 'account', label: 'Account', status: 'complete' },\n * { id: 'profile', label: 'Profile', status: 'current' },\n * { id: 'review', label: 'Review', status: 'pending' },\n * ]\n * <StepIndicator steps={steps} />\n * <StepIndicator steps={steps} orientation=\"vertical\" />\n *\n * // Interactive \u2014 only completed + current become clickable by default\n * <StepIndicator steps={steps} onStepClick={(id) => goToStep(id)} />\n * ```\n */\n\nexport type StepIndicatorStatus = 'pending' | 'current' | 'complete' | 'error'\n\nexport type StepIndicatorStep = {\n /** Stable id, returned by onStepClick. */\n id: string\n /** Label rendered beside the dot. */\n label: string\n /** Optional sub-text rendered below the label (vertical only). */\n description?: string\n /** Current visual state. */\n status: StepIndicatorStatus\n}\n\nconst rootVariants = cva('flex', {\n variants: {\n orientation: {\n horizontal: 'flex-row items-center gap-4',\n vertical: 'flex-col items-stretch gap-2',\n },\n },\n defaultVariants: { orientation: 'horizontal' },\n})\n\n// Horizontal item: dot + gap + label, no own bg/border (lives inline).\nconst horizontalItemVariants = cva('inline-flex items-center gap-2', {\n variants: {\n status: {\n pending: '',\n current: '',\n complete: '',\n error: '',\n },\n },\n defaultVariants: { status: 'pending' },\n})\n\n// Vertical item: pill container per Figma `Step Indicator Vertical Items\n// [1.1]` (`cornerRadius: 10`). Active item raises with bg-background;\n// pending / complete items sit on bg-muted/40 (Figma `#F7F7F7`).\nconst verticalItemVariants = cva(\n 'inline-flex items-center gap-2.5 rounded-lg px-3 py-2 transition-colors w-full',\n {\n variants: {\n status: {\n pending: 'bg-muted/40',\n current: 'bg-background ring-1 ring-border',\n complete: 'bg-muted/40',\n error: 'bg-status-error-bg ring-1 ring-status-error-border',\n },\n },\n defaultVariants: { status: 'pending' },\n },\n)\n\n// Dot \u2014 20\u00D720 rounded-full per Figma. Solid bg per state, no internal\n// number (Figma sample uses the Default state as an empty white circle).\nconst dotVariants = cva(\n 'relative z-10 inline-flex shrink-0 items-center justify-center rounded-full transition-colors',\n {\n variants: {\n size: {\n sm: 'size-4',\n default: 'size-5',\n },\n status: {\n pending: 'border border-muted-foreground/30 bg-background',\n current: 'bg-accent-indigo text-accent-indigo-foreground',\n complete:\n 'bg-status-success-icon text-status-success-bg',\n error: 'bg-status-error-icon text-status-error-bg',\n },\n },\n defaultVariants: { size: 'default', status: 'pending' },\n },\n)\n\nconst labelVariants = cva('text-sm leading-tight', {\n variants: {\n status: {\n pending: 'text-muted-foreground',\n current: 'text-foreground font-medium',\n complete: 'text-muted-foreground',\n error: 'text-status-error-text font-medium',\n },\n },\n defaultVariants: { status: 'pending' },\n})\n\nexport type StepIndicatorProps = React.HTMLAttributes<HTMLOListElement> &\n VariantProps<typeof rootVariants> & {\n steps: StepIndicatorStep[]\n size?: 'sm' | 'default'\n /** Optional callback. When provided, every step becomes a button. */\n onStepClick?: (stepId: string) => void\n /** Optional restriction on which statuses are clickable. Defaults to\n * `['complete', 'current']` \u2014 past + present only. */\n clickableStatuses?: StepIndicatorStatus[]\n }\n\nconst DEFAULT_CLICKABLE: StepIndicatorStatus[] = ['complete', 'current']\n\nfunction renderDotContent(status: StepIndicatorStatus): React.ReactNode {\n if (status === 'complete') return <Check aria-hidden=\"true\" className=\"size-3\" />\n if (status === 'error') return <X aria-hidden=\"true\" className=\"size-3\" />\n return null\n}\n\nexport const StepIndicator = React.forwardRef<HTMLOListElement, StepIndicatorProps>(\n (\n {\n className,\n steps,\n orientation,\n size,\n onStepClick,\n clickableStatuses = DEFAULT_CLICKABLE,\n ...props\n },\n ref,\n ) => {\n const t = useT()\n const isVertical = orientation === 'vertical'\n const interactive = typeof onStepClick === 'function'\n\n return (\n <ol\n ref={ref}\n data-slot=\"step-indicator\"\n aria-orientation={isVertical ? 'vertical' : 'horizontal'}\n className={cn(rootVariants({ orientation }), className)}\n {...props}\n >\n {steps.map((step, index) => {\n const isLast = index === steps.length - 1\n const canClick = interactive && clickableStatuses.includes(step.status)\n\n const dotNode = (\n <span\n data-slot=\"step-indicator-dot\"\n data-status={step.status}\n className={cn(dotVariants({ size, status: step.status }))}\n aria-current={step.status === 'current' ? 'step' : undefined}\n >\n {renderDotContent(step.status)}\n </span>\n )\n\n const labelNode = (\n <div className=\"min-w-0\">\n <span className={cn(labelVariants({ status: step.status }))}>{step.label}</span>\n {step.description && isVertical ? (\n <span className=\"mt-0.5 block text-xs text-muted-foreground leading-tight\">\n {step.description}\n </span>\n ) : null}\n </div>\n )\n\n // Vertical trailing chevron \u2014 Figma shows it only on the\n // Active item to mark \"you are here\" (the rest of the rail\n // is bg + rounded-lg + dot).\n const verticalTrailingChevron =\n isVertical && step.status === 'current' ? (\n <ChevronRight\n aria-hidden=\"true\"\n className=\"ml-auto size-4 text-muted-foreground\"\n />\n ) : null\n\n const itemBody = isVertical ? (\n <span\n className={cn(\n verticalItemVariants({ status: step.status }),\n canClick && 'cursor-pointer',\n )}\n >\n {dotNode}\n {labelNode}\n {verticalTrailingChevron}\n </span>\n ) : (\n <span className={cn(horizontalItemVariants({ status: step.status }))}>\n {dotNode}\n {labelNode}\n </span>\n )\n\n return (\n <li\n key={step.id}\n data-slot=\"step-indicator-item\"\n data-status={step.status}\n className={cn(\n isVertical ? 'list-none' : 'flex items-center gap-4 list-none',\n )}\n >\n {canClick ? (\n <button\n type=\"button\"\n onClick={() => onStepClick?.(step.id)}\n aria-label={t('ui.stepIndicator.goToStep.ariaLabel', 'Go to step: {label}', { label: step.label })}\n className={cn(\n 'inline-flex items-center gap-2',\n isVertical && 'w-full',\n 'rounded-md outline-none focus-visible:shadow-focus text-left',\n )}\n >\n {itemBody}\n </button>\n ) : (\n itemBody\n )}\n {/* Horizontal connector \u2014 Figma uses a chevron-right\n icon between items, NOT a line. */}\n {!isVertical && !isLast ? (\n <ChevronRight\n aria-hidden=\"true\"\n data-slot=\"step-indicator-connector\"\n className=\"size-4 shrink-0 text-muted-foreground/50\"\n />\n ) : null}\n </li>\n )\n })}\n </ol>\n )\n },\n)\nStepIndicator.displayName = 'StepIndicator'\n\nexport {\n rootVariants as stepIndicatorRootVariants,\n dotVariants as stepIndicatorDotVariants,\n horizontalItemVariants as stepIndicatorHorizontalItemVariants,\n verticalItemVariants as stepIndicatorVerticalItemVariants,\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,27 +1,119 @@
1
+ "use client";
1
2
  import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
2
4
  import { cn } from "@open-mercato/shared/lib/utils";
3
- function Table({ className, ...props }) {
4
- return /* @__PURE__ */ jsx("table", { className: cn("w-full text-sm", className), ...props });
5
+ const TableContext = React.createContext({ variant: "default" });
6
+ function Table({ className, variant = "default", ...props }) {
7
+ const contextValue = React.useMemo(() => ({ variant }), [variant]);
8
+ return /* @__PURE__ */ jsx(TableContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
9
+ "table",
10
+ {
11
+ "data-slot": "table",
12
+ "data-variant": variant,
13
+ className: cn("w-full text-sm", className),
14
+ ...props
15
+ }
16
+ ) });
5
17
  }
6
- function TableHeader({ className, ...props }) {
7
- return /* @__PURE__ */ jsx("thead", { className: cn(className), ...props });
18
+ function TableHeader({
19
+ className,
20
+ ...props
21
+ }) {
22
+ return /* @__PURE__ */ jsx(
23
+ "thead",
24
+ {
25
+ "data-slot": "table-header",
26
+ className: cn("bg-muted/40 [&_tr]:border-b", className),
27
+ ...props
28
+ }
29
+ );
8
30
  }
9
31
  function TableBody(props) {
10
- return /* @__PURE__ */ jsx("tbody", { ...props });
32
+ return /* @__PURE__ */ jsx("tbody", { "data-slot": "table-body", ...props });
33
+ }
34
+ function TableFooter({
35
+ className,
36
+ ...props
37
+ }) {
38
+ return /* @__PURE__ */ jsx(
39
+ "tfoot",
40
+ {
41
+ "data-slot": "table-footer",
42
+ className: cn("border-t bg-muted/40 font-medium", className),
43
+ ...props
44
+ }
45
+ );
11
46
  }
12
47
  function TableRow({ className, ...props }) {
13
- return /* @__PURE__ */ jsx("tr", { className: cn("border-b last:border-b-0", className), ...props });
48
+ const { variant } = React.useContext(TableContext);
49
+ return /* @__PURE__ */ jsx(
50
+ "tr",
51
+ {
52
+ "data-slot": "table-row",
53
+ className: cn(
54
+ "border-b last:border-b-0 transition-colors",
55
+ // Row hover affordance — only applies to body rows (header
56
+ // rows have their own bg-muted/40 strip and shouldn't bounce
57
+ // on hover). Children that are inside <thead> bypass via
58
+ // CSS-attribute selectors at the consumer level if needed.
59
+ "[&:not(thead_*)]:hover:bg-muted/30",
60
+ variant === "striped" ? "[&:not(thead_*)]:even:bg-muted/20" : "",
61
+ className
62
+ ),
63
+ ...props
64
+ }
65
+ );
66
+ }
67
+ function TableHead({
68
+ className,
69
+ ref,
70
+ ...props
71
+ }) {
72
+ return /* @__PURE__ */ jsx(
73
+ "th",
74
+ {
75
+ ref,
76
+ "data-slot": "table-head",
77
+ className: cn(
78
+ "text-left font-medium px-4 py-2 whitespace-nowrap text-muted-foreground",
79
+ className
80
+ ),
81
+ ...props
82
+ }
83
+ );
14
84
  }
15
- function TableHead({ className, ref, ...props }) {
16
- return /* @__PURE__ */ jsx("th", { ref, className: cn("text-left font-medium px-4 py-2 whitespace-nowrap text-muted-foreground", className), ...props });
85
+ function TableCell({
86
+ className,
87
+ ...props
88
+ }) {
89
+ return /* @__PURE__ */ jsx(
90
+ "td",
91
+ {
92
+ "data-slot": "table-cell",
93
+ className: cn("px-4 py-2", className),
94
+ ...props
95
+ }
96
+ );
17
97
  }
18
- function TableCell({ className, ...props }) {
19
- return /* @__PURE__ */ jsx("td", { className: cn("px-4 py-2", className), ...props });
98
+ function TableCaption({
99
+ className,
100
+ ...props
101
+ }) {
102
+ return /* @__PURE__ */ jsx(
103
+ "caption",
104
+ {
105
+ "data-slot": "table-caption",
106
+ className: cn("mt-2 text-sm text-muted-foreground", className),
107
+ ...props
108
+ }
109
+ );
20
110
  }
21
111
  export {
22
112
  Table,
23
113
  TableBody,
114
+ TableCaption,
24
115
  TableCell,
116
+ TableFooter,
25
117
  TableHead,
26
118
  TableHeader,
27
119
  TableRow