@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
@@ -2,12 +2,34 @@
2
2
 
3
3
  import * as React from 'react'
4
4
  import { createContext, useContext } from 'react'
5
+
5
6
  import { cn } from '@open-mercato/shared/lib/utils'
6
7
  import { Button } from './button'
7
8
 
9
+ /**
10
+ * Tab navigation primitive — backward compatible with the original
11
+ * pill-style API. Phase B.5 rewrite adds the Figma underline variant
12
+ * (`Tab Menu Horizontal [1.1]` in `553:734`), the vertical orientation
13
+ * (`Tab Menu Vertical [1.1]`), and per-tab `leading` icon + `count`
14
+ * badge slots.
15
+ *
16
+ * Backward compatibility (6 import sites):
17
+ * integrations/[id]/page.tsx
18
+ * scheduler JobLogsModal.tsx
19
+ * checkout LinkTemplateForm.tsx
20
+ * search VectorSearchSection.tsx + FulltextSearchSection.tsx
21
+ * ai-assistant AiPlaygroundPageClient.tsx
22
+ *
23
+ * All six call `<Tabs>` + `<TabsList>` + `<TabsTrigger>` +
24
+ * `<TabsContent>` with the default pill look. The default `variant`
25
+ * stays `pill` so they keep rendering verbatim.
26
+ */
27
+
8
28
  type TabsContextValue = {
9
29
  value: string
10
30
  onValueChange: (value: string) => void
31
+ variant: 'pill' | 'underline'
32
+ orientation: 'horizontal' | 'vertical'
11
33
  }
12
34
 
13
35
  export const TabsContext = createContext<TabsContextValue | undefined>(undefined)
@@ -20,10 +42,23 @@ export function useTabsContext() {
20
42
  return context
21
43
  }
22
44
 
45
+ export type TabsVariant = 'pill' | 'underline'
46
+ export type TabsOrientation = 'horizontal' | 'vertical'
47
+
23
48
  export type TabsProps = {
24
49
  value?: string
25
50
  defaultValue?: string
26
51
  onValueChange?: (value: string) => void
52
+ /** Visual style. `pill` is the original look (bg-muted container,
53
+ * bg-background selected pill). `underline` matches Figma `Tab Menu
54
+ * Horizontal [1.1]` — flat strip with a thin border-bottom and an
55
+ * accent-indigo underline on the active tab. */
56
+ variant?: TabsVariant
57
+ /** Tab strip orientation. `horizontal` lays the strip in a row;
58
+ * `vertical` lays it in a column. The default `pill` variant
59
+ * supports both; the underline variant supports only `horizontal`
60
+ * (per Figma — vertical underline is not a documented pattern). */
61
+ orientation?: TabsOrientation
27
62
  children: React.ReactNode
28
63
  className?: string
29
64
  }
@@ -32,6 +67,8 @@ export function Tabs({
32
67
  value: controlledValue,
33
68
  defaultValue,
34
69
  onValueChange,
70
+ variant = 'pill',
71
+ orientation = 'horizontal',
35
72
  children,
36
73
  className,
37
74
  }: TabsProps) {
@@ -50,13 +87,20 @@ export function Tabs({
50
87
  )
51
88
 
52
89
  const contextValue = React.useMemo<TabsContextValue>(
53
- () => ({ value, onValueChange: handleValueChange }),
54
- [value, handleValueChange],
90
+ () => ({ value, onValueChange: handleValueChange, variant, orientation }),
91
+ [value, handleValueChange, variant, orientation],
55
92
  )
56
93
 
57
94
  return (
58
95
  <TabsContext.Provider value={contextValue}>
59
- <div className={className}>{children}</div>
96
+ <div
97
+ data-slot="tabs"
98
+ data-variant={variant}
99
+ data-orientation={orientation}
100
+ className={cn(orientation === 'vertical' ? 'flex gap-4' : '', className)}
101
+ >
102
+ {children}
103
+ </div>
60
104
  </TabsContext.Provider>
61
105
  )
62
106
  }
@@ -67,13 +111,26 @@ export type TabsListProps = {
67
111
  }
68
112
 
69
113
  export function TabsList({ children, className }: TabsListProps) {
114
+ const { variant, orientation } = useTabsContext()
115
+
116
+ const baseClasses =
117
+ variant === 'underline'
118
+ ? // Underline strip — flat, full-width border-bottom for the rail,
119
+ // selected trigger gets its own border-b-2 accent below.
120
+ orientation === 'vertical'
121
+ ? 'inline-flex flex-col items-stretch gap-1 border-r border-input pr-3'
122
+ : 'inline-flex h-10 items-center justify-start gap-4 border-b border-input text-muted-foreground'
123
+ : // Pill strip (original) — rounded container, bg-muted shell.
124
+ orientation === 'vertical'
125
+ ? 'inline-flex flex-col items-stretch gap-1 rounded-lg bg-muted p-1 text-muted-foreground'
126
+ : 'inline-flex h-9 items-center justify-start gap-1 rounded-lg bg-muted p-1 text-muted-foreground'
127
+
70
128
  return (
71
129
  <div
72
- className={cn(
73
- 'inline-flex h-9 items-center justify-start rounded-lg bg-muted p-1 text-muted-foreground',
74
- className,
75
- )}
130
+ data-slot="tabs-list"
131
+ className={cn(baseClasses, className)}
76
132
  role="tablist"
133
+ aria-orientation={orientation}
77
134
  >
78
135
  {children}
79
136
  </div>
@@ -85,12 +142,86 @@ export type TabsTriggerProps = {
85
142
  children: React.ReactNode
86
143
  className?: string
87
144
  disabled?: boolean
145
+ /** Optional leading icon slot — typically a lucide-react icon at
146
+ * `size-4`. Per Figma `Tab Menu Horizontal Items [1.1]`. */
147
+ leading?: React.ReactNode
148
+ /** Optional trailing count badge — typically a numeric count or
149
+ * "New". Rendered as a muted pill that highlights to indigo when
150
+ * the tab is selected. */
151
+ count?: React.ReactNode
88
152
  }
89
153
 
90
- export function TabsTrigger({ value, children, className, disabled }: TabsTriggerProps) {
91
- const { value: selectedValue, onValueChange } = useTabsContext()
154
+ export function TabsTrigger({
155
+ value,
156
+ children,
157
+ className,
158
+ disabled,
159
+ leading,
160
+ count,
161
+ }: TabsTriggerProps) {
162
+ const { value: selectedValue, onValueChange, variant, orientation } = useTabsContext()
92
163
  const isSelected = selectedValue === value
93
164
 
165
+ if (variant === 'underline') {
166
+ return (
167
+ <button
168
+ type="button"
169
+ role="tab"
170
+ aria-selected={isSelected}
171
+ disabled={disabled}
172
+ onClick={() => onValueChange(value)}
173
+ data-slot="tabs-trigger"
174
+ data-state={isSelected ? 'active' : 'inactive'}
175
+ data-variant="underline"
176
+ className={cn(
177
+ // Underline trigger — flat, bottom-border accent when active.
178
+ // Negative margin-bottom -1px so the active accent sits on
179
+ // top of the rail's border-bottom (rather than below it).
180
+ orientation === 'vertical'
181
+ ? 'group relative inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground outline-none transition-colors'
182
+ : 'group relative -mb-px inline-flex items-center gap-2 border-b-2 border-transparent px-1 py-2 text-sm font-medium text-muted-foreground outline-none transition-colors',
183
+ 'hover:text-foreground focus-visible:shadow-focus',
184
+ 'disabled:pointer-events-none disabled:opacity-50',
185
+ isSelected
186
+ ? orientation === 'vertical'
187
+ ? 'bg-muted/40 text-foreground'
188
+ : 'border-accent-indigo font-semibold text-foreground'
189
+ : '',
190
+ className,
191
+ )}
192
+ >
193
+ {leading ? (
194
+ <span
195
+ data-slot="tabs-trigger-leading"
196
+ aria-hidden="true"
197
+ className={cn(
198
+ 'inline-flex shrink-0 items-center justify-center',
199
+ isSelected ? 'text-accent-indigo' : 'text-muted-foreground',
200
+ )}
201
+ >
202
+ {leading}
203
+ </span>
204
+ ) : null}
205
+ <span className="min-w-0 truncate">{children}</span>
206
+ {count !== undefined ? (
207
+ <span
208
+ data-slot="tabs-trigger-count"
209
+ className={cn(
210
+ 'inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full px-1.5 text-xs font-medium',
211
+ isSelected
212
+ ? 'bg-accent-indigo/10 text-accent-indigo'
213
+ : 'bg-muted text-muted-foreground',
214
+ )}
215
+ >
216
+ {count}
217
+ </span>
218
+ ) : null}
219
+ </button>
220
+ )
221
+ }
222
+
223
+ // Pill variant (original look) — `<Button variant="ghost" size="sm">`
224
+ // with state classes. Backward compatible.
94
225
  return (
95
226
  <Button
96
227
  type="button"
@@ -100,14 +231,40 @@ export function TabsTrigger({ value, children, className, disabled }: TabsTrigge
100
231
  aria-selected={isSelected}
101
232
  disabled={disabled}
102
233
  onClick={() => onValueChange(value)}
234
+ data-slot="tabs-trigger"
235
+ data-state={isSelected ? 'active' : 'inactive'}
236
+ data-variant="pill"
103
237
  className={cn(
238
+ 'gap-2',
104
239
  isSelected
105
240
  ? 'bg-background text-foreground shadow'
106
241
  : 'hover:bg-background/80 hover:text-foreground',
107
242
  className,
108
243
  )}
109
244
  >
110
- {children}
245
+ {leading ? (
246
+ <span
247
+ data-slot="tabs-trigger-leading"
248
+ aria-hidden="true"
249
+ className="inline-flex shrink-0 items-center justify-center"
250
+ >
251
+ {leading}
252
+ </span>
253
+ ) : null}
254
+ <span>{children}</span>
255
+ {count !== undefined ? (
256
+ <span
257
+ data-slot="tabs-trigger-count"
258
+ className={cn(
259
+ 'inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full px-1.5 text-xs font-medium',
260
+ isSelected
261
+ ? 'bg-accent-indigo/10 text-accent-indigo'
262
+ : 'bg-background/60 text-muted-foreground',
263
+ )}
264
+ >
265
+ {count}
266
+ </span>
267
+ ) : null}
111
268
  </Button>
112
269
  )
113
270
  }
@@ -119,7 +276,7 @@ export type TabsContentProps = {
119
276
  }
120
277
 
121
278
  export function TabsContent({ value, children, className }: TabsContentProps) {
122
- const { value: selectedValue } = useTabsContext()
279
+ const { value: selectedValue, orientation } = useTabsContext()
123
280
 
124
281
  if (selectedValue !== value) {
125
282
  return null
@@ -128,7 +285,12 @@ export function TabsContent({ value, children, className }: TabsContentProps) {
128
285
  return (
129
286
  <div
130
287
  role="tabpanel"
131
- className={cn('mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', className)}
288
+ data-slot="tabs-content"
289
+ className={cn(
290
+ orientation === 'vertical' ? 'flex-1 min-w-0' : 'mt-2',
291
+ 'ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
292
+ className,
293
+ )}
132
294
  >
133
295
  {children}
134
296
  </div>
@@ -164,7 +164,7 @@ const slotVariants = cva(
164
164
  state: {
165
165
  default: 'bg-background hover:bg-muted/50 text-muted-foreground cursor-pointer',
166
166
  hover: 'bg-muted/50 text-muted-foreground cursor-pointer',
167
- active: 'bg-brand-violet/10 text-brand-violet cursor-pointer',
167
+ active: 'bg-primary/10 text-primary cursor-pointer',
168
168
  disabled: 'bg-background text-muted-foreground/40 cursor-not-allowed',
169
169
  },
170
170
  },
@@ -225,7 +225,7 @@ export const TimePickerSlot = React.forwardRef<HTMLButtonElement, TimePickerSlot
225
225
  <span className="flex min-w-0 flex-1 items-center gap-1">
226
226
  <span>{display.main}</span>
227
227
  {display.suffix ? (
228
- <span className={effectiveState === 'active' ? 'text-brand-violet/60' : 'text-muted-foreground/60'}>
228
+ <span className={effectiveState === 'active' ? 'text-primary/60' : 'text-muted-foreground/60'}>
229
229
  {display.suffix}
230
230
  </span>
231
231
  ) : null}
@@ -234,14 +234,14 @@ export const TimePickerSlot = React.forwardRef<HTMLButtonElement, TimePickerSlot
234
234
  <span className="flex min-w-0 items-center justify-end gap-1">
235
235
  <span>{right.main}</span>
236
236
  {right.suffix ? (
237
- <span className={effectiveState === 'active' ? 'text-brand-violet/60' : 'text-muted-foreground/60'}>
237
+ <span className={effectiveState === 'active' ? 'text-primary/60' : 'text-muted-foreground/60'}>
238
238
  {right.suffix}
239
239
  </span>
240
240
  ) : null}
241
241
  </span>
242
242
  ) : null}
243
243
  {effectiveState === 'active' ? (
244
- <Check className="size-3.5 shrink-0 text-brand-violet" aria-hidden="true" />
244
+ <Check className="size-3.5 shrink-0 text-primary" aria-hidden="true" />
245
245
  ) : null}
246
246
  </button>
247
247
  )
@@ -257,7 +257,7 @@ const durationChipVariants = cva(
257
257
  default:
258
258
  'bg-background border border-border shadow-xs text-muted-foreground hover:bg-muted/40 cursor-pointer',
259
259
  hover: 'bg-muted/40 border border-border text-muted-foreground cursor-pointer',
260
- active: 'bg-brand-violet/10 text-brand-violet cursor-pointer',
260
+ active: 'bg-primary/10 text-primary cursor-pointer',
261
261
  disabled:
262
262
  'bg-background border border-border text-muted-foreground/40 cursor-not-allowed shadow-xs',
263
263
  },
@@ -462,8 +462,7 @@ export function HorizontalScrollRow({
462
462
  <div
463
463
  ref={scrollRef}
464
464
  className={cn(
465
- 'flex items-center gap-2 overflow-x-auto py-3.5',
466
- '[scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden',
465
+ 'scrollbar-hide flex items-center gap-2 overflow-x-auto py-3.5',
467
466
  contentClassName,
468
467
  )}
469
468
  role="group"
@@ -705,8 +704,7 @@ function TimePickerCard({
705
704
  return (
706
705
  <div
707
706
  className={cn(
708
- 'flex w-[348px] flex-col overflow-hidden rounded-[20px] border border-border bg-background',
709
- 'shadow-[0px_1px_2px_0px_rgba(10,13,20,0.03),0px_24px_24px_-12px_rgba(51,51,51,0.04),0px_12px_12px_-6px_rgba(51,51,51,0.04)]',
707
+ 'flex w-80 flex-col overflow-hidden rounded-xl border border-border bg-background shadow-xl',
710
708
  className,
711
709
  )}
712
710
  role="dialog"
@@ -799,7 +797,7 @@ function TimePickerCard({
799
797
  type="button"
800
798
  onClick={action.onClick}
801
799
  disabled={disabled}
802
- className="flex h-9 w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium text-brand-violet transition-colors hover:bg-brand-violet/10 cursor-pointer disabled:pointer-events-none disabled:opacity-60"
800
+ className="flex h-9 w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium text-primary transition-colors hover:bg-primary/10 cursor-pointer disabled:pointer-events-none disabled:opacity-60"
803
801
  data-slot="time-picker-pinned-action"
804
802
  >
805
803
  {action.icon ? (
@@ -809,7 +807,7 @@ function TimePickerCard({
809
807
  ) : null}
810
808
  <span className="flex-1 text-left">{action.label}</span>
811
809
  {action.rightText ? (
812
- <span className="text-brand-violet/60">{action.rightText}</span>
810
+ <span className="text-primary/60">{action.rightText}</span>
813
811
  ) : null}
814
812
  </button>
815
813
  ))}