torch-glare 2.5.2 → 2.5.4
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/apps/lib/components/ActionButton.tsx +39 -6
- package/apps/lib/components/Badge.tsx +5 -2
- package/apps/lib/components/BadgeField.tsx +1 -1
- package/apps/lib/components/Button.tsx +4 -4
- package/apps/lib/components/Calendar.tsx +7 -17
- package/apps/lib/components/ColorPicker.tsx +1 -1
- package/apps/lib/components/DataViews/data-views.tsx +1 -1
- package/apps/lib/components/DataViews/filters/filters.tsx +1 -1
- package/apps/lib/components/DataViews/header.tsx +5 -2
- package/apps/lib/components/DataViews/panel/section.tsx +3 -1
- package/apps/lib/components/DataViews/views/board-view.tsx +3 -1
- package/apps/lib/components/DataViews/views/inbox-view.tsx +4 -1
- package/apps/lib/components/DataViews/views/pane-views.tsx +5 -1
- package/apps/lib/components/DataViews/views/table-view.tsx +67 -8
- package/apps/lib/components/DataViews/views/tree-view.tsx +5 -3
- package/apps/lib/components/Drawer.tsx +18 -1
- package/apps/lib/components/FormBuilder/context.ts +9 -1
- package/apps/lib/components/FormBuilder/submit.tsx +11 -2
- package/apps/lib/components/FormBuilder/types.ts +5 -1
- package/apps/lib/components/FormRenderer/form-renderer.tsx +15 -3
- package/apps/lib/components/FormRenderer/stepper.tsx +35 -14
- package/apps/lib/components/FormRenderer/types.ts +1 -1
- package/apps/lib/components/Input.tsx +19 -4
- package/apps/lib/components/Popover.tsx +93 -55
- package/apps/lib/components/SearchableSelect.tsx +12 -11
- package/apps/lib/components/SearchableTree.tsx +10 -11
- package/apps/lib/components/SearchableTreeDialog.tsx +10 -11
- package/apps/lib/components/SectionBlock.tsx +14 -2
- package/apps/lib/components/Select.tsx +35 -11
- package/apps/lib/components/SimpleSelect.tsx +2 -2
- package/apps/lib/components/SlideDatePicker.tsx +17 -4
- package/apps/lib/components/Stepper.tsx +329 -180
- package/apps/lib/components/Switch.tsx +2 -2
- package/apps/lib/components/Table.tsx +55 -30
- package/apps/lib/components/Textarea.tsx +29 -4
- package/apps/lib/components/TreeDropDown.tsx +18 -6
- package/apps/lib/components/TreeFolder/TreeFolder.tsx +61 -61
- package/apps/lib/registry.json +7 -16
- package/apps/lib/tsconfig.tsbuildinfo +1 -1
- package/docs/components/action-button.md +3 -1
- package/docs/components/data-views/index.md +7 -0
- package/docs/components/form-builder.md +4 -2
- package/docs/components/form-renderer.md +1 -1
- package/docs/components/stepper.md +119 -24
- package/docs/components/table.md +33 -0
- package/docs/how-to/forms-with-form-builder.md +3 -1
- package/docs/reference/tailwind-plugins.md +42 -0
- package/docs/tutorials/getting-started.md +32 -11
- package/package.json +1 -1
- package/apps/lib/components/FormStepper.tsx +0 -272
- package/docs/components/form-stepper.md +0 -250
|
@@ -1,16 +1,37 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import React, { forwardRef, HTMLAttributes, ReactNode, createContext, useContext } from "react";
|
|
4
|
-
import { cva
|
|
4
|
+
import { cva } from "class-variance-authority";
|
|
5
5
|
import { cn } from "../utils/cn";
|
|
6
6
|
import { Themes } from "../utils/types";
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Stepper — the pill-shaped multi-step indicator from Figma `FormStepper-1.0`.
|
|
10
|
+
*
|
|
11
|
+
* This is the single stepper in the library: it merges the Figma pill (three semantic types with
|
|
12
|
+
* resting/hover/selected states and a corner badge) with the orientation, sizes and connector that
|
|
13
|
+
* used to live in a separate generic component.
|
|
14
|
+
*
|
|
15
|
+
* `type` + `selected` express the four states the generic one had:
|
|
16
|
+
* pending → type="default" unselected · active → type="default" selected
|
|
17
|
+
* completed → type="success" · error → type="negative"
|
|
18
|
+
*
|
|
19
|
+
* Not to be confused with `FormRenderer.Stepper`, which is the wizard *behaviour* (step state,
|
|
20
|
+
* validation, Back/Next) and renders this component as its rail.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
type StepperType = "default" | "success" | "negative";
|
|
26
|
+
type StepperOrientation = "horizontal" | "vertical";
|
|
27
|
+
type StepperSize = "S" | "M" | "L";
|
|
28
|
+
|
|
8
29
|
// ─── Context ─────────────────────────────────────────────────────────────────
|
|
9
30
|
|
|
10
31
|
interface StepperContextValue {
|
|
11
32
|
activeStep: number;
|
|
12
|
-
orientation:
|
|
13
|
-
size:
|
|
33
|
+
orientation: StepperOrientation;
|
|
34
|
+
size: StepperSize;
|
|
14
35
|
}
|
|
15
36
|
|
|
16
37
|
const StepperContext = createContext<StepperContextValue>({
|
|
@@ -23,11 +44,20 @@ const useStepperContext = () => useContext(StepperContext);
|
|
|
23
44
|
|
|
24
45
|
// ─── Stepper Root ────────────────────────────────────────────────────────────
|
|
25
46
|
|
|
26
|
-
|
|
47
|
+
interface StepperProps extends HTMLAttributes<HTMLDivElement> {
|
|
48
|
+
theme?: Themes;
|
|
49
|
+
activeStep?: number;
|
|
50
|
+
/** Direction the step pills flow. Vertical is the FormRenderer rail. */
|
|
51
|
+
orientation?: StepperOrientation;
|
|
52
|
+
/** Scales the pill, indicator and label. Figma defines M; S and L scale from it. */
|
|
53
|
+
size?: StepperSize;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const stepperStyles = cva(["flex"], {
|
|
27
57
|
variants: {
|
|
28
58
|
orientation: {
|
|
29
|
-
horizontal: "flex-row items-
|
|
30
|
-
vertical: "flex-col",
|
|
59
|
+
horizontal: "flex-row items-center gap-3",
|
|
60
|
+
vertical: "flex-col items-start gap-[4px]",
|
|
31
61
|
},
|
|
32
62
|
},
|
|
33
63
|
defaultVariants: {
|
|
@@ -35,21 +65,9 @@ const stepperStyles = cva(["flex gap-0"], {
|
|
|
35
65
|
},
|
|
36
66
|
});
|
|
37
67
|
|
|
38
|
-
interface StepperProps extends HTMLAttributes<HTMLDivElement>, VariantProps<typeof stepperStyles> {
|
|
39
|
-
theme?: Themes;
|
|
40
|
-
activeStep?: number;
|
|
41
|
-
size?: "S" | "M" | "L";
|
|
42
|
-
}
|
|
43
|
-
|
|
44
68
|
const Stepper = forwardRef<HTMLDivElement, StepperProps>(
|
|
45
|
-
({ className,
|
|
46
|
-
<StepperContext.Provider
|
|
47
|
-
value={{
|
|
48
|
-
activeStep,
|
|
49
|
-
orientation: orientation ?? "horizontal",
|
|
50
|
-
size: size ?? "M",
|
|
51
|
-
}}
|
|
52
|
-
>
|
|
69
|
+
({ className, theme, activeStep = 0, orientation = "horizontal", size = "M", ...props }, ref) => (
|
|
70
|
+
<StepperContext.Provider value={{ activeStep, orientation, size }}>
|
|
53
71
|
<div
|
|
54
72
|
ref={ref}
|
|
55
73
|
data-theme={theme}
|
|
@@ -62,43 +80,74 @@ const Stepper = forwardRef<HTMLDivElement, StepperProps>(
|
|
|
62
80
|
);
|
|
63
81
|
Stepper.displayName = "Stepper";
|
|
64
82
|
|
|
65
|
-
// ─── Step
|
|
83
|
+
// ─── Step (the pill wrapper) ─────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
const stepStyles = cva(
|
|
86
|
+
[
|
|
87
|
+
"group/step inline-flex items-center shrink-0",
|
|
88
|
+
"rounded-full p-[2px] max-w-[144px]",
|
|
89
|
+
"transition-all duration-200 ease-in-out",
|
|
90
|
+
"cursor-pointer select-none outline-none",
|
|
91
|
+
],
|
|
92
|
+
{
|
|
93
|
+
variants: {
|
|
94
|
+
// M is the size Figma draws (28px pill / 24px indicator); S and L scale from it.
|
|
95
|
+
size: {
|
|
96
|
+
S: "h-[24px]",
|
|
97
|
+
M: "h-[28px]",
|
|
98
|
+
L: "h-[34px]",
|
|
99
|
+
},
|
|
100
|
+
// Selection drives the pill background; a raised surface appears on hover for unselected
|
|
101
|
+
// pills. `drop-shadow` rather than `shadow`: Figma draws a filter that follows the pill's
|
|
102
|
+
// rounded silhouette, where a box-shadow would ring the layout box.
|
|
103
|
+
selected: {
|
|
104
|
+
true: [
|
|
105
|
+
"bg-background-presentation-button-hover",
|
|
106
|
+
// Figma's onSelect and onSelect-Hoverd carry the same shadow — a selected pill does not
|
|
107
|
+
// deepen when you hover it.
|
|
108
|
+
"drop-shadow-[0_0_16px_rgba(0,0,0,0.2)]",
|
|
109
|
+
],
|
|
110
|
+
false: [
|
|
111
|
+
"bg-transparent",
|
|
112
|
+
// Figma binds this to the raw palette colour `White/00`, which would stay solid white in
|
|
113
|
+
// dark theme. `form-base` is the themable equivalent — identical in light, and a raised
|
|
114
|
+
// surface rather than a white slab in dark.
|
|
115
|
+
"hover:bg-background-presentation-form-base",
|
|
116
|
+
"hover:drop-shadow-[0_0_16px_rgba(0,0,0,0.05)]",
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
defaultVariants: {
|
|
121
|
+
selected: false,
|
|
122
|
+
size: "M",
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
);
|
|
66
126
|
|
|
67
|
-
interface StepProps extends HTMLAttributes<HTMLDivElement> {
|
|
127
|
+
interface StepProps extends Omit<HTMLAttributes<HTMLDivElement>, "type"> {
|
|
68
128
|
index?: number;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
isError?: boolean;
|
|
129
|
+
type?: StepperType;
|
|
130
|
+
selected?: boolean;
|
|
72
131
|
}
|
|
73
132
|
|
|
74
133
|
const Step = forwardRef<HTMLDivElement, StepProps>(
|
|
75
|
-
({ className, index = 0,
|
|
76
|
-
const { activeStep,
|
|
77
|
-
|
|
78
|
-
const computedActive = isActive ?? index === activeStep;
|
|
79
|
-
const computedCompleted = isCompleted ?? index < activeStep;
|
|
80
|
-
const computedError = isError ?? false;
|
|
134
|
+
({ className, index = 0, type = "default", selected, children, ...props }, ref) => {
|
|
135
|
+
const { activeStep, size } = useStepperContext();
|
|
136
|
+
const isSelected = selected ?? index === activeStep;
|
|
81
137
|
|
|
82
138
|
return (
|
|
83
139
|
<div
|
|
84
140
|
ref={ref}
|
|
85
|
-
data-
|
|
86
|
-
data-
|
|
87
|
-
|
|
88
|
-
data-orientation={orientation}
|
|
89
|
-
className={cn(
|
|
90
|
-
"flex group/step",
|
|
91
|
-
orientation === "horizontal" ? "shrink-0 flex-col items-center gap-2" : "flex-row gap-3",
|
|
92
|
-
className,
|
|
93
|
-
)}
|
|
141
|
+
data-selected={isSelected || undefined}
|
|
142
|
+
data-type={type}
|
|
143
|
+
className={cn(stepStyles({ selected: isSelected, size }), className)}
|
|
94
144
|
{...props}
|
|
95
145
|
>
|
|
96
146
|
{React.Children.map(children, (child) => {
|
|
97
147
|
if (!React.isValidElement(child)) return child;
|
|
98
148
|
return React.cloneElement(child as React.ReactElement<Record<string, unknown>>, {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
_error: computedError,
|
|
149
|
+
_selected: isSelected,
|
|
150
|
+
_type: type,
|
|
102
151
|
_index: index,
|
|
103
152
|
});
|
|
104
153
|
})}
|
|
@@ -108,60 +157,117 @@ const Step = forwardRef<HTMLDivElement, StepProps>(
|
|
|
108
157
|
);
|
|
109
158
|
Step.displayName = "Step";
|
|
110
159
|
|
|
111
|
-
// ───
|
|
160
|
+
// ─── StepIndicator (circle badge) ────────────────────────────────────────────
|
|
112
161
|
|
|
113
162
|
const stepIndicatorStyles = cva(
|
|
114
163
|
[
|
|
115
|
-
"flex items-center justify-center shrink-0
|
|
116
|
-
"
|
|
117
|
-
"transition-
|
|
118
|
-
"typography-body-
|
|
119
|
-
"[&_i]:leading-none [&_i]:flex [&_i]:items-center [&_i]:justify-center",
|
|
164
|
+
"relative inline-flex items-center justify-center shrink-0",
|
|
165
|
+
"rounded-full",
|
|
166
|
+
"transition-colors duration-200 ease-in-out",
|
|
167
|
+
"typography-body-medium-medium",
|
|
120
168
|
],
|
|
121
169
|
{
|
|
122
170
|
variants: {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
171
|
+
// The pill is `p-[2px]`, so the indicator is always the pill height minus 4. It is a capsule,
|
|
172
|
+
// not a circle: `min-w` + `px` let a two-digit step number widen it, as Figma draws.
|
|
173
|
+
size: {
|
|
174
|
+
S: "h-[20px] min-w-[20px] px-[5px] text-[12px]",
|
|
175
|
+
M: "h-[24px] min-w-[24px] px-[6px] text-[14px]",
|
|
176
|
+
L: "h-[30px] min-w-[30px] px-[7px] text-[16px]",
|
|
177
|
+
},
|
|
178
|
+
type: {
|
|
179
|
+
// `ring` rather than `border`: a ring is a box-shadow and takes no layout space, so the
|
|
180
|
+
// `min-w`/`px` capsule above lands on Figma's exact width. A 3px border would eat the
|
|
181
|
+
// content box under `border-box` and push a single digit past it.
|
|
182
|
+
default: ["ring-[3px] ring-inset"],
|
|
183
|
+
success: [
|
|
184
|
+
"bg-background-presentation-state-success-primary text-content-presentation-global-primary-inverse",
|
|
128
185
|
],
|
|
129
|
-
|
|
130
|
-
"bg-background-presentation-state-
|
|
131
|
-
"border-border-presentation-state-focus",
|
|
132
|
-
"text-content-presentation-state-information",
|
|
186
|
+
negative: [
|
|
187
|
+
"bg-background-presentation-state-negative-primary text-content-presentation-global-primary-inverse",
|
|
133
188
|
],
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
189
|
+
},
|
|
190
|
+
// Only meaningful for type=default. Selected = filled blue, otherwise
|
|
191
|
+
// ring color comes from the resting/hover compound variants below.
|
|
192
|
+
selected: {
|
|
193
|
+
true: "",
|
|
194
|
+
false: "",
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
defaultVariants: {
|
|
198
|
+
type: "default",
|
|
199
|
+
selected: false,
|
|
200
|
+
size: "M",
|
|
201
|
+
},
|
|
202
|
+
compoundVariants: [
|
|
203
|
+
// Default type, not selected: gray ring + gray number at rest;
|
|
204
|
+
// hover (driven by parent .group/step) flips ring blue, number primary.
|
|
205
|
+
{
|
|
206
|
+
type: "default",
|
|
207
|
+
selected: false,
|
|
208
|
+
className: [
|
|
209
|
+
"bg-transparent ring-border-presentation-stepper-default text-content-presentation-global-secondary",
|
|
210
|
+
"group-hover/step:ring-content-presentation-state-information",
|
|
211
|
+
"group-hover/step:text-content-presentation-global-primary",
|
|
138
212
|
],
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
213
|
+
},
|
|
214
|
+
// Default type, selected: solid blue fill, inverse number, no ring.
|
|
215
|
+
{
|
|
216
|
+
type: "default",
|
|
217
|
+
selected: true,
|
|
218
|
+
className: [
|
|
219
|
+
"bg-background-presentation-state-information-primary ring-transparent text-content-presentation-global-primary-inverse",
|
|
143
220
|
],
|
|
144
221
|
},
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const stepIndicatorBadgeStyles = cva(
|
|
227
|
+
[
|
|
228
|
+
"absolute",
|
|
229
|
+
"rounded-full",
|
|
230
|
+
"inline-flex items-center justify-center",
|
|
231
|
+
"border border-background-presentation-body-primary",
|
|
232
|
+
// Figma sits the glyph in a fixed square frame. Without pinning the box, a Remix icon's
|
|
233
|
+
// advance width is wider than its font-size and pushes the badge past its designed 15px.
|
|
234
|
+
"[&_i]:leading-none [&_i]:flex [&_i]:items-center [&_i]:justify-center",
|
|
235
|
+
// LTR: badge top-right; RTL: badge top-left
|
|
236
|
+
"ltr:right-[-4.94px] rtl:left-[-4.94px]",
|
|
237
|
+
],
|
|
238
|
+
{
|
|
239
|
+
variants: {
|
|
145
240
|
size: {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
241
|
+
// Padding absorbs the 1px ring: Figma draws its stroke inside the 15px frame, where
|
|
242
|
+
// `border-box` adds it on top. Its documented 2.667px would render a 16.9px badge.
|
|
243
|
+
S: "h-[13px] min-w-[13px] px-[1.35px] -top-[4px] [&_i]:text-[8.3px] [&_i]:size-[8.3px]",
|
|
244
|
+
M: "h-[15px] min-w-[15px] px-[1.7px] -top-[5px] [&_i]:text-[9.6px] [&_i]:size-[9.6px]",
|
|
245
|
+
L: "h-[18px] min-w-[18px] px-[2.25px] -top-[6px] [&_i]:text-[11.5px] [&_i]:size-[11.5px]",
|
|
246
|
+
},
|
|
247
|
+
type: {
|
|
248
|
+
success:
|
|
249
|
+
"bg-background-presentation-state-success-primary text-content-presentation-global-primary-inverse",
|
|
250
|
+
negative:
|
|
251
|
+
"bg-background-presentation-state-negative-primary text-content-presentation-global-primary-inverse",
|
|
149
252
|
},
|
|
150
253
|
},
|
|
151
254
|
defaultVariants: {
|
|
152
|
-
state: "pending",
|
|
153
255
|
size: "M",
|
|
154
256
|
},
|
|
155
257
|
},
|
|
156
258
|
);
|
|
157
259
|
|
|
158
260
|
interface StepIndicatorProps extends HTMLAttributes<HTMLDivElement> {
|
|
261
|
+
/** Icon in the corner badge (success/negative types only). */
|
|
262
|
+
badgeIcon?: ReactNode;
|
|
263
|
+
/** Replaces the step number in the circle. */
|
|
159
264
|
icon?: ReactNode;
|
|
265
|
+
/** Replaces the circle's content when the step is `success`. */
|
|
160
266
|
completedIcon?: ReactNode;
|
|
267
|
+
/** Replaces the circle's content when the step is `negative`. */
|
|
161
268
|
errorIcon?: ReactNode;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
_error?: boolean;
|
|
269
|
+
_selected?: boolean;
|
|
270
|
+
_type?: StepperType;
|
|
165
271
|
_index?: number;
|
|
166
272
|
}
|
|
167
273
|
|
|
@@ -169,12 +275,12 @@ const StepIndicator = forwardRef<HTMLDivElement, StepIndicatorProps>(
|
|
|
169
275
|
(
|
|
170
276
|
{
|
|
171
277
|
className,
|
|
278
|
+
badgeIcon,
|
|
172
279
|
icon,
|
|
173
280
|
completedIcon,
|
|
174
281
|
errorIcon,
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
_error,
|
|
282
|
+
_selected,
|
|
283
|
+
_type = "default",
|
|
178
284
|
_index = 0,
|
|
179
285
|
children,
|
|
180
286
|
...props
|
|
@@ -182,139 +288,181 @@ const StepIndicator = forwardRef<HTMLDivElement, StepIndicatorProps>(
|
|
|
182
288
|
ref,
|
|
183
289
|
) => {
|
|
184
290
|
const { size } = useStepperContext();
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
};
|
|
291
|
+
const showBadge = _type === "success" || _type === "negative";
|
|
292
|
+
const defaultBadgeIcon =
|
|
293
|
+
_type === "success" ? <i className="ri-check-line" /> : <i className="ri-information-fill" />;
|
|
294
|
+
|
|
295
|
+
// Most specific first: a per-state icon, then a general one, then children, then the number.
|
|
296
|
+
const content =
|
|
297
|
+
(_type === "negative" && errorIcon) ||
|
|
298
|
+
(_type === "success" && completedIcon) ||
|
|
299
|
+
icon ||
|
|
300
|
+
children ||
|
|
301
|
+
_index + 1;
|
|
197
302
|
|
|
198
303
|
return (
|
|
199
|
-
<div
|
|
200
|
-
{
|
|
304
|
+
<div
|
|
305
|
+
ref={ref}
|
|
306
|
+
className={cn(stepIndicatorStyles({ type: _type, selected: !!_selected, size }), className)}
|
|
307
|
+
{...props}
|
|
308
|
+
>
|
|
309
|
+
{content}
|
|
310
|
+
{showBadge && (
|
|
311
|
+
<span
|
|
312
|
+
className={cn(
|
|
313
|
+
stepIndicatorBadgeStyles({
|
|
314
|
+
type: _type as "success" | "negative",
|
|
315
|
+
size,
|
|
316
|
+
}),
|
|
317
|
+
)}
|
|
318
|
+
aria-hidden
|
|
319
|
+
>
|
|
320
|
+
{badgeIcon ?? defaultBadgeIcon}
|
|
321
|
+
</span>
|
|
322
|
+
)}
|
|
201
323
|
</div>
|
|
202
324
|
);
|
|
203
325
|
},
|
|
204
326
|
);
|
|
205
327
|
StepIndicator.displayName = "StepIndicator";
|
|
206
328
|
|
|
207
|
-
// ───
|
|
329
|
+
// ─── StepLabel ───────────────────────────────────────────────────────────────
|
|
208
330
|
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
331
|
+
const stepLabelStyles = cva(
|
|
332
|
+
[
|
|
333
|
+
// Figma caps the label and ellipsizes it, so a long step title clips inside the pill rather
|
|
334
|
+
// than stretching it. Callers that need more room pass their own `max-w`.
|
|
335
|
+
"max-w-[106px] truncate",
|
|
336
|
+
"transition-[padding,color] duration-200 ease-in-out",
|
|
337
|
+
// At rest the label sits 6px from the circle. On hover (parent pill
|
|
338
|
+
// hovered) the gap grows to 9px — matches figma onHover/onSelect-Hoverd.
|
|
339
|
+
// The outer edge keeps a constant 12px.
|
|
340
|
+
"ltr:pl-[6px] ltr:pr-[12px] rtl:pr-[6px] rtl:pl-[12px]",
|
|
341
|
+
"group-hover/step:ltr:pl-[9px] group-hover/step:rtl:pr-[9px]",
|
|
342
|
+
],
|
|
343
|
+
{
|
|
344
|
+
variants: {
|
|
345
|
+
size: {
|
|
346
|
+
S: "typography-body-small-medium",
|
|
347
|
+
M: "typography-body-small-medium",
|
|
348
|
+
L: "typography-body-medium-medium",
|
|
349
|
+
},
|
|
350
|
+
selected: {
|
|
351
|
+
true: "text-content-presentation-global-primary-light",
|
|
352
|
+
false: "",
|
|
353
|
+
},
|
|
354
|
+
// Resting colour depends on the type, which is why this cannot be driven by `selected` alone:
|
|
355
|
+
// a default step reads de-emphasized until you hover it, while a success/negative step is
|
|
356
|
+
// already resolved and reads at full strength.
|
|
357
|
+
type: {
|
|
358
|
+
default: "",
|
|
359
|
+
success: "",
|
|
360
|
+
negative: "",
|
|
361
|
+
},
|
|
214
362
|
},
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
363
|
+
defaultVariants: {
|
|
364
|
+
selected: false,
|
|
365
|
+
type: "default",
|
|
366
|
+
size: "M",
|
|
218
367
|
},
|
|
368
|
+
compoundVariants: [
|
|
369
|
+
{
|
|
370
|
+
selected: false,
|
|
371
|
+
type: "default",
|
|
372
|
+
className: [
|
|
373
|
+
"text-content-presentation-global-secondary",
|
|
374
|
+
"group-hover/step:text-content-presentation-global-primary",
|
|
375
|
+
],
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
selected: false,
|
|
379
|
+
type: "success",
|
|
380
|
+
className: "text-content-presentation-global-primary",
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
selected: false,
|
|
384
|
+
type: "negative",
|
|
385
|
+
className: "text-content-presentation-global-primary",
|
|
386
|
+
},
|
|
387
|
+
],
|
|
219
388
|
},
|
|
220
|
-
|
|
221
|
-
orientation: "horizontal",
|
|
222
|
-
state: "pending",
|
|
223
|
-
},
|
|
224
|
-
compoundVariants: [
|
|
225
|
-
{
|
|
226
|
-
orientation: "horizontal",
|
|
227
|
-
className: "self-start",
|
|
228
|
-
},
|
|
229
|
-
],
|
|
230
|
-
});
|
|
389
|
+
);
|
|
231
390
|
|
|
232
|
-
interface
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
_error?: boolean;
|
|
391
|
+
interface StepLabelProps extends HTMLAttributes<HTMLDivElement> {
|
|
392
|
+
_selected?: boolean;
|
|
393
|
+
_type?: StepperType;
|
|
236
394
|
_index?: number;
|
|
237
395
|
}
|
|
238
396
|
|
|
239
|
-
const
|
|
397
|
+
const StepLabel = forwardRef<HTMLDivElement, StepLabelProps>(
|
|
240
398
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- excluded from {...props} spread
|
|
241
|
-
({ className,
|
|
242
|
-
const {
|
|
243
|
-
|
|
399
|
+
({ className, _selected, _type = "default", _index: _i, ...props }, ref) => {
|
|
400
|
+
const { size } = useStepperContext();
|
|
244
401
|
return (
|
|
245
402
|
<div
|
|
246
403
|
ref={ref}
|
|
247
|
-
className={cn(
|
|
248
|
-
connectorStyles({
|
|
249
|
-
orientation,
|
|
250
|
-
state: _completed ? "completed" : "pending",
|
|
251
|
-
}),
|
|
252
|
-
className,
|
|
253
|
-
)}
|
|
404
|
+
className={cn(stepLabelStyles({ selected: !!_selected, type: _type, size }), className)}
|
|
254
405
|
{...props}
|
|
255
406
|
/>
|
|
256
407
|
);
|
|
257
408
|
},
|
|
258
409
|
);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
// ─── Step Label ──────────────────────────────────────────────────────────────
|
|
410
|
+
StepLabel.displayName = "StepLabel";
|
|
262
411
|
|
|
263
|
-
|
|
264
|
-
_active?: boolean;
|
|
265
|
-
_completed?: boolean;
|
|
266
|
-
_error?: boolean;
|
|
267
|
-
_index?: number;
|
|
268
|
-
}
|
|
412
|
+
// ─── StepConnector (the bar between steps) ───────────────────────────────────
|
|
269
413
|
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
"
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
414
|
+
const stepConnectorStyles = cva(
|
|
415
|
+
["shrink-0 rounded-full transition-colors duration-200 ease-in-out"],
|
|
416
|
+
{
|
|
417
|
+
variants: {
|
|
418
|
+
orientation: {
|
|
419
|
+
horizontal: "h-[3px] w-[16px] self-center",
|
|
420
|
+
vertical: "w-[3px] h-[16px] mt-[2px]",
|
|
421
|
+
},
|
|
422
|
+
size: {
|
|
423
|
+
S: "",
|
|
424
|
+
M: "",
|
|
425
|
+
L: "",
|
|
426
|
+
},
|
|
427
|
+
completed: {
|
|
428
|
+
true: "bg-border-presentation-state-focus",
|
|
429
|
+
false: "bg-border-presentation-stepper-default",
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
defaultVariants: {
|
|
433
|
+
orientation: "horizontal",
|
|
434
|
+
size: "M",
|
|
435
|
+
completed: false,
|
|
436
|
+
},
|
|
437
|
+
compoundVariants: [
|
|
438
|
+
// Vertical: centre the bar under the indicator — the pill's 2px padding, plus half the
|
|
439
|
+
// indicator, minus half the bar. Which is why the inset tracks the size.
|
|
440
|
+
{ orientation: "vertical", size: "S", className: "ms-[10.5px]" },
|
|
441
|
+
{ orientation: "vertical", size: "M", className: "ms-[12.5px]" },
|
|
442
|
+
{ orientation: "vertical", size: "L", className: "ms-[15.5px]" },
|
|
443
|
+
],
|
|
444
|
+
},
|
|
289
445
|
);
|
|
290
|
-
StepLabel.displayName = "StepLabel";
|
|
291
|
-
|
|
292
|
-
// ─── Step Description ────────────────────────────────────────────────────────
|
|
293
446
|
|
|
294
|
-
interface
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
_error?: boolean;
|
|
298
|
-
_index?: number;
|
|
447
|
+
interface StepConnectorProps extends HTMLAttributes<HTMLDivElement> {
|
|
448
|
+
/** Draws the bar in the focus colour, for the run of steps already passed. */
|
|
449
|
+
completed?: boolean;
|
|
299
450
|
}
|
|
300
451
|
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
{...props}
|
|
314
|
-
/>
|
|
315
|
-
),
|
|
452
|
+
const StepConnector = forwardRef<HTMLDivElement, StepConnectorProps>(
|
|
453
|
+
({ className, completed = false, ...props }, ref) => {
|
|
454
|
+
const { orientation, size } = useStepperContext();
|
|
455
|
+
return (
|
|
456
|
+
<div
|
|
457
|
+
ref={ref}
|
|
458
|
+
aria-hidden
|
|
459
|
+
className={cn(stepConnectorStyles({ orientation, size, completed }), className)}
|
|
460
|
+
{...props}
|
|
461
|
+
/>
|
|
462
|
+
);
|
|
463
|
+
},
|
|
316
464
|
);
|
|
317
|
-
|
|
465
|
+
StepConnector.displayName = "StepConnector";
|
|
318
466
|
|
|
319
467
|
// ─── Exports ─────────────────────────────────────────────────────────────────
|
|
320
468
|
|
|
@@ -322,10 +470,11 @@ export {
|
|
|
322
470
|
Stepper,
|
|
323
471
|
Step,
|
|
324
472
|
StepIndicator,
|
|
325
|
-
StepConnector,
|
|
326
473
|
StepLabel,
|
|
327
|
-
|
|
474
|
+
StepConnector,
|
|
328
475
|
stepperStyles,
|
|
476
|
+
stepStyles,
|
|
329
477
|
stepIndicatorStyles,
|
|
330
|
-
|
|
478
|
+
stepLabelStyles,
|
|
479
|
+
stepConnectorStyles,
|
|
331
480
|
};
|
|
@@ -56,9 +56,9 @@ function Switchers({
|
|
|
56
56
|
>
|
|
57
57
|
<div
|
|
58
58
|
className={cn(
|
|
59
|
-
"absolute
|
|
59
|
+
"absolute start-[2px] w-[24px] h-[24px] rounded-full bg-background-presentation-switcher-knob shadow-[0px_0px_0px_1px_rgba(0,0,0,0.04),0px_3px_8px_0px_rgba(0,0,0,0.15),0px_3px_1px_0px_rgba(0,0,0,0.06)] transition-all duration-200",
|
|
60
60
|
{
|
|
61
|
-
"
|
|
61
|
+
"start-[22px]": active,
|
|
62
62
|
}
|
|
63
63
|
)}
|
|
64
64
|
/>
|