@seedgrid/fe-components 2026.3.27 → 2026.3.28

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.
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { type WizardStepNavigation } from "./logic";
2
3
  export type SgWizardPageProps = {
3
4
  title?: string;
4
5
  icon?: React.ReactNode;
@@ -23,6 +24,7 @@ export type SgWizardProps = {
23
24
  initialStep?: number;
24
25
  labels?: Partial<SgWizardLabels>;
25
26
  stepper?: SgWizardStepper;
27
+ stepNavigation?: WizardStepNavigation;
26
28
  className?: string;
27
29
  style?: React.CSSProperties;
28
30
  };
@@ -1 +1 @@
1
- {"version":3,"file":"SgWizard.d.ts","sourceRoot":"","sources":["../../src/wizard/SgWizard.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,2CAEpD;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;AAE5D,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B,CAAC;AAiFF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,2CA+H5C"}
1
+ {"version":3,"file":"SgWizard.d.ts","sourceRoot":"","sources":["../../src/wizard/SgWizard.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAIL,KAAK,oBAAoB,EAC1B,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,2CAEpD;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;AAE5D,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B,CAAC;AAqGF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,2CA2J5C"}
@@ -1,32 +1,35 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
4
  import { t, useComponentsI18n } from "../i18n";
5
- import { canProceedWizardAction, clampWizardStep } from "./logic";
5
+ import { canNavigateToWizardStep, canProceedWizardAction, clampWizardStep } from "./logic";
6
6
  export function SgWizardPage(props) {
7
7
  return _jsx("div", { className: props.className, style: props.style, children: props.children });
8
8
  }
9
9
  function CheckIcon({ className }) {
10
10
  return (_jsx("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "M20 6 9 17l-5-5" }) }));
11
11
  }
12
- function StepperBar({ i18n, pages, currentStep, mode }) {
12
+ function StepperBar({ i18n, pages, currentStep, mode, canNavigateStep, onNavigateStep }) {
13
13
  return (_jsx("nav", { "aria-label": t(i18n, "components.wizard.progress"), className: "mb-8", children: _jsx("ol", { className: "flex items-center", children: pages.map((page, i) => {
14
14
  const isCompleted = i < currentStep;
15
15
  const isCurrent = i === currentStep;
16
+ const isClickable = canNavigateStep(i);
16
17
  const title = page.props.title ?? t(i18n, "components.wizard.step", { step: i + 1 });
17
18
  const icon = page.props.icon;
18
19
  const isLast = i === pages.length - 1;
19
- return (_jsxs("li", { className: `flex items-center ${isLast ? "" : "flex-1"}`, children: [_jsxs("div", { className: "flex flex-col items-center", children: [_jsx("div", { className: `flex items-center justify-center rounded-full border-2 transition-colors duration-200 ${isCompleted
20
- ? "border-[hsl(var(--primary))] bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]"
21
- : isCurrent
22
- ? "border-[hsl(var(--primary))] bg-[rgb(var(--sg-surface,var(--sg-bg)))] text-[hsl(var(--primary))]"
23
- : "border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] text-muted-foreground"}`, style: { width: 40, height: 40 }, children: isCompleted ? (_jsx(CheckIcon, { className: "size-5" })) : mode === "icons" && icon ? (_jsx("span", { className: "flex items-center justify-center size-5", children: icon })) : (_jsx("span", { className: "text-sm font-semibold", children: i + 1 })) }), _jsx("span", { className: `mt-2 text-xs font-medium text-center max-w-[80px] leading-tight ${isCompleted || isCurrent ? "text-[hsl(var(--primary))]" : "text-muted-foreground"}`, children: title })] }), !isLast ? (_jsx("div", { className: "flex-1 mx-2 self-start", style: { marginTop: 19 }, children: _jsx("div", { className: `h-0.5 w-full transition-colors duration-200 ${isCompleted ? "bg-[hsl(var(--primary))]" : "bg-border"}` }) })) : null] }, i));
20
+ const content = (_jsxs(_Fragment, { children: [_jsx("div", { "aria-current": isCurrent ? "step" : undefined, className: `flex items-center justify-center rounded-full border-2 transition-colors duration-200 ${isCompleted
21
+ ? "border-[hsl(var(--primary))] bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]"
22
+ : isCurrent
23
+ ? "border-[hsl(var(--primary))] bg-[rgb(var(--sg-surface,var(--sg-bg)))] text-[hsl(var(--primary))]"
24
+ : "border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] text-muted-foreground"}`, style: { width: 40, height: 40 }, children: isCompleted ? (_jsx(CheckIcon, { className: "size-5" })) : mode === "icons" && icon ? (_jsx("span", { className: "flex items-center justify-center size-5", children: icon })) : (_jsx("span", { className: "text-sm font-semibold", children: i + 1 })) }), _jsx("span", { className: `mt-2 text-xs font-medium text-center max-w-[80px] leading-tight ${isCompleted || isCurrent ? "text-[hsl(var(--primary))]" : "text-muted-foreground"}`, children: title })] }));
25
+ return (_jsxs("li", { className: `flex items-center ${isLast ? "" : "flex-1"}`, children: [isClickable ? (_jsx("button", { type: "button", onClick: () => onNavigateStep(i), className: "flex cursor-pointer flex-col items-center rounded-md outline-none transition-opacity duration-200 hover:opacity-90 focus-visible:ring-2 focus-visible:ring-[hsl(var(--primary))] focus-visible:ring-offset-2", "data-sg-wizard-step": i, children: content })) : (_jsx("div", { className: "flex flex-col items-center", "data-sg-wizard-step": i, children: content })), !isLast ? (_jsx("div", { className: "flex-1 mx-2 self-start", style: { marginTop: 19 }, children: _jsx("div", { className: `h-0.5 w-full transition-colors duration-200 ${isCompleted ? "bg-[hsl(var(--primary))]" : "bg-border"}` }) })) : null] }, i));
24
26
  }) }) }));
25
27
  }
26
28
  export function SgWizard(props) {
27
29
  const i18n = useComponentsI18n();
28
30
  const pages = React.Children.toArray(props.children).filter((child) => React.isValidElement(child) && child.type === SgWizardPage);
29
31
  const stepper = props.stepper ?? "none";
32
+ const stepNavigation = props.stepNavigation ?? "previous-and-next";
30
33
  const [step, setStep] = React.useState(() => clampWizardStep(props.initialStep, pages.length));
31
34
  const [isFinishing, setIsFinishing] = React.useState(false);
32
35
  const [isValidating, setIsValidating] = React.useState(false);
@@ -76,11 +79,28 @@ export function SgWizard(props) {
76
79
  setIsValidating(false);
77
80
  }
78
81
  };
82
+ const canNavigateStep = React.useCallback((targetStep) => {
83
+ return canNavigateToWizardStep({
84
+ currentStep: step,
85
+ targetStep,
86
+ pageCount: pages.length,
87
+ stepNavigation
88
+ });
89
+ }, [pages.length, step, stepNavigation]);
79
90
  const goPrevious = () => {
80
91
  if (isFirst)
81
92
  return;
82
93
  setStep((prev) => Math.max(prev - 1, 0));
83
94
  };
95
+ const handleStepNavigation = async (targetStep) => {
96
+ if (!canNavigateStep(targetStep))
97
+ return;
98
+ if (targetStep < step) {
99
+ setStep(targetStep);
100
+ return;
101
+ }
102
+ await goNext();
103
+ };
84
104
  const handleFinish = async () => {
85
105
  if (isFinishing)
86
106
  return;
@@ -108,5 +128,5 @@ export function SgWizard(props) {
108
128
  setIsFinishing(false);
109
129
  }
110
130
  };
111
- return (_jsxs("div", { className: props.className, style: props.style, children: [stepper !== "none" ? (_jsx(StepperBar, { pages: pages, currentStep: step, mode: stepper, i18n: i18n })) : null, _jsx("div", { ref: pageRef, children: pages[step] }), _jsxs("div", { className: "mt-6 flex flex-wrap items-center justify-between gap-3", children: [_jsx("div", { children: !isFirst ? (_jsx("button", { type: "button", onClick: goPrevious, className: "inline-flex h-10 items-center justify-center rounded-full border border-border px-5 text-sm font-semibold text-foreground/80 transition hover:bg-muted/60", children: labels.previous })) : null }), _jsx("div", { className: "flex items-center gap-3", children: !isLast ? (_jsx("button", { type: "button", onClick: goNext, disabled: isValidating, className: "inline-flex h-10 items-center justify-center rounded-full bg-[hsl(var(--primary))] px-5 text-sm font-semibold text-white shadow-lg shadow-[hsl(var(--primary)/0.35)] transition hover:brightness-95 disabled:cursor-not-allowed disabled:opacity-60", children: labels.next })) : (_jsx("button", { type: "button", onClick: handleFinish, disabled: isFinishing || isValidating, className: "inline-flex h-10 items-center justify-center rounded-full bg-[hsl(var(--primary))] px-5 text-sm font-semibold text-white shadow-lg shadow-[hsl(var(--primary)/0.35)] transition hover:brightness-95 disabled:cursor-not-allowed disabled:opacity-60", children: labels.finish })) })] })] }));
131
+ return (_jsxs("div", { className: props.className, style: props.style, children: [stepper !== "none" ? (_jsx(StepperBar, { pages: pages, currentStep: step, mode: stepper, i18n: i18n, canNavigateStep: canNavigateStep, onNavigateStep: handleStepNavigation })) : null, _jsx("div", { ref: pageRef, children: pages[step] }), _jsxs("div", { className: "mt-6 flex flex-wrap items-center justify-between gap-3", children: [_jsx("div", { children: !isFirst ? (_jsx("button", { type: "button", onClick: goPrevious, className: "inline-flex h-10 items-center justify-center rounded-full border border-border px-5 text-sm font-semibold text-foreground/80 transition hover:bg-muted/60", children: labels.previous })) : null }), _jsx("div", { className: "flex items-center gap-3", children: !isLast ? (_jsx("button", { type: "button", onClick: goNext, disabled: isValidating, className: "inline-flex h-10 items-center justify-center rounded-full bg-[hsl(var(--primary))] px-5 text-sm font-semibold text-white shadow-lg shadow-[hsl(var(--primary)/0.35)] transition hover:brightness-95 disabled:cursor-not-allowed disabled:opacity-60", children: labels.next })) : (_jsx("button", { type: "button", onClick: handleFinish, disabled: isFinishing || isValidating, className: "inline-flex h-10 items-center justify-center rounded-full bg-[hsl(var(--primary))] px-5 text-sm font-semibold text-white shadow-lg shadow-[hsl(var(--primary)/0.35)] transition hover:brightness-95 disabled:cursor-not-allowed disabled:opacity-60", children: labels.finish })) })] })] }));
112
132
  }
@@ -1,4 +1,11 @@
1
1
  export declare function clampWizardStep(initialStep: number | undefined, pageCount: number): number;
2
+ export type WizardStepNavigation = "none" | "previous" | "previous-and-next";
3
+ export declare function canNavigateToWizardStep(args: {
4
+ currentStep: number;
5
+ targetStep: number;
6
+ pageCount: number;
7
+ stepNavigation?: WizardStepNavigation;
8
+ }): boolean;
2
9
  export type WizardGuardRunner = {
3
10
  validateCurrentPage: () => boolean | Promise<boolean>;
4
11
  validateStep?: (index: number) => boolean | Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"logic.d.ts","sourceRoot":"","sources":["../../src/wizard/logic.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG1F;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,mBAAmB,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,CAexF"}
1
+ {"version":3,"file":"logic.d.ts","sourceRoot":"","sources":["../../src/wizard/logic.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG1F;AAED,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,UAAU,GAAG,mBAAmB,CAAC;AAE7E,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC,GAAG,OAAO,CAWV;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,mBAAmB,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,CAexF"}
@@ -2,6 +2,16 @@ export function clampWizardStep(initialStep, pageCount) {
2
2
  const step = initialStep ?? 0;
3
3
  return Math.min(Math.max(step, 0), Math.max(pageCount - 1, 0));
4
4
  }
5
+ export function canNavigateToWizardStep(args) {
6
+ const { currentStep, targetStep, pageCount, stepNavigation = "previous-and-next" } = args;
7
+ if (targetStep < 0 || targetStep >= pageCount || targetStep === currentStep) {
8
+ return false;
9
+ }
10
+ if (targetStep < currentStep) {
11
+ return stepNavigation === "previous" || stepNavigation === "previous-and-next";
12
+ }
13
+ return stepNavigation === "previous-and-next" && targetStep === currentStep + 1;
14
+ }
5
15
  export async function canProceedWizardAction(runner) {
6
16
  const pageValid = await runner.validateCurrentPage();
7
17
  if (!pageValid)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seedgrid/fe-components",
3
- "version": "2026.3.27",
3
+ "version": "2026.3.28",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {