@openzeppelin/ui-components 1.4.0 → 1.5.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 +545 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +104 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +104 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +544 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1605,12 +1605,12 @@ SelectSeparator.displayName = _radix_ui_react_select.Separator.displayName;
|
|
|
1605
1605
|
* Can render as a button or anchor element depending on whether href is provided.
|
|
1606
1606
|
*/
|
|
1607
1607
|
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
|
|
1608
|
+
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
1609
|
const content = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1610
1610
|
className: "flex items-center gap-2",
|
|
1611
1611
|
children: [icon, children]
|
|
1612
1612
|
}), badge && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1613
|
-
className: "text-xs px-2 py-
|
|
1613
|
+
className: "text-xs px-2 py-0.5 bg-muted text-muted-foreground rounded-full font-medium",
|
|
1614
1614
|
children: badge
|
|
1615
1615
|
})] });
|
|
1616
1616
|
if (href) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
@@ -1855,6 +1855,546 @@ function ViewContractStateButton({ contractAddress, onToggle }) {
|
|
|
1855
1855
|
});
|
|
1856
1856
|
}
|
|
1857
1857
|
|
|
1858
|
+
//#endregion
|
|
1859
|
+
//#region src/components/ui/wizard/WizardStepper.tsx
|
|
1860
|
+
function resolveState(step, index, currentStepIndex, furthestStepIndex) {
|
|
1861
|
+
if (step.status === "completed" || step.status === "skipped") return "completed";
|
|
1862
|
+
if (index === currentStepIndex) return "current";
|
|
1863
|
+
if (step.isInvalid && (index < currentStepIndex || index <= furthestStepIndex)) return "invalid";
|
|
1864
|
+
if (index < currentStepIndex) return "completed";
|
|
1865
|
+
if (index <= furthestStepIndex) return "visited";
|
|
1866
|
+
return "upcoming";
|
|
1867
|
+
}
|
|
1868
|
+
function canClick(state, freeNavigation = false) {
|
|
1869
|
+
if (freeNavigation) return true;
|
|
1870
|
+
return state !== "upcoming";
|
|
1871
|
+
}
|
|
1872
|
+
function StepCircle({ state, index }) {
|
|
1873
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1874
|
+
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"),
|
|
1875
|
+
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
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
function StepLabel({ title, state, isSkipped }) {
|
|
1879
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1880
|
+
className: "min-w-0 flex-1",
|
|
1881
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1882
|
+
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"),
|
|
1883
|
+
children: title
|
|
1884
|
+
}), isSkipped && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1885
|
+
className: "mt-0.5 block text-[11px] text-zinc-400",
|
|
1886
|
+
children: "Skipped"
|
|
1887
|
+
})]
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
function VerticalStepper({ steps, currentStepIndex, furthestStepIndex = currentStepIndex, onStepClick, freeNavigation, className }) {
|
|
1891
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
|
|
1892
|
+
"aria-label": "Wizard steps",
|
|
1893
|
+
className: (0, _openzeppelin_ui_utils.cn)("rounded-2xl border border-zinc-200 bg-white p-6", className),
|
|
1894
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1895
|
+
className: "flex flex-col gap-1",
|
|
1896
|
+
children: steps.map((step, index) => {
|
|
1897
|
+
const state = resolveState(step, index, currentStepIndex, furthestStepIndex);
|
|
1898
|
+
const clickable = canClick(state, freeNavigation) && !!onStepClick;
|
|
1899
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1900
|
+
type: "button",
|
|
1901
|
+
onClick: () => clickable && onStepClick?.(index),
|
|
1902
|
+
disabled: !clickable,
|
|
1903
|
+
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"),
|
|
1904
|
+
"aria-current": state === "current" ? "step" : void 0,
|
|
1905
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepCircle, {
|
|
1906
|
+
state,
|
|
1907
|
+
index
|
|
1908
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepLabel, {
|
|
1909
|
+
title: step.title,
|
|
1910
|
+
state,
|
|
1911
|
+
isSkipped: step.status === "skipped"
|
|
1912
|
+
})]
|
|
1913
|
+
}, step.id);
|
|
1914
|
+
})
|
|
1915
|
+
})
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
function HorizontalStepper({ steps, currentStepIndex, furthestStepIndex = currentStepIndex, onStepClick, freeNavigation, className }) {
|
|
1919
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
|
|
1920
|
+
"aria-label": "Wizard steps",
|
|
1921
|
+
className: (0, _openzeppelin_ui_utils.cn)("rounded-2xl border border-zinc-200 bg-white p-6", className),
|
|
1922
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1923
|
+
className: "flex w-full items-center",
|
|
1924
|
+
children: steps.map((step, index) => {
|
|
1925
|
+
const state = resolveState(step, index, currentStepIndex, furthestStepIndex);
|
|
1926
|
+
const clickable = canClick(state, freeNavigation) && !!onStepClick;
|
|
1927
|
+
const isLast = index === steps.length - 1;
|
|
1928
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.default.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1929
|
+
type: "button",
|
|
1930
|
+
onClick: () => clickable && onStepClick?.(index),
|
|
1931
|
+
disabled: !clickable,
|
|
1932
|
+
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"),
|
|
1933
|
+
"aria-current": state === "current" ? "step" : void 0,
|
|
1934
|
+
"aria-label": `Step ${index + 1}: ${step.title}`,
|
|
1935
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepCircle, {
|
|
1936
|
+
state,
|
|
1937
|
+
index
|
|
1938
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1939
|
+
className: "hidden sm:block",
|
|
1940
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepLabel, {
|
|
1941
|
+
title: step.title,
|
|
1942
|
+
state,
|
|
1943
|
+
isSkipped: step.status === "skipped"
|
|
1944
|
+
})
|
|
1945
|
+
})]
|
|
1946
|
+
}), !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);
|
|
1947
|
+
})
|
|
1948
|
+
})
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1951
|
+
/**
|
|
1952
|
+
* A stepper component for navigating through a series of steps.
|
|
1953
|
+
*
|
|
1954
|
+
* @param props - The props for the WizardStepper component.
|
|
1955
|
+
* @returns A React node representing the stepper component.
|
|
1956
|
+
*/
|
|
1957
|
+
function WizardStepper(props) {
|
|
1958
|
+
const { variant = "horizontal", ...rest } = props;
|
|
1959
|
+
return variant === "vertical" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VerticalStepper, {
|
|
1960
|
+
...rest,
|
|
1961
|
+
variant
|
|
1962
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HorizontalStepper, {
|
|
1963
|
+
...rest,
|
|
1964
|
+
variant
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
//#endregion
|
|
1969
|
+
//#region src/components/ui/wizard/WizardNavigation.tsx
|
|
1970
|
+
/**
|
|
1971
|
+
* A navigation component for the wizard.
|
|
1972
|
+
*
|
|
1973
|
+
* @param props - The props for the WizardNavigation component.
|
|
1974
|
+
* @returns A React node representing the navigation component.
|
|
1975
|
+
*/
|
|
1976
|
+
function WizardNavigation({ isFirstStep, isLastStep, canProceed = true, onPrevious, onNext, onCancel, extraActions, nextLabel = "Next", lastStepLabel = "Finish", className }) {
|
|
1977
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1978
|
+
className: (0, _openzeppelin_ui_utils.cn)("flex items-center justify-between", className),
|
|
1979
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1980
|
+
className: "flex gap-2",
|
|
1981
|
+
children: [onCancel && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
|
|
1982
|
+
type: "button",
|
|
1983
|
+
variant: "outline",
|
|
1984
|
+
onClick: onCancel,
|
|
1985
|
+
className: "gap-2",
|
|
1986
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "size-4" }), "Cancel"]
|
|
1987
|
+
}), !isFirstStep && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
|
|
1988
|
+
type: "button",
|
|
1989
|
+
variant: "outline",
|
|
1990
|
+
onClick: onPrevious,
|
|
1991
|
+
className: "gap-2",
|
|
1992
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronLeft, { className: "size-4" }), "Previous"]
|
|
1993
|
+
})]
|
|
1994
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1995
|
+
className: "flex gap-2",
|
|
1996
|
+
children: [extraActions, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
|
|
1997
|
+
type: "button",
|
|
1998
|
+
onClick: onNext,
|
|
1999
|
+
disabled: !canProceed,
|
|
2000
|
+
className: "gap-2",
|
|
2001
|
+
children: [isLastStep ? lastStepLabel : nextLabel, !isLastStep && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, { className: "size-4" })]
|
|
2002
|
+
})]
|
|
2003
|
+
})]
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
//#endregion
|
|
2008
|
+
//#region src/components/ui/wizard/hooks.ts
|
|
2009
|
+
/**
|
|
2010
|
+
* Clamp a step index into the valid range for the current wizard.
|
|
2011
|
+
*/
|
|
2012
|
+
function getSafeStepIndex(stepCount, currentStepIndex) {
|
|
2013
|
+
if (stepCount === 0) return 0;
|
|
2014
|
+
return Math.max(0, Math.min(currentStepIndex, stepCount - 1));
|
|
2015
|
+
}
|
|
2016
|
+
/**
|
|
2017
|
+
* Track the highest step reached unless a controlled value is provided.
|
|
2018
|
+
*/
|
|
2019
|
+
function useFurthestStepIndex(currentStepIndex, controlledFurthestStepIndex) {
|
|
2020
|
+
const [internalFurthestStepIndex, setInternalFurthestStepIndex] = (0, react.useState)(currentStepIndex);
|
|
2021
|
+
(0, react.useEffect)(() => {
|
|
2022
|
+
setInternalFurthestStepIndex((prev) => Math.max(prev, currentStepIndex));
|
|
2023
|
+
}, [currentStepIndex]);
|
|
2024
|
+
return controlledFurthestStepIndex ?? internalFurthestStepIndex;
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Keep the scrollable wizard's active and visited step state in sync with scrolling and clicks.
|
|
2028
|
+
*/
|
|
2029
|
+
function useScrollableWizardStepTracking({ steps, currentStepIndex, onStepChange, scrollRef, sectionId, scrollPadding = SCROLL_PADDING_PX }) {
|
|
2030
|
+
const safeIndex = getSafeStepIndex(steps.length, currentStepIndex);
|
|
2031
|
+
const initialIndexRef = (0, react.useRef)(safeIndex);
|
|
2032
|
+
const rafRef = (0, react.useRef)(null);
|
|
2033
|
+
const manualSelectionIndexRef = (0, react.useRef)(null);
|
|
2034
|
+
const stepsRef = (0, react.useRef)(steps);
|
|
2035
|
+
const sectionIdRef = (0, react.useRef)(sectionId);
|
|
2036
|
+
const onStepChangeRef = (0, react.useRef)(onStepChange);
|
|
2037
|
+
const scrollPaddingRef = (0, react.useRef)(scrollPadding);
|
|
2038
|
+
(0, react.useEffect)(() => {
|
|
2039
|
+
stepsRef.current = steps;
|
|
2040
|
+
sectionIdRef.current = sectionId;
|
|
2041
|
+
onStepChangeRef.current = onStepChange;
|
|
2042
|
+
scrollPaddingRef.current = scrollPadding;
|
|
2043
|
+
});
|
|
2044
|
+
const [activeIndex, setActiveIndex] = (0, react.useState)(initialIndexRef.current);
|
|
2045
|
+
const activeIndexRef = (0, react.useRef)(initialIndexRef.current);
|
|
2046
|
+
const [furthestStepIndex, setFurthestStepIndex] = (0, react.useState)(initialIndexRef.current);
|
|
2047
|
+
const isMountedRef = (0, react.useRef)(false);
|
|
2048
|
+
const clearManualSelection = (0, react.useCallback)(() => {
|
|
2049
|
+
manualSelectionIndexRef.current = null;
|
|
2050
|
+
}, []);
|
|
2051
|
+
(0, react.useEffect)(() => {
|
|
2052
|
+
const container = scrollRef.current;
|
|
2053
|
+
if (!container) return;
|
|
2054
|
+
const ownerDocument = container.ownerDocument;
|
|
2055
|
+
isMountedRef.current = false;
|
|
2056
|
+
let didCompleteInitialRaf = false;
|
|
2057
|
+
const releaseManualSelectionOnUserScroll = () => {
|
|
2058
|
+
clearManualSelection();
|
|
2059
|
+
};
|
|
2060
|
+
const handleKeyDown = (event) => {
|
|
2061
|
+
if (isScrollableNavigationKey(event)) clearManualSelection();
|
|
2062
|
+
};
|
|
2063
|
+
const handleScroll = () => {
|
|
2064
|
+
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
|
|
2065
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
2066
|
+
const currentSteps = stepsRef.current;
|
|
2067
|
+
const currentSectionId = sectionIdRef.current;
|
|
2068
|
+
const currentOnStepChange = onStepChangeRef.current;
|
|
2069
|
+
if (currentSteps.length === 0) return;
|
|
2070
|
+
const manualSelectionIndex = manualSelectionIndexRef.current;
|
|
2071
|
+
const naturalState = resolveScrollableActiveIndex(container, currentSteps, currentSectionId);
|
|
2072
|
+
const naturalActiveIndex = naturalState.activeIndex;
|
|
2073
|
+
const newActiveIndex = manualSelectionIndex ?? naturalActiveIndex;
|
|
2074
|
+
const shouldCommitFurthestStepIndex = manualSelectionIndex !== null ? true : naturalState.commitFurthestStepIndex;
|
|
2075
|
+
if (activeIndexRef.current !== newActiveIndex) {
|
|
2076
|
+
activeIndexRef.current = newActiveIndex;
|
|
2077
|
+
setActiveIndex(newActiveIndex);
|
|
2078
|
+
if (isMountedRef.current) {
|
|
2079
|
+
lastEmittedIndexRef.current = newActiveIndex;
|
|
2080
|
+
currentOnStepChange(newActiveIndex);
|
|
2081
|
+
}
|
|
2082
|
+
} else setActiveIndex(newActiveIndex);
|
|
2083
|
+
if (shouldCommitFurthestStepIndex) setFurthestStepIndex((prev) => Math.max(prev, newActiveIndex));
|
|
2084
|
+
rafRef.current = null;
|
|
2085
|
+
if (!didCompleteInitialRaf) {
|
|
2086
|
+
didCompleteInitialRaf = true;
|
|
2087
|
+
isMountedRef.current = true;
|
|
2088
|
+
}
|
|
2089
|
+
});
|
|
2090
|
+
};
|
|
2091
|
+
container.addEventListener("wheel", releaseManualSelectionOnUserScroll, { passive: true });
|
|
2092
|
+
container.addEventListener("touchmove", releaseManualSelectionOnUserScroll, { passive: true });
|
|
2093
|
+
container.addEventListener("pointerdown", releaseManualSelectionOnUserScroll);
|
|
2094
|
+
ownerDocument.addEventListener("keydown", handleKeyDown);
|
|
2095
|
+
container.addEventListener("scroll", handleScroll, { passive: true });
|
|
2096
|
+
handleScroll();
|
|
2097
|
+
return () => {
|
|
2098
|
+
isMountedRef.current = false;
|
|
2099
|
+
container.removeEventListener("wheel", releaseManualSelectionOnUserScroll);
|
|
2100
|
+
container.removeEventListener("touchmove", releaseManualSelectionOnUserScroll);
|
|
2101
|
+
container.removeEventListener("pointerdown", releaseManualSelectionOnUserScroll);
|
|
2102
|
+
ownerDocument.removeEventListener("keydown", handleKeyDown);
|
|
2103
|
+
container.removeEventListener("scroll", handleScroll);
|
|
2104
|
+
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
|
|
2105
|
+
};
|
|
2106
|
+
}, [clearManualSelection, scrollRef]);
|
|
2107
|
+
const lastEmittedIndexRef = (0, react.useRef)(safeIndex);
|
|
2108
|
+
(0, react.useEffect)(() => {
|
|
2109
|
+
const newSafeIndex = getSafeStepIndex(stepsRef.current.length, currentStepIndex);
|
|
2110
|
+
if (newSafeIndex === lastEmittedIndexRef.current) return;
|
|
2111
|
+
lastEmittedIndexRef.current = newSafeIndex;
|
|
2112
|
+
activeIndexRef.current = newSafeIndex;
|
|
2113
|
+
setActiveIndex(newSafeIndex);
|
|
2114
|
+
setFurthestStepIndex((prev) => Math.max(prev, newSafeIndex));
|
|
2115
|
+
const step = stepsRef.current[newSafeIndex];
|
|
2116
|
+
if (!step) return;
|
|
2117
|
+
const sectionElement = scrollRef.current?.querySelector(`#${CSS.escape(sectionIdRef.current(step.id))}`);
|
|
2118
|
+
if (scrollRef.current && sectionElement) scrollSectionIntoView(scrollRef.current, sectionElement, scrollPaddingRef.current);
|
|
2119
|
+
}, [currentStepIndex, scrollRef]);
|
|
2120
|
+
return {
|
|
2121
|
+
activeIndex,
|
|
2122
|
+
furthestStepIndex,
|
|
2123
|
+
scrollToSection: (0, react.useCallback)((index) => {
|
|
2124
|
+
const step = stepsRef.current[index];
|
|
2125
|
+
if (!step) return;
|
|
2126
|
+
manualSelectionIndexRef.current = index;
|
|
2127
|
+
activeIndexRef.current = index;
|
|
2128
|
+
lastEmittedIndexRef.current = index;
|
|
2129
|
+
setActiveIndex(index);
|
|
2130
|
+
setFurthestStepIndex((prev) => Math.max(prev, index));
|
|
2131
|
+
onStepChangeRef.current(index);
|
|
2132
|
+
const container = scrollRef.current;
|
|
2133
|
+
const sectionElement = container?.querySelector(`#${CSS.escape(sectionIdRef.current(step.id))}`);
|
|
2134
|
+
if (container && sectionElement) scrollSectionIntoView(container, sectionElement, scrollPaddingRef.current);
|
|
2135
|
+
}, [scrollRef])
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
function resolveScrollableActiveIndex(container, steps, sectionId) {
|
|
2139
|
+
if (steps.length === 0) return {
|
|
2140
|
+
activeIndex: 0,
|
|
2141
|
+
commitFurthestStepIndex: false
|
|
2142
|
+
};
|
|
2143
|
+
const containerRect = container.getBoundingClientRect();
|
|
2144
|
+
const anchorY = containerRect.top + Math.min(containerRect.height * .35, 220);
|
|
2145
|
+
const isScrollable = container.scrollHeight > container.clientHeight + 1;
|
|
2146
|
+
const isAtBottom = isScrollable && container.scrollTop + container.clientHeight >= container.scrollHeight - 1;
|
|
2147
|
+
const isNearBottom = isScrollable && container.scrollTop + container.clientHeight >= container.scrollHeight - 4;
|
|
2148
|
+
if (isAtBottom) return {
|
|
2149
|
+
activeIndex: steps.length - 1,
|
|
2150
|
+
commitFurthestStepIndex: false
|
|
2151
|
+
};
|
|
2152
|
+
let activeIndex = 0;
|
|
2153
|
+
let highestScore = Number.NEGATIVE_INFINITY;
|
|
2154
|
+
for (let i = 0; i < steps.length; i++) {
|
|
2155
|
+
const sectionMetrics = getSectionMetrics(container, steps[i].id, sectionId, containerRect);
|
|
2156
|
+
if (!sectionMetrics) continue;
|
|
2157
|
+
const score = scoreScrollableStep({
|
|
2158
|
+
stepIndex: i,
|
|
2159
|
+
stepCount: steps.length,
|
|
2160
|
+
containerRect,
|
|
2161
|
+
anchorY,
|
|
2162
|
+
isNearBottom,
|
|
2163
|
+
...sectionMetrics
|
|
2164
|
+
});
|
|
2165
|
+
if (score >= highestScore) {
|
|
2166
|
+
highestScore = score;
|
|
2167
|
+
activeIndex = i;
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
return {
|
|
2171
|
+
activeIndex,
|
|
2172
|
+
commitFurthestStepIndex: true
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2175
|
+
const SCROLL_PADDING_PX = 32;
|
|
2176
|
+
function getSectionElement(container, stepId, sectionId) {
|
|
2177
|
+
return container.querySelector(`#${CSS.escape(sectionId(stepId))}`);
|
|
2178
|
+
}
|
|
2179
|
+
function scrollSectionIntoView(container, sectionElement, padding) {
|
|
2180
|
+
const elementTop = sectionElement.getBoundingClientRect().top;
|
|
2181
|
+
const containerTop = container.getBoundingClientRect().top;
|
|
2182
|
+
const targetScrollTop = container.scrollTop + (elementTop - containerTop) - padding;
|
|
2183
|
+
container.scrollTo({
|
|
2184
|
+
top: targetScrollTop,
|
|
2185
|
+
behavior: "smooth"
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
function getSectionMetrics(container, stepId, sectionId, containerRect) {
|
|
2189
|
+
const sectionElement = getSectionElement(container, stepId, sectionId);
|
|
2190
|
+
if (!sectionElement) return null;
|
|
2191
|
+
const sectionRect = sectionElement.getBoundingClientRect();
|
|
2192
|
+
return {
|
|
2193
|
+
sectionRect,
|
|
2194
|
+
visibleHeight: getVisibleHeight(containerRect, sectionRect)
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
function getVisibleHeight(containerRect, sectionRect) {
|
|
2198
|
+
return Math.max(0, Math.min(sectionRect.bottom, containerRect.bottom) - Math.max(sectionRect.top, containerRect.top));
|
|
2199
|
+
}
|
|
2200
|
+
function scoreScrollableStep({ stepIndex, stepCount, containerRect, sectionRect, visibleHeight, anchorY, isNearBottom }) {
|
|
2201
|
+
const isVisible = visibleHeight > 0;
|
|
2202
|
+
const focusBandTop = containerRect.top + Math.min(containerRect.height * .2, 140);
|
|
2203
|
+
const focusBandBottom = containerRect.top + Math.min(containerRect.height * .55, 360);
|
|
2204
|
+
const focusBandOverlap = getBandOverlapHeight(sectionRect, focusBandTop, focusBandBottom);
|
|
2205
|
+
const distanceToFocusBand = focusBandOverlap > 0 ? 0 : Math.min(Math.abs(sectionRect.top - focusBandBottom), Math.abs(sectionRect.bottom - focusBandTop));
|
|
2206
|
+
const lastStepProminent = stepIndex === stepCount - 1 && visibleHeight >= Math.min(sectionRect.height, containerRect.height) * .25 && sectionRect.top <= containerRect.top + containerRect.height * .65;
|
|
2207
|
+
let score = isVisible ? visibleHeight : Number.NEGATIVE_INFINITY;
|
|
2208
|
+
if (focusBandOverlap > 0) score += 12e3 + focusBandOverlap * 25;
|
|
2209
|
+
if (sectionRect.top <= anchorY) score += 250;
|
|
2210
|
+
score += Math.max(0, 1e3 - distanceToFocusBand);
|
|
2211
|
+
if (isNearBottom && lastStepProminent && isVisible) score += 15e3;
|
|
2212
|
+
return score;
|
|
2213
|
+
}
|
|
2214
|
+
function getBandOverlapHeight(sectionRect, bandTop, bandBottom) {
|
|
2215
|
+
return Math.max(0, Math.min(sectionRect.bottom, bandBottom) - Math.max(sectionRect.top, bandTop));
|
|
2216
|
+
}
|
|
2217
|
+
function isScrollableNavigationKey(event) {
|
|
2218
|
+
if (event.metaKey || event.ctrlKey || event.altKey) return false;
|
|
2219
|
+
return [
|
|
2220
|
+
"ArrowDown",
|
|
2221
|
+
"ArrowUp",
|
|
2222
|
+
"PageDown",
|
|
2223
|
+
"PageUp",
|
|
2224
|
+
"Home",
|
|
2225
|
+
"End",
|
|
2226
|
+
" "
|
|
2227
|
+
].includes(event.key);
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
//#endregion
|
|
2231
|
+
//#region src/components/ui/wizard/WizardLayout.tsx
|
|
2232
|
+
function PagedLayout({ steps, currentStepIndex, furthestStepIndex: furthestStepIndexProp, onStepChange, onComplete, onCancel, navActions, header, variant, className }) {
|
|
2233
|
+
const safeIndex = getSafeStepIndex(steps.length, currentStepIndex);
|
|
2234
|
+
const resolvedFurthestStepIndex = useFurthestStepIndex(safeIndex, furthestStepIndexProp);
|
|
2235
|
+
if (steps.length === 0) return null;
|
|
2236
|
+
const isFirstStep = safeIndex === 0;
|
|
2237
|
+
const isLastStep = safeIndex === steps.length - 1;
|
|
2238
|
+
const currentStep = steps[safeIndex];
|
|
2239
|
+
const canProceed = currentStep?.isValid !== false;
|
|
2240
|
+
const handleNext = () => {
|
|
2241
|
+
if (isLastStep) {
|
|
2242
|
+
onComplete?.();
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2245
|
+
onStepChange(safeIndex + 1);
|
|
2246
|
+
};
|
|
2247
|
+
const handlePrevious = () => {
|
|
2248
|
+
if (!isFirstStep) onStepChange(safeIndex - 1);
|
|
2249
|
+
};
|
|
2250
|
+
const stepDefs = toStepDefs(steps, safeIndex);
|
|
2251
|
+
const footer = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2252
|
+
className: "shrink-0 border-t border-border bg-background px-8 py-4",
|
|
2253
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2254
|
+
className: "mx-auto max-w-5xl",
|
|
2255
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WizardNavigation, {
|
|
2256
|
+
isFirstStep,
|
|
2257
|
+
isLastStep,
|
|
2258
|
+
canProceed,
|
|
2259
|
+
onPrevious: handlePrevious,
|
|
2260
|
+
onNext: handleNext,
|
|
2261
|
+
onCancel,
|
|
2262
|
+
extraActions: navActions
|
|
2263
|
+
})
|
|
2264
|
+
})
|
|
2265
|
+
});
|
|
2266
|
+
if (variant === "vertical") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2267
|
+
className: (0, _openzeppelin_ui_utils.cn)("flex h-full gap-6", className),
|
|
2268
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2269
|
+
className: "w-[220px] shrink-0 py-6 pl-6",
|
|
2270
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WizardStepper, {
|
|
2271
|
+
variant: "vertical",
|
|
2272
|
+
steps: stepDefs,
|
|
2273
|
+
currentStepIndex: safeIndex,
|
|
2274
|
+
furthestStepIndex: resolvedFurthestStepIndex,
|
|
2275
|
+
onStepClick: onStepChange,
|
|
2276
|
+
className: "h-full"
|
|
2277
|
+
})
|
|
2278
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2279
|
+
className: "flex min-w-0 flex-1 flex-col overflow-hidden",
|
|
2280
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2281
|
+
className: "flex-1 overflow-y-auto p-8",
|
|
2282
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2283
|
+
className: "mx-auto max-w-5xl",
|
|
2284
|
+
children: [header, currentStep?.component]
|
|
2285
|
+
})
|
|
2286
|
+
}), footer]
|
|
2287
|
+
})]
|
|
2288
|
+
});
|
|
2289
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2290
|
+
className: (0, _openzeppelin_ui_utils.cn)("flex h-full flex-col", className),
|
|
2291
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2292
|
+
className: "shrink-0 p-6 pb-0",
|
|
2293
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WizardStepper, {
|
|
2294
|
+
variant: "horizontal",
|
|
2295
|
+
steps: stepDefs,
|
|
2296
|
+
currentStepIndex: safeIndex,
|
|
2297
|
+
furthestStepIndex: resolvedFurthestStepIndex,
|
|
2298
|
+
onStepClick: onStepChange
|
|
2299
|
+
})
|
|
2300
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2301
|
+
className: "flex min-w-0 flex-1 flex-col overflow-hidden",
|
|
2302
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2303
|
+
className: "flex-1 overflow-y-auto p-8",
|
|
2304
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2305
|
+
className: "mx-auto max-w-5xl",
|
|
2306
|
+
children: [header, currentStep?.component]
|
|
2307
|
+
})
|
|
2308
|
+
}), footer]
|
|
2309
|
+
})]
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
function ScrollableLayout({ steps, currentStepIndex, onStepChange, header, onComplete, scrollPadding, className }) {
|
|
2313
|
+
const instanceId = (0, react.useId)();
|
|
2314
|
+
const scrollRef = (0, react.useRef)(null);
|
|
2315
|
+
const sectionId = (0, react.useCallback)((stepId) => `wizard-section-${instanceId}-${stepId}`, [instanceId]);
|
|
2316
|
+
const { activeIndex, furthestStepIndex, scrollToSection } = useScrollableWizardStepTracking({
|
|
2317
|
+
steps,
|
|
2318
|
+
currentStepIndex,
|
|
2319
|
+
onStepChange,
|
|
2320
|
+
scrollRef,
|
|
2321
|
+
sectionId,
|
|
2322
|
+
scrollPadding
|
|
2323
|
+
});
|
|
2324
|
+
if (steps.length === 0) return null;
|
|
2325
|
+
const stepDefs = toStepDefs(steps, activeIndex);
|
|
2326
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2327
|
+
className: (0, _openzeppelin_ui_utils.cn)("flex h-full gap-6", className),
|
|
2328
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2329
|
+
className: "w-[220px] shrink-0 py-6 pl-6",
|
|
2330
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WizardStepper, {
|
|
2331
|
+
variant: "vertical",
|
|
2332
|
+
steps: stepDefs,
|
|
2333
|
+
currentStepIndex: activeIndex,
|
|
2334
|
+
furthestStepIndex,
|
|
2335
|
+
onStepClick: scrollToSection,
|
|
2336
|
+
freeNavigation: true,
|
|
2337
|
+
className: "h-full"
|
|
2338
|
+
})
|
|
2339
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2340
|
+
ref: scrollRef,
|
|
2341
|
+
className: "flex min-w-0 flex-1 flex-col overflow-y-auto p-8",
|
|
2342
|
+
children: [
|
|
2343
|
+
header,
|
|
2344
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2345
|
+
className: "space-y-12",
|
|
2346
|
+
children: steps.map((step) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
2347
|
+
id: sectionId(step.id),
|
|
2348
|
+
children: step.component
|
|
2349
|
+
}, step.id))
|
|
2350
|
+
}),
|
|
2351
|
+
onComplete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2352
|
+
className: "flex justify-end pt-8",
|
|
2353
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
2354
|
+
type: "button",
|
|
2355
|
+
onClick: onComplete,
|
|
2356
|
+
children: "Finish"
|
|
2357
|
+
})
|
|
2358
|
+
})
|
|
2359
|
+
]
|
|
2360
|
+
})]
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
function toStepDefs(steps, currentStepIndex) {
|
|
2364
|
+
return steps.map((s, i) => {
|
|
2365
|
+
const isInvalid = s.isInvalid ?? s.isValid === false;
|
|
2366
|
+
const status = s.status ?? (i < currentStepIndex && !isInvalid ? "completed" : "pending");
|
|
2367
|
+
return {
|
|
2368
|
+
id: s.id,
|
|
2369
|
+
title: s.title,
|
|
2370
|
+
status,
|
|
2371
|
+
isInvalid
|
|
2372
|
+
};
|
|
2373
|
+
});
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* A layout component for the wizard.
|
|
2377
|
+
*
|
|
2378
|
+
* @param props - The props for the WizardLayout component.
|
|
2379
|
+
* @returns A React node representing the layout component.
|
|
2380
|
+
*/
|
|
2381
|
+
function WizardLayout(props) {
|
|
2382
|
+
const { variant = "horizontal", ...rest } = props;
|
|
2383
|
+
if (variant === "scrollable") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollableLayout, {
|
|
2384
|
+
steps: rest.steps,
|
|
2385
|
+
currentStepIndex: rest.currentStepIndex,
|
|
2386
|
+
onStepChange: rest.onStepChange,
|
|
2387
|
+
header: rest.header,
|
|
2388
|
+
onComplete: rest.onComplete,
|
|
2389
|
+
scrollPadding: rest.scrollPadding,
|
|
2390
|
+
className: rest.className
|
|
2391
|
+
});
|
|
2392
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PagedLayout, {
|
|
2393
|
+
...rest,
|
|
2394
|
+
variant
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2397
|
+
|
|
1858
2398
|
//#endregion
|
|
1859
2399
|
//#region src/components/fields/address-suggestion/context.ts
|
|
1860
2400
|
/**
|
|
@@ -5860,6 +6400,9 @@ exports.TooltipProvider = TooltipProvider;
|
|
|
5860
6400
|
exports.TooltipTrigger = TooltipTrigger;
|
|
5861
6401
|
exports.UrlField = UrlField;
|
|
5862
6402
|
exports.ViewContractStateButton = ViewContractStateButton;
|
|
6403
|
+
exports.WizardLayout = WizardLayout;
|
|
6404
|
+
exports.WizardNavigation = WizardNavigation;
|
|
6405
|
+
exports.WizardStepper = WizardStepper;
|
|
5863
6406
|
exports.buttonVariants = buttonVariants;
|
|
5864
6407
|
exports.computeChildTouched = computeChildTouched;
|
|
5865
6408
|
exports.createFocusManager = createFocusManager;
|