@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 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.5 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" ? "h-10" : "h-11", className);
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-1 bg-muted text-muted-foreground rounded-full font-medium",
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;