@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,287 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { Check, ChevronRight, X } from 'lucide-react'
5
+ import { cva, type VariantProps } from 'class-variance-authority'
6
+
7
+ import { cn } from '@open-mercato/shared/lib/utils'
8
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
9
+
10
+ /**
11
+ * Multi-step progress indicator for wizards, onboarding flows, checkout
12
+ * funnels.
13
+ *
14
+ * Anchored on Figma:
15
+ * - `Step Indicator Horizontal [1.1]` (DS OM componentSet `3507:28`) —
16
+ * horizontal flow: each item followed by a chevron-right icon
17
+ * connector except the last item, which has no trailing connector
18
+ * - `Step Indicator Horizontal Items [1.1]` (componentSet `3505:3498`) —
19
+ * horizontal item with 3 states (Default / Active / Completed)
20
+ * - `Step Indicator Vertical [1.1]` (componentSet `3507:227`) — vertical
21
+ * pill list (each item is its own bg+rounded container)
22
+ * - `Step Indicator Vertical Items [1.1]` (componentSet `3507:190`) —
23
+ * vertical item with 3 states
24
+ *
25
+ * Distinct from `Progress` (continuous %) — StepIndicator is discrete
26
+ * (Step 2 of 5) with named labels per step.
27
+ *
28
+ * The four `status` values supported by this primitive are:
29
+ *
30
+ * - `'pending'` — Figma "Default" (white dot, muted label)
31
+ * - `'current'` — Figma "Active" (accent-indigo dot, foreground label)
32
+ * - `'complete'` — Figma "Completed" (status-success dot with check)
33
+ * - `'error'` — extension beyond the Figma source. Figma does not
34
+ * model an error state, but real product surfaces (failed checkout
35
+ * step, rejected workflow step) need one. Rendered with
36
+ * `status-error-icon` + an X glyph, paralleling the success branch.
37
+ *
38
+ * ```tsx
39
+ * const steps: StepIndicatorStep[] = [
40
+ * { id: 'account', label: 'Account', status: 'complete' },
41
+ * { id: 'profile', label: 'Profile', status: 'current' },
42
+ * { id: 'review', label: 'Review', status: 'pending' },
43
+ * ]
44
+ * <StepIndicator steps={steps} />
45
+ * <StepIndicator steps={steps} orientation="vertical" />
46
+ *
47
+ * // Interactive — only completed + current become clickable by default
48
+ * <StepIndicator steps={steps} onStepClick={(id) => goToStep(id)} />
49
+ * ```
50
+ */
51
+
52
+ export type StepIndicatorStatus = 'pending' | 'current' | 'complete' | 'error'
53
+
54
+ export type StepIndicatorStep = {
55
+ /** Stable id, returned by onStepClick. */
56
+ id: string
57
+ /** Label rendered beside the dot. */
58
+ label: string
59
+ /** Optional sub-text rendered below the label (vertical only). */
60
+ description?: string
61
+ /** Current visual state. */
62
+ status: StepIndicatorStatus
63
+ }
64
+
65
+ const rootVariants = cva('flex', {
66
+ variants: {
67
+ orientation: {
68
+ horizontal: 'flex-row items-center gap-4',
69
+ vertical: 'flex-col items-stretch gap-2',
70
+ },
71
+ },
72
+ defaultVariants: { orientation: 'horizontal' },
73
+ })
74
+
75
+ // Horizontal item: dot + gap + label, no own bg/border (lives inline).
76
+ const horizontalItemVariants = cva('inline-flex items-center gap-2', {
77
+ variants: {
78
+ status: {
79
+ pending: '',
80
+ current: '',
81
+ complete: '',
82
+ error: '',
83
+ },
84
+ },
85
+ defaultVariants: { status: 'pending' },
86
+ })
87
+
88
+ // Vertical item: pill container per Figma `Step Indicator Vertical Items
89
+ // [1.1]` (`cornerRadius: 10`). Active item raises with bg-background;
90
+ // pending / complete items sit on bg-muted/40 (Figma `#F7F7F7`).
91
+ const verticalItemVariants = cva(
92
+ 'inline-flex items-center gap-2.5 rounded-lg px-3 py-2 transition-colors w-full',
93
+ {
94
+ variants: {
95
+ status: {
96
+ pending: 'bg-muted/40',
97
+ current: 'bg-background ring-1 ring-border',
98
+ complete: 'bg-muted/40',
99
+ error: 'bg-status-error-bg ring-1 ring-status-error-border',
100
+ },
101
+ },
102
+ defaultVariants: { status: 'pending' },
103
+ },
104
+ )
105
+
106
+ // Dot — 20×20 rounded-full per Figma. Solid bg per state, no internal
107
+ // number (Figma sample uses the Default state as an empty white circle).
108
+ const dotVariants = cva(
109
+ 'relative z-10 inline-flex shrink-0 items-center justify-center rounded-full transition-colors',
110
+ {
111
+ variants: {
112
+ size: {
113
+ sm: 'size-4',
114
+ default: 'size-5',
115
+ },
116
+ status: {
117
+ pending: 'border border-muted-foreground/30 bg-background',
118
+ current: 'bg-accent-indigo text-accent-indigo-foreground',
119
+ complete:
120
+ 'bg-status-success-icon text-status-success-bg',
121
+ error: 'bg-status-error-icon text-status-error-bg',
122
+ },
123
+ },
124
+ defaultVariants: { size: 'default', status: 'pending' },
125
+ },
126
+ )
127
+
128
+ const labelVariants = cva('text-sm leading-tight', {
129
+ variants: {
130
+ status: {
131
+ pending: 'text-muted-foreground',
132
+ current: 'text-foreground font-medium',
133
+ complete: 'text-muted-foreground',
134
+ error: 'text-status-error-text font-medium',
135
+ },
136
+ },
137
+ defaultVariants: { status: 'pending' },
138
+ })
139
+
140
+ export type StepIndicatorProps = React.HTMLAttributes<HTMLOListElement> &
141
+ VariantProps<typeof rootVariants> & {
142
+ steps: StepIndicatorStep[]
143
+ size?: 'sm' | 'default'
144
+ /** Optional callback. When provided, every step becomes a button. */
145
+ onStepClick?: (stepId: string) => void
146
+ /** Optional restriction on which statuses are clickable. Defaults to
147
+ * `['complete', 'current']` — past + present only. */
148
+ clickableStatuses?: StepIndicatorStatus[]
149
+ }
150
+
151
+ const DEFAULT_CLICKABLE: StepIndicatorStatus[] = ['complete', 'current']
152
+
153
+ function renderDotContent(status: StepIndicatorStatus): React.ReactNode {
154
+ if (status === 'complete') return <Check aria-hidden="true" className="size-3" />
155
+ if (status === 'error') return <X aria-hidden="true" className="size-3" />
156
+ return null
157
+ }
158
+
159
+ export const StepIndicator = React.forwardRef<HTMLOListElement, StepIndicatorProps>(
160
+ (
161
+ {
162
+ className,
163
+ steps,
164
+ orientation,
165
+ size,
166
+ onStepClick,
167
+ clickableStatuses = DEFAULT_CLICKABLE,
168
+ ...props
169
+ },
170
+ ref,
171
+ ) => {
172
+ const t = useT()
173
+ const isVertical = orientation === 'vertical'
174
+ const interactive = typeof onStepClick === 'function'
175
+
176
+ return (
177
+ <ol
178
+ ref={ref}
179
+ data-slot="step-indicator"
180
+ aria-orientation={isVertical ? 'vertical' : 'horizontal'}
181
+ className={cn(rootVariants({ orientation }), className)}
182
+ {...props}
183
+ >
184
+ {steps.map((step, index) => {
185
+ const isLast = index === steps.length - 1
186
+ const canClick = interactive && clickableStatuses.includes(step.status)
187
+
188
+ const dotNode = (
189
+ <span
190
+ data-slot="step-indicator-dot"
191
+ data-status={step.status}
192
+ className={cn(dotVariants({ size, status: step.status }))}
193
+ aria-current={step.status === 'current' ? 'step' : undefined}
194
+ >
195
+ {renderDotContent(step.status)}
196
+ </span>
197
+ )
198
+
199
+ const labelNode = (
200
+ <div className="min-w-0">
201
+ <span className={cn(labelVariants({ status: step.status }))}>{step.label}</span>
202
+ {step.description && isVertical ? (
203
+ <span className="mt-0.5 block text-xs text-muted-foreground leading-tight">
204
+ {step.description}
205
+ </span>
206
+ ) : null}
207
+ </div>
208
+ )
209
+
210
+ // Vertical trailing chevron — Figma shows it only on the
211
+ // Active item to mark "you are here" (the rest of the rail
212
+ // is bg + rounded-lg + dot).
213
+ const verticalTrailingChevron =
214
+ isVertical && step.status === 'current' ? (
215
+ <ChevronRight
216
+ aria-hidden="true"
217
+ className="ml-auto size-4 text-muted-foreground"
218
+ />
219
+ ) : null
220
+
221
+ const itemBody = isVertical ? (
222
+ <span
223
+ className={cn(
224
+ verticalItemVariants({ status: step.status }),
225
+ canClick && 'cursor-pointer',
226
+ )}
227
+ >
228
+ {dotNode}
229
+ {labelNode}
230
+ {verticalTrailingChevron}
231
+ </span>
232
+ ) : (
233
+ <span className={cn(horizontalItemVariants({ status: step.status }))}>
234
+ {dotNode}
235
+ {labelNode}
236
+ </span>
237
+ )
238
+
239
+ return (
240
+ <li
241
+ key={step.id}
242
+ data-slot="step-indicator-item"
243
+ data-status={step.status}
244
+ className={cn(
245
+ isVertical ? 'list-none' : 'flex items-center gap-4 list-none',
246
+ )}
247
+ >
248
+ {canClick ? (
249
+ <button
250
+ type="button"
251
+ onClick={() => onStepClick?.(step.id)}
252
+ aria-label={t('ui.stepIndicator.goToStep.ariaLabel', 'Go to step: {label}', { label: step.label })}
253
+ className={cn(
254
+ 'inline-flex items-center gap-2',
255
+ isVertical && 'w-full',
256
+ 'rounded-md outline-none focus-visible:shadow-focus text-left',
257
+ )}
258
+ >
259
+ {itemBody}
260
+ </button>
261
+ ) : (
262
+ itemBody
263
+ )}
264
+ {/* Horizontal connector — Figma uses a chevron-right
265
+ icon between items, NOT a line. */}
266
+ {!isVertical && !isLast ? (
267
+ <ChevronRight
268
+ aria-hidden="true"
269
+ data-slot="step-indicator-connector"
270
+ className="size-4 shrink-0 text-muted-foreground/50"
271
+ />
272
+ ) : null}
273
+ </li>
274
+ )
275
+ })}
276
+ </ol>
277
+ )
278
+ },
279
+ )
280
+ StepIndicator.displayName = 'StepIndicator'
281
+
282
+ export {
283
+ rootVariants as stepIndicatorRootVariants,
284
+ dotVariants as stepIndicatorDotVariants,
285
+ horizontalItemVariants as stepIndicatorHorizontalItemVariants,
286
+ verticalItemVariants as stepIndicatorVerticalItemVariants,
287
+ }
@@ -1,27 +1,158 @@
1
+ "use client"
2
+
1
3
  import * as React from 'react'
4
+
2
5
  import { cn } from '@open-mercato/shared/lib/utils'
3
6
 
4
- export function Table({ className, ...props }: React.HTMLAttributes<HTMLTableElement>) {
5
- return <table className={cn('w-full text-sm', className)} {...props} />
7
+ /**
8
+ * Low-level table primitive used by `DataTable` and a handful of
9
+ * direct consumers (`DictionaryEntriesEditor.tsx`). Phase B.6 polish
10
+ * aligns the chrome with Figma `Table` page (`553:14955`):
11
+ *
12
+ * - `Table Header Cell [1.1]` (`587:5793`) — subtle `bg-muted/40`
13
+ * header strip + sortable chevron slot
14
+ * - `Table Row Cell [1.1]` (`553:22175`) — comfortable padding,
15
+ * hover affordance
16
+ * - `Sorting Icons [1.1]` (`581:2327`) — handled by DataTable
17
+ * - Assembled examples in `Blocks` (167144:147461 et al.) confirm
18
+ * the chrome.
19
+ *
20
+ * Backward compatibility: every existing export (`Table`,
21
+ * `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell`)
22
+ * stays callable verbatim. Header now ships a subtle `bg-muted/40`
23
+ * strip — visible-but-soft polish per Figma. Row hover ships
24
+ * `hover:bg-muted/30`. Both default to the new look since the spec
25
+ * scope calls for "row-hover state token" + "header cell padding
26
+ * alignment with Figma".
27
+ *
28
+ * New (additive):
29
+ * - `<Table variant="striped">` — even-row bg-muted/20 tint per
30
+ * Figma striped block.
31
+ * - `<TableFooter>` — bordered top footer row group.
32
+ * - `<TableCaption>` — accessible caption above the table.
33
+ */
34
+
35
+ type TableContextValue = {
36
+ variant: 'default' | 'striped'
37
+ }
38
+
39
+ const TableContext = React.createContext<TableContextValue>({ variant: 'default' })
40
+
41
+ export type TableVariant = 'default' | 'striped'
42
+
43
+ export type TableProps = React.HTMLAttributes<HTMLTableElement> & {
44
+ /** `striped` adds even-row `bg-muted/20` tint per Figma. */
45
+ variant?: TableVariant
6
46
  }
7
47
 
8
- export function TableHeader({ className, ...props }: React.HTMLAttributes<HTMLTableSectionElement>) {
9
- return <thead className={cn(className)} {...props} />
48
+ export function Table({ className, variant = 'default', ...props }: TableProps) {
49
+ const contextValue = React.useMemo(() => ({ variant }), [variant])
50
+ return (
51
+ <TableContext.Provider value={contextValue}>
52
+ <table
53
+ data-slot="table"
54
+ data-variant={variant}
55
+ className={cn('w-full text-sm', className)}
56
+ {...props}
57
+ />
58
+ </TableContext.Provider>
59
+ )
60
+ }
61
+
62
+ export function TableHeader({
63
+ className,
64
+ ...props
65
+ }: React.HTMLAttributes<HTMLTableSectionElement>) {
66
+ return (
67
+ <thead
68
+ data-slot="table-header"
69
+ className={cn('bg-muted/40 [&_tr]:border-b', className)}
70
+ {...props}
71
+ />
72
+ )
10
73
  }
11
74
 
12
75
  export function TableBody(props: React.HTMLAttributes<HTMLTableSectionElement>) {
13
- return <tbody {...props} />
76
+ return <tbody data-slot="table-body" {...props} />
77
+ }
78
+
79
+ export function TableFooter({
80
+ className,
81
+ ...props
82
+ }: React.HTMLAttributes<HTMLTableSectionElement>) {
83
+ return (
84
+ <tfoot
85
+ data-slot="table-footer"
86
+ className={cn('border-t bg-muted/40 font-medium', className)}
87
+ {...props}
88
+ />
89
+ )
14
90
  }
15
91
 
16
- export function TableRow({ className, ...props }: React.HTMLAttributes<HTMLTableRowElement>) {
17
- return <tr className={cn('border-b last:border-b-0', className)} {...props} />
92
+ export type TableRowProps = React.HTMLAttributes<HTMLTableRowElement>
93
+
94
+ export function TableRow({ className, ...props }: TableRowProps) {
95
+ const { variant } = React.useContext(TableContext)
96
+ return (
97
+ <tr
98
+ data-slot="table-row"
99
+ className={cn(
100
+ 'border-b last:border-b-0 transition-colors',
101
+ // Row hover affordance — only applies to body rows (header
102
+ // rows have their own bg-muted/40 strip and shouldn't bounce
103
+ // on hover). Children that are inside <thead> bypass via
104
+ // CSS-attribute selectors at the consumer level if needed.
105
+ '[&:not(thead_*)]:hover:bg-muted/30',
106
+ variant === 'striped' ? '[&:not(thead_*)]:even:bg-muted/20' : '',
107
+ className,
108
+ )}
109
+ {...props}
110
+ />
111
+ )
18
112
  }
19
113
 
20
- export function TableHead({ className, ref, ...props }: React.ThHTMLAttributes<HTMLTableCellElement> & { ref?: React.Ref<HTMLTableCellElement> }) {
21
- return <th ref={ref} className={cn('text-left font-medium px-4 py-2 whitespace-nowrap text-muted-foreground', className)} {...props} />
114
+ export function TableHead({
115
+ className,
116
+ ref,
117
+ ...props
118
+ }: React.ThHTMLAttributes<HTMLTableCellElement> & {
119
+ ref?: React.Ref<HTMLTableCellElement>
120
+ }) {
121
+ return (
122
+ <th
123
+ ref={ref}
124
+ data-slot="table-head"
125
+ className={cn(
126
+ 'text-left font-medium px-4 py-2 whitespace-nowrap text-muted-foreground',
127
+ className,
128
+ )}
129
+ {...props}
130
+ />
131
+ )
22
132
  }
23
133
 
24
- export function TableCell({ className, ...props }: React.TdHTMLAttributes<HTMLTableCellElement>) {
25
- return <td className={cn('px-4 py-2', className)} {...props} />
134
+ export function TableCell({
135
+ className,
136
+ ...props
137
+ }: React.TdHTMLAttributes<HTMLTableCellElement>) {
138
+ return (
139
+ <td
140
+ data-slot="table-cell"
141
+ className={cn('px-4 py-2', className)}
142
+ {...props}
143
+ />
144
+ )
26
145
  }
27
146
 
147
+ export function TableCaption({
148
+ className,
149
+ ...props
150
+ }: React.HTMLAttributes<HTMLTableCaptionElement>) {
151
+ return (
152
+ <caption
153
+ data-slot="table-caption"
154
+ className={cn('mt-2 text-sm text-muted-foreground', className)}
155
+ {...props}
156
+ />
157
+ )
158
+ }