@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.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +14 -0
- package/dist/backend/DataTable.js +28 -62
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/backend/detail/AttachmentMetadataDialog.js +1 -1
- package/dist/backend/detail/AttachmentMetadataDialog.js.map +2 -2
- package/dist/primitives/activity-feed.js +196 -0
- package/dist/primitives/activity-feed.js.map +7 -0
- package/dist/primitives/avatar.js +140 -14
- package/dist/primitives/avatar.js.map +3 -3
- package/dist/primitives/badge.js +94 -5
- package/dist/primitives/badge.js.map +2 -2
- package/dist/primitives/button-group.js +52 -0
- package/dist/primitives/button-group.js.map +7 -0
- package/dist/primitives/color-picker.js +520 -0
- package/dist/primitives/color-picker.js.map +7 -0
- package/dist/primitives/command-menu.js +355 -0
- package/dist/primitives/command-menu.js.map +7 -0
- package/dist/primitives/dialog.js +210 -35
- package/dist/primitives/dialog.js.map +2 -2
- package/dist/primitives/drawer.js +219 -0
- package/dist/primitives/drawer.js.map +7 -0
- package/dist/primitives/notification-feed.js +213 -0
- package/dist/primitives/notification-feed.js.map +7 -0
- package/dist/primitives/notification.js +23 -2
- package/dist/primitives/notification.js.map +2 -2
- package/dist/primitives/pagination.js +245 -0
- package/dist/primitives/pagination.js.map +7 -0
- package/dist/primitives/progress.js +178 -5
- package/dist/primitives/progress.js.map +2 -2
- package/dist/primitives/rating.js +174 -0
- package/dist/primitives/rating.js.map +7 -0
- package/dist/primitives/scroll-area.js +82 -0
- package/dist/primitives/scroll-area.js.map +7 -0
- package/dist/primitives/segmented-control.js +101 -0
- package/dist/primitives/segmented-control.js.map +7 -0
- package/dist/primitives/separator.js +103 -4
- package/dist/primitives/separator.js.map +2 -2
- package/dist/primitives/slider.js +115 -0
- package/dist/primitives/slider.js.map +7 -0
- package/dist/primitives/step-indicator.js +187 -0
- package/dist/primitives/step-indicator.js.map +7 -0
- package/dist/primitives/table.js +102 -10
- package/dist/primitives/table.js.map +2 -2
- package/dist/primitives/tabs.js +122 -14
- package/dist/primitives/tabs.js.map +2 -2
- package/dist/primitives/time-picker.js +9 -11
- package/dist/primitives/time-picker.js.map +2 -2
- package/package.json +8 -4
- package/src/backend/DataTable.tsx +43 -60
- package/src/backend/detail/AttachmentMetadataDialog.tsx +1 -1
- package/src/primitives/__tests__/activity-feed.test.tsx +216 -0
- package/src/primitives/__tests__/avatar.test.tsx +131 -0
- package/src/primitives/__tests__/badge.test.tsx +189 -0
- package/src/primitives/__tests__/button-group.test.tsx +117 -0
- package/src/primitives/__tests__/color-picker.test.tsx +589 -0
- package/src/primitives/__tests__/command-menu.test.tsx +231 -0
- package/src/primitives/__tests__/dialog.test.tsx +290 -0
- package/src/primitives/__tests__/drawer.test.tsx +264 -0
- package/src/primitives/__tests__/notification-feed.test.tsx +303 -0
- package/src/primitives/__tests__/notification.test.tsx +102 -2
- package/src/primitives/__tests__/pagination.test.tsx +268 -0
- package/src/primitives/__tests__/progress.test.tsx +222 -0
- package/src/primitives/__tests__/rating.test.tsx +164 -0
- package/src/primitives/__tests__/scroll-area.test.tsx +109 -0
- package/src/primitives/__tests__/segmented-control.test.tsx +129 -0
- package/src/primitives/__tests__/separator.test.tsx +143 -0
- package/src/primitives/__tests__/slider.test.tsx +86 -0
- package/src/primitives/__tests__/step-indicator.test.tsx +205 -0
- package/src/primitives/__tests__/table.test.tsx +199 -0
- package/src/primitives/__tests__/tabs.test.tsx +139 -0
- package/src/primitives/activity-feed.tsx +326 -0
- package/src/primitives/avatar.tsx +225 -22
- package/src/primitives/badge.tsx +141 -6
- package/src/primitives/button-group.tsx +100 -0
- package/src/primitives/color-picker.tsx +693 -0
- package/src/primitives/command-menu.tsx +503 -0
- package/src/primitives/dialog.tsx +315 -51
- package/src/primitives/drawer.tsx +338 -0
- package/src/primitives/notification-feed.tsx +358 -0
- package/src/primitives/notification.tsx +42 -2
- package/src/primitives/pagination.tsx +376 -0
- package/src/primitives/progress.tsx +267 -6
- package/src/primitives/rating.tsx +245 -0
- package/src/primitives/scroll-area.tsx +169 -0
- package/src/primitives/segmented-control.tsx +162 -0
- package/src/primitives/separator.tsx +163 -3
- package/src/primitives/slider.tsx +160 -0
- package/src/primitives/step-indicator.tsx +287 -0
- package/src/primitives/table.tsx +142 -11
- package/src/primitives/tabs.tsx +174 -12
- package/src/primitives/time-picker.tsx +9 -11
|
@@ -1,28 +1,201 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
3
4
|
import { cn } from "@open-mercato/shared/lib/utils";
|
|
4
|
-
|
|
5
|
+
const PROGRESS_TONE_FILL = {
|
|
6
|
+
accent: "bg-accent-indigo",
|
|
7
|
+
success: "bg-status-success-icon",
|
|
8
|
+
warning: "bg-status-warning-icon",
|
|
9
|
+
destructive: "bg-status-error-icon",
|
|
10
|
+
muted: "bg-muted-foreground"
|
|
11
|
+
};
|
|
12
|
+
const progressTrackVariants = cva(
|
|
13
|
+
"relative w-full overflow-hidden rounded-full bg-input",
|
|
14
|
+
{
|
|
15
|
+
variants: {
|
|
16
|
+
size: {
|
|
17
|
+
sm: "h-1",
|
|
18
|
+
default: "h-2",
|
|
19
|
+
lg: "h-3"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: { size: "default" }
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
const progressFillVariants = cva(
|
|
26
|
+
"h-full transition-all duration-300 ease-in-out",
|
|
27
|
+
{
|
|
28
|
+
variants: {
|
|
29
|
+
tone: {
|
|
30
|
+
accent: PROGRESS_TONE_FILL.accent,
|
|
31
|
+
success: PROGRESS_TONE_FILL.success,
|
|
32
|
+
warning: PROGRESS_TONE_FILL.warning,
|
|
33
|
+
destructive: PROGRESS_TONE_FILL.destructive,
|
|
34
|
+
muted: PROGRESS_TONE_FILL.muted
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
defaultVariants: { tone: "accent" }
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
function Progress({
|
|
41
|
+
value,
|
|
42
|
+
max = 100,
|
|
43
|
+
size,
|
|
44
|
+
tone,
|
|
45
|
+
showValue,
|
|
46
|
+
label,
|
|
47
|
+
description,
|
|
48
|
+
className,
|
|
49
|
+
fillClassName,
|
|
50
|
+
...props
|
|
51
|
+
}) {
|
|
5
52
|
const percentage = Math.min(100, Math.max(0, Math.round(value / max * 100)));
|
|
6
|
-
|
|
53
|
+
const hasLabelRow = label !== void 0 || showValue === true;
|
|
54
|
+
const bar = /* @__PURE__ */ jsx(
|
|
7
55
|
"div",
|
|
8
56
|
{
|
|
9
57
|
role: "progressbar",
|
|
10
58
|
"aria-valuenow": value,
|
|
11
59
|
"aria-valuemin": 0,
|
|
12
60
|
"aria-valuemax": max,
|
|
13
|
-
|
|
61
|
+
"data-slot": "progress",
|
|
62
|
+
"data-tone": tone ?? "accent",
|
|
63
|
+
className: cn(progressTrackVariants({ size }), className),
|
|
14
64
|
...props,
|
|
15
65
|
children: /* @__PURE__ */ jsx(
|
|
16
66
|
"div",
|
|
17
67
|
{
|
|
18
|
-
|
|
68
|
+
"data-slot": "progress-fill",
|
|
69
|
+
className: cn(progressFillVariants({ tone }), fillClassName),
|
|
19
70
|
style: { width: `${percentage}%` }
|
|
20
71
|
}
|
|
21
72
|
)
|
|
22
73
|
}
|
|
23
74
|
);
|
|
75
|
+
if (!hasLabelRow && description === void 0) return bar;
|
|
76
|
+
return /* @__PURE__ */ jsxs("div", { "data-slot": "progress-wrapper", className: "flex w-full flex-col gap-1.5", children: [
|
|
77
|
+
hasLabelRow ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 text-sm font-medium text-foreground", children: [
|
|
78
|
+
/* @__PURE__ */ jsx("span", { "data-slot": "progress-label", className: "min-w-0 truncate", children: label }),
|
|
79
|
+
showValue ? /* @__PURE__ */ jsxs(
|
|
80
|
+
"span",
|
|
81
|
+
{
|
|
82
|
+
"data-slot": "progress-value",
|
|
83
|
+
className: "shrink-0 text-muted-foreground",
|
|
84
|
+
children: [
|
|
85
|
+
percentage,
|
|
86
|
+
"%"
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
) : null
|
|
90
|
+
] }) : null,
|
|
91
|
+
bar,
|
|
92
|
+
description !== void 0 ? /* @__PURE__ */ jsx("div", { "data-slot": "progress-description", className: "text-sm text-muted-foreground", children: description }) : null
|
|
93
|
+
] });
|
|
94
|
+
}
|
|
95
|
+
const CIRCULAR_SIZE_MAP = {
|
|
96
|
+
xs: { box: 24, stroke: 3, textClass: "text-[9px]" },
|
|
97
|
+
sm: { box: 32, stroke: 3, textClass: "text-[10px]" },
|
|
98
|
+
default: { box: 48, stroke: 4, textClass: "text-xs" },
|
|
99
|
+
lg: { box: 64, stroke: 5, textClass: "text-sm" }
|
|
100
|
+
};
|
|
101
|
+
const CIRCULAR_TONE_STROKE = {
|
|
102
|
+
accent: "stroke-accent-indigo",
|
|
103
|
+
success: "stroke-status-success-icon",
|
|
104
|
+
warning: "stroke-status-warning-icon",
|
|
105
|
+
destructive: "stroke-status-error-icon",
|
|
106
|
+
muted: "stroke-muted-foreground"
|
|
107
|
+
};
|
|
108
|
+
function CircularProgress({
|
|
109
|
+
value,
|
|
110
|
+
max = 100,
|
|
111
|
+
size = "default",
|
|
112
|
+
tone = "accent",
|
|
113
|
+
showValue,
|
|
114
|
+
children,
|
|
115
|
+
ariaLabel,
|
|
116
|
+
className,
|
|
117
|
+
trackClassName,
|
|
118
|
+
fillClassName
|
|
119
|
+
}) {
|
|
120
|
+
const percentage = Math.min(100, Math.max(0, Math.round(value / max * 100)));
|
|
121
|
+
const { box, stroke, textClass } = CIRCULAR_SIZE_MAP[size];
|
|
122
|
+
const radius = (box - stroke) / 2;
|
|
123
|
+
const circumference = 2 * Math.PI * radius;
|
|
124
|
+
const dashOffset = circumference * (1 - percentage / 100);
|
|
125
|
+
const half = box / 2;
|
|
126
|
+
return /* @__PURE__ */ jsxs(
|
|
127
|
+
"div",
|
|
128
|
+
{
|
|
129
|
+
role: "progressbar",
|
|
130
|
+
"aria-valuenow": value,
|
|
131
|
+
"aria-valuemin": 0,
|
|
132
|
+
"aria-valuemax": max,
|
|
133
|
+
"aria-label": ariaLabel ?? `${percentage}%`,
|
|
134
|
+
"data-slot": "circular-progress",
|
|
135
|
+
"data-tone": tone,
|
|
136
|
+
"data-size": size,
|
|
137
|
+
className: cn("relative inline-flex items-center justify-center", className),
|
|
138
|
+
children: [
|
|
139
|
+
/* @__PURE__ */ jsxs(
|
|
140
|
+
"svg",
|
|
141
|
+
{
|
|
142
|
+
width: box,
|
|
143
|
+
height: box,
|
|
144
|
+
viewBox: `0 0 ${box} ${box}`,
|
|
145
|
+
className: "-rotate-90",
|
|
146
|
+
"aria-hidden": "true",
|
|
147
|
+
children: [
|
|
148
|
+
/* @__PURE__ */ jsx(
|
|
149
|
+
"circle",
|
|
150
|
+
{
|
|
151
|
+
"data-slot": "circular-progress-track",
|
|
152
|
+
cx: half,
|
|
153
|
+
cy: half,
|
|
154
|
+
r: radius,
|
|
155
|
+
fill: "none",
|
|
156
|
+
strokeWidth: stroke,
|
|
157
|
+
className: cn("stroke-input", trackClassName)
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
/* @__PURE__ */ jsx(
|
|
161
|
+
"circle",
|
|
162
|
+
{
|
|
163
|
+
"data-slot": "circular-progress-fill",
|
|
164
|
+
cx: half,
|
|
165
|
+
cy: half,
|
|
166
|
+
r: radius,
|
|
167
|
+
fill: "none",
|
|
168
|
+
strokeWidth: stroke,
|
|
169
|
+
strokeLinecap: "round",
|
|
170
|
+
strokeDasharray: circumference,
|
|
171
|
+
strokeDashoffset: dashOffset,
|
|
172
|
+
className: cn(
|
|
173
|
+
CIRCULAR_TONE_STROKE[tone],
|
|
174
|
+
"transition-all duration-300 ease-in-out",
|
|
175
|
+
fillClassName
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
),
|
|
182
|
+
showValue ? /* @__PURE__ */ jsx(
|
|
183
|
+
"span",
|
|
184
|
+
{
|
|
185
|
+
"data-slot": "circular-progress-value",
|
|
186
|
+
className: cn(
|
|
187
|
+
"absolute inset-0 inline-flex items-center justify-center font-medium text-foreground",
|
|
188
|
+
textClass
|
|
189
|
+
),
|
|
190
|
+
children: children ?? `${percentage}%`
|
|
191
|
+
}
|
|
192
|
+
) : null
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
);
|
|
24
196
|
}
|
|
25
197
|
export {
|
|
198
|
+
CircularProgress,
|
|
26
199
|
Progress
|
|
27
200
|
};
|
|
28
201
|
//# sourceMappingURL=progress.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/primitives/progress.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nexport interface ProgressProps
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cva, type VariantProps } from 'class-variance-authority'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\n\n/**\n * Determinate progress bar \u2014 both linear and circular. Linear is the\n * default `Progress` export; circular is `CircularProgress`.\n *\n * Figma source: DS Open Mercato `Progress Bar` page (`450:17758`):\n * `Progress Bar [1.1]` (`450:17821`), `Progress Bar Label [1.1]`\n * (`515:3758`), `Progress Bar Line [1.1]` (`450:17810`, 5 tone\n * variants: muted / accent / destructive / warning / success),\n * `Circular Progress Bar [1.1]` (`466:4652`).\n *\n * Backward compatibility (Phase B rewrite): the original\n * `<Progress value={n} max={100} className=\"...\" />` API stays\n * callable verbatim. The 3 existing call sites\n * (`packages/ui/src/backend/NextStepCallout.tsx`,\n * `packages/core/.../data_sync/.../runs/[id]/page.tsx`,\n * `packages/sync-akeneo/.../akeneo-config/widget.client.tsx`)\n * keep working without changes. New optional props (`size`, `tone`,\n * `showValue`, `label`, `description`) are additive.\n *\n * ```tsx\n * // Backward-compatible bare bar:\n * <Progress value={50} />\n *\n * // Labelled variant per `Progress Bar Label [1.1]`:\n * <Progress\n * value={80}\n * label=\"Data Storage\"\n * showValue\n * description=\"Upgrade to unlock unlimited storage.\"\n * />\n *\n * // Tone variants per `Progress Bar Line [1.1]`:\n * <Progress value={42} tone=\"success\" />\n * <Progress value={42} tone=\"warning\" />\n * <Progress value={42} tone=\"destructive\" />\n *\n * // Circular variant per `Circular Progress Bar [1.1]`:\n * <CircularProgress value={75} size=\"default\" showValue />\n * ```\n */\n\nexport type ProgressTone =\n | 'accent'\n | 'success'\n | 'warning'\n | 'destructive'\n | 'muted'\n\nconst PROGRESS_TONE_FILL: Record<ProgressTone, string> = {\n accent: 'bg-accent-indigo',\n success: 'bg-status-success-icon',\n warning: 'bg-status-warning-icon',\n destructive: 'bg-status-error-icon',\n muted: 'bg-muted-foreground',\n}\n\nconst progressTrackVariants = cva(\n 'relative w-full overflow-hidden rounded-full bg-input',\n {\n variants: {\n size: {\n sm: 'h-1',\n default: 'h-2',\n lg: 'h-3',\n },\n },\n defaultVariants: { size: 'default' },\n },\n)\n\nconst progressFillVariants = cva(\n 'h-full transition-all duration-300 ease-in-out',\n {\n variants: {\n tone: {\n accent: PROGRESS_TONE_FILL.accent,\n success: PROGRESS_TONE_FILL.success,\n warning: PROGRESS_TONE_FILL.warning,\n destructive: PROGRESS_TONE_FILL.destructive,\n muted: PROGRESS_TONE_FILL.muted,\n },\n },\n defaultVariants: { tone: 'accent' },\n },\n)\n\nexport interface ProgressProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof progressTrackVariants> {\n value: number\n max?: number\n /** Color of the filled portion. Default `accent` (indigo). */\n tone?: ProgressTone\n /** Show the percentage badge on the right of the label row. */\n showValue?: boolean\n /** Optional label rendered above the bar. */\n label?: React.ReactNode\n /** Optional description rendered below the bar in muted text. */\n description?: React.ReactNode\n /** Override the inner fill className (e.g. to apply a gradient). */\n fillClassName?: string\n}\n\nexport function Progress({\n value,\n max = 100,\n size,\n tone,\n showValue,\n label,\n description,\n className,\n fillClassName,\n ...props\n}: ProgressProps) {\n const percentage = Math.min(100, Math.max(0, Math.round((value / max) * 100)))\n const hasLabelRow = label !== undefined || showValue === true\n const bar = (\n <div\n role=\"progressbar\"\n aria-valuenow={value}\n aria-valuemin={0}\n aria-valuemax={max}\n data-slot=\"progress\"\n data-tone={tone ?? 'accent'}\n className={cn(progressTrackVariants({ size }), className)}\n {...props}\n >\n <div\n data-slot=\"progress-fill\"\n className={cn(progressFillVariants({ tone }), fillClassName)}\n style={{ width: `${percentage}%` }}\n />\n </div>\n )\n\n if (!hasLabelRow && description === undefined) return bar\n\n return (\n <div data-slot=\"progress-wrapper\" className=\"flex w-full flex-col gap-1.5\">\n {hasLabelRow ? (\n <div className=\"flex items-center justify-between gap-3 text-sm font-medium text-foreground\">\n <span data-slot=\"progress-label\" className=\"min-w-0 truncate\">\n {label}\n </span>\n {showValue ? (\n <span\n data-slot=\"progress-value\"\n className=\"shrink-0 text-muted-foreground\"\n >\n {percentage}%\n </span>\n ) : null}\n </div>\n ) : null}\n {bar}\n {description !== undefined ? (\n <div data-slot=\"progress-description\" className=\"text-sm text-muted-foreground\">\n {description}\n </div>\n ) : null}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------\n// CircularProgress\n// ---------------------------------------------------------------------\n\nconst CIRCULAR_SIZE_MAP = {\n xs: { box: 24, stroke: 3, textClass: 'text-[9px]' },\n sm: { box: 32, stroke: 3, textClass: 'text-[10px]' },\n default: { box: 48, stroke: 4, textClass: 'text-xs' },\n lg: { box: 64, stroke: 5, textClass: 'text-sm' },\n} as const\n\nexport type CircularProgressSize = keyof typeof CIRCULAR_SIZE_MAP\n\nconst CIRCULAR_TONE_STROKE: Record<ProgressTone, string> = {\n accent: 'stroke-accent-indigo',\n success: 'stroke-status-success-icon',\n warning: 'stroke-status-warning-icon',\n destructive: 'stroke-status-error-icon',\n muted: 'stroke-muted-foreground',\n}\n\nexport interface CircularProgressProps {\n value: number\n max?: number\n size?: CircularProgressSize\n tone?: ProgressTone\n /** Render the percentage in the centre of the ring. */\n showValue?: boolean\n /** Override the inner content (replaces the default percentage label\n * when `showValue` is true). Renders only when `showValue` is true. */\n children?: React.ReactNode\n /** Accessible label for the progressbar. Falls back to `${percentage}%`. */\n ariaLabel?: string\n className?: string\n /** Override the track ring className (defaults to `stroke-input`). */\n trackClassName?: string\n /** Override the fill ring className (defaults per `tone`). */\n fillClassName?: string\n}\n\nexport function CircularProgress({\n value,\n max = 100,\n size = 'default',\n tone = 'accent',\n showValue,\n children,\n ariaLabel,\n className,\n trackClassName,\n fillClassName,\n}: CircularProgressProps) {\n const percentage = Math.min(100, Math.max(0, Math.round((value / max) * 100)))\n const { box, stroke, textClass } = CIRCULAR_SIZE_MAP[size]\n const radius = (box - stroke) / 2\n const circumference = 2 * Math.PI * radius\n const dashOffset = circumference * (1 - percentage / 100)\n const half = box / 2\n return (\n <div\n role=\"progressbar\"\n aria-valuenow={value}\n aria-valuemin={0}\n aria-valuemax={max}\n aria-label={ariaLabel ?? `${percentage}%`}\n data-slot=\"circular-progress\"\n data-tone={tone}\n data-size={size}\n className={cn('relative inline-flex items-center justify-center', className)}\n >\n <svg\n width={box}\n height={box}\n viewBox={`0 0 ${box} ${box}`}\n className=\"-rotate-90\"\n aria-hidden=\"true\"\n >\n <circle\n data-slot=\"circular-progress-track\"\n cx={half}\n cy={half}\n r={radius}\n fill=\"none\"\n strokeWidth={stroke}\n className={cn('stroke-input', trackClassName)}\n />\n <circle\n data-slot=\"circular-progress-fill\"\n cx={half}\n cy={half}\n r={radius}\n fill=\"none\"\n strokeWidth={stroke}\n strokeLinecap=\"round\"\n strokeDasharray={circumference}\n strokeDashoffset={dashOffset}\n className={cn(\n CIRCULAR_TONE_STROKE[tone],\n 'transition-all duration-300 ease-in-out',\n fillClassName,\n )}\n />\n </svg>\n {showValue ? (\n <span\n data-slot=\"circular-progress-value\"\n className={cn(\n 'absolute inset-0 inline-flex items-center justify-center font-medium text-foreground',\n textClass,\n )}\n >\n {children ?? `${percentage}%`}\n </span>\n ) : null}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Circle, Heart, Star, StarHalf } 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 ratingRootVariants = cva("inline-flex items-center gap-0.5", {
|
|
9
|
+
variants: {
|
|
10
|
+
size: {
|
|
11
|
+
sm: "[&>*]:size-4",
|
|
12
|
+
default: "[&>*]:size-5",
|
|
13
|
+
lg: "[&>*]:size-6"
|
|
14
|
+
},
|
|
15
|
+
disabled: {
|
|
16
|
+
true: "cursor-not-allowed opacity-60",
|
|
17
|
+
false: ""
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
defaultVariants: { size: "default", disabled: false }
|
|
21
|
+
});
|
|
22
|
+
const ratingItemColorVariants = cva("transition-colors", {
|
|
23
|
+
variants: {
|
|
24
|
+
fill: {
|
|
25
|
+
full: "fill-status-warning-icon text-status-warning-icon",
|
|
26
|
+
half: "fill-status-warning-icon text-status-warning-icon",
|
|
27
|
+
empty: "fill-transparent text-muted-foreground/30"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
defaultVariants: { fill: "empty" }
|
|
31
|
+
});
|
|
32
|
+
function resolveFillState(index, value, allowHalf) {
|
|
33
|
+
if (value >= index + 1) return "full";
|
|
34
|
+
if (allowHalf && value >= index + 0.5) return "half";
|
|
35
|
+
return "empty";
|
|
36
|
+
}
|
|
37
|
+
function StarFull(props) {
|
|
38
|
+
return /* @__PURE__ */ jsx(Star, { "aria-hidden": "true", ...props });
|
|
39
|
+
}
|
|
40
|
+
function StarHalfIcon(props) {
|
|
41
|
+
return /* @__PURE__ */ jsx(StarHalf, { "aria-hidden": "true", ...props });
|
|
42
|
+
}
|
|
43
|
+
function HeartIcon(props) {
|
|
44
|
+
return /* @__PURE__ */ jsx(Heart, { "aria-hidden": "true", ...props });
|
|
45
|
+
}
|
|
46
|
+
function CircleIcon(props) {
|
|
47
|
+
return /* @__PURE__ */ jsx(Circle, { "aria-hidden": "true", ...props });
|
|
48
|
+
}
|
|
49
|
+
function renderIcon(iconType, fill, className) {
|
|
50
|
+
if (iconType === "heart") {
|
|
51
|
+
return /* @__PURE__ */ jsx(HeartIcon, { className });
|
|
52
|
+
}
|
|
53
|
+
if (iconType === "circle") {
|
|
54
|
+
return /* @__PURE__ */ jsx(CircleIcon, { className });
|
|
55
|
+
}
|
|
56
|
+
if (fill === "half") return /* @__PURE__ */ jsx(StarHalfIcon, { className });
|
|
57
|
+
return /* @__PURE__ */ jsx(StarFull, { className });
|
|
58
|
+
}
|
|
59
|
+
const Rating = React.forwardRef(
|
|
60
|
+
({
|
|
61
|
+
className,
|
|
62
|
+
value,
|
|
63
|
+
max = 5,
|
|
64
|
+
onChange,
|
|
65
|
+
icon = "star",
|
|
66
|
+
allowHalf = false,
|
|
67
|
+
size,
|
|
68
|
+
disabled,
|
|
69
|
+
...rest
|
|
70
|
+
}, ref) => {
|
|
71
|
+
const t = useT();
|
|
72
|
+
const interactive = typeof onChange === "function";
|
|
73
|
+
const handleSelect = React.useCallback(
|
|
74
|
+
(next) => {
|
|
75
|
+
if (disabled) return;
|
|
76
|
+
onChange?.(next);
|
|
77
|
+
},
|
|
78
|
+
[disabled, onChange]
|
|
79
|
+
);
|
|
80
|
+
if (!interactive) {
|
|
81
|
+
return /* @__PURE__ */ jsx(
|
|
82
|
+
"span",
|
|
83
|
+
{
|
|
84
|
+
ref,
|
|
85
|
+
role: "img",
|
|
86
|
+
"aria-label": rest["aria-label"] ?? t("ui.rating.summary.ariaLabel", "{value} out of {max}", { value, max }),
|
|
87
|
+
"data-slot": "rating",
|
|
88
|
+
className: cn(ratingRootVariants({ size, disabled }), className),
|
|
89
|
+
...rest,
|
|
90
|
+
children: Array.from({ length: max }).map((_, index) => {
|
|
91
|
+
const fill = resolveFillState(index, value, allowHalf);
|
|
92
|
+
return /* @__PURE__ */ jsx(
|
|
93
|
+
"span",
|
|
94
|
+
{
|
|
95
|
+
"data-slot": "rating-item",
|
|
96
|
+
"data-fill": fill,
|
|
97
|
+
className: "inline-flex items-center justify-center",
|
|
98
|
+
children: renderIcon(icon, fill, ratingItemColorVariants({ fill }))
|
|
99
|
+
},
|
|
100
|
+
index
|
|
101
|
+
);
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return /* @__PURE__ */ jsx(
|
|
107
|
+
"span",
|
|
108
|
+
{
|
|
109
|
+
ref,
|
|
110
|
+
role: "radiogroup",
|
|
111
|
+
"aria-label": rest["aria-label"],
|
|
112
|
+
"data-slot": "rating",
|
|
113
|
+
className: cn(ratingRootVariants({ size, disabled }), className),
|
|
114
|
+
...rest,
|
|
115
|
+
children: Array.from({ length: max }).map((_, index) => {
|
|
116
|
+
const fill = resolveFillState(index, value, allowHalf);
|
|
117
|
+
const isCurrent = value === 0 ? index === 0 : Math.ceil(value) - 1 === index;
|
|
118
|
+
return /* @__PURE__ */ jsx(
|
|
119
|
+
"button",
|
|
120
|
+
{
|
|
121
|
+
type: "button",
|
|
122
|
+
role: "radio",
|
|
123
|
+
"aria-checked": fill !== "empty",
|
|
124
|
+
"aria-label": t("ui.rating.item.ariaLabel", "{position} of {max}", { position: index + 1, max }),
|
|
125
|
+
tabIndex: isCurrent ? 0 : -1,
|
|
126
|
+
"data-slot": "rating-item",
|
|
127
|
+
"data-fill": fill,
|
|
128
|
+
disabled,
|
|
129
|
+
onClick: (event) => {
|
|
130
|
+
if (!allowHalf) {
|
|
131
|
+
handleSelect(index + 1);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
135
|
+
const isLeftHalf = event.clientX - rect.left < rect.width / 2;
|
|
136
|
+
handleSelect(isLeftHalf ? index + 0.5 : index + 1);
|
|
137
|
+
},
|
|
138
|
+
onKeyDown: (event) => {
|
|
139
|
+
if (event.key === "ArrowRight" || event.key === "ArrowUp") {
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
handleSelect(Math.min(max, value + (allowHalf ? 0.5 : 1)));
|
|
142
|
+
} else if (event.key === "ArrowLeft" || event.key === "ArrowDown") {
|
|
143
|
+
event.preventDefault();
|
|
144
|
+
handleSelect(Math.max(0, value - (allowHalf ? 0.5 : 1)));
|
|
145
|
+
} else if (event.key === "Home") {
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
handleSelect(allowHalf ? 0.5 : 1);
|
|
148
|
+
} else if (event.key === "End") {
|
|
149
|
+
event.preventDefault();
|
|
150
|
+
handleSelect(max);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
className: cn(
|
|
154
|
+
"inline-flex items-center justify-center rounded-sm outline-none",
|
|
155
|
+
"focus-visible:shadow-focus",
|
|
156
|
+
"enabled:hover:scale-110 enabled:hover:transition-transform",
|
|
157
|
+
"disabled:cursor-not-allowed"
|
|
158
|
+
),
|
|
159
|
+
children: renderIcon(icon, fill, ratingItemColorVariants({ fill }))
|
|
160
|
+
},
|
|
161
|
+
index
|
|
162
|
+
);
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
Rating.displayName = "Rating";
|
|
169
|
+
export {
|
|
170
|
+
Rating,
|
|
171
|
+
ratingItemColorVariants,
|
|
172
|
+
ratingRootVariants
|
|
173
|
+
};
|
|
174
|
+
//# sourceMappingURL=rating.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/primitives/rating.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Circle, Heart, Star, StarHalf } 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 * 1-to-N star / heart / dot rating per Figma `Rating & Review [1.0]`\n * (DS Open Mercato componentSet `199969:1797`). Two distinct modes:\n *\n * - **Read-only display** \u2014 no `onChange` prop. Renders `role=\"img\"` with\n * an `aria-label` like \"4.5 out of 5 stars\". Used in product reviews,\n * feedback summaries.\n * - **Interactive input** \u2014 `onChange` provided. Renders as a row of\n * focusable buttons; arrow keys / number keys navigate, click /\n * Enter / Space commits. Used in submission forms.\n *\n * ```tsx\n * // Read-only\n * <Rating value={4.5} max={5} />\n *\n * // Interactive\n * const [v, setV] = React.useState(0)\n * <Rating value={v} onChange={setV} aria-label=\"Your rating\" />\n *\n * // Half precision (stars only \u2014 Lucide ships StarHalf but not HeartHalf)\n * <Rating value={3.5} max={5} allowHalf />\n *\n * // Heart variant\n * <Rating value={3} max={5} icon=\"heart\" />\n * ```\n */\n\nconst ratingRootVariants = cva('inline-flex items-center gap-0.5', {\n variants: {\n size: {\n sm: '[&>*]:size-4',\n default: '[&>*]:size-5',\n lg: '[&>*]:size-6',\n },\n disabled: {\n true: 'cursor-not-allowed opacity-60',\n false: '',\n },\n },\n defaultVariants: { size: 'default', disabled: false },\n})\n\nconst ratingItemColorVariants = cva('transition-colors', {\n variants: {\n fill: {\n full: 'fill-status-warning-icon text-status-warning-icon',\n half: 'fill-status-warning-icon text-status-warning-icon',\n empty: 'fill-transparent text-muted-foreground/30',\n },\n },\n defaultVariants: { fill: 'empty' },\n})\n\ntype FillState = 'full' | 'half' | 'empty'\n\nfunction resolveFillState(index: number, value: number, allowHalf: boolean): FillState {\n if (value >= index + 1) return 'full'\n if (allowHalf && value >= index + 0.5) return 'half'\n return 'empty'\n}\n\nfunction StarFull(props: React.ComponentProps<typeof Star>) {\n return <Star aria-hidden=\"true\" {...props} />\n}\n\nfunction StarHalfIcon(props: React.ComponentProps<typeof StarHalf>) {\n return <StarHalf aria-hidden=\"true\" {...props} />\n}\n\nfunction HeartIcon(props: React.ComponentProps<typeof Heart>) {\n return <Heart aria-hidden=\"true\" {...props} />\n}\n\nfunction CircleIcon(props: React.ComponentProps<typeof Circle>) {\n return <Circle aria-hidden=\"true\" {...props} />\n}\n\nfunction renderIcon(\n iconType: 'star' | 'heart' | 'circle',\n fill: FillState,\n className: string,\n): React.ReactElement {\n if (iconType === 'heart') {\n // Lucide does not ship HeartHalf \u2014 half precision is treated as full\n // for hearts (consumer should set allowHalf=false when icon=\"heart\").\n return <HeartIcon className={className} />\n }\n if (iconType === 'circle') {\n return <CircleIcon className={className} />\n }\n // star\n if (fill === 'half') return <StarHalfIcon className={className} />\n return <StarFull className={className} />\n}\n\nexport type RatingProps = Omit<React.HTMLAttributes<HTMLSpanElement>, 'onChange'> &\n VariantProps<typeof ratingRootVariants> & {\n /** Current rating value (0..max). Floats allowed when `allowHalf` is `true`. */\n value: number\n /** Total number of items. Defaults to 5. */\n max?: number\n /** Optional change handler. Presence flips the primitive into interactive mode. */\n onChange?: (next: number) => void\n /** Which icon glyph to render. Defaults to `'star'`. */\n icon?: 'star' | 'heart' | 'circle'\n /** Enable half-step precision (stars only \u2014 Lucide has no HeartHalf). */\n allowHalf?: boolean\n /** When true, dim the control and block clicks. Inherited via `disabled` prop on the root. */\n disabled?: boolean\n /** Required when interactive \u2014 screen-readers announce this as the group label. */\n 'aria-label'?: string\n }\n\nexport const Rating = React.forwardRef<HTMLSpanElement, RatingProps>(\n (\n {\n className,\n value,\n max = 5,\n onChange,\n icon = 'star',\n allowHalf = false,\n size,\n disabled,\n ...rest\n },\n ref,\n ) => {\n const t = useT()\n const interactive = typeof onChange === 'function'\n const handleSelect = React.useCallback(\n (next: number) => {\n if (disabled) return\n onChange?.(next)\n },\n [disabled, onChange],\n )\n\n // Read-only display path \u2014 plain span container with aria-label.\n if (!interactive) {\n return (\n <span\n ref={ref}\n role=\"img\"\n aria-label={rest['aria-label'] ?? t('ui.rating.summary.ariaLabel', '{value} out of {max}', { value, max })}\n data-slot=\"rating\"\n className={cn(ratingRootVariants({ size, disabled }), className)}\n {...rest}\n >\n {Array.from({ length: max }).map((_, index) => {\n const fill = resolveFillState(index, value, allowHalf)\n return (\n <span\n key={index}\n data-slot=\"rating-item\"\n data-fill={fill}\n className=\"inline-flex items-center justify-center\"\n >\n {renderIcon(icon, fill, ratingItemColorVariants({ fill }))}\n </span>\n )\n })}\n </span>\n )\n }\n\n // Interactive path \u2014 N buttons. Arrow keys move focus between items;\n // Home / End jump to first / last. Click / Enter / Space commits the\n // value (index + 1 for full clicks; index + 0.5 if allowHalf && the\n // click landed on the left half of the icon).\n return (\n <span\n ref={ref}\n role=\"radiogroup\"\n aria-label={rest['aria-label']}\n data-slot=\"rating\"\n className={cn(ratingRootVariants({ size, disabled }), className)}\n {...rest}\n >\n {Array.from({ length: max }).map((_, index) => {\n const fill = resolveFillState(index, value, allowHalf)\n // Empty rating (value === 0) falls back to the first item so it stays Tab-reachable.\n const isCurrent = value === 0 ? index === 0 : Math.ceil(value) - 1 === index\n return (\n <button\n key={index}\n type=\"button\"\n role=\"radio\"\n aria-checked={fill !== 'empty'}\n aria-label={t('ui.rating.item.ariaLabel', '{position} of {max}', { position: index + 1, max })}\n tabIndex={isCurrent ? 0 : -1}\n data-slot=\"rating-item\"\n data-fill={fill}\n disabled={disabled}\n onClick={(event) => {\n if (!allowHalf) {\n handleSelect(index + 1)\n return\n }\n const rect = event.currentTarget.getBoundingClientRect()\n const isLeftHalf = event.clientX - rect.left < rect.width / 2\n handleSelect(isLeftHalf ? index + 0.5 : index + 1)\n }}\n onKeyDown={(event) => {\n if (event.key === 'ArrowRight' || event.key === 'ArrowUp') {\n event.preventDefault()\n handleSelect(Math.min(max, value + (allowHalf ? 0.5 : 1)))\n } else if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {\n event.preventDefault()\n handleSelect(Math.max(0, value - (allowHalf ? 0.5 : 1)))\n } else if (event.key === 'Home') {\n event.preventDefault()\n handleSelect(allowHalf ? 0.5 : 1)\n } else if (event.key === 'End') {\n event.preventDefault()\n handleSelect(max)\n }\n }}\n className={cn(\n 'inline-flex items-center justify-center rounded-sm outline-none',\n 'focus-visible:shadow-focus',\n 'enabled:hover:scale-110 enabled:hover:transition-transform',\n 'disabled:cursor-not-allowed',\n )}\n >\n {renderIcon(icon, fill, ratingItemColorVariants({ fill }))}\n </button>\n )\n })}\n </span>\n )\n },\n)\nRating.displayName = 'Rating'\n\nexport { ratingRootVariants, ratingItemColorVariants }\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
|
5
|
+
import { cn } from "@open-mercato/shared/lib/utils";
|
|
6
|
+
const ScrollAreaRoot = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
7
|
+
ScrollAreaPrimitive.Root,
|
|
8
|
+
{
|
|
9
|
+
ref,
|
|
10
|
+
"data-slot": "scroll-area-root",
|
|
11
|
+
className: cn("relative overflow-hidden", className),
|
|
12
|
+
...props
|
|
13
|
+
}
|
|
14
|
+
));
|
|
15
|
+
ScrollAreaRoot.displayName = ScrollAreaPrimitive.Root.displayName;
|
|
16
|
+
const ScrollAreaViewport = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
17
|
+
ScrollAreaPrimitive.Viewport,
|
|
18
|
+
{
|
|
19
|
+
ref,
|
|
20
|
+
"data-slot": "scroll-area-viewport",
|
|
21
|
+
className: cn("h-full w-full rounded-[inherit]", className),
|
|
22
|
+
...props
|
|
23
|
+
}
|
|
24
|
+
));
|
|
25
|
+
ScrollAreaViewport.displayName = ScrollAreaPrimitive.Viewport.displayName;
|
|
26
|
+
const ScrollAreaScrollbar = React.forwardRef(({ className, orientation = "vertical", ...props }, ref) => /* @__PURE__ */ jsx(
|
|
27
|
+
ScrollAreaPrimitive.ScrollAreaScrollbar,
|
|
28
|
+
{
|
|
29
|
+
ref,
|
|
30
|
+
orientation,
|
|
31
|
+
"data-slot": "scroll-area-scrollbar",
|
|
32
|
+
className: cn(
|
|
33
|
+
"flex touch-none select-none transition-colors",
|
|
34
|
+
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-px",
|
|
35
|
+
orientation === "horizontal" && "h-2.5 w-full border-t border-t-transparent p-px",
|
|
36
|
+
className
|
|
37
|
+
),
|
|
38
|
+
...props
|
|
39
|
+
}
|
|
40
|
+
));
|
|
41
|
+
ScrollAreaScrollbar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
|
42
|
+
const ScrollAreaThumb = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
43
|
+
ScrollAreaPrimitive.ScrollAreaThumb,
|
|
44
|
+
{
|
|
45
|
+
ref,
|
|
46
|
+
"data-slot": "scroll-area-thumb",
|
|
47
|
+
className: cn(
|
|
48
|
+
"relative flex-1 rounded-full bg-muted-foreground/30 transition-colors hover:bg-muted-foreground/50",
|
|
49
|
+
className
|
|
50
|
+
),
|
|
51
|
+
...props
|
|
52
|
+
}
|
|
53
|
+
));
|
|
54
|
+
ScrollAreaThumb.displayName = ScrollAreaPrimitive.ScrollAreaThumb.displayName;
|
|
55
|
+
const ScrollAreaCorner = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
56
|
+
ScrollAreaPrimitive.Corner,
|
|
57
|
+
{
|
|
58
|
+
ref,
|
|
59
|
+
"data-slot": "scroll-area-corner",
|
|
60
|
+
className: cn("bg-transparent", className),
|
|
61
|
+
...props
|
|
62
|
+
}
|
|
63
|
+
));
|
|
64
|
+
ScrollAreaCorner.displayName = ScrollAreaPrimitive.Corner.displayName;
|
|
65
|
+
const ScrollArea = React.forwardRef(
|
|
66
|
+
({ children, className, scrollbars = "vertical", viewportClassName, scrollbarClassName, thumbClassName, ...props }, ref) => /* @__PURE__ */ jsxs(ScrollAreaRoot, { ref, className, ...props, children: [
|
|
67
|
+
/* @__PURE__ */ jsx(ScrollAreaViewport, { className: viewportClassName, children }),
|
|
68
|
+
scrollbars === "vertical" || scrollbars === "both" ? /* @__PURE__ */ jsx(ScrollAreaScrollbar, { orientation: "vertical", className: scrollbarClassName, children: /* @__PURE__ */ jsx(ScrollAreaThumb, { className: thumbClassName }) }) : null,
|
|
69
|
+
scrollbars === "horizontal" || scrollbars === "both" ? /* @__PURE__ */ jsx(ScrollAreaScrollbar, { orientation: "horizontal", className: scrollbarClassName, children: /* @__PURE__ */ jsx(ScrollAreaThumb, { className: thumbClassName }) }) : null,
|
|
70
|
+
/* @__PURE__ */ jsx(ScrollAreaCorner, {})
|
|
71
|
+
] })
|
|
72
|
+
);
|
|
73
|
+
ScrollArea.displayName = "ScrollArea";
|
|
74
|
+
export {
|
|
75
|
+
ScrollArea,
|
|
76
|
+
ScrollAreaCorner,
|
|
77
|
+
ScrollAreaRoot,
|
|
78
|
+
ScrollAreaScrollbar,
|
|
79
|
+
ScrollAreaThumb,
|
|
80
|
+
ScrollAreaViewport
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=scroll-area.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/primitives/scroll-area.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\n\n/**\n * DS-styled scrollable container. Wraps Radix `ScrollArea` with token-driven\n * thumb / track styling that matches the rest of the design system instead of\n * native OS scrollbars (which render inconsistently across macOS / Windows /\n * Linux).\n *\n * No dedicated Figma node \u2014 DS Open Mercato library did not ship a `ScrollArea`\n * master component at the time this primitive was authored. Styling is inferred\n * from the DS scrollbar token decisions used elsewhere (muted-foreground thumb,\n * transparent track, hover state, `rounded-full` thumb shape).\n *\n * Usage \u2014 primary single-element form:\n *\n * ```tsx\n * <ScrollArea className=\"h-72\">\n * <div className=\"p-4\">{children}</div>\n * </ScrollArea>\n * ```\n *\n * For advanced layouts that need separate access to the `Viewport`,\n * `Scrollbar`, or `Corner`, use the compound API directly:\n *\n * ```tsx\n * <ScrollAreaRoot className=\"h-72\">\n * <ScrollAreaViewport>{children}</ScrollAreaViewport>\n * <ScrollAreaScrollbar orientation=\"vertical\">\n * <ScrollAreaThumb />\n * </ScrollAreaScrollbar>\n * <ScrollAreaCorner />\n * </ScrollAreaRoot>\n * ```\n */\n\nconst ScrollAreaRoot = React.forwardRef<\n React.ElementRef<typeof ScrollAreaPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <ScrollAreaPrimitive.Root\n ref={ref}\n data-slot=\"scroll-area-root\"\n className={cn('relative overflow-hidden', className)}\n {...props}\n />\n))\nScrollAreaRoot.displayName = ScrollAreaPrimitive.Root.displayName\n\nconst ScrollAreaViewport = React.forwardRef<\n React.ElementRef<typeof ScrollAreaPrimitive.Viewport>,\n React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Viewport>\n>(({ className, ...props }, ref) => (\n <ScrollAreaPrimitive.Viewport\n ref={ref}\n data-slot=\"scroll-area-viewport\"\n className={cn('h-full w-full rounded-[inherit]', className)}\n {...props}\n />\n))\nScrollAreaViewport.displayName = ScrollAreaPrimitive.Viewport.displayName\n\nconst ScrollAreaScrollbar = React.forwardRef<\n React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,\n React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>\n>(({ className, orientation = 'vertical', ...props }, ref) => (\n <ScrollAreaPrimitive.ScrollAreaScrollbar\n ref={ref}\n orientation={orientation}\n data-slot=\"scroll-area-scrollbar\"\n className={cn(\n 'flex touch-none select-none transition-colors',\n orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent p-px',\n orientation === 'horizontal' && 'h-2.5 w-full border-t border-t-transparent p-px',\n className,\n )}\n {...props}\n />\n))\nScrollAreaScrollbar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName\n\nconst ScrollAreaThumb = React.forwardRef<\n React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaThumb>,\n React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaThumb>\n>(({ className, ...props }, ref) => (\n <ScrollAreaPrimitive.ScrollAreaThumb\n ref={ref}\n data-slot=\"scroll-area-thumb\"\n className={cn(\n 'relative flex-1 rounded-full bg-muted-foreground/30 transition-colors hover:bg-muted-foreground/50',\n className,\n )}\n {...props}\n />\n))\nScrollAreaThumb.displayName = ScrollAreaPrimitive.ScrollAreaThumb.displayName\n\nconst ScrollAreaCorner = React.forwardRef<\n React.ElementRef<typeof ScrollAreaPrimitive.Corner>,\n React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Corner>\n>(({ className, ...props }, ref) => (\n <ScrollAreaPrimitive.Corner\n ref={ref}\n data-slot=\"scroll-area-corner\"\n className={cn('bg-transparent', className)}\n {...props}\n />\n))\nScrollAreaCorner.displayName = ScrollAreaPrimitive.Corner.displayName\n\ntype ScrollAreaProps = React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> & {\n /**\n * Which scrollbars to render. Defaults to `'vertical'` \u2014 the most common\n * case. Use `'both'` for two-axis scroll (e.g. wide table preview) or\n * `'horizontal'` for a horizontal-only carousel.\n */\n scrollbars?: 'vertical' | 'horizontal' | 'both'\n /** Optional className applied to the inner `Viewport`. */\n viewportClassName?: string\n /** Optional className applied to every `Scrollbar`. */\n scrollbarClassName?: string\n /** Optional className applied to every `Thumb`. */\n thumbClassName?: string\n}\n\n/**\n * Convenience wrapper that composes Root + Viewport + one or two Scrollbars +\n * Corner in a single element. Reach for `ScrollAreaRoot` + sub-components\n * directly when the wrapper's defaults don't fit (e.g. you need conditional\n * scrollbar rendering or a custom viewport).\n */\nconst ScrollArea = React.forwardRef<\n React.ElementRef<typeof ScrollAreaPrimitive.Root>,\n ScrollAreaProps\n>(\n (\n { children, className, scrollbars = 'vertical', viewportClassName, scrollbarClassName, thumbClassName, ...props },\n ref,\n ) => (\n <ScrollAreaRoot ref={ref} className={className} {...props}>\n <ScrollAreaViewport className={viewportClassName}>{children}</ScrollAreaViewport>\n {(scrollbars === 'vertical' || scrollbars === 'both') ? (\n <ScrollAreaScrollbar orientation=\"vertical\" className={scrollbarClassName}>\n <ScrollAreaThumb className={thumbClassName} />\n </ScrollAreaScrollbar>\n ) : null}\n {(scrollbars === 'horizontal' || scrollbars === 'both') ? (\n <ScrollAreaScrollbar orientation=\"horizontal\" className={scrollbarClassName}>\n <ScrollAreaThumb className={thumbClassName} />\n </ScrollAreaScrollbar>\n ) : null}\n <ScrollAreaCorner />\n </ScrollAreaRoot>\n ),\n)\nScrollArea.displayName = 'ScrollArea'\n\nexport {\n ScrollArea,\n ScrollAreaRoot,\n ScrollAreaViewport,\n ScrollAreaScrollbar,\n ScrollAreaThumb,\n ScrollAreaCorner,\n}\n"],
|
|
5
|
+
"mappings": ";AA4CE,cAmGE,YAnGF;AA1CF,YAAY,WAAW;AACvB,YAAY,yBAAyB;AAErC,SAAS,UAAU;AAmCnB,MAAM,iBAAiB,MAAM,WAG3B,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAC1B;AAAA,EAAC,oBAAoB;AAAA,EAApB;AAAA,IACC;AAAA,IACA,aAAU;AAAA,IACV,WAAW,GAAG,4BAA4B,SAAS;AAAA,IAClD,GAAG;AAAA;AACN,CACD;AACD,eAAe,cAAc,oBAAoB,KAAK;AAEtD,MAAM,qBAAqB,MAAM,WAG/B,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAC1B;AAAA,EAAC,oBAAoB;AAAA,EAApB;AAAA,IACC;AAAA,IACA,aAAU;AAAA,IACV,WAAW,GAAG,mCAAmC,SAAS;AAAA,IACzD,GAAG;AAAA;AACN,CACD;AACD,mBAAmB,cAAc,oBAAoB,SAAS;AAE9D,MAAM,sBAAsB,MAAM,WAGhC,CAAC,EAAE,WAAW,cAAc,YAAY,GAAG,MAAM,GAAG,QACpD;AAAA,EAAC,oBAAoB;AAAA,EAApB;AAAA,IACC;AAAA,IACA;AAAA,IACA,aAAU;AAAA,IACV,WAAW;AAAA,MACT;AAAA,MACA,gBAAgB,cAAc;AAAA,MAC9B,gBAAgB,gBAAgB;AAAA,MAChC;AAAA,IACF;AAAA,IACC,GAAG;AAAA;AACN,CACD;AACD,oBAAoB,cAAc,oBAAoB,oBAAoB;AAE1E,MAAM,kBAAkB,MAAM,WAG5B,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAC1B;AAAA,EAAC,oBAAoB;AAAA,EAApB;AAAA,IACC;AAAA,IACA,aAAU;AAAA,IACV,WAAW;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,IACC,GAAG;AAAA;AACN,CACD;AACD,gBAAgB,cAAc,oBAAoB,gBAAgB;AAElE,MAAM,mBAAmB,MAAM,WAG7B,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAC1B;AAAA,EAAC,oBAAoB;AAAA,EAApB;AAAA,IACC;AAAA,IACA,aAAU;AAAA,IACV,WAAW,GAAG,kBAAkB,SAAS;AAAA,IACxC,GAAG;AAAA;AACN,CACD;AACD,iBAAiB,cAAc,oBAAoB,OAAO;AAuB1D,MAAM,aAAa,MAAM;AAAA,EAIvB,CACE,EAAE,UAAU,WAAW,aAAa,YAAY,mBAAmB,oBAAoB,gBAAgB,GAAG,MAAM,GAChH,QAEA,qBAAC,kBAAe,KAAU,WAAuB,GAAG,OAClD;AAAA,wBAAC,sBAAmB,WAAW,mBAAoB,UAAS;AAAA,IAC1D,eAAe,cAAc,eAAe,SAC5C,oBAAC,uBAAoB,aAAY,YAAW,WAAW,oBACrD,8BAAC,mBAAgB,WAAW,gBAAgB,GAC9C,IACE;AAAA,IACF,eAAe,gBAAgB,eAAe,SAC9C,oBAAC,uBAAoB,aAAY,cAAa,WAAW,oBACvD,8BAAC,mBAAgB,WAAW,gBAAgB,GAC9C,IACE;AAAA,IACJ,oBAAC,oBAAiB;AAAA,KACpB;AAEJ;AACA,WAAW,cAAc;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
|
5
|
+
import { cva } from "class-variance-authority";
|
|
6
|
+
import { cn } from "@open-mercato/shared/lib/utils";
|
|
7
|
+
const SegmentedControlContext = React.createContext({
|
|
8
|
+
size: "default",
|
|
9
|
+
disabled: false
|
|
10
|
+
});
|
|
11
|
+
const trackVariants = cva(
|
|
12
|
+
// Pill-shaped track with subtle inner padding so selected items render
|
|
13
|
+
// a smaller inner pill (the iOS slide-thumb effect). Track is muted —
|
|
14
|
+
// selected item raises with bg-background + shadow-sm. We use full
|
|
15
|
+
// `bg-muted` (not /40) so the contrast between track and a selected
|
|
16
|
+
// bg-background item stays visible in the light theme; in dark mode
|
|
17
|
+
// the token already darkens further so the contrast holds.
|
|
18
|
+
//
|
|
19
|
+
// Height math (box-border on every element):
|
|
20
|
+
// default → track h-8 (32px) − 2px border − 2px padding (p-px ×2) = 28px → matches item h-7
|
|
21
|
+
// sm → track h-7 (28px) − 2px border − 2px padding (p-px ×2) = 24px → matches item h-6
|
|
22
|
+
// Using `p-0.5` (2px each side = 4px total) instead breaks both sizes by
|
|
23
|
+
// 2px so the selected pill clips top and bottom against the track border.
|
|
24
|
+
"inline-flex w-fit gap-0 rounded-full border border-input bg-muted p-px transition-colors",
|
|
25
|
+
{
|
|
26
|
+
variants: {
|
|
27
|
+
size: {
|
|
28
|
+
sm: "h-7",
|
|
29
|
+
default: "h-8"
|
|
30
|
+
},
|
|
31
|
+
disabled: {
|
|
32
|
+
true: "cursor-not-allowed opacity-60",
|
|
33
|
+
false: ""
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
defaultVariants: {
|
|
37
|
+
size: "default",
|
|
38
|
+
disabled: false
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
const itemVariants = cva(
|
|
43
|
+
// Items are pills that fill the track minus its 2px inner padding.
|
|
44
|
+
// Selected state lifts via bg-background + shadow-sm + font-semibold
|
|
45
|
+
// (the font-weight bump gives a secondary visual cue beyond color +
|
|
46
|
+
// shadow, important when the surrounding theme is high-key and the
|
|
47
|
+
// shadow alone is hard to read). Unselected text is muted; hover
|
|
48
|
+
// only nudges color (no bg change — keeps the track flat).
|
|
49
|
+
"inline-flex items-center justify-center rounded-full font-medium transition-all outline-none focus-visible:shadow-focus disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-background data-[state=checked]:text-foreground data-[state=checked]:font-semibold data-[state=checked]:shadow-sm data-[state=unchecked]:bg-transparent data-[state=unchecked]:text-muted-foreground data-[state=unchecked]:hover:text-foreground",
|
|
50
|
+
{
|
|
51
|
+
variants: {
|
|
52
|
+
size: {
|
|
53
|
+
sm: "h-6 px-2.5 text-xs",
|
|
54
|
+
default: "h-7 px-3 text-sm"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
defaultVariants: {
|
|
58
|
+
size: "default"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
const SegmentedControl = React.forwardRef(({ className, size, disabled, children, ...props }, ref) => {
|
|
63
|
+
const ctx = React.useMemo(
|
|
64
|
+
() => ({ size: size ?? "default", disabled: disabled ?? false }),
|
|
65
|
+
[size, disabled]
|
|
66
|
+
);
|
|
67
|
+
return /* @__PURE__ */ jsx(SegmentedControlContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
|
|
68
|
+
RadioGroupPrimitive.Root,
|
|
69
|
+
{
|
|
70
|
+
ref,
|
|
71
|
+
orientation: "horizontal",
|
|
72
|
+
disabled: disabled ?? void 0,
|
|
73
|
+
"data-slot": "segmented-control",
|
|
74
|
+
className: cn(trackVariants({ size, disabled }), className),
|
|
75
|
+
...props,
|
|
76
|
+
children
|
|
77
|
+
}
|
|
78
|
+
) });
|
|
79
|
+
});
|
|
80
|
+
SegmentedControl.displayName = "SegmentedControl";
|
|
81
|
+
const SegmentedControlItem = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
82
|
+
const { size } = React.useContext(SegmentedControlContext);
|
|
83
|
+
return /* @__PURE__ */ jsx(
|
|
84
|
+
RadioGroupPrimitive.Item,
|
|
85
|
+
{
|
|
86
|
+
ref,
|
|
87
|
+
"data-slot": "segmented-control-item",
|
|
88
|
+
className: cn(itemVariants({ size }), className),
|
|
89
|
+
...props,
|
|
90
|
+
children
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
SegmentedControlItem.displayName = "SegmentedControlItem";
|
|
95
|
+
export {
|
|
96
|
+
SegmentedControl,
|
|
97
|
+
SegmentedControlItem,
|
|
98
|
+
itemVariants as segmentedControlItemVariants,
|
|
99
|
+
trackVariants as segmentedControlTrackVariants
|
|
100
|
+
};
|
|
101
|
+
//# sourceMappingURL=segmented-control.js.map
|