@openzeppelin/ui-components 1.4.0 → 1.6.0
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/dist/index.cjs +637 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +158 -8
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +153 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +635 -30
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -7,6 +7,8 @@ let react = require("react");
|
|
|
7
7
|
react = require_ErrorMessage.__toESM(react);
|
|
8
8
|
let _openzeppelin_ui_utils = require("@openzeppelin/ui-utils");
|
|
9
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
+
let _radix_ui_react_tooltip = require("@radix-ui/react-tooltip");
|
|
11
|
+
_radix_ui_react_tooltip = require_ErrorMessage.__toESM(_radix_ui_react_tooltip);
|
|
10
12
|
let _radix_ui_react_slot = require("@radix-ui/react-slot");
|
|
11
13
|
let react_day_picker = require("react-day-picker");
|
|
12
14
|
let _radix_ui_react_checkbox = require("@radix-ui/react-checkbox");
|
|
@@ -31,8 +33,6 @@ let _radix_ui_react_collapsible = require("@radix-ui/react-collapsible");
|
|
|
31
33
|
_radix_ui_react_collapsible = require_ErrorMessage.__toESM(_radix_ui_react_collapsible);
|
|
32
34
|
let _radix_ui_react_tabs = require("@radix-ui/react-tabs");
|
|
33
35
|
_radix_ui_react_tabs = require_ErrorMessage.__toESM(_radix_ui_react_tabs);
|
|
34
|
-
let _radix_ui_react_tooltip = require("@radix-ui/react-tooltip");
|
|
35
|
-
_radix_ui_react_tooltip = require_ErrorMessage.__toESM(_radix_ui_react_tooltip);
|
|
36
36
|
let _openzeppelin_ui_types = require("@openzeppelin/ui-types");
|
|
37
37
|
let sonner = require("sonner");
|
|
38
38
|
let next_themes = require("next-themes");
|
|
@@ -114,6 +114,19 @@ const AccordionContent = react.forwardRef(({ className, children, variant: varia
|
|
|
114
114
|
});
|
|
115
115
|
AccordionContent.displayName = "AccordionContent";
|
|
116
116
|
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/components/ui/tooltip.tsx
|
|
119
|
+
const TooltipProvider = _radix_ui_react_tooltip.Provider;
|
|
120
|
+
const Tooltip = _radix_ui_react_tooltip.Root;
|
|
121
|
+
const TooltipTrigger = _radix_ui_react_tooltip.Trigger;
|
|
122
|
+
const TooltipContent = react.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_tooltip.Portal, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_tooltip.Content, {
|
|
123
|
+
ref,
|
|
124
|
+
sideOffset,
|
|
125
|
+
className: (0, _openzeppelin_ui_utils.cn)("bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 overflow-hidden rounded-md px-3 py-1.5 text-xs", className),
|
|
126
|
+
...props
|
|
127
|
+
}) }));
|
|
128
|
+
TooltipContent.displayName = _radix_ui_react_tooltip.Content.displayName;
|
|
129
|
+
|
|
117
130
|
//#endregion
|
|
118
131
|
//#region src/components/ui/address-display/context.ts
|
|
119
132
|
/**
|
|
@@ -126,8 +139,20 @@ const AddressLabelContext = (0, react.createContext)(null);
|
|
|
126
139
|
//#endregion
|
|
127
140
|
//#region src/components/ui/address-display/address-display.tsx
|
|
128
141
|
/**
|
|
142
|
+
* True when the primary input can hover (e.g. desktop with mouse).
|
|
143
|
+
* Touch-first phones typically report false. SSR assumes hover-capable.
|
|
144
|
+
*/
|
|
145
|
+
function usePrefersHover() {
|
|
146
|
+
return react.useSyncExternalStore(react.useCallback((onStoreChange) => {
|
|
147
|
+
if (typeof window === "undefined" || typeof window.matchMedia === "undefined") return () => {};
|
|
148
|
+
const mq = window.matchMedia("(hover: hover)");
|
|
149
|
+
mq.addEventListener("change", onStoreChange);
|
|
150
|
+
return () => mq.removeEventListener("change", onStoreChange);
|
|
151
|
+
}, []), () => typeof window !== "undefined" && typeof window.matchMedia !== "undefined" ? window.matchMedia("(hover: hover)").matches : true, () => true);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
129
154
|
* Displays a blockchain address with optional truncation, copy button,
|
|
130
|
-
* explorer link, and human-readable label.
|
|
155
|
+
* explorer link, tooltip, and human-readable label.
|
|
131
156
|
*
|
|
132
157
|
* Labels are resolved in priority order:
|
|
133
158
|
* 1. Explicit `label` prop
|
|
@@ -152,11 +177,22 @@ const AddressLabelContext = (0, react.createContext)(null);
|
|
|
152
177
|
*
|
|
153
178
|
* // Suppress label resolution for a specific instance
|
|
154
179
|
* <AddressDisplay address="0x742d35Cc..." disableLabel />
|
|
180
|
+
*
|
|
181
|
+
* // Reveal full address on hover (still truncated when idle)
|
|
182
|
+
* <AddressDisplay address="0x742d35Cc..." untruncateOnHover />
|
|
183
|
+
*
|
|
184
|
+
* // Tooltip with full address on hover + copy icon on hover
|
|
185
|
+
* <AddressDisplay address="0x742d35Cc..." showTooltip showCopyButton showCopyButtonOnHover />
|
|
186
|
+
*
|
|
187
|
+
* // Inline variant (no chip background) — useful inside wallet bars
|
|
188
|
+
* <AddressDisplay address="0x742d35Cc..." variant="inline" showTooltip showCopyButton />
|
|
155
189
|
* ```
|
|
156
190
|
*/
|
|
157
|
-
function AddressDisplay({ address, truncate = true, startChars = 6, endChars = 4, showCopyButton = false, showCopyButtonOnHover = false, explorerUrl, label: labelProp, onLabelEdit: onLabelEditProp, networkId, disableLabel = false, className, ...props }) {
|
|
191
|
+
function AddressDisplay({ address, truncate = true, untruncateOnHover = false, startChars = 6, endChars = 4, showCopyButton = false, showCopyButtonOnHover = false, explorerUrl, label: labelProp, onLabelEdit: onLabelEditProp, networkId, disableLabel = false, showTooltip = false, variant = "chip", className, onPointerEnter, onPointerLeave, onMouseEnter, onMouseLeave, onClick, ...props }) {
|
|
158
192
|
const [copied, setCopied] = react.useState(false);
|
|
193
|
+
const [isHovered, setIsHovered] = react.useState(false);
|
|
159
194
|
const copyTimeoutRef = react.useRef(null);
|
|
195
|
+
const prefersHover = usePrefersHover();
|
|
160
196
|
const resolver = react.useContext(AddressLabelContext);
|
|
161
197
|
const resolvedLabel = disableLabel ? void 0 : labelProp ?? resolver?.resolveLabel(address, networkId);
|
|
162
198
|
const contextEditHandler = react.useCallback(() => {
|
|
@@ -167,7 +203,25 @@ function AddressDisplay({ address, truncate = true, startChars = 6, endChars = 4
|
|
|
167
203
|
networkId
|
|
168
204
|
]);
|
|
169
205
|
const editHandler = disableLabel ? void 0 : onLabelEditProp ?? (resolver?.onEditLabel ? contextEditHandler : void 0);
|
|
170
|
-
const
|
|
206
|
+
const canUntruncate = untruncateOnHover && truncate && !showTooltip;
|
|
207
|
+
const showFullAddress = !truncate || canUntruncate && isHovered;
|
|
208
|
+
const displayAddress = showFullAddress ? address : (0, _openzeppelin_ui_utils.truncateMiddle)(address, startChars, endChars);
|
|
209
|
+
const addressTextClassName = (0, _openzeppelin_ui_utils.cn)(!showFullAddress && "truncate", (showFullAddress || !truncate) && "break-all");
|
|
210
|
+
const expandInteractionClassName = canUntruncate && !prefersHover ? "cursor-pointer" : void 0;
|
|
211
|
+
const handlePointerEnter = (e) => {
|
|
212
|
+
if (canUntruncate && prefersHover) setIsHovered(true);
|
|
213
|
+
onPointerEnter?.(e);
|
|
214
|
+
onMouseEnter?.(e);
|
|
215
|
+
};
|
|
216
|
+
const handlePointerLeave = (e) => {
|
|
217
|
+
if (canUntruncate && prefersHover) setIsHovered(false);
|
|
218
|
+
onPointerLeave?.(e);
|
|
219
|
+
onMouseLeave?.(e);
|
|
220
|
+
};
|
|
221
|
+
const handleUntruncateClick = (e) => {
|
|
222
|
+
if (canUntruncate && !prefersHover) setIsHovered((open) => !open);
|
|
223
|
+
onClick?.(e);
|
|
224
|
+
};
|
|
171
225
|
const handleCopy = (e) => {
|
|
172
226
|
e.stopPropagation();
|
|
173
227
|
navigator.clipboard.writeText(address);
|
|
@@ -183,6 +237,7 @@ function AddressDisplay({ address, truncate = true, startChars = 6, endChars = 4
|
|
|
183
237
|
if (copyTimeoutRef.current) window.clearTimeout(copyTimeoutRef.current);
|
|
184
238
|
};
|
|
185
239
|
}, []);
|
|
240
|
+
const isChip = variant === "chip";
|
|
186
241
|
const actionButtons = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
187
242
|
showCopyButton && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
188
243
|
type: "button",
|
|
@@ -195,6 +250,9 @@ function AddressDisplay({ address, truncate = true, startChars = 6, endChars = 4
|
|
|
195
250
|
href: explorerUrl,
|
|
196
251
|
target: "_blank",
|
|
197
252
|
rel: "noopener noreferrer",
|
|
253
|
+
onClick: (e) => {
|
|
254
|
+
e.stopPropagation();
|
|
255
|
+
},
|
|
198
256
|
className: "ml-1.5 shrink-0 text-slate-500 transition-colors hover:text-slate-700",
|
|
199
257
|
"aria-label": "View in explorer",
|
|
200
258
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ExternalLink, { className: "h-3.5 w-3.5" })
|
|
@@ -210,28 +268,48 @@ function AddressDisplay({ address, truncate = true, startChars = 6, endChars = 4
|
|
|
210
268
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Pencil, { className: "h-3.5 w-3.5" })
|
|
211
269
|
})
|
|
212
270
|
] });
|
|
213
|
-
|
|
214
|
-
|
|
271
|
+
const shouldShowTooltip = showTooltip && truncate;
|
|
272
|
+
const wrapWithTooltip = (content) => {
|
|
273
|
+
if (!shouldShowTooltip) return content;
|
|
274
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipProvider, {
|
|
275
|
+
delayDuration: 300,
|
|
276
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Tooltip, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTrigger, {
|
|
277
|
+
asChild: true,
|
|
278
|
+
children: content
|
|
279
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipContent, {
|
|
280
|
+
className: "font-mono text-xs",
|
|
281
|
+
children: address
|
|
282
|
+
})] })
|
|
283
|
+
});
|
|
284
|
+
};
|
|
285
|
+
if (resolvedLabel) return wrapWithTooltip(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
286
|
+
className: (0, _openzeppelin_ui_utils.cn)("group inline-flex max-w-full min-w-0 flex-col", isChip && "rounded-md bg-slate-100 px-2 py-1", "text-xs text-slate-700", expandInteractionClassName, className),
|
|
287
|
+
onPointerEnter: handlePointerEnter,
|
|
288
|
+
onPointerLeave: handlePointerLeave,
|
|
289
|
+
onClick: handleUntruncateClick,
|
|
215
290
|
...props,
|
|
216
291
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
217
292
|
className: "truncate font-sans font-medium text-slate-900 leading-snug",
|
|
218
293
|
children: resolvedLabel
|
|
219
294
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
220
|
-
className: "flex items-center font-mono text-[10px] text-slate-400 leading-snug",
|
|
295
|
+
className: "flex min-w-0 items-center font-mono text-[10px] text-slate-400 leading-snug",
|
|
221
296
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
222
|
-
className:
|
|
297
|
+
className: addressTextClassName,
|
|
223
298
|
children: displayAddress
|
|
224
299
|
}), actionButtons]
|
|
225
300
|
})]
|
|
226
|
-
});
|
|
227
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
228
|
-
className: (0, _openzeppelin_ui_utils.cn)("group inline-flex max-w-full items-center rounded-md bg-slate-100 px-2 py-1", "text-xs font-mono text-slate-700", className),
|
|
301
|
+
}));
|
|
302
|
+
return wrapWithTooltip(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
303
|
+
className: (0, _openzeppelin_ui_utils.cn)("group inline-flex max-w-full min-w-0 items-center", isChip && "rounded-md bg-slate-100 px-2 py-1", "text-xs font-mono text-slate-700", expandInteractionClassName, className),
|
|
304
|
+
onPointerEnter: handlePointerEnter,
|
|
305
|
+
onPointerLeave: handlePointerLeave,
|
|
306
|
+
onClick: handleUntruncateClick,
|
|
229
307
|
...props,
|
|
230
308
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
231
|
-
className:
|
|
309
|
+
className: addressTextClassName,
|
|
232
310
|
children: displayAddress
|
|
233
311
|
}), actionButtons]
|
|
234
|
-
});
|
|
312
|
+
}));
|
|
235
313
|
}
|
|
236
314
|
|
|
237
315
|
//#endregion
|
|
@@ -1605,12 +1683,12 @@ SelectSeparator.displayName = _radix_ui_react_select.Separator.displayName;
|
|
|
1605
1683
|
* Can render as a button or anchor element depending on whether href is provided.
|
|
1606
1684
|
*/
|
|
1607
1685
|
function SidebarButton({ icon, children, onClick, size = "default", badge, disabled = false, isSelected = false, href, target, rel, className }) {
|
|
1608
|
-
const commonClass = (0, _openzeppelin_ui_utils.cn)("group relative flex items-center gap-2 px-3 py-2
|
|
1686
|
+
const commonClass = (0, _openzeppelin_ui_utils.cn)("group relative flex flex-wrap items-center gap-x-2 gap-y-0.5 px-3 py-2 rounded-lg font-semibold text-sm transition-colors", badge ? "justify-between" : "justify-start", disabled ? "text-gray-400 cursor-not-allowed" : isSelected ? "text-[#111928] bg-neutral-100" : "text-gray-600 hover:text-gray-700 cursor-pointer hover:before:content-[\"\"] hover:before:absolute hover:before:inset-x-0 hover:before:top-1 hover:before:bottom-1 hover:before:bg-muted/80 hover:before:rounded-lg hover:before:-z-10", size === "small" ? "min-h-10" : "min-h-11", className);
|
|
1609
1687
|
const content = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1610
1688
|
className: "flex items-center gap-2",
|
|
1611
1689
|
children: [icon, children]
|
|
1612
1690
|
}), badge && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1613
|
-
className: "text-xs px-2 py-
|
|
1691
|
+
className: "text-xs px-2 py-0.5 bg-muted text-muted-foreground rounded-full font-medium",
|
|
1614
1692
|
children: badge
|
|
1615
1693
|
})] });
|
|
1616
1694
|
if (href) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
@@ -1808,19 +1886,6 @@ const Textarea = react.forwardRef(({ className, ...props }, ref) => {
|
|
|
1808
1886
|
});
|
|
1809
1887
|
Textarea.displayName = "Textarea";
|
|
1810
1888
|
|
|
1811
|
-
//#endregion
|
|
1812
|
-
//#region src/components/ui/tooltip.tsx
|
|
1813
|
-
const TooltipProvider = _radix_ui_react_tooltip.Provider;
|
|
1814
|
-
const Tooltip = _radix_ui_react_tooltip.Root;
|
|
1815
|
-
const TooltipTrigger = _radix_ui_react_tooltip.Trigger;
|
|
1816
|
-
const TooltipContent = react.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_tooltip.Content, {
|
|
1817
|
-
ref,
|
|
1818
|
-
sideOffset,
|
|
1819
|
-
className: (0, _openzeppelin_ui_utils.cn)("bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 overflow-hidden rounded-md px-3 py-1.5 text-xs", className),
|
|
1820
|
-
...props
|
|
1821
|
-
}));
|
|
1822
|
-
TooltipContent.displayName = _radix_ui_react_tooltip.Content.displayName;
|
|
1823
|
-
|
|
1824
1889
|
//#endregion
|
|
1825
1890
|
//#region src/components/ui/view-contract-state-button.tsx
|
|
1826
1891
|
/**
|
|
@@ -1855,6 +1920,546 @@ function ViewContractStateButton({ contractAddress, onToggle }) {
|
|
|
1855
1920
|
});
|
|
1856
1921
|
}
|
|
1857
1922
|
|
|
1923
|
+
//#endregion
|
|
1924
|
+
//#region src/components/ui/wizard/WizardStepper.tsx
|
|
1925
|
+
function resolveState(step, index, currentStepIndex, furthestStepIndex) {
|
|
1926
|
+
if (step.status === "completed" || step.status === "skipped") return "completed";
|
|
1927
|
+
if (index === currentStepIndex) return "current";
|
|
1928
|
+
if (step.isInvalid && (index < currentStepIndex || index <= furthestStepIndex)) return "invalid";
|
|
1929
|
+
if (index < currentStepIndex) return "completed";
|
|
1930
|
+
if (index <= furthestStepIndex) return "visited";
|
|
1931
|
+
return "upcoming";
|
|
1932
|
+
}
|
|
1933
|
+
function canClick(state, freeNavigation = false) {
|
|
1934
|
+
if (freeNavigation) return true;
|
|
1935
|
+
return state !== "upcoming";
|
|
1936
|
+
}
|
|
1937
|
+
function StepCircle({ state, index }) {
|
|
1938
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1939
|
+
className: (0, _openzeppelin_ui_utils.cn)("flex size-6 shrink-0 items-center justify-center rounded-full text-xs font-semibold transition-all", state === "completed" && "bg-blue-600 text-white", state === "current" && "bg-blue-600 text-white ring-2 ring-blue-200", state === "visited" && "bg-blue-100 text-blue-600 ring-1 ring-blue-300", state === "invalid" && "bg-red-100 text-red-600 ring-1 ring-red-300", state === "upcoming" && "bg-zinc-100 text-zinc-400"),
|
|
1940
|
+
children: state === "completed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, { className: "size-3.5" }) : state === "visited" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Pencil, { className: "size-3" }) : state === "invalid" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlertCircle, { className: "size-3.5" }) : index + 1
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
function StepLabel({ title, state, isSkipped }) {
|
|
1944
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1945
|
+
className: "min-w-0 flex-1",
|
|
1946
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1947
|
+
className: (0, _openzeppelin_ui_utils.cn)("text-sm font-medium transition-colors", state === "current" && "text-blue-700", state === "completed" && "text-zinc-700", state === "visited" && "text-blue-600", state === "invalid" && "text-red-600", state === "upcoming" && "text-zinc-400"),
|
|
1948
|
+
children: title
|
|
1949
|
+
}), isSkipped && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1950
|
+
className: "mt-0.5 block text-[11px] text-zinc-400",
|
|
1951
|
+
children: "Skipped"
|
|
1952
|
+
})]
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
function VerticalStepper({ steps, currentStepIndex, furthestStepIndex = currentStepIndex, onStepClick, freeNavigation, className }) {
|
|
1956
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
|
|
1957
|
+
"aria-label": "Wizard steps",
|
|
1958
|
+
className: (0, _openzeppelin_ui_utils.cn)("rounded-2xl border border-zinc-200 bg-white p-6", className),
|
|
1959
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1960
|
+
className: "flex flex-col gap-1",
|
|
1961
|
+
children: steps.map((step, index) => {
|
|
1962
|
+
const state = resolveState(step, index, currentStepIndex, furthestStepIndex);
|
|
1963
|
+
const clickable = canClick(state, freeNavigation) && !!onStepClick;
|
|
1964
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1965
|
+
type: "button",
|
|
1966
|
+
onClick: () => clickable && onStepClick?.(index),
|
|
1967
|
+
disabled: !clickable,
|
|
1968
|
+
className: (0, _openzeppelin_ui_utils.cn)("flex items-center gap-3 rounded-xl border border-transparent px-3 py-3 text-left transition-all duration-150", clickable ? "cursor-pointer" : "cursor-not-allowed opacity-50", state === "current" && "border-blue-200 bg-blue-50", state === "completed" && "bg-white hover:bg-gray-50", state === "visited" && "bg-white hover:bg-blue-50/50", state === "invalid" && "border-red-200 bg-red-50 hover:bg-red-100/60", state === "upcoming" && "bg-white"),
|
|
1969
|
+
"aria-current": state === "current" ? "step" : void 0,
|
|
1970
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepCircle, {
|
|
1971
|
+
state,
|
|
1972
|
+
index
|
|
1973
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepLabel, {
|
|
1974
|
+
title: step.title,
|
|
1975
|
+
state,
|
|
1976
|
+
isSkipped: step.status === "skipped"
|
|
1977
|
+
})]
|
|
1978
|
+
}, step.id);
|
|
1979
|
+
})
|
|
1980
|
+
})
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
function HorizontalStepper({ steps, currentStepIndex, furthestStepIndex = currentStepIndex, onStepClick, freeNavigation, className }) {
|
|
1984
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
|
|
1985
|
+
"aria-label": "Wizard steps",
|
|
1986
|
+
className: (0, _openzeppelin_ui_utils.cn)("rounded-2xl border border-zinc-200 bg-white p-6", className),
|
|
1987
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1988
|
+
className: "flex w-full items-center",
|
|
1989
|
+
children: steps.map((step, index) => {
|
|
1990
|
+
const state = resolveState(step, index, currentStepIndex, furthestStepIndex);
|
|
1991
|
+
const clickable = canClick(state, freeNavigation) && !!onStepClick;
|
|
1992
|
+
const isLast = index === steps.length - 1;
|
|
1993
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.default.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1994
|
+
type: "button",
|
|
1995
|
+
onClick: () => clickable && onStepClick?.(index),
|
|
1996
|
+
disabled: !clickable,
|
|
1997
|
+
className: (0, _openzeppelin_ui_utils.cn)("flex items-center gap-2 rounded-xl border border-transparent px-3 py-2 text-left transition-all duration-150", clickable ? "cursor-pointer" : "cursor-not-allowed opacity-50", state === "current" && "border-blue-200 bg-blue-50", state === "completed" && "bg-white hover:bg-gray-50", state === "visited" && "bg-white hover:bg-blue-50/50", state === "invalid" && "border-red-200 bg-red-50 hover:bg-red-100/60", state === "upcoming" && "bg-white"),
|
|
1998
|
+
"aria-current": state === "current" ? "step" : void 0,
|
|
1999
|
+
"aria-label": `Step ${index + 1}: ${step.title}`,
|
|
2000
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepCircle, {
|
|
2001
|
+
state,
|
|
2002
|
+
index
|
|
2003
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2004
|
+
className: "hidden sm:block",
|
|
2005
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepLabel, {
|
|
2006
|
+
title: step.title,
|
|
2007
|
+
state,
|
|
2008
|
+
isSkipped: step.status === "skipped"
|
|
2009
|
+
})
|
|
2010
|
+
})]
|
|
2011
|
+
}), !isLast && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: (0, _openzeppelin_ui_utils.cn)("mx-1 h-px flex-1 transition-colors sm:mx-2", index < currentStepIndex ? "bg-blue-600" : "bg-zinc-200") })] }, step.id);
|
|
2012
|
+
})
|
|
2013
|
+
})
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
2016
|
+
/**
|
|
2017
|
+
* A stepper component for navigating through a series of steps.
|
|
2018
|
+
*
|
|
2019
|
+
* @param props - The props for the WizardStepper component.
|
|
2020
|
+
* @returns A React node representing the stepper component.
|
|
2021
|
+
*/
|
|
2022
|
+
function WizardStepper(props) {
|
|
2023
|
+
const { variant = "horizontal", ...rest } = props;
|
|
2024
|
+
return variant === "vertical" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VerticalStepper, {
|
|
2025
|
+
...rest,
|
|
2026
|
+
variant
|
|
2027
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HorizontalStepper, {
|
|
2028
|
+
...rest,
|
|
2029
|
+
variant
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
//#endregion
|
|
2034
|
+
//#region src/components/ui/wizard/WizardNavigation.tsx
|
|
2035
|
+
/**
|
|
2036
|
+
* A navigation component for the wizard.
|
|
2037
|
+
*
|
|
2038
|
+
* @param props - The props for the WizardNavigation component.
|
|
2039
|
+
* @returns A React node representing the navigation component.
|
|
2040
|
+
*/
|
|
2041
|
+
function WizardNavigation({ isFirstStep, isLastStep, canProceed = true, onPrevious, onNext, onCancel, extraActions, nextLabel = "Next", lastStepLabel = "Finish", className }) {
|
|
2042
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2043
|
+
className: (0, _openzeppelin_ui_utils.cn)("flex items-center justify-between", className),
|
|
2044
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2045
|
+
className: "flex gap-2",
|
|
2046
|
+
children: [onCancel && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
|
|
2047
|
+
type: "button",
|
|
2048
|
+
variant: "outline",
|
|
2049
|
+
onClick: onCancel,
|
|
2050
|
+
className: "gap-2",
|
|
2051
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "size-4" }), "Cancel"]
|
|
2052
|
+
}), !isFirstStep && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
|
|
2053
|
+
type: "button",
|
|
2054
|
+
variant: "outline",
|
|
2055
|
+
onClick: onPrevious,
|
|
2056
|
+
className: "gap-2",
|
|
2057
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronLeft, { className: "size-4" }), "Previous"]
|
|
2058
|
+
})]
|
|
2059
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2060
|
+
className: "flex gap-2",
|
|
2061
|
+
children: [extraActions, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
|
|
2062
|
+
type: "button",
|
|
2063
|
+
onClick: onNext,
|
|
2064
|
+
disabled: !canProceed,
|
|
2065
|
+
className: "gap-2",
|
|
2066
|
+
children: [isLastStep ? lastStepLabel : nextLabel, !isLastStep && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, { className: "size-4" })]
|
|
2067
|
+
})]
|
|
2068
|
+
})]
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
//#endregion
|
|
2073
|
+
//#region src/components/ui/wizard/hooks.ts
|
|
2074
|
+
/**
|
|
2075
|
+
* Clamp a step index into the valid range for the current wizard.
|
|
2076
|
+
*/
|
|
2077
|
+
function getSafeStepIndex(stepCount, currentStepIndex) {
|
|
2078
|
+
if (stepCount === 0) return 0;
|
|
2079
|
+
return Math.max(0, Math.min(currentStepIndex, stepCount - 1));
|
|
2080
|
+
}
|
|
2081
|
+
/**
|
|
2082
|
+
* Track the highest step reached unless a controlled value is provided.
|
|
2083
|
+
*/
|
|
2084
|
+
function useFurthestStepIndex(currentStepIndex, controlledFurthestStepIndex) {
|
|
2085
|
+
const [internalFurthestStepIndex, setInternalFurthestStepIndex] = (0, react.useState)(currentStepIndex);
|
|
2086
|
+
(0, react.useEffect)(() => {
|
|
2087
|
+
setInternalFurthestStepIndex((prev) => Math.max(prev, currentStepIndex));
|
|
2088
|
+
}, [currentStepIndex]);
|
|
2089
|
+
return controlledFurthestStepIndex ?? internalFurthestStepIndex;
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Keep the scrollable wizard's active and visited step state in sync with scrolling and clicks.
|
|
2093
|
+
*/
|
|
2094
|
+
function useScrollableWizardStepTracking({ steps, currentStepIndex, onStepChange, scrollRef, sectionId, scrollPadding = SCROLL_PADDING_PX }) {
|
|
2095
|
+
const safeIndex = getSafeStepIndex(steps.length, currentStepIndex);
|
|
2096
|
+
const initialIndexRef = (0, react.useRef)(safeIndex);
|
|
2097
|
+
const rafRef = (0, react.useRef)(null);
|
|
2098
|
+
const manualSelectionIndexRef = (0, react.useRef)(null);
|
|
2099
|
+
const stepsRef = (0, react.useRef)(steps);
|
|
2100
|
+
const sectionIdRef = (0, react.useRef)(sectionId);
|
|
2101
|
+
const onStepChangeRef = (0, react.useRef)(onStepChange);
|
|
2102
|
+
const scrollPaddingRef = (0, react.useRef)(scrollPadding);
|
|
2103
|
+
(0, react.useEffect)(() => {
|
|
2104
|
+
stepsRef.current = steps;
|
|
2105
|
+
sectionIdRef.current = sectionId;
|
|
2106
|
+
onStepChangeRef.current = onStepChange;
|
|
2107
|
+
scrollPaddingRef.current = scrollPadding;
|
|
2108
|
+
});
|
|
2109
|
+
const [activeIndex, setActiveIndex] = (0, react.useState)(initialIndexRef.current);
|
|
2110
|
+
const activeIndexRef = (0, react.useRef)(initialIndexRef.current);
|
|
2111
|
+
const [furthestStepIndex, setFurthestStepIndex] = (0, react.useState)(initialIndexRef.current);
|
|
2112
|
+
const isMountedRef = (0, react.useRef)(false);
|
|
2113
|
+
const clearManualSelection = (0, react.useCallback)(() => {
|
|
2114
|
+
manualSelectionIndexRef.current = null;
|
|
2115
|
+
}, []);
|
|
2116
|
+
(0, react.useEffect)(() => {
|
|
2117
|
+
const container = scrollRef.current;
|
|
2118
|
+
if (!container) return;
|
|
2119
|
+
const ownerDocument = container.ownerDocument;
|
|
2120
|
+
isMountedRef.current = false;
|
|
2121
|
+
let didCompleteInitialRaf = false;
|
|
2122
|
+
const releaseManualSelectionOnUserScroll = () => {
|
|
2123
|
+
clearManualSelection();
|
|
2124
|
+
};
|
|
2125
|
+
const handleKeyDown = (event) => {
|
|
2126
|
+
if (isScrollableNavigationKey(event)) clearManualSelection();
|
|
2127
|
+
};
|
|
2128
|
+
const handleScroll = () => {
|
|
2129
|
+
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
|
|
2130
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
2131
|
+
const currentSteps = stepsRef.current;
|
|
2132
|
+
const currentSectionId = sectionIdRef.current;
|
|
2133
|
+
const currentOnStepChange = onStepChangeRef.current;
|
|
2134
|
+
if (currentSteps.length === 0) return;
|
|
2135
|
+
const manualSelectionIndex = manualSelectionIndexRef.current;
|
|
2136
|
+
const naturalState = resolveScrollableActiveIndex(container, currentSteps, currentSectionId);
|
|
2137
|
+
const naturalActiveIndex = naturalState.activeIndex;
|
|
2138
|
+
const newActiveIndex = manualSelectionIndex ?? naturalActiveIndex;
|
|
2139
|
+
const shouldCommitFurthestStepIndex = manualSelectionIndex !== null ? true : naturalState.commitFurthestStepIndex;
|
|
2140
|
+
if (activeIndexRef.current !== newActiveIndex) {
|
|
2141
|
+
activeIndexRef.current = newActiveIndex;
|
|
2142
|
+
setActiveIndex(newActiveIndex);
|
|
2143
|
+
if (isMountedRef.current) {
|
|
2144
|
+
lastEmittedIndexRef.current = newActiveIndex;
|
|
2145
|
+
currentOnStepChange(newActiveIndex);
|
|
2146
|
+
}
|
|
2147
|
+
} else setActiveIndex(newActiveIndex);
|
|
2148
|
+
if (shouldCommitFurthestStepIndex) setFurthestStepIndex((prev) => Math.max(prev, newActiveIndex));
|
|
2149
|
+
rafRef.current = null;
|
|
2150
|
+
if (!didCompleteInitialRaf) {
|
|
2151
|
+
didCompleteInitialRaf = true;
|
|
2152
|
+
isMountedRef.current = true;
|
|
2153
|
+
}
|
|
2154
|
+
});
|
|
2155
|
+
};
|
|
2156
|
+
container.addEventListener("wheel", releaseManualSelectionOnUserScroll, { passive: true });
|
|
2157
|
+
container.addEventListener("touchmove", releaseManualSelectionOnUserScroll, { passive: true });
|
|
2158
|
+
container.addEventListener("pointerdown", releaseManualSelectionOnUserScroll);
|
|
2159
|
+
ownerDocument.addEventListener("keydown", handleKeyDown);
|
|
2160
|
+
container.addEventListener("scroll", handleScroll, { passive: true });
|
|
2161
|
+
handleScroll();
|
|
2162
|
+
return () => {
|
|
2163
|
+
isMountedRef.current = false;
|
|
2164
|
+
container.removeEventListener("wheel", releaseManualSelectionOnUserScroll);
|
|
2165
|
+
container.removeEventListener("touchmove", releaseManualSelectionOnUserScroll);
|
|
2166
|
+
container.removeEventListener("pointerdown", releaseManualSelectionOnUserScroll);
|
|
2167
|
+
ownerDocument.removeEventListener("keydown", handleKeyDown);
|
|
2168
|
+
container.removeEventListener("scroll", handleScroll);
|
|
2169
|
+
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
|
|
2170
|
+
};
|
|
2171
|
+
}, [clearManualSelection, scrollRef]);
|
|
2172
|
+
const lastEmittedIndexRef = (0, react.useRef)(safeIndex);
|
|
2173
|
+
(0, react.useEffect)(() => {
|
|
2174
|
+
const newSafeIndex = getSafeStepIndex(stepsRef.current.length, currentStepIndex);
|
|
2175
|
+
if (newSafeIndex === lastEmittedIndexRef.current) return;
|
|
2176
|
+
lastEmittedIndexRef.current = newSafeIndex;
|
|
2177
|
+
activeIndexRef.current = newSafeIndex;
|
|
2178
|
+
setActiveIndex(newSafeIndex);
|
|
2179
|
+
setFurthestStepIndex((prev) => Math.max(prev, newSafeIndex));
|
|
2180
|
+
const step = stepsRef.current[newSafeIndex];
|
|
2181
|
+
if (!step) return;
|
|
2182
|
+
const sectionElement = scrollRef.current?.querySelector(`#${CSS.escape(sectionIdRef.current(step.id))}`);
|
|
2183
|
+
if (scrollRef.current && sectionElement) scrollSectionIntoView(scrollRef.current, sectionElement, scrollPaddingRef.current);
|
|
2184
|
+
}, [currentStepIndex, scrollRef]);
|
|
2185
|
+
return {
|
|
2186
|
+
activeIndex,
|
|
2187
|
+
furthestStepIndex,
|
|
2188
|
+
scrollToSection: (0, react.useCallback)((index) => {
|
|
2189
|
+
const step = stepsRef.current[index];
|
|
2190
|
+
if (!step) return;
|
|
2191
|
+
manualSelectionIndexRef.current = index;
|
|
2192
|
+
activeIndexRef.current = index;
|
|
2193
|
+
lastEmittedIndexRef.current = index;
|
|
2194
|
+
setActiveIndex(index);
|
|
2195
|
+
setFurthestStepIndex((prev) => Math.max(prev, index));
|
|
2196
|
+
onStepChangeRef.current(index);
|
|
2197
|
+
const container = scrollRef.current;
|
|
2198
|
+
const sectionElement = container?.querySelector(`#${CSS.escape(sectionIdRef.current(step.id))}`);
|
|
2199
|
+
if (container && sectionElement) scrollSectionIntoView(container, sectionElement, scrollPaddingRef.current);
|
|
2200
|
+
}, [scrollRef])
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
function resolveScrollableActiveIndex(container, steps, sectionId) {
|
|
2204
|
+
if (steps.length === 0) return {
|
|
2205
|
+
activeIndex: 0,
|
|
2206
|
+
commitFurthestStepIndex: false
|
|
2207
|
+
};
|
|
2208
|
+
const containerRect = container.getBoundingClientRect();
|
|
2209
|
+
const anchorY = containerRect.top + Math.min(containerRect.height * .35, 220);
|
|
2210
|
+
const isScrollable = container.scrollHeight > container.clientHeight + 1;
|
|
2211
|
+
const isAtBottom = isScrollable && container.scrollTop + container.clientHeight >= container.scrollHeight - 1;
|
|
2212
|
+
const isNearBottom = isScrollable && container.scrollTop + container.clientHeight >= container.scrollHeight - 4;
|
|
2213
|
+
if (isAtBottom) return {
|
|
2214
|
+
activeIndex: steps.length - 1,
|
|
2215
|
+
commitFurthestStepIndex: false
|
|
2216
|
+
};
|
|
2217
|
+
let activeIndex = 0;
|
|
2218
|
+
let highestScore = Number.NEGATIVE_INFINITY;
|
|
2219
|
+
for (let i = 0; i < steps.length; i++) {
|
|
2220
|
+
const sectionMetrics = getSectionMetrics(container, steps[i].id, sectionId, containerRect);
|
|
2221
|
+
if (!sectionMetrics) continue;
|
|
2222
|
+
const score = scoreScrollableStep({
|
|
2223
|
+
stepIndex: i,
|
|
2224
|
+
stepCount: steps.length,
|
|
2225
|
+
containerRect,
|
|
2226
|
+
anchorY,
|
|
2227
|
+
isNearBottom,
|
|
2228
|
+
...sectionMetrics
|
|
2229
|
+
});
|
|
2230
|
+
if (score >= highestScore) {
|
|
2231
|
+
highestScore = score;
|
|
2232
|
+
activeIndex = i;
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
return {
|
|
2236
|
+
activeIndex,
|
|
2237
|
+
commitFurthestStepIndex: true
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
const SCROLL_PADDING_PX = 32;
|
|
2241
|
+
function getSectionElement(container, stepId, sectionId) {
|
|
2242
|
+
return container.querySelector(`#${CSS.escape(sectionId(stepId))}`);
|
|
2243
|
+
}
|
|
2244
|
+
function scrollSectionIntoView(container, sectionElement, padding) {
|
|
2245
|
+
const elementTop = sectionElement.getBoundingClientRect().top;
|
|
2246
|
+
const containerTop = container.getBoundingClientRect().top;
|
|
2247
|
+
const targetScrollTop = container.scrollTop + (elementTop - containerTop) - padding;
|
|
2248
|
+
container.scrollTo({
|
|
2249
|
+
top: targetScrollTop,
|
|
2250
|
+
behavior: "smooth"
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2253
|
+
function getSectionMetrics(container, stepId, sectionId, containerRect) {
|
|
2254
|
+
const sectionElement = getSectionElement(container, stepId, sectionId);
|
|
2255
|
+
if (!sectionElement) return null;
|
|
2256
|
+
const sectionRect = sectionElement.getBoundingClientRect();
|
|
2257
|
+
return {
|
|
2258
|
+
sectionRect,
|
|
2259
|
+
visibleHeight: getVisibleHeight(containerRect, sectionRect)
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
function getVisibleHeight(containerRect, sectionRect) {
|
|
2263
|
+
return Math.max(0, Math.min(sectionRect.bottom, containerRect.bottom) - Math.max(sectionRect.top, containerRect.top));
|
|
2264
|
+
}
|
|
2265
|
+
function scoreScrollableStep({ stepIndex, stepCount, containerRect, sectionRect, visibleHeight, anchorY, isNearBottom }) {
|
|
2266
|
+
const isVisible = visibleHeight > 0;
|
|
2267
|
+
const focusBandTop = containerRect.top + Math.min(containerRect.height * .2, 140);
|
|
2268
|
+
const focusBandBottom = containerRect.top + Math.min(containerRect.height * .55, 360);
|
|
2269
|
+
const focusBandOverlap = getBandOverlapHeight(sectionRect, focusBandTop, focusBandBottom);
|
|
2270
|
+
const distanceToFocusBand = focusBandOverlap > 0 ? 0 : Math.min(Math.abs(sectionRect.top - focusBandBottom), Math.abs(sectionRect.bottom - focusBandTop));
|
|
2271
|
+
const lastStepProminent = stepIndex === stepCount - 1 && visibleHeight >= Math.min(sectionRect.height, containerRect.height) * .25 && sectionRect.top <= containerRect.top + containerRect.height * .65;
|
|
2272
|
+
let score = isVisible ? visibleHeight : Number.NEGATIVE_INFINITY;
|
|
2273
|
+
if (focusBandOverlap > 0) score += 12e3 + focusBandOverlap * 25;
|
|
2274
|
+
if (sectionRect.top <= anchorY) score += 250;
|
|
2275
|
+
score += Math.max(0, 1e3 - distanceToFocusBand);
|
|
2276
|
+
if (isNearBottom && lastStepProminent && isVisible) score += 15e3;
|
|
2277
|
+
return score;
|
|
2278
|
+
}
|
|
2279
|
+
function getBandOverlapHeight(sectionRect, bandTop, bandBottom) {
|
|
2280
|
+
return Math.max(0, Math.min(sectionRect.bottom, bandBottom) - Math.max(sectionRect.top, bandTop));
|
|
2281
|
+
}
|
|
2282
|
+
function isScrollableNavigationKey(event) {
|
|
2283
|
+
if (event.metaKey || event.ctrlKey || event.altKey) return false;
|
|
2284
|
+
return [
|
|
2285
|
+
"ArrowDown",
|
|
2286
|
+
"ArrowUp",
|
|
2287
|
+
"PageDown",
|
|
2288
|
+
"PageUp",
|
|
2289
|
+
"Home",
|
|
2290
|
+
"End",
|
|
2291
|
+
" "
|
|
2292
|
+
].includes(event.key);
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
//#endregion
|
|
2296
|
+
//#region src/components/ui/wizard/WizardLayout.tsx
|
|
2297
|
+
function PagedLayout({ steps, currentStepIndex, furthestStepIndex: furthestStepIndexProp, onStepChange, onComplete, onCancel, navActions, header, variant, className }) {
|
|
2298
|
+
const safeIndex = getSafeStepIndex(steps.length, currentStepIndex);
|
|
2299
|
+
const resolvedFurthestStepIndex = useFurthestStepIndex(safeIndex, furthestStepIndexProp);
|
|
2300
|
+
if (steps.length === 0) return null;
|
|
2301
|
+
const isFirstStep = safeIndex === 0;
|
|
2302
|
+
const isLastStep = safeIndex === steps.length - 1;
|
|
2303
|
+
const currentStep = steps[safeIndex];
|
|
2304
|
+
const canProceed = currentStep?.isValid !== false;
|
|
2305
|
+
const handleNext = () => {
|
|
2306
|
+
if (isLastStep) {
|
|
2307
|
+
onComplete?.();
|
|
2308
|
+
return;
|
|
2309
|
+
}
|
|
2310
|
+
onStepChange(safeIndex + 1);
|
|
2311
|
+
};
|
|
2312
|
+
const handlePrevious = () => {
|
|
2313
|
+
if (!isFirstStep) onStepChange(safeIndex - 1);
|
|
2314
|
+
};
|
|
2315
|
+
const stepDefs = toStepDefs(steps, safeIndex);
|
|
2316
|
+
const footer = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2317
|
+
className: "shrink-0 border-t border-border bg-background px-8 py-4",
|
|
2318
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2319
|
+
className: "mx-auto max-w-5xl",
|
|
2320
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WizardNavigation, {
|
|
2321
|
+
isFirstStep,
|
|
2322
|
+
isLastStep,
|
|
2323
|
+
canProceed,
|
|
2324
|
+
onPrevious: handlePrevious,
|
|
2325
|
+
onNext: handleNext,
|
|
2326
|
+
onCancel,
|
|
2327
|
+
extraActions: navActions
|
|
2328
|
+
})
|
|
2329
|
+
})
|
|
2330
|
+
});
|
|
2331
|
+
if (variant === "vertical") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2332
|
+
className: (0, _openzeppelin_ui_utils.cn)("flex h-full gap-6", className),
|
|
2333
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2334
|
+
className: "w-[220px] shrink-0 py-6 pl-6",
|
|
2335
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WizardStepper, {
|
|
2336
|
+
variant: "vertical",
|
|
2337
|
+
steps: stepDefs,
|
|
2338
|
+
currentStepIndex: safeIndex,
|
|
2339
|
+
furthestStepIndex: resolvedFurthestStepIndex,
|
|
2340
|
+
onStepClick: onStepChange,
|
|
2341
|
+
className: "h-full"
|
|
2342
|
+
})
|
|
2343
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2344
|
+
className: "flex min-w-0 flex-1 flex-col overflow-hidden",
|
|
2345
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2346
|
+
className: "flex-1 overflow-y-auto p-8",
|
|
2347
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2348
|
+
className: "mx-auto max-w-5xl",
|
|
2349
|
+
children: [header, currentStep?.component]
|
|
2350
|
+
})
|
|
2351
|
+
}), footer]
|
|
2352
|
+
})]
|
|
2353
|
+
});
|
|
2354
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2355
|
+
className: (0, _openzeppelin_ui_utils.cn)("flex h-full flex-col", className),
|
|
2356
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2357
|
+
className: "shrink-0 p-6 pb-0",
|
|
2358
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WizardStepper, {
|
|
2359
|
+
variant: "horizontal",
|
|
2360
|
+
steps: stepDefs,
|
|
2361
|
+
currentStepIndex: safeIndex,
|
|
2362
|
+
furthestStepIndex: resolvedFurthestStepIndex,
|
|
2363
|
+
onStepClick: onStepChange
|
|
2364
|
+
})
|
|
2365
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2366
|
+
className: "flex min-w-0 flex-1 flex-col overflow-hidden",
|
|
2367
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2368
|
+
className: "flex-1 overflow-y-auto p-8",
|
|
2369
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2370
|
+
className: "mx-auto max-w-5xl",
|
|
2371
|
+
children: [header, currentStep?.component]
|
|
2372
|
+
})
|
|
2373
|
+
}), footer]
|
|
2374
|
+
})]
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
function ScrollableLayout({ steps, currentStepIndex, onStepChange, header, onComplete, scrollPadding, className }) {
|
|
2378
|
+
const instanceId = (0, react.useId)();
|
|
2379
|
+
const scrollRef = (0, react.useRef)(null);
|
|
2380
|
+
const sectionId = (0, react.useCallback)((stepId) => `wizard-section-${instanceId}-${stepId}`, [instanceId]);
|
|
2381
|
+
const { activeIndex, furthestStepIndex, scrollToSection } = useScrollableWizardStepTracking({
|
|
2382
|
+
steps,
|
|
2383
|
+
currentStepIndex,
|
|
2384
|
+
onStepChange,
|
|
2385
|
+
scrollRef,
|
|
2386
|
+
sectionId,
|
|
2387
|
+
scrollPadding
|
|
2388
|
+
});
|
|
2389
|
+
if (steps.length === 0) return null;
|
|
2390
|
+
const stepDefs = toStepDefs(steps, activeIndex);
|
|
2391
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2392
|
+
className: (0, _openzeppelin_ui_utils.cn)("flex h-full gap-6", className),
|
|
2393
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2394
|
+
className: "w-[220px] shrink-0 py-6 pl-6",
|
|
2395
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WizardStepper, {
|
|
2396
|
+
variant: "vertical",
|
|
2397
|
+
steps: stepDefs,
|
|
2398
|
+
currentStepIndex: activeIndex,
|
|
2399
|
+
furthestStepIndex,
|
|
2400
|
+
onStepClick: scrollToSection,
|
|
2401
|
+
freeNavigation: true,
|
|
2402
|
+
className: "h-full"
|
|
2403
|
+
})
|
|
2404
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2405
|
+
ref: scrollRef,
|
|
2406
|
+
className: "flex min-w-0 flex-1 flex-col overflow-y-auto p-8",
|
|
2407
|
+
children: [
|
|
2408
|
+
header,
|
|
2409
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2410
|
+
className: "space-y-12",
|
|
2411
|
+
children: steps.map((step) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
2412
|
+
id: sectionId(step.id),
|
|
2413
|
+
children: step.component
|
|
2414
|
+
}, step.id))
|
|
2415
|
+
}),
|
|
2416
|
+
onComplete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2417
|
+
className: "flex justify-end pt-8",
|
|
2418
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
2419
|
+
type: "button",
|
|
2420
|
+
onClick: onComplete,
|
|
2421
|
+
children: "Finish"
|
|
2422
|
+
})
|
|
2423
|
+
})
|
|
2424
|
+
]
|
|
2425
|
+
})]
|
|
2426
|
+
});
|
|
2427
|
+
}
|
|
2428
|
+
function toStepDefs(steps, currentStepIndex) {
|
|
2429
|
+
return steps.map((s, i) => {
|
|
2430
|
+
const isInvalid = s.isInvalid ?? s.isValid === false;
|
|
2431
|
+
const status = s.status ?? (i < currentStepIndex && !isInvalid ? "completed" : "pending");
|
|
2432
|
+
return {
|
|
2433
|
+
id: s.id,
|
|
2434
|
+
title: s.title,
|
|
2435
|
+
status,
|
|
2436
|
+
isInvalid
|
|
2437
|
+
};
|
|
2438
|
+
});
|
|
2439
|
+
}
|
|
2440
|
+
/**
|
|
2441
|
+
* A layout component for the wizard.
|
|
2442
|
+
*
|
|
2443
|
+
* @param props - The props for the WizardLayout component.
|
|
2444
|
+
* @returns A React node representing the layout component.
|
|
2445
|
+
*/
|
|
2446
|
+
function WizardLayout(props) {
|
|
2447
|
+
const { variant = "horizontal", ...rest } = props;
|
|
2448
|
+
if (variant === "scrollable") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollableLayout, {
|
|
2449
|
+
steps: rest.steps,
|
|
2450
|
+
currentStepIndex: rest.currentStepIndex,
|
|
2451
|
+
onStepChange: rest.onStepChange,
|
|
2452
|
+
header: rest.header,
|
|
2453
|
+
onComplete: rest.onComplete,
|
|
2454
|
+
scrollPadding: rest.scrollPadding,
|
|
2455
|
+
className: rest.className
|
|
2456
|
+
});
|
|
2457
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PagedLayout, {
|
|
2458
|
+
...rest,
|
|
2459
|
+
variant
|
|
2460
|
+
});
|
|
2461
|
+
}
|
|
2462
|
+
|
|
1858
2463
|
//#endregion
|
|
1859
2464
|
//#region src/components/fields/address-suggestion/context.ts
|
|
1860
2465
|
/**
|
|
@@ -5860,6 +6465,9 @@ exports.TooltipProvider = TooltipProvider;
|
|
|
5860
6465
|
exports.TooltipTrigger = TooltipTrigger;
|
|
5861
6466
|
exports.UrlField = UrlField;
|
|
5862
6467
|
exports.ViewContractStateButton = ViewContractStateButton;
|
|
6468
|
+
exports.WizardLayout = WizardLayout;
|
|
6469
|
+
exports.WizardNavigation = WizardNavigation;
|
|
6470
|
+
exports.WizardStepper = WizardStepper;
|
|
5863
6471
|
exports.buttonVariants = buttonVariants;
|
|
5864
6472
|
exports.computeChildTouched = computeChildTouched;
|
|
5865
6473
|
exports.createFocusManager = createFocusManager;
|