@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.mjs
CHANGED
|
@@ -3,9 +3,10 @@ import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
4
|
import { AlertCircle, Calendar as Calendar$1, Check, CheckCircle, CheckCircle2, CheckIcon, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Circle, CloudOff, Copy, DollarSign, ExternalLink as ExternalLink$1, ExternalLinkIcon, Eye, EyeOff, File, FileText, GripVertical, Hash, Info, Loader2, Menu, MoreHorizontal, Network, Pencil, Plus, Search, Settings, Timer, Upload, X } from "lucide-react";
|
|
5
5
|
import * as React$1 from "react";
|
|
6
|
-
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
6
|
+
import React, { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
7
7
|
import { cn, getDefaultValueForType, getInvalidUrlMessage, getServiceDisplayName, isValidUrl, truncateMiddle, validateBytesSimple } from "@openzeppelin/ui-utils";
|
|
8
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
9
10
|
import { Slot, Slottable } from "@radix-ui/react-slot";
|
|
10
11
|
import { DayPicker } from "react-day-picker";
|
|
11
12
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
@@ -20,7 +21,6 @@ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
|
|
20
21
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
21
22
|
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
|
|
22
23
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
23
|
-
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
24
24
|
import { isMapEntryArray } from "@openzeppelin/ui-types";
|
|
25
25
|
import { Toaster as Toaster$1, toast } from "sonner";
|
|
26
26
|
import { useTheme } from "next-themes";
|
|
@@ -102,6 +102,19 @@ const AccordionContent = React$1.forwardRef(({ className, children, variant: var
|
|
|
102
102
|
});
|
|
103
103
|
AccordionContent.displayName = "AccordionContent";
|
|
104
104
|
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/components/ui/tooltip.tsx
|
|
107
|
+
const TooltipProvider = TooltipPrimitive.Provider;
|
|
108
|
+
const Tooltip = TooltipPrimitive.Root;
|
|
109
|
+
const TooltipTrigger = TooltipPrimitive.Trigger;
|
|
110
|
+
const TooltipContent = React$1.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsx(TooltipPrimitive.Content, {
|
|
111
|
+
ref,
|
|
112
|
+
sideOffset,
|
|
113
|
+
className: 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),
|
|
114
|
+
...props
|
|
115
|
+
}) }));
|
|
116
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
117
|
+
|
|
105
118
|
//#endregion
|
|
106
119
|
//#region src/components/ui/address-display/context.ts
|
|
107
120
|
/**
|
|
@@ -114,8 +127,20 @@ const AddressLabelContext = createContext(null);
|
|
|
114
127
|
//#endregion
|
|
115
128
|
//#region src/components/ui/address-display/address-display.tsx
|
|
116
129
|
/**
|
|
130
|
+
* True when the primary input can hover (e.g. desktop with mouse).
|
|
131
|
+
* Touch-first phones typically report false. SSR assumes hover-capable.
|
|
132
|
+
*/
|
|
133
|
+
function usePrefersHover() {
|
|
134
|
+
return React$1.useSyncExternalStore(React$1.useCallback((onStoreChange) => {
|
|
135
|
+
if (typeof window === "undefined" || typeof window.matchMedia === "undefined") return () => {};
|
|
136
|
+
const mq = window.matchMedia("(hover: hover)");
|
|
137
|
+
mq.addEventListener("change", onStoreChange);
|
|
138
|
+
return () => mq.removeEventListener("change", onStoreChange);
|
|
139
|
+
}, []), () => typeof window !== "undefined" && typeof window.matchMedia !== "undefined" ? window.matchMedia("(hover: hover)").matches : true, () => true);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
117
142
|
* Displays a blockchain address with optional truncation, copy button,
|
|
118
|
-
* explorer link, and human-readable label.
|
|
143
|
+
* explorer link, tooltip, and human-readable label.
|
|
119
144
|
*
|
|
120
145
|
* Labels are resolved in priority order:
|
|
121
146
|
* 1. Explicit `label` prop
|
|
@@ -140,11 +165,22 @@ const AddressLabelContext = createContext(null);
|
|
|
140
165
|
*
|
|
141
166
|
* // Suppress label resolution for a specific instance
|
|
142
167
|
* <AddressDisplay address="0x742d35Cc..." disableLabel />
|
|
168
|
+
*
|
|
169
|
+
* // Reveal full address on hover (still truncated when idle)
|
|
170
|
+
* <AddressDisplay address="0x742d35Cc..." untruncateOnHover />
|
|
171
|
+
*
|
|
172
|
+
* // Tooltip with full address on hover + copy icon on hover
|
|
173
|
+
* <AddressDisplay address="0x742d35Cc..." showTooltip showCopyButton showCopyButtonOnHover />
|
|
174
|
+
*
|
|
175
|
+
* // Inline variant (no chip background) — useful inside wallet bars
|
|
176
|
+
* <AddressDisplay address="0x742d35Cc..." variant="inline" showTooltip showCopyButton />
|
|
143
177
|
* ```
|
|
144
178
|
*/
|
|
145
|
-
function AddressDisplay({ address, truncate = true, startChars = 6, endChars = 4, showCopyButton = false, showCopyButtonOnHover = false, explorerUrl, label: labelProp, onLabelEdit: onLabelEditProp, networkId, disableLabel = false, className, ...props }) {
|
|
179
|
+
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 }) {
|
|
146
180
|
const [copied, setCopied] = React$1.useState(false);
|
|
181
|
+
const [isHovered, setIsHovered] = React$1.useState(false);
|
|
147
182
|
const copyTimeoutRef = React$1.useRef(null);
|
|
183
|
+
const prefersHover = usePrefersHover();
|
|
148
184
|
const resolver = React$1.useContext(AddressLabelContext);
|
|
149
185
|
const resolvedLabel = disableLabel ? void 0 : labelProp ?? resolver?.resolveLabel(address, networkId);
|
|
150
186
|
const contextEditHandler = React$1.useCallback(() => {
|
|
@@ -155,7 +191,25 @@ function AddressDisplay({ address, truncate = true, startChars = 6, endChars = 4
|
|
|
155
191
|
networkId
|
|
156
192
|
]);
|
|
157
193
|
const editHandler = disableLabel ? void 0 : onLabelEditProp ?? (resolver?.onEditLabel ? contextEditHandler : void 0);
|
|
158
|
-
const
|
|
194
|
+
const canUntruncate = untruncateOnHover && truncate && !showTooltip;
|
|
195
|
+
const showFullAddress = !truncate || canUntruncate && isHovered;
|
|
196
|
+
const displayAddress = showFullAddress ? address : truncateMiddle(address, startChars, endChars);
|
|
197
|
+
const addressTextClassName = cn(!showFullAddress && "truncate", (showFullAddress || !truncate) && "break-all");
|
|
198
|
+
const expandInteractionClassName = canUntruncate && !prefersHover ? "cursor-pointer" : void 0;
|
|
199
|
+
const handlePointerEnter = (e) => {
|
|
200
|
+
if (canUntruncate && prefersHover) setIsHovered(true);
|
|
201
|
+
onPointerEnter?.(e);
|
|
202
|
+
onMouseEnter?.(e);
|
|
203
|
+
};
|
|
204
|
+
const handlePointerLeave = (e) => {
|
|
205
|
+
if (canUntruncate && prefersHover) setIsHovered(false);
|
|
206
|
+
onPointerLeave?.(e);
|
|
207
|
+
onMouseLeave?.(e);
|
|
208
|
+
};
|
|
209
|
+
const handleUntruncateClick = (e) => {
|
|
210
|
+
if (canUntruncate && !prefersHover) setIsHovered((open) => !open);
|
|
211
|
+
onClick?.(e);
|
|
212
|
+
};
|
|
159
213
|
const handleCopy = (e) => {
|
|
160
214
|
e.stopPropagation();
|
|
161
215
|
navigator.clipboard.writeText(address);
|
|
@@ -171,6 +225,7 @@ function AddressDisplay({ address, truncate = true, startChars = 6, endChars = 4
|
|
|
171
225
|
if (copyTimeoutRef.current) window.clearTimeout(copyTimeoutRef.current);
|
|
172
226
|
};
|
|
173
227
|
}, []);
|
|
228
|
+
const isChip = variant === "chip";
|
|
174
229
|
const actionButtons = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
175
230
|
showCopyButton && /* @__PURE__ */ jsx("button", {
|
|
176
231
|
type: "button",
|
|
@@ -183,6 +238,9 @@ function AddressDisplay({ address, truncate = true, startChars = 6, endChars = 4
|
|
|
183
238
|
href: explorerUrl,
|
|
184
239
|
target: "_blank",
|
|
185
240
|
rel: "noopener noreferrer",
|
|
241
|
+
onClick: (e) => {
|
|
242
|
+
e.stopPropagation();
|
|
243
|
+
},
|
|
186
244
|
className: "ml-1.5 shrink-0 text-slate-500 transition-colors hover:text-slate-700",
|
|
187
245
|
"aria-label": "View in explorer",
|
|
188
246
|
children: /* @__PURE__ */ jsx(ExternalLink$1, { className: "h-3.5 w-3.5" })
|
|
@@ -198,28 +256,48 @@ function AddressDisplay({ address, truncate = true, startChars = 6, endChars = 4
|
|
|
198
256
|
children: /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" })
|
|
199
257
|
})
|
|
200
258
|
] });
|
|
201
|
-
|
|
202
|
-
|
|
259
|
+
const shouldShowTooltip = showTooltip && truncate;
|
|
260
|
+
const wrapWithTooltip = (content) => {
|
|
261
|
+
if (!shouldShowTooltip) return content;
|
|
262
|
+
return /* @__PURE__ */ jsx(TooltipProvider, {
|
|
263
|
+
delayDuration: 300,
|
|
264
|
+
children: /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
265
|
+
asChild: true,
|
|
266
|
+
children: content
|
|
267
|
+
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
268
|
+
className: "font-mono text-xs",
|
|
269
|
+
children: address
|
|
270
|
+
})] })
|
|
271
|
+
});
|
|
272
|
+
};
|
|
273
|
+
if (resolvedLabel) return wrapWithTooltip(/* @__PURE__ */ jsxs("div", {
|
|
274
|
+
className: 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),
|
|
275
|
+
onPointerEnter: handlePointerEnter,
|
|
276
|
+
onPointerLeave: handlePointerLeave,
|
|
277
|
+
onClick: handleUntruncateClick,
|
|
203
278
|
...props,
|
|
204
279
|
children: [/* @__PURE__ */ jsx("span", {
|
|
205
280
|
className: "truncate font-sans font-medium text-slate-900 leading-snug",
|
|
206
281
|
children: resolvedLabel
|
|
207
282
|
}), /* @__PURE__ */ jsxs("div", {
|
|
208
|
-
className: "flex items-center font-mono text-[10px] text-slate-400 leading-snug",
|
|
283
|
+
className: "flex min-w-0 items-center font-mono text-[10px] text-slate-400 leading-snug",
|
|
209
284
|
children: [/* @__PURE__ */ jsx("span", {
|
|
210
|
-
className:
|
|
285
|
+
className: addressTextClassName,
|
|
211
286
|
children: displayAddress
|
|
212
287
|
}), actionButtons]
|
|
213
288
|
})]
|
|
214
|
-
});
|
|
215
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
216
|
-
className: 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),
|
|
289
|
+
}));
|
|
290
|
+
return wrapWithTooltip(/* @__PURE__ */ jsxs("div", {
|
|
291
|
+
className: 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),
|
|
292
|
+
onPointerEnter: handlePointerEnter,
|
|
293
|
+
onPointerLeave: handlePointerLeave,
|
|
294
|
+
onClick: handleUntruncateClick,
|
|
217
295
|
...props,
|
|
218
296
|
children: [/* @__PURE__ */ jsx("span", {
|
|
219
|
-
className:
|
|
297
|
+
className: addressTextClassName,
|
|
220
298
|
children: displayAddress
|
|
221
299
|
}), actionButtons]
|
|
222
|
-
});
|
|
300
|
+
}));
|
|
223
301
|
}
|
|
224
302
|
|
|
225
303
|
//#endregion
|
|
@@ -1593,12 +1671,12 @@ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
|
|
1593
1671
|
* Can render as a button or anchor element depending on whether href is provided.
|
|
1594
1672
|
*/
|
|
1595
1673
|
function SidebarButton({ icon, children, onClick, size = "default", badge, disabled = false, isSelected = false, href, target, rel, className }) {
|
|
1596
|
-
const commonClass = cn("group relative flex items-center gap-2 px-3 py-2
|
|
1674
|
+
const commonClass = 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);
|
|
1597
1675
|
const content = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
1598
1676
|
className: "flex items-center gap-2",
|
|
1599
1677
|
children: [icon, children]
|
|
1600
1678
|
}), badge && /* @__PURE__ */ jsx("span", {
|
|
1601
|
-
className: "text-xs px-2 py-
|
|
1679
|
+
className: "text-xs px-2 py-0.5 bg-muted text-muted-foreground rounded-full font-medium",
|
|
1602
1680
|
children: badge
|
|
1603
1681
|
})] });
|
|
1604
1682
|
if (href) return /* @__PURE__ */ jsx("a", {
|
|
@@ -1796,19 +1874,6 @@ const Textarea = React$1.forwardRef(({ className, ...props }, ref) => {
|
|
|
1796
1874
|
});
|
|
1797
1875
|
Textarea.displayName = "Textarea";
|
|
1798
1876
|
|
|
1799
|
-
//#endregion
|
|
1800
|
-
//#region src/components/ui/tooltip.tsx
|
|
1801
|
-
const TooltipProvider = TooltipPrimitive.Provider;
|
|
1802
|
-
const Tooltip = TooltipPrimitive.Root;
|
|
1803
|
-
const TooltipTrigger = TooltipPrimitive.Trigger;
|
|
1804
|
-
const TooltipContent = React$1.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(TooltipPrimitive.Content, {
|
|
1805
|
-
ref,
|
|
1806
|
-
sideOffset,
|
|
1807
|
-
className: 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),
|
|
1808
|
-
...props
|
|
1809
|
-
}));
|
|
1810
|
-
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
1811
|
-
|
|
1812
1877
|
//#endregion
|
|
1813
1878
|
//#region src/components/ui/view-contract-state-button.tsx
|
|
1814
1879
|
/**
|
|
@@ -1843,6 +1908,546 @@ function ViewContractStateButton({ contractAddress, onToggle }) {
|
|
|
1843
1908
|
});
|
|
1844
1909
|
}
|
|
1845
1910
|
|
|
1911
|
+
//#endregion
|
|
1912
|
+
//#region src/components/ui/wizard/WizardStepper.tsx
|
|
1913
|
+
function resolveState(step, index, currentStepIndex, furthestStepIndex) {
|
|
1914
|
+
if (step.status === "completed" || step.status === "skipped") return "completed";
|
|
1915
|
+
if (index === currentStepIndex) return "current";
|
|
1916
|
+
if (step.isInvalid && (index < currentStepIndex || index <= furthestStepIndex)) return "invalid";
|
|
1917
|
+
if (index < currentStepIndex) return "completed";
|
|
1918
|
+
if (index <= furthestStepIndex) return "visited";
|
|
1919
|
+
return "upcoming";
|
|
1920
|
+
}
|
|
1921
|
+
function canClick(state, freeNavigation = false) {
|
|
1922
|
+
if (freeNavigation) return true;
|
|
1923
|
+
return state !== "upcoming";
|
|
1924
|
+
}
|
|
1925
|
+
function StepCircle({ state, index }) {
|
|
1926
|
+
return /* @__PURE__ */ jsx("span", {
|
|
1927
|
+
className: 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"),
|
|
1928
|
+
children: state === "completed" ? /* @__PURE__ */ jsx(Check, { className: "size-3.5" }) : state === "visited" ? /* @__PURE__ */ jsx(Pencil, { className: "size-3" }) : state === "invalid" ? /* @__PURE__ */ jsx(AlertCircle, { className: "size-3.5" }) : index + 1
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
function StepLabel({ title, state, isSkipped }) {
|
|
1932
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1933
|
+
className: "min-w-0 flex-1",
|
|
1934
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1935
|
+
className: 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"),
|
|
1936
|
+
children: title
|
|
1937
|
+
}), isSkipped && /* @__PURE__ */ jsx("span", {
|
|
1938
|
+
className: "mt-0.5 block text-[11px] text-zinc-400",
|
|
1939
|
+
children: "Skipped"
|
|
1940
|
+
})]
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
function VerticalStepper({ steps, currentStepIndex, furthestStepIndex = currentStepIndex, onStepClick, freeNavigation, className }) {
|
|
1944
|
+
return /* @__PURE__ */ jsx("nav", {
|
|
1945
|
+
"aria-label": "Wizard steps",
|
|
1946
|
+
className: cn("rounded-2xl border border-zinc-200 bg-white p-6", className),
|
|
1947
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1948
|
+
className: "flex flex-col gap-1",
|
|
1949
|
+
children: steps.map((step, index) => {
|
|
1950
|
+
const state = resolveState(step, index, currentStepIndex, furthestStepIndex);
|
|
1951
|
+
const clickable = canClick(state, freeNavigation) && !!onStepClick;
|
|
1952
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
1953
|
+
type: "button",
|
|
1954
|
+
onClick: () => clickable && onStepClick?.(index),
|
|
1955
|
+
disabled: !clickable,
|
|
1956
|
+
className: 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"),
|
|
1957
|
+
"aria-current": state === "current" ? "step" : void 0,
|
|
1958
|
+
children: [/* @__PURE__ */ jsx(StepCircle, {
|
|
1959
|
+
state,
|
|
1960
|
+
index
|
|
1961
|
+
}), /* @__PURE__ */ jsx(StepLabel, {
|
|
1962
|
+
title: step.title,
|
|
1963
|
+
state,
|
|
1964
|
+
isSkipped: step.status === "skipped"
|
|
1965
|
+
})]
|
|
1966
|
+
}, step.id);
|
|
1967
|
+
})
|
|
1968
|
+
})
|
|
1969
|
+
});
|
|
1970
|
+
}
|
|
1971
|
+
function HorizontalStepper({ steps, currentStepIndex, furthestStepIndex = currentStepIndex, onStepClick, freeNavigation, className }) {
|
|
1972
|
+
return /* @__PURE__ */ jsx("nav", {
|
|
1973
|
+
"aria-label": "Wizard steps",
|
|
1974
|
+
className: cn("rounded-2xl border border-zinc-200 bg-white p-6", className),
|
|
1975
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1976
|
+
className: "flex w-full items-center",
|
|
1977
|
+
children: steps.map((step, index) => {
|
|
1978
|
+
const state = resolveState(step, index, currentStepIndex, furthestStepIndex);
|
|
1979
|
+
const clickable = canClick(state, freeNavigation) && !!onStepClick;
|
|
1980
|
+
const isLast = index === steps.length - 1;
|
|
1981
|
+
return /* @__PURE__ */ jsxs(React.Fragment, { children: [/* @__PURE__ */ jsxs("button", {
|
|
1982
|
+
type: "button",
|
|
1983
|
+
onClick: () => clickable && onStepClick?.(index),
|
|
1984
|
+
disabled: !clickable,
|
|
1985
|
+
className: 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"),
|
|
1986
|
+
"aria-current": state === "current" ? "step" : void 0,
|
|
1987
|
+
"aria-label": `Step ${index + 1}: ${step.title}`,
|
|
1988
|
+
children: [/* @__PURE__ */ jsx(StepCircle, {
|
|
1989
|
+
state,
|
|
1990
|
+
index
|
|
1991
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1992
|
+
className: "hidden sm:block",
|
|
1993
|
+
children: /* @__PURE__ */ jsx(StepLabel, {
|
|
1994
|
+
title: step.title,
|
|
1995
|
+
state,
|
|
1996
|
+
isSkipped: step.status === "skipped"
|
|
1997
|
+
})
|
|
1998
|
+
})]
|
|
1999
|
+
}), !isLast && /* @__PURE__ */ jsx("div", { className: cn("mx-1 h-px flex-1 transition-colors sm:mx-2", index < currentStepIndex ? "bg-blue-600" : "bg-zinc-200") })] }, step.id);
|
|
2000
|
+
})
|
|
2001
|
+
})
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
2004
|
+
/**
|
|
2005
|
+
* A stepper component for navigating through a series of steps.
|
|
2006
|
+
*
|
|
2007
|
+
* @param props - The props for the WizardStepper component.
|
|
2008
|
+
* @returns A React node representing the stepper component.
|
|
2009
|
+
*/
|
|
2010
|
+
function WizardStepper(props) {
|
|
2011
|
+
const { variant = "horizontal", ...rest } = props;
|
|
2012
|
+
return variant === "vertical" ? /* @__PURE__ */ jsx(VerticalStepper, {
|
|
2013
|
+
...rest,
|
|
2014
|
+
variant
|
|
2015
|
+
}) : /* @__PURE__ */ jsx(HorizontalStepper, {
|
|
2016
|
+
...rest,
|
|
2017
|
+
variant
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
//#endregion
|
|
2022
|
+
//#region src/components/ui/wizard/WizardNavigation.tsx
|
|
2023
|
+
/**
|
|
2024
|
+
* A navigation component for the wizard.
|
|
2025
|
+
*
|
|
2026
|
+
* @param props - The props for the WizardNavigation component.
|
|
2027
|
+
* @returns A React node representing the navigation component.
|
|
2028
|
+
*/
|
|
2029
|
+
function WizardNavigation({ isFirstStep, isLastStep, canProceed = true, onPrevious, onNext, onCancel, extraActions, nextLabel = "Next", lastStepLabel = "Finish", className }) {
|
|
2030
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2031
|
+
className: cn("flex items-center justify-between", className),
|
|
2032
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2033
|
+
className: "flex gap-2",
|
|
2034
|
+
children: [onCancel && /* @__PURE__ */ jsxs(Button, {
|
|
2035
|
+
type: "button",
|
|
2036
|
+
variant: "outline",
|
|
2037
|
+
onClick: onCancel,
|
|
2038
|
+
className: "gap-2",
|
|
2039
|
+
children: [/* @__PURE__ */ jsx(X, { className: "size-4" }), "Cancel"]
|
|
2040
|
+
}), !isFirstStep && /* @__PURE__ */ jsxs(Button, {
|
|
2041
|
+
type: "button",
|
|
2042
|
+
variant: "outline",
|
|
2043
|
+
onClick: onPrevious,
|
|
2044
|
+
className: "gap-2",
|
|
2045
|
+
children: [/* @__PURE__ */ jsx(ChevronLeft, { className: "size-4" }), "Previous"]
|
|
2046
|
+
})]
|
|
2047
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
2048
|
+
className: "flex gap-2",
|
|
2049
|
+
children: [extraActions, /* @__PURE__ */ jsxs(Button, {
|
|
2050
|
+
type: "button",
|
|
2051
|
+
onClick: onNext,
|
|
2052
|
+
disabled: !canProceed,
|
|
2053
|
+
className: "gap-2",
|
|
2054
|
+
children: [isLastStep ? lastStepLabel : nextLabel, !isLastStep && /* @__PURE__ */ jsx(ChevronRight, { className: "size-4" })]
|
|
2055
|
+
})]
|
|
2056
|
+
})]
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
//#endregion
|
|
2061
|
+
//#region src/components/ui/wizard/hooks.ts
|
|
2062
|
+
/**
|
|
2063
|
+
* Clamp a step index into the valid range for the current wizard.
|
|
2064
|
+
*/
|
|
2065
|
+
function getSafeStepIndex(stepCount, currentStepIndex) {
|
|
2066
|
+
if (stepCount === 0) return 0;
|
|
2067
|
+
return Math.max(0, Math.min(currentStepIndex, stepCount - 1));
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* Track the highest step reached unless a controlled value is provided.
|
|
2071
|
+
*/
|
|
2072
|
+
function useFurthestStepIndex(currentStepIndex, controlledFurthestStepIndex) {
|
|
2073
|
+
const [internalFurthestStepIndex, setInternalFurthestStepIndex] = useState(currentStepIndex);
|
|
2074
|
+
useEffect(() => {
|
|
2075
|
+
setInternalFurthestStepIndex((prev) => Math.max(prev, currentStepIndex));
|
|
2076
|
+
}, [currentStepIndex]);
|
|
2077
|
+
return controlledFurthestStepIndex ?? internalFurthestStepIndex;
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Keep the scrollable wizard's active and visited step state in sync with scrolling and clicks.
|
|
2081
|
+
*/
|
|
2082
|
+
function useScrollableWizardStepTracking({ steps, currentStepIndex, onStepChange, scrollRef, sectionId, scrollPadding = SCROLL_PADDING_PX }) {
|
|
2083
|
+
const safeIndex = getSafeStepIndex(steps.length, currentStepIndex);
|
|
2084
|
+
const initialIndexRef = useRef(safeIndex);
|
|
2085
|
+
const rafRef = useRef(null);
|
|
2086
|
+
const manualSelectionIndexRef = useRef(null);
|
|
2087
|
+
const stepsRef = useRef(steps);
|
|
2088
|
+
const sectionIdRef = useRef(sectionId);
|
|
2089
|
+
const onStepChangeRef = useRef(onStepChange);
|
|
2090
|
+
const scrollPaddingRef = useRef(scrollPadding);
|
|
2091
|
+
useEffect(() => {
|
|
2092
|
+
stepsRef.current = steps;
|
|
2093
|
+
sectionIdRef.current = sectionId;
|
|
2094
|
+
onStepChangeRef.current = onStepChange;
|
|
2095
|
+
scrollPaddingRef.current = scrollPadding;
|
|
2096
|
+
});
|
|
2097
|
+
const [activeIndex, setActiveIndex] = useState(initialIndexRef.current);
|
|
2098
|
+
const activeIndexRef = useRef(initialIndexRef.current);
|
|
2099
|
+
const [furthestStepIndex, setFurthestStepIndex] = useState(initialIndexRef.current);
|
|
2100
|
+
const isMountedRef = useRef(false);
|
|
2101
|
+
const clearManualSelection = useCallback(() => {
|
|
2102
|
+
manualSelectionIndexRef.current = null;
|
|
2103
|
+
}, []);
|
|
2104
|
+
useEffect(() => {
|
|
2105
|
+
const container = scrollRef.current;
|
|
2106
|
+
if (!container) return;
|
|
2107
|
+
const ownerDocument = container.ownerDocument;
|
|
2108
|
+
isMountedRef.current = false;
|
|
2109
|
+
let didCompleteInitialRaf = false;
|
|
2110
|
+
const releaseManualSelectionOnUserScroll = () => {
|
|
2111
|
+
clearManualSelection();
|
|
2112
|
+
};
|
|
2113
|
+
const handleKeyDown = (event) => {
|
|
2114
|
+
if (isScrollableNavigationKey(event)) clearManualSelection();
|
|
2115
|
+
};
|
|
2116
|
+
const handleScroll = () => {
|
|
2117
|
+
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
|
|
2118
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
2119
|
+
const currentSteps = stepsRef.current;
|
|
2120
|
+
const currentSectionId = sectionIdRef.current;
|
|
2121
|
+
const currentOnStepChange = onStepChangeRef.current;
|
|
2122
|
+
if (currentSteps.length === 0) return;
|
|
2123
|
+
const manualSelectionIndex = manualSelectionIndexRef.current;
|
|
2124
|
+
const naturalState = resolveScrollableActiveIndex(container, currentSteps, currentSectionId);
|
|
2125
|
+
const naturalActiveIndex = naturalState.activeIndex;
|
|
2126
|
+
const newActiveIndex = manualSelectionIndex ?? naturalActiveIndex;
|
|
2127
|
+
const shouldCommitFurthestStepIndex = manualSelectionIndex !== null ? true : naturalState.commitFurthestStepIndex;
|
|
2128
|
+
if (activeIndexRef.current !== newActiveIndex) {
|
|
2129
|
+
activeIndexRef.current = newActiveIndex;
|
|
2130
|
+
setActiveIndex(newActiveIndex);
|
|
2131
|
+
if (isMountedRef.current) {
|
|
2132
|
+
lastEmittedIndexRef.current = newActiveIndex;
|
|
2133
|
+
currentOnStepChange(newActiveIndex);
|
|
2134
|
+
}
|
|
2135
|
+
} else setActiveIndex(newActiveIndex);
|
|
2136
|
+
if (shouldCommitFurthestStepIndex) setFurthestStepIndex((prev) => Math.max(prev, newActiveIndex));
|
|
2137
|
+
rafRef.current = null;
|
|
2138
|
+
if (!didCompleteInitialRaf) {
|
|
2139
|
+
didCompleteInitialRaf = true;
|
|
2140
|
+
isMountedRef.current = true;
|
|
2141
|
+
}
|
|
2142
|
+
});
|
|
2143
|
+
};
|
|
2144
|
+
container.addEventListener("wheel", releaseManualSelectionOnUserScroll, { passive: true });
|
|
2145
|
+
container.addEventListener("touchmove", releaseManualSelectionOnUserScroll, { passive: true });
|
|
2146
|
+
container.addEventListener("pointerdown", releaseManualSelectionOnUserScroll);
|
|
2147
|
+
ownerDocument.addEventListener("keydown", handleKeyDown);
|
|
2148
|
+
container.addEventListener("scroll", handleScroll, { passive: true });
|
|
2149
|
+
handleScroll();
|
|
2150
|
+
return () => {
|
|
2151
|
+
isMountedRef.current = false;
|
|
2152
|
+
container.removeEventListener("wheel", releaseManualSelectionOnUserScroll);
|
|
2153
|
+
container.removeEventListener("touchmove", releaseManualSelectionOnUserScroll);
|
|
2154
|
+
container.removeEventListener("pointerdown", releaseManualSelectionOnUserScroll);
|
|
2155
|
+
ownerDocument.removeEventListener("keydown", handleKeyDown);
|
|
2156
|
+
container.removeEventListener("scroll", handleScroll);
|
|
2157
|
+
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
|
|
2158
|
+
};
|
|
2159
|
+
}, [clearManualSelection, scrollRef]);
|
|
2160
|
+
const lastEmittedIndexRef = useRef(safeIndex);
|
|
2161
|
+
useEffect(() => {
|
|
2162
|
+
const newSafeIndex = getSafeStepIndex(stepsRef.current.length, currentStepIndex);
|
|
2163
|
+
if (newSafeIndex === lastEmittedIndexRef.current) return;
|
|
2164
|
+
lastEmittedIndexRef.current = newSafeIndex;
|
|
2165
|
+
activeIndexRef.current = newSafeIndex;
|
|
2166
|
+
setActiveIndex(newSafeIndex);
|
|
2167
|
+
setFurthestStepIndex((prev) => Math.max(prev, newSafeIndex));
|
|
2168
|
+
const step = stepsRef.current[newSafeIndex];
|
|
2169
|
+
if (!step) return;
|
|
2170
|
+
const sectionElement = scrollRef.current?.querySelector(`#${CSS.escape(sectionIdRef.current(step.id))}`);
|
|
2171
|
+
if (scrollRef.current && sectionElement) scrollSectionIntoView(scrollRef.current, sectionElement, scrollPaddingRef.current);
|
|
2172
|
+
}, [currentStepIndex, scrollRef]);
|
|
2173
|
+
return {
|
|
2174
|
+
activeIndex,
|
|
2175
|
+
furthestStepIndex,
|
|
2176
|
+
scrollToSection: useCallback((index) => {
|
|
2177
|
+
const step = stepsRef.current[index];
|
|
2178
|
+
if (!step) return;
|
|
2179
|
+
manualSelectionIndexRef.current = index;
|
|
2180
|
+
activeIndexRef.current = index;
|
|
2181
|
+
lastEmittedIndexRef.current = index;
|
|
2182
|
+
setActiveIndex(index);
|
|
2183
|
+
setFurthestStepIndex((prev) => Math.max(prev, index));
|
|
2184
|
+
onStepChangeRef.current(index);
|
|
2185
|
+
const container = scrollRef.current;
|
|
2186
|
+
const sectionElement = container?.querySelector(`#${CSS.escape(sectionIdRef.current(step.id))}`);
|
|
2187
|
+
if (container && sectionElement) scrollSectionIntoView(container, sectionElement, scrollPaddingRef.current);
|
|
2188
|
+
}, [scrollRef])
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2191
|
+
function resolveScrollableActiveIndex(container, steps, sectionId) {
|
|
2192
|
+
if (steps.length === 0) return {
|
|
2193
|
+
activeIndex: 0,
|
|
2194
|
+
commitFurthestStepIndex: false
|
|
2195
|
+
};
|
|
2196
|
+
const containerRect = container.getBoundingClientRect();
|
|
2197
|
+
const anchorY = containerRect.top + Math.min(containerRect.height * .35, 220);
|
|
2198
|
+
const isScrollable = container.scrollHeight > container.clientHeight + 1;
|
|
2199
|
+
const isAtBottom = isScrollable && container.scrollTop + container.clientHeight >= container.scrollHeight - 1;
|
|
2200
|
+
const isNearBottom = isScrollable && container.scrollTop + container.clientHeight >= container.scrollHeight - 4;
|
|
2201
|
+
if (isAtBottom) return {
|
|
2202
|
+
activeIndex: steps.length - 1,
|
|
2203
|
+
commitFurthestStepIndex: false
|
|
2204
|
+
};
|
|
2205
|
+
let activeIndex = 0;
|
|
2206
|
+
let highestScore = Number.NEGATIVE_INFINITY;
|
|
2207
|
+
for (let i = 0; i < steps.length; i++) {
|
|
2208
|
+
const sectionMetrics = getSectionMetrics(container, steps[i].id, sectionId, containerRect);
|
|
2209
|
+
if (!sectionMetrics) continue;
|
|
2210
|
+
const score = scoreScrollableStep({
|
|
2211
|
+
stepIndex: i,
|
|
2212
|
+
stepCount: steps.length,
|
|
2213
|
+
containerRect,
|
|
2214
|
+
anchorY,
|
|
2215
|
+
isNearBottom,
|
|
2216
|
+
...sectionMetrics
|
|
2217
|
+
});
|
|
2218
|
+
if (score >= highestScore) {
|
|
2219
|
+
highestScore = score;
|
|
2220
|
+
activeIndex = i;
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
return {
|
|
2224
|
+
activeIndex,
|
|
2225
|
+
commitFurthestStepIndex: true
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
const SCROLL_PADDING_PX = 32;
|
|
2229
|
+
function getSectionElement(container, stepId, sectionId) {
|
|
2230
|
+
return container.querySelector(`#${CSS.escape(sectionId(stepId))}`);
|
|
2231
|
+
}
|
|
2232
|
+
function scrollSectionIntoView(container, sectionElement, padding) {
|
|
2233
|
+
const elementTop = sectionElement.getBoundingClientRect().top;
|
|
2234
|
+
const containerTop = container.getBoundingClientRect().top;
|
|
2235
|
+
const targetScrollTop = container.scrollTop + (elementTop - containerTop) - padding;
|
|
2236
|
+
container.scrollTo({
|
|
2237
|
+
top: targetScrollTop,
|
|
2238
|
+
behavior: "smooth"
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
2241
|
+
function getSectionMetrics(container, stepId, sectionId, containerRect) {
|
|
2242
|
+
const sectionElement = getSectionElement(container, stepId, sectionId);
|
|
2243
|
+
if (!sectionElement) return null;
|
|
2244
|
+
const sectionRect = sectionElement.getBoundingClientRect();
|
|
2245
|
+
return {
|
|
2246
|
+
sectionRect,
|
|
2247
|
+
visibleHeight: getVisibleHeight(containerRect, sectionRect)
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
function getVisibleHeight(containerRect, sectionRect) {
|
|
2251
|
+
return Math.max(0, Math.min(sectionRect.bottom, containerRect.bottom) - Math.max(sectionRect.top, containerRect.top));
|
|
2252
|
+
}
|
|
2253
|
+
function scoreScrollableStep({ stepIndex, stepCount, containerRect, sectionRect, visibleHeight, anchorY, isNearBottom }) {
|
|
2254
|
+
const isVisible = visibleHeight > 0;
|
|
2255
|
+
const focusBandTop = containerRect.top + Math.min(containerRect.height * .2, 140);
|
|
2256
|
+
const focusBandBottom = containerRect.top + Math.min(containerRect.height * .55, 360);
|
|
2257
|
+
const focusBandOverlap = getBandOverlapHeight(sectionRect, focusBandTop, focusBandBottom);
|
|
2258
|
+
const distanceToFocusBand = focusBandOverlap > 0 ? 0 : Math.min(Math.abs(sectionRect.top - focusBandBottom), Math.abs(sectionRect.bottom - focusBandTop));
|
|
2259
|
+
const lastStepProminent = stepIndex === stepCount - 1 && visibleHeight >= Math.min(sectionRect.height, containerRect.height) * .25 && sectionRect.top <= containerRect.top + containerRect.height * .65;
|
|
2260
|
+
let score = isVisible ? visibleHeight : Number.NEGATIVE_INFINITY;
|
|
2261
|
+
if (focusBandOverlap > 0) score += 12e3 + focusBandOverlap * 25;
|
|
2262
|
+
if (sectionRect.top <= anchorY) score += 250;
|
|
2263
|
+
score += Math.max(0, 1e3 - distanceToFocusBand);
|
|
2264
|
+
if (isNearBottom && lastStepProminent && isVisible) score += 15e3;
|
|
2265
|
+
return score;
|
|
2266
|
+
}
|
|
2267
|
+
function getBandOverlapHeight(sectionRect, bandTop, bandBottom) {
|
|
2268
|
+
return Math.max(0, Math.min(sectionRect.bottom, bandBottom) - Math.max(sectionRect.top, bandTop));
|
|
2269
|
+
}
|
|
2270
|
+
function isScrollableNavigationKey(event) {
|
|
2271
|
+
if (event.metaKey || event.ctrlKey || event.altKey) return false;
|
|
2272
|
+
return [
|
|
2273
|
+
"ArrowDown",
|
|
2274
|
+
"ArrowUp",
|
|
2275
|
+
"PageDown",
|
|
2276
|
+
"PageUp",
|
|
2277
|
+
"Home",
|
|
2278
|
+
"End",
|
|
2279
|
+
" "
|
|
2280
|
+
].includes(event.key);
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
//#endregion
|
|
2284
|
+
//#region src/components/ui/wizard/WizardLayout.tsx
|
|
2285
|
+
function PagedLayout({ steps, currentStepIndex, furthestStepIndex: furthestStepIndexProp, onStepChange, onComplete, onCancel, navActions, header, variant, className }) {
|
|
2286
|
+
const safeIndex = getSafeStepIndex(steps.length, currentStepIndex);
|
|
2287
|
+
const resolvedFurthestStepIndex = useFurthestStepIndex(safeIndex, furthestStepIndexProp);
|
|
2288
|
+
if (steps.length === 0) return null;
|
|
2289
|
+
const isFirstStep = safeIndex === 0;
|
|
2290
|
+
const isLastStep = safeIndex === steps.length - 1;
|
|
2291
|
+
const currentStep = steps[safeIndex];
|
|
2292
|
+
const canProceed = currentStep?.isValid !== false;
|
|
2293
|
+
const handleNext = () => {
|
|
2294
|
+
if (isLastStep) {
|
|
2295
|
+
onComplete?.();
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
onStepChange(safeIndex + 1);
|
|
2299
|
+
};
|
|
2300
|
+
const handlePrevious = () => {
|
|
2301
|
+
if (!isFirstStep) onStepChange(safeIndex - 1);
|
|
2302
|
+
};
|
|
2303
|
+
const stepDefs = toStepDefs(steps, safeIndex);
|
|
2304
|
+
const footer = /* @__PURE__ */ jsx("div", {
|
|
2305
|
+
className: "shrink-0 border-t border-border bg-background px-8 py-4",
|
|
2306
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2307
|
+
className: "mx-auto max-w-5xl",
|
|
2308
|
+
children: /* @__PURE__ */ jsx(WizardNavigation, {
|
|
2309
|
+
isFirstStep,
|
|
2310
|
+
isLastStep,
|
|
2311
|
+
canProceed,
|
|
2312
|
+
onPrevious: handlePrevious,
|
|
2313
|
+
onNext: handleNext,
|
|
2314
|
+
onCancel,
|
|
2315
|
+
extraActions: navActions
|
|
2316
|
+
})
|
|
2317
|
+
})
|
|
2318
|
+
});
|
|
2319
|
+
if (variant === "vertical") return /* @__PURE__ */ jsxs("div", {
|
|
2320
|
+
className: cn("flex h-full gap-6", className),
|
|
2321
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2322
|
+
className: "w-[220px] shrink-0 py-6 pl-6",
|
|
2323
|
+
children: /* @__PURE__ */ jsx(WizardStepper, {
|
|
2324
|
+
variant: "vertical",
|
|
2325
|
+
steps: stepDefs,
|
|
2326
|
+
currentStepIndex: safeIndex,
|
|
2327
|
+
furthestStepIndex: resolvedFurthestStepIndex,
|
|
2328
|
+
onStepClick: onStepChange,
|
|
2329
|
+
className: "h-full"
|
|
2330
|
+
})
|
|
2331
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
2332
|
+
className: "flex min-w-0 flex-1 flex-col overflow-hidden",
|
|
2333
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2334
|
+
className: "flex-1 overflow-y-auto p-8",
|
|
2335
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2336
|
+
className: "mx-auto max-w-5xl",
|
|
2337
|
+
children: [header, currentStep?.component]
|
|
2338
|
+
})
|
|
2339
|
+
}), footer]
|
|
2340
|
+
})]
|
|
2341
|
+
});
|
|
2342
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2343
|
+
className: cn("flex h-full flex-col", className),
|
|
2344
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2345
|
+
className: "shrink-0 p-6 pb-0",
|
|
2346
|
+
children: /* @__PURE__ */ jsx(WizardStepper, {
|
|
2347
|
+
variant: "horizontal",
|
|
2348
|
+
steps: stepDefs,
|
|
2349
|
+
currentStepIndex: safeIndex,
|
|
2350
|
+
furthestStepIndex: resolvedFurthestStepIndex,
|
|
2351
|
+
onStepClick: onStepChange
|
|
2352
|
+
})
|
|
2353
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
2354
|
+
className: "flex min-w-0 flex-1 flex-col overflow-hidden",
|
|
2355
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2356
|
+
className: "flex-1 overflow-y-auto p-8",
|
|
2357
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2358
|
+
className: "mx-auto max-w-5xl",
|
|
2359
|
+
children: [header, currentStep?.component]
|
|
2360
|
+
})
|
|
2361
|
+
}), footer]
|
|
2362
|
+
})]
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
function ScrollableLayout({ steps, currentStepIndex, onStepChange, header, onComplete, scrollPadding, className }) {
|
|
2366
|
+
const instanceId = useId();
|
|
2367
|
+
const scrollRef = useRef(null);
|
|
2368
|
+
const sectionId = useCallback((stepId) => `wizard-section-${instanceId}-${stepId}`, [instanceId]);
|
|
2369
|
+
const { activeIndex, furthestStepIndex, scrollToSection } = useScrollableWizardStepTracking({
|
|
2370
|
+
steps,
|
|
2371
|
+
currentStepIndex,
|
|
2372
|
+
onStepChange,
|
|
2373
|
+
scrollRef,
|
|
2374
|
+
sectionId,
|
|
2375
|
+
scrollPadding
|
|
2376
|
+
});
|
|
2377
|
+
if (steps.length === 0) return null;
|
|
2378
|
+
const stepDefs = toStepDefs(steps, activeIndex);
|
|
2379
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2380
|
+
className: cn("flex h-full gap-6", className),
|
|
2381
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2382
|
+
className: "w-[220px] shrink-0 py-6 pl-6",
|
|
2383
|
+
children: /* @__PURE__ */ jsx(WizardStepper, {
|
|
2384
|
+
variant: "vertical",
|
|
2385
|
+
steps: stepDefs,
|
|
2386
|
+
currentStepIndex: activeIndex,
|
|
2387
|
+
furthestStepIndex,
|
|
2388
|
+
onStepClick: scrollToSection,
|
|
2389
|
+
freeNavigation: true,
|
|
2390
|
+
className: "h-full"
|
|
2391
|
+
})
|
|
2392
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
2393
|
+
ref: scrollRef,
|
|
2394
|
+
className: "flex min-w-0 flex-1 flex-col overflow-y-auto p-8",
|
|
2395
|
+
children: [
|
|
2396
|
+
header,
|
|
2397
|
+
/* @__PURE__ */ jsx("div", {
|
|
2398
|
+
className: "space-y-12",
|
|
2399
|
+
children: steps.map((step) => /* @__PURE__ */ jsx("section", {
|
|
2400
|
+
id: sectionId(step.id),
|
|
2401
|
+
children: step.component
|
|
2402
|
+
}, step.id))
|
|
2403
|
+
}),
|
|
2404
|
+
onComplete && /* @__PURE__ */ jsx("div", {
|
|
2405
|
+
className: "flex justify-end pt-8",
|
|
2406
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
2407
|
+
type: "button",
|
|
2408
|
+
onClick: onComplete,
|
|
2409
|
+
children: "Finish"
|
|
2410
|
+
})
|
|
2411
|
+
})
|
|
2412
|
+
]
|
|
2413
|
+
})]
|
|
2414
|
+
});
|
|
2415
|
+
}
|
|
2416
|
+
function toStepDefs(steps, currentStepIndex) {
|
|
2417
|
+
return steps.map((s, i) => {
|
|
2418
|
+
const isInvalid = s.isInvalid ?? s.isValid === false;
|
|
2419
|
+
const status = s.status ?? (i < currentStepIndex && !isInvalid ? "completed" : "pending");
|
|
2420
|
+
return {
|
|
2421
|
+
id: s.id,
|
|
2422
|
+
title: s.title,
|
|
2423
|
+
status,
|
|
2424
|
+
isInvalid
|
|
2425
|
+
};
|
|
2426
|
+
});
|
|
2427
|
+
}
|
|
2428
|
+
/**
|
|
2429
|
+
* A layout component for the wizard.
|
|
2430
|
+
*
|
|
2431
|
+
* @param props - The props for the WizardLayout component.
|
|
2432
|
+
* @returns A React node representing the layout component.
|
|
2433
|
+
*/
|
|
2434
|
+
function WizardLayout(props) {
|
|
2435
|
+
const { variant = "horizontal", ...rest } = props;
|
|
2436
|
+
if (variant === "scrollable") return /* @__PURE__ */ jsx(ScrollableLayout, {
|
|
2437
|
+
steps: rest.steps,
|
|
2438
|
+
currentStepIndex: rest.currentStepIndex,
|
|
2439
|
+
onStepChange: rest.onStepChange,
|
|
2440
|
+
header: rest.header,
|
|
2441
|
+
onComplete: rest.onComplete,
|
|
2442
|
+
scrollPadding: rest.scrollPadding,
|
|
2443
|
+
className: rest.className
|
|
2444
|
+
});
|
|
2445
|
+
return /* @__PURE__ */ jsx(PagedLayout, {
|
|
2446
|
+
...rest,
|
|
2447
|
+
variant
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
|
|
1846
2451
|
//#endregion
|
|
1847
2452
|
//#region src/components/fields/address-suggestion/context.ts
|
|
1848
2453
|
/**
|
|
@@ -5720,5 +6325,5 @@ const Toaster = ({ ...props }) => {
|
|
|
5720
6325
|
};
|
|
5721
6326
|
|
|
5722
6327
|
//#endregion
|
|
5723
|
-
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, AddressDisplay, AddressField, AddressLabelProvider, AddressSuggestionProvider, Alert, AlertDescription, AlertTitle, AmountField, ArrayField, ArrayObjectField, Banner, BaseField, BigIntField, BooleanField, Button, BytesField, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, DateRangePicker, DateTimeField, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EcosystemDropdown, EcosystemIcon, EmptyState, EnumField, ErrorMessage, ExternalLink, FileUploadField, Footer, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Header, INTEGER_HTML_PATTERN, INTEGER_INPUT_PATTERN, INTEGER_PATTERN, Input, Label, LoadingButton, MapEntryRow, MapField, MidnightIcon, NetworkErrorNotificationProvider, NetworkIcon, NetworkSelector, NetworkServiceErrorBanner, NetworkStatusBadge, NumberField, ObjectField, OverflowMenu, PasswordField, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, RadioField, RadioGroup, RadioGroupItem, RelayerDetailsCard, Select, SelectContent, SelectField, SelectGroup, SelectGroupedField, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SidebarButton, SidebarGroup, SidebarLayout, SidebarSection, Tabs, TabsContent, TabsList, TabsTrigger, TextAreaField, TextField, Textarea, Toaster, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UrlField, ViewContractStateButton, buttonVariants, computeChildTouched, createFocusManager, createValidationResult, formatValidationError, getAccessibilityProps, getDescribedById, getErrorMessage, getValidationStateClasses, getWidthClasses, handleEscapeKey, handleKeyboardEvent, handleNumericKeys, handleToggleKeys, handleValidationError, hasFieldError, isDuplicateMapKey, useAddressLabel, useAddressSuggestions, useDuplicateKeyIndexes, useMapFieldSync, useNetworkErrorAwareAdapter, useNetworkErrorReporter, useNetworkErrors, validateField, validateMapEntries, validateMapStructure };
|
|
6328
|
+
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, AddressDisplay, AddressField, AddressLabelProvider, AddressSuggestionProvider, Alert, AlertDescription, AlertTitle, AmountField, ArrayField, ArrayObjectField, Banner, BaseField, BigIntField, BooleanField, Button, BytesField, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, DateRangePicker, DateTimeField, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EcosystemDropdown, EcosystemIcon, EmptyState, EnumField, ErrorMessage, ExternalLink, FileUploadField, Footer, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Header, INTEGER_HTML_PATTERN, INTEGER_INPUT_PATTERN, INTEGER_PATTERN, Input, Label, LoadingButton, MapEntryRow, MapField, MidnightIcon, NetworkErrorNotificationProvider, NetworkIcon, NetworkSelector, NetworkServiceErrorBanner, NetworkStatusBadge, NumberField, ObjectField, OverflowMenu, PasswordField, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, RadioField, RadioGroup, RadioGroupItem, RelayerDetailsCard, Select, SelectContent, SelectField, SelectGroup, SelectGroupedField, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SidebarButton, SidebarGroup, SidebarLayout, SidebarSection, Tabs, TabsContent, TabsList, TabsTrigger, TextAreaField, TextField, Textarea, Toaster, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UrlField, ViewContractStateButton, WizardLayout, WizardNavigation, WizardStepper, buttonVariants, computeChildTouched, createFocusManager, createValidationResult, formatValidationError, getAccessibilityProps, getDescribedById, getErrorMessage, getValidationStateClasses, getWidthClasses, handleEscapeKey, handleKeyboardEvent, handleNumericKeys, handleToggleKeys, handleValidationError, hasFieldError, isDuplicateMapKey, useAddressLabel, useAddressSuggestions, useDuplicateKeyIndexes, useMapFieldSync, useNetworkErrorAwareAdapter, useNetworkErrorReporter, useNetworkErrors, validateField, validateMapEntries, validateMapStructure };
|
|
5724
6329
|
//# sourceMappingURL=index.mjs.map
|