@eifi1/ui-kit 0.9.0 → 0.10.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/README.md +21 -11
- package/dist/components/alert-banner.d.ts +32 -5
- package/dist/components/alert-banner.js +57 -14
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +4 -0
- package/dist/components/breadcrumbs.d.ts +60 -0
- package/dist/components/breadcrumbs.js +88 -0
- package/dist/components/breadcrumbs.js.map +1 -0
- package/dist/components/bulk-action-bar.d.ts +66 -0
- package/dist/components/bulk-action-bar.js +149 -0
- package/dist/components/bulk-action-bar.js.map +1 -0
- package/dist/components/calculator.d.ts +4 -0
- package/dist/components/chip.d.ts +31 -5
- package/dist/components/chip.js +85 -5
- package/dist/components/chip.js.map +1 -1
- package/dist/components/copy-button.d.ts +13 -4
- package/dist/components/copy-button.js +17 -4
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/date-picker.d.ts +22 -2
- package/dist/components/date-picker.js +11 -3
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/description-list.d.ts +43 -6
- package/dist/components/description-list.js +91 -11
- package/dist/components/description-list.js.map +1 -1
- package/dist/components/dialog-frame.d.ts +7 -0
- package/dist/components/dialog-frame.js.map +1 -1
- package/dist/components/disclosure.d.ts +57 -2
- package/dist/components/disclosure.js +24 -12
- package/dist/components/disclosure.js.map +1 -1
- package/dist/components/file-dropzone.d.ts +3 -4
- package/dist/components/file-dropzone.js +2 -1
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/full-bleed-dialog.d.ts +23 -4
- package/dist/components/full-bleed-dialog.js +11 -2
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/list.d.ts +174 -0
- package/dist/components/list.js +215 -0
- package/dist/components/list.js.map +1 -0
- package/dist/components/menu-item.d.ts +99 -0
- package/dist/components/menu-item.js +83 -0
- package/dist/components/menu-item.js.map +1 -0
- package/dist/components/modal.d.ts +23 -1
- package/dist/components/modal.js +36 -10
- package/dist/components/modal.js.map +1 -1
- package/dist/components/number-field.d.ts +4 -0
- package/dist/components/number-input.d.ts +4 -0
- package/dist/components/numpad-sheet.d.ts +4 -0
- package/dist/components/page-contents.js +1 -2
- package/dist/components/page-contents.js.map +1 -1
- package/dist/components/page-header.d.ts +37 -0
- package/dist/components/page-header.js +36 -0
- package/dist/components/page-header.js.map +1 -0
- package/dist/components/series-chart-labels.d.ts +3 -0
- package/dist/components/series-chart-labels.js +2 -1
- package/dist/components/series-chart-labels.js.map +1 -1
- package/dist/components/series-chart.d.ts +16 -0
- package/dist/components/series-chart.js +137 -3
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/status-dot.d.ts +49 -0
- package/dist/components/status-dot.js +52 -0
- package/dist/components/status-dot.js.map +1 -0
- package/dist/components/table.d.ts +39 -6
- package/dist/components/table.js +43 -4
- package/dist/components/table.js.map +1 -1
- package/dist/components/text.d.ts +63 -0
- package/dist/components/text.js +23 -0
- package/dist/components/text.js.map +1 -0
- package/dist/components/toast.d.ts +167 -0
- package/dist/components/toast.js +226 -0
- package/dist/components/toast.js.map +1 -0
- package/dist/components/toggle-group.d.ts +29 -1
- package/dist/components/toggle-group.js +52 -3
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/tooltip.d.ts +38 -15
- package/dist/components/tooltip.js +85 -47
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/ui.d.ts +104 -4
- package/dist/components/ui.js +112 -8
- package/dist/components/ui.js.map +1 -1
- package/dist/hooks/use-close-transition.d.ts +7 -1
- package/dist/hooks/use-close-transition.js +5 -2
- package/dist/hooks/use-close-transition.js.map +1 -1
- package/dist/hooks/use-copy-to-clipboard.js +1 -1
- package/dist/hooks/use-copy-to-clipboard.js.map +1 -1
- package/dist/i18n/defaults.d.ts +4 -0
- package/dist/i18n/defaults.js +9 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +8 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/de-CH-informal.d.ts +4 -0
- package/dist/i18n/locales/de-CH.d.ts +4 -0
- package/dist/i18n/locales/de-informal.d.ts +4 -0
- package/dist/i18n/locales/de.d.ts +4 -0
- package/dist/i18n/locales/de.js +21 -1
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +4 -0
- package/dist/i18n/locales/es.js +21 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +4 -0
- package/dist/i18n/locales/fr.js +21 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +4 -0
- package/dist/i18n/locales/hu.js +21 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +4 -0
- package/dist/i18n/locales/it.js +21 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +4 -0
- package/dist/i18n/locales/zh.js +21 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.d.ts +15 -7
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/clipping.d.ts +9 -0
- package/dist/lib/clipping.js +14 -0
- package/dist/lib/clipping.js.map +1 -0
- package/dist/search/command-palette.d.ts +19 -2
- package/dist/search/command-palette.js +16 -5
- package/dist/search/command-palette.js.map +1 -1
- package/dist/search/global-search.d.ts +26 -1
- package/dist/search/global-search.js +29 -13
- package/dist/search/global-search.js.map +1 -1
- package/dist/search.d.ts +1 -1
- package/dist/wizard/stepper-nav.js +1 -1
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +2 -2
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +1 -1
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/wizard-summary.js +3 -3
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/package.json +16 -12
- package/src/components/alert-banner.tsx +117 -18
- package/src/components/breadcrumbs.tsx +168 -0
- package/src/components/bulk-action-bar.tsx +256 -0
- package/src/components/chip.tsx +128 -7
- package/src/components/copy-button.tsx +29 -7
- package/src/components/date-picker.tsx +49 -1
- package/src/components/description-list.tsx +168 -15
- package/src/components/dialog-frame.tsx +7 -0
- package/src/components/disclosure.tsx +93 -21
- package/src/components/file-dropzone.tsx +9 -10
- package/src/components/full-bleed-dialog.tsx +42 -5
- package/src/components/list.tsx +417 -0
- package/src/components/menu-item.tsx +214 -0
- package/src/components/modal.tsx +80 -17
- package/src/components/page-contents.tsx +4 -4
- package/src/components/page-header.tsx +68 -0
- package/src/components/series-chart-labels.ts +4 -0
- package/src/components/series-chart.tsx +253 -3
- package/src/components/status-dot.tsx +99 -0
- package/src/components/table.tsx +91 -6
- package/src/components/text.tsx +89 -0
- package/src/components/toast.tsx +441 -0
- package/src/components/toggle-group.tsx +82 -3
- package/src/components/tooltip.tsx +186 -100
- package/src/components/ui.tsx +237 -8
- package/src/hooks/use-close-transition.ts +14 -5
- package/src/hooks/use-copy-to-clipboard.ts +1 -1
- package/src/i18n/defaults.ts +8 -0
- package/src/i18n/kit-labels.tsx +8 -0
- package/src/i18n/locales/de.ts +20 -0
- package/src/i18n/locales/es.ts +20 -0
- package/src/i18n/locales/fr.ts +20 -0
- package/src/i18n/locales/hu.ts +20 -0
- package/src/i18n/locales/it.ts +20 -0
- package/src/i18n/locales/zh.ts +20 -0
- package/src/index.ts +26 -0
- package/src/lib/clipping.ts +16 -0
- package/src/search/command-palette.tsx +42 -5
- package/src/search/global-search.tsx +59 -18
- package/src/wizard/stepper-nav.tsx +2 -1
- package/src/wizard/types.ts +2 -2
- package/src/wizard/use-wizard.ts +3 -3
- package/src/wizard/wizard-summary.tsx +14 -10
- package/tokens.css +97 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/wizard/stepper-nav.tsx"],"sourcesContent":["import { useId, useMemo } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { Check } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { Button, Spinner } from \"../components/ui\";\nimport type { ButtonVariant } from \"../components/ui\";\nimport { Modal } from \"../components/modal\";\nimport { alertFrameClass } from \"../components/alert-banner\";\nimport { WizardContextProvider } from \"./wizard-context\";\nimport { DEFAULT_WIZARD_LABELS } from \"./types\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport type { UseWizardReturn, WizardLabels } from \"./types\";\n\n/**\n * The chrome around a wizard: the step indicator, the step content, and the\n * Cancel / Back / Skip / Next / Finish bar — plus the cancel-confirmation\n * dialog, because \"are you sure you want to lose this\" is part of the wizard's\n * contract rather than something each app should re-decide.\n *\n * It is a component and not a set of parts for one reason: which button shows,\n * and whether it is enabled, is a function of `wizard` state that was getting\n * re-derived (and re-derived slightly differently) at every call site. Finish is\n * gated on `canFinish`, Next and Skip on `nextBlocked`, and Skip only appears on\n * an `optional` step that is not the last — that policy lives here now.\n *\n * The `<StepperNav>` is also what publishes {@link WizardContextProvider}, which\n * is how a mounted step registers its own validator. A step's\n * `useRhfWizardStep` / `useWizardStepValidate` therefore only works inside one.\n *\n * Which buttons show is read off `wizard` (`canCancel`, `canGoBack`, `canDone`,\n * `isCommitStep`), so the policy options — `cancellable`, `onExit`, `commits`,\n * `onDone` — are all set on `useWizard`. What is set HERE is how the Finish button\n * looks and what wraps it, because that is presentation and usually depends on\n * state the app holds (a \"replace everything\" toggle, a write lock).\n */\nexport function StepperNav<TData extends Record<string, unknown>>({\n wizard,\n children,\n title,\n description,\n className,\n labels,\n finishVariant = \"brand\",\n finishDisabled = false,\n doneDisabled = false,\n renderFinish,\n}: {\n wizard: UseWizardReturn<TData>;\n children: ReactNode;\n title?: ReactNode;\n description?: ReactNode;\n className?: string;\n labels?: Partial<WizardLabels>;\n /** The Finish button's variant — `\"danger\"` for a destructive commit (an import\n * that replaces what is there). Default `\"brand\"`. Next keeps `\"brand\"`. */\n finishVariant?: ButtonVariant;\n /** Disable Finish for a reason of the app's own, on top of the kit's gates\n * (`canFinish`, submitting, validating). */\n finishDisabled?: boolean;\n /**\n * Disable Done — the button of the last step after a commit — while the step still\n * has work of its own running. keksdose's YNAB import ends on a recurring step whose\n * convert loop writes one rule at a time (ynab-import-panel.tsx): leaving mid-loop\n * abandons the rest. Until now the panel dropped `onDone` for the duration, which\n * takes the button away instead of saying \"not yet\": disabled, it stays where the\n * eye left it and comes back to life when the loop ends.\n */\n doneDisabled?: boolean;\n /**\n * Wrap the Finish button: receives the kit's button element — already labelled,\n * gated and wired to `wizard.finish` — and returns what to render in its place.\n *\n * ```tsx\n * renderFinish={(button) => <SaveGuard lock={lock}>{button}</SaveGuard>}\n * ```\n *\n * A wrapper and not a replacement (`finishButton: (props) => …`) or a hook\n * (`onBeforeFinish`): the app's need is to put something AROUND the commit — a\n * write-lock tooltip, which has to sit on a wrapping element because a disabled\n * button receives no pointer events — while the kit keeps owning what the button\n * says and when it is enabled. A replacement would hand every app the gating to\n * re-derive (the problem this component exists to end); `onBeforeFinish` can veto\n * a click but cannot explain a disabled button. The element is a plain `<Button>`,\n * so a wrapper that clones it with `disabled` works as expected.\n */\n renderFinish?: (button: ReactElement) => ReactNode;\n}) {\n const l = useKitLabels(\"wizard\", DEFAULT_WIZARD_LABELS, labels);\n const cancelTitleId = useId();\n\n const contextValue = useMemo(\n () => ({\n registerStepValidate: wizard.registerStepValidate,\n setNextBlocked: wizard.setNextBlocked,\n }),\n [wizard.registerStepValidate, wizard.setNextBlocked],\n );\n\n // Finish is only valid once every step before the last has been completed or\n // skipped (derived once in useWizard). Prevents a restored last-step index\n // (empty data after a reload) from firing an empty submit.\n const canFinish = wizard.canFinish;\n\n // Pending while the step's validators run (an async server check): a spinner and\n // `aria-busy`, and disabled — the hook's own re-entry guard still refuses a second\n // press, this just stops it looking like the first one did nothing.\n const pending = wizard.isValidating;\n const stepNextLabel = wizard.currentStep.nextLabel;\n\n let forwardButton: ReactNode = null;\n if (wizard.isCommitStep) {\n const finishButton = (\n <Button\n variant={finishVariant}\n data-tour=\"wizard-finish\"\n onClick={wizard.finish}\n disabled={wizard.isSubmitting || pending || !canFinish || finishDisabled}\n aria-busy={wizard.isSubmitting || pending || undefined}\n >\n {/* Decorative: the button's text is what a reader hears. */}\n {pending && <Spinner label={null} className=\"size-4\" />}\n {wizard.isSubmitting ? l.submitting : (stepNextLabel ?? l.finish)}\n </Button>\n );\n forwardButton = renderFinish ? renderFinish(finishButton) : finishButton;\n } else if (wizard.isLastStep) {\n // The last step after a commit. Nothing to finish; Done only when the app gave\n // an `onDone` — otherwise the step carries its own way out.\n forwardButton = wizard.canDone ? (\n <Button variant=\"brand\" data-tour=\"wizard-done\" onClick={wizard.done} disabled={doneDisabled}>\n {stepNextLabel ?? l.done ?? DEFAULT_WIZARD_LABELS.done}\n </Button>\n ) : null;\n } else {\n forwardButton = (\n <Button\n variant=\"brand\"\n data-tour=\"wizard-next\"\n onClick={wizard.goNext}\n disabled={wizard.nextBlocked || pending}\n aria-busy={pending || undefined}\n >\n {pending && <Spinner label={null} className=\"size-4\" />}\n {stepNextLabel ?? l.next}\n </Button>\n );\n }\n\n return (\n <div className={cn(\"flex flex-col gap-6\", className)}>\n {(title || description) && (\n <div>\n {title && (\n <h1 className=\"text-2xl font-bold tracking-tight text-[var(--text-primary)]\">\n {title}\n </h1>\n )}\n {description && (\n <p className=\"mt-1 text-sm text-[var(--text-muted)]\">{description}</p>\n )}\n </div>\n )}\n\n {/* Step indicator */}\n <nav aria-label={l.steps}>\n <ol className=\"flex w-full items-center\">\n {wizard.steps.map((step, index) => {\n const status = wizard.stepStatus(index);\n const isLast = index === wizard.steps.length - 1;\n\n return (\n <li key={step.id} className={cn(\"flex items-center\", !isLast && \"flex-1\")}>\n <button\n type=\"button\"\n onClick={() => wizard.goToStep(index)}\n // `canGoToStep` adds the commit lock: once past a committing step,\n // the steps up to it are done with and cannot be reopened.\n disabled={status === \"upcoming\" || !wizard.canGoToStep(index)}\n aria-current={status === \"active\" ? \"step\" : undefined}\n className={cn(\n \"group flex flex-col items-center gap-1.5\",\n status === \"upcoming\" ? \"cursor-default\" : \"cursor-pointer\",\n )}\n >\n {/* Circle */}\n <span\n className={cn(\n \"flex size-8 shrink-0 items-center justify-center rounded-full border-2 text-xs font-semibold transition-colors\",\n status === \"completed\" &&\n \"border-[var(--brand)] bg-[var(--brand)] text-[var(--brand-contrast)]\",\n status === \"active\" &&\n \"border-[var(--brand)] bg-[var(--bg-surface)] text-[var(--brand)]\",\n status === \"upcoming\" &&\n \"border-[var(--border)] bg-[var(--bg-surface)] text-[var(--text-placeholder)]\",\n status === \"skipped\" &&\n \"border-[var(--border)] bg-[var(--bg-surface-2)] text-[var(--text-muted)]\",\n )}\n >\n {status === \"completed\" ? <Check className=\"size-4\" /> : index + 1}\n </span>\n {/* Label — hidden on mobile, where the line below carries it */}\n <span\n className={cn(\n \"hidden text-xs font-medium md:block\",\n status === \"active\" || status === \"completed\"\n ? \"text-[var(--text-primary)]\"\n : \"text-[var(--text-muted)]\",\n )}\n >\n {step.label}\n </span>\n </button>\n\n {/* Connector line */}\n {!isLast && (\n <div\n className={cn(\n \"mx-2 h-0.5 flex-1\",\n status === \"completed\" ? \"bg-[var(--brand)]\" : \"bg-[var(--border)]\",\n )}\n />\n )}\n </li>\n );\n })}\n </ol>\n {/* Mobile: the labels above are hidden, so name the current step here */}\n <p className=\"mt-2 text-center text-sm font-medium text-[var(--text-primary)] md:hidden\">\n {l.step(wizard.currentStepIndex + 1, wizard.steps.length)}\n {\" — \"}\n {wizard.currentStep.label}\n </p>\n </nav>\n\n {/* Step content */}\n <WizardContextProvider value={contextValue}>\n <div className=\"min-h-[300px]\">{children}</div>\n </WizardContextProvider>\n\n {/* Submit error */}\n {wizard.error && (\n <div\n role=\"alert\"\n className={cn(\n \"text-sm text-[var(--danger)]\",\n alertFrameClass(\"danger\"),\n )}\n >\n {wizard.error}\n </div>\n )}\n\n {/* Navigation bar */}\n <div className=\"flex items-center justify-between border-t border-[var(--border)] pt-4\">\n {/* The empty span keeps the right-hand group on the right when Cancel is off. */}\n {wizard.canCancel ? (\n <Button variant=\"secondary\" onClick={wizard.cancel}>\n {l.cancel}\n </Button>\n ) : (\n <span />\n )}\n <div className=\"flex items-center gap-2\">\n {wizard.canGoBack && (\n // Disabled, not hidden, while the commit runs: it comes back if the commit\n // fails, and a button that vanishes mid-click is its own kind of surprise.\n <Button variant=\"secondary\" onClick={wizard.goBack} disabled={wizard.isSubmitting}>\n {l.back}\n </Button>\n )}\n {wizard.currentStep.optional && !wizard.isLastStep && !wizard.isCommitStep && (\n <Button\n variant=\"ghost\"\n onClick={wizard.skip}\n disabled={wizard.nextBlocked || wizard.isValidating}\n >\n {l.skip}\n </Button>\n )}\n {forwardButton}\n </div>\n </div>\n\n {/* Cancel confirmation */}\n {wizard.showCancelDialog && (\n <Modal onClose={wizard.dismissCancel} labelledBy={cancelTitleId} className=\"space-y-4\">\n <div className=\"space-y-1.5\">\n <h2 id={cancelTitleId} className=\"text-lg font-semibold text-[var(--text-primary)]\">\n {l.cancelTitle}\n </h2>\n <p className=\"text-sm text-[var(--text-muted)]\">{l.confirmCancel}</p>\n </div>\n <div className=\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\">\n <Button variant=\"secondary\" onClick={wizard.dismissCancel}>\n {l.cancelDismissLabel}\n </Button>\n <Button variant=\"danger\" onClick={wizard.confirmCancel}>\n {l.cancelConfirmLabel}\n </Button>\n </div>\n </Modal>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/wizard/stepper-nav.tsx"],"sourcesContent":["import { useId, useMemo } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { Check } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { Button, Spinner } from \"../components/ui\";\nimport type { ButtonVariant } from \"../components/ui\";\nimport { Modal } from \"../components/modal\";\nimport { alertFrameClass } from \"../components/alert-banner\";\nimport { WizardContextProvider } from \"./wizard-context\";\nimport { DEFAULT_WIZARD_LABELS } from \"./types\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport type { UseWizardReturn, WizardLabels } from \"./types\";\n\n/**\n * The chrome around a wizard: the step indicator, the step content, and the\n * Cancel / Back / Skip / Next / Finish bar — plus the cancel-confirmation\n * dialog, because \"are you sure you want to lose this\" is part of the wizard's\n * contract rather than something each app should re-decide.\n *\n * It is a component and not a set of parts for one reason: which button shows,\n * and whether it is enabled, is a function of `wizard` state that was getting\n * re-derived (and re-derived slightly differently) at every call site. Finish is\n * gated on `canFinish`, Next and Skip on `nextBlocked`, and Skip only appears on\n * an `optional` step that is not the last — that policy lives here now.\n *\n * The `<StepperNav>` is also what publishes {@link WizardContextProvider}, which\n * is how a mounted step registers its own validator. A step's\n * `useRhfWizardStep` / `useWizardStepValidate` therefore only works inside one.\n *\n * Which buttons show is read off `wizard` (`canCancel`, `canGoBack`, `canDone`,\n * `isCommitStep`), so the policy options — `cancellable`, `onExit`, `commits`,\n * `onDone` — are all set on `useWizard`. What is set HERE is how the Finish button\n * looks and what wraps it, because that is presentation and usually depends on\n * state the app holds (a \"replace everything\" toggle, a write lock).\n */\nexport function StepperNav<TData extends Record<string, unknown>>({\n wizard,\n children,\n title,\n description,\n className,\n labels,\n finishVariant = \"brand\",\n finishDisabled = false,\n doneDisabled = false,\n renderFinish,\n}: {\n wizard: UseWizardReturn<TData>;\n children: ReactNode;\n title?: ReactNode;\n description?: ReactNode;\n className?: string;\n labels?: Partial<WizardLabels>;\n /** The Finish button's variant — `\"danger\"` for a destructive commit (an import\n * that replaces what is there). Default `\"brand\"`. Next keeps `\"brand\"`. */\n finishVariant?: ButtonVariant;\n /** Disable Finish for a reason of the app's own, on top of the kit's gates\n * (`canFinish`, submitting, validating). */\n finishDisabled?: boolean;\n /**\n * Disable Done — the button of the last step after a commit — while the step still\n * has work of its own running. keksdose's YNAB import ends on a recurring step whose\n * convert loop writes one rule at a time (ynab-import-panel.tsx): leaving mid-loop\n * abandons the rest. Until now the panel dropped `onDone` for the duration, which\n * takes the button away instead of saying \"not yet\": disabled, it stays where the\n * eye left it and comes back to life when the loop ends.\n */\n doneDisabled?: boolean;\n /**\n * Wrap the Finish button: receives the kit's button element — already labelled,\n * gated and wired to `wizard.finish` — and returns what to render in its place.\n *\n * ```tsx\n * renderFinish={(button) => <SaveGuard lock={lock}>{button}</SaveGuard>}\n * ```\n *\n * A wrapper and not a replacement (`finishButton: (props) => …`) or a hook\n * (`onBeforeFinish`): the app's need is to put something AROUND the commit — a\n * write-lock tooltip, which has to sit on a wrapping element because a disabled\n * button receives no pointer events — while the kit keeps owning what the button\n * says and when it is enabled. A replacement would hand every app the gating to\n * re-derive (the problem this component exists to end); `onBeforeFinish` can veto\n * a click but cannot explain a disabled button. The element is a plain `<Button>`,\n * so a wrapper that clones it with `disabled` works as expected.\n */\n renderFinish?: (button: ReactElement) => ReactNode;\n}) {\n const l = useKitLabels(\"wizard\", DEFAULT_WIZARD_LABELS, labels);\n const cancelTitleId = useId();\n\n const contextValue = useMemo(\n () => ({\n registerStepValidate: wizard.registerStepValidate,\n setNextBlocked: wizard.setNextBlocked,\n }),\n [wizard.registerStepValidate, wizard.setNextBlocked],\n );\n\n // Finish is only valid once every step before the last has been completed or\n // skipped (derived once in useWizard). Prevents a restored last-step index\n // (empty data after a reload) from firing an empty submit.\n const canFinish = wizard.canFinish;\n\n // Pending while the step's validators run (an async server check): a spinner and\n // `aria-busy`, and disabled — the hook's own re-entry guard still refuses a second\n // press, this just stops it looking like the first one did nothing.\n const pending = wizard.isValidating;\n const stepNextLabel = wizard.currentStep.nextLabel;\n\n // Every branch below assigns it; no initial value to overwrite.\n let forwardButton: ReactNode;\n if (wizard.isCommitStep) {\n const finishButton = (\n <Button\n variant={finishVariant}\n data-tour=\"wizard-finish\"\n onClick={wizard.finish}\n disabled={wizard.isSubmitting || pending || !canFinish || finishDisabled}\n aria-busy={wizard.isSubmitting || pending || undefined}\n >\n {/* Decorative: the button's text is what a reader hears. */}\n {pending && <Spinner label={null} className=\"size-4\" />}\n {wizard.isSubmitting ? l.submitting : (stepNextLabel ?? l.finish)}\n </Button>\n );\n forwardButton = renderFinish ? renderFinish(finishButton) : finishButton;\n } else if (wizard.isLastStep) {\n // The last step after a commit. Nothing to finish; Done only when the app gave\n // an `onDone` — otherwise the step carries its own way out.\n forwardButton = wizard.canDone ? (\n <Button variant=\"brand\" data-tour=\"wizard-done\" onClick={wizard.done} disabled={doneDisabled}>\n {stepNextLabel ?? l.done ?? DEFAULT_WIZARD_LABELS.done}\n </Button>\n ) : null;\n } else {\n forwardButton = (\n <Button\n variant=\"brand\"\n data-tour=\"wizard-next\"\n onClick={wizard.goNext}\n disabled={wizard.nextBlocked || pending}\n aria-busy={pending || undefined}\n >\n {pending && <Spinner label={null} className=\"size-4\" />}\n {stepNextLabel ?? l.next}\n </Button>\n );\n }\n\n return (\n <div className={cn(\"flex flex-col gap-6\", className)}>\n {(title || description) && (\n <div>\n {title && (\n <h1 className=\"text-2xl font-bold tracking-tight text-[var(--text-primary)]\">\n {title}\n </h1>\n )}\n {description && (\n <p className=\"mt-1 text-sm text-[var(--text-muted)]\">{description}</p>\n )}\n </div>\n )}\n\n {/* Step indicator */}\n <nav aria-label={l.steps}>\n <ol className=\"flex w-full items-center\">\n {wizard.steps.map((step, index) => {\n const status = wizard.stepStatus(index);\n const isLast = index === wizard.steps.length - 1;\n\n return (\n <li key={step.id} className={cn(\"flex items-center\", !isLast && \"flex-1\")}>\n <button\n type=\"button\"\n onClick={() => wizard.goToStep(index)}\n // `canGoToStep` adds the commit lock: once past a committing step,\n // the steps up to it are done with and cannot be reopened.\n disabled={status === \"upcoming\" || !wizard.canGoToStep(index)}\n aria-current={status === \"active\" ? \"step\" : undefined}\n className={cn(\n \"group flex flex-col items-center gap-1.5\",\n status === \"upcoming\" ? \"cursor-default\" : \"cursor-pointer\",\n )}\n >\n {/* Circle */}\n <span\n className={cn(\n \"flex size-8 shrink-0 items-center justify-center rounded-full border-2 text-xs font-semibold transition-colors\",\n status === \"completed\" &&\n \"border-[var(--brand)] bg-[var(--brand)] text-[var(--brand-contrast)]\",\n status === \"active\" &&\n \"border-[var(--brand)] bg-[var(--bg-surface)] text-[var(--brand)]\",\n status === \"upcoming\" &&\n \"border-[var(--border)] bg-[var(--bg-surface)] text-[var(--text-placeholder)]\",\n status === \"skipped\" &&\n \"border-[var(--border)] bg-[var(--bg-surface-2)] text-[var(--text-muted)]\",\n )}\n >\n {status === \"completed\" ? <Check className=\"size-4\" /> : index + 1}\n </span>\n {/* Label — hidden on mobile, where the line below carries it */}\n <span\n className={cn(\n \"hidden text-xs font-medium md:block\",\n status === \"active\" || status === \"completed\"\n ? \"text-[var(--text-primary)]\"\n : \"text-[var(--text-muted)]\",\n )}\n >\n {step.label}\n </span>\n </button>\n\n {/* Connector line */}\n {!isLast && (\n <div\n className={cn(\n \"mx-2 h-0.5 flex-1\",\n status === \"completed\" ? \"bg-[var(--brand)]\" : \"bg-[var(--border)]\",\n )}\n />\n )}\n </li>\n );\n })}\n </ol>\n {/* Mobile: the labels above are hidden, so name the current step here */}\n <p className=\"mt-2 text-center text-sm font-medium text-[var(--text-primary)] md:hidden\">\n {l.step(wizard.currentStepIndex + 1, wizard.steps.length)}\n {\" — \"}\n {wizard.currentStep.label}\n </p>\n </nav>\n\n {/* Step content */}\n <WizardContextProvider value={contextValue}>\n <div className=\"min-h-[300px]\">{children}</div>\n </WizardContextProvider>\n\n {/* Submit error */}\n {wizard.error && (\n <div\n role=\"alert\"\n className={cn(\n \"text-sm text-[var(--danger)]\",\n alertFrameClass(\"danger\"),\n )}\n >\n {wizard.error}\n </div>\n )}\n\n {/* Navigation bar */}\n <div className=\"flex items-center justify-between border-t border-[var(--border)] pt-4\">\n {/* The empty span keeps the right-hand group on the right when Cancel is off. */}\n {wizard.canCancel ? (\n <Button variant=\"secondary\" onClick={wizard.cancel}>\n {l.cancel}\n </Button>\n ) : (\n <span />\n )}\n <div className=\"flex items-center gap-2\">\n {wizard.canGoBack && (\n // Disabled, not hidden, while the commit runs: it comes back if the commit\n // fails, and a button that vanishes mid-click is its own kind of surprise.\n <Button variant=\"secondary\" onClick={wizard.goBack} disabled={wizard.isSubmitting}>\n {l.back}\n </Button>\n )}\n {wizard.currentStep.optional && !wizard.isLastStep && !wizard.isCommitStep && (\n <Button\n variant=\"ghost\"\n onClick={wizard.skip}\n disabled={wizard.nextBlocked || wizard.isValidating}\n >\n {l.skip}\n </Button>\n )}\n {forwardButton}\n </div>\n </div>\n\n {/* Cancel confirmation */}\n {wizard.showCancelDialog && (\n <Modal onClose={wizard.dismissCancel} labelledBy={cancelTitleId} className=\"space-y-4\">\n <div className=\"space-y-1.5\">\n <h2 id={cancelTitleId} className=\"text-lg font-semibold text-[var(--text-primary)]\">\n {l.cancelTitle}\n </h2>\n <p className=\"text-sm text-[var(--text-muted)]\">{l.confirmCancel}</p>\n </div>\n <div className=\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\">\n <Button variant=\"secondary\" onClick={wizard.dismissCancel}>\n {l.cancelDismissLabel}\n </Button>\n <Button variant=\"danger\" onClick={wizard.confirmCancel}>\n {l.cancelConfirmLabel}\n </Button>\n </div>\n </Modal>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/wizard/types.d.ts
CHANGED
|
@@ -110,8 +110,8 @@ interface UseWizardOptions<TData extends Record<string, unknown>> {
|
|
|
110
110
|
*/
|
|
111
111
|
missingRequiredMessage?: string;
|
|
112
112
|
/**
|
|
113
|
-
* How to surface {@link missingRequiredMessage}. Defaults to
|
|
114
|
-
* (an optional peer,
|
|
113
|
+
* How to surface {@link missingRequiredMessage}. Defaults to the kit's `toast.error`
|
|
114
|
+
* (sonner, an optional peer, is loaded only when a toast is shown). Pass your own to
|
|
115
115
|
* route it somewhere else, or to a no-op to silence it.
|
|
116
116
|
*/
|
|
117
117
|
onValidationFailed?: (message: string) => void;
|
package/dist/wizard/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/wizard/types.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nexport type FieldErrors = Record<string, string>;\n\nexport type ValidateResult = boolean | { ok: boolean; errors?: FieldErrors };\n\nexport interface WizardStepConfig {\n /** Unique step identifier. */\n id: string;\n /**\n * What the step is called in the indicator.\n *\n * A `ReactNode`, not a translation key: the engine never resolves strings.\n * An app with i18n maps its own keys at the `useWizard` call — the same split\n * `TourStep` and `DataTableLabels` already use.\n */\n label: ReactNode;\n /** Whether the step can be skipped. */\n optional?: boolean;\n /**\n * Validation gate before advancing.\n * - Return `true` / `false` to just block.\n * - Return `{ ok: false, errors: { fieldName: \"message\" } }` to also surface\n * per-field error messages via `wizard.fieldErrors`.\n */\n validate?: () => ValidateResult | Promise<ValidateResult>;\n /**\n * The forward button's label on THIS step, in place of `labels.next` — \"Analyse\",\n * \"Import\", \"Send code\". On the committing step (see {@link commits}) it labels\n * Finish instead of `labels.finish`; `labels.submitting` still replaces it while\n * the commit runs. Like `label`, a node the app has already translated.\n */\n nextLabel?: ReactNode;\n /**\n * This step's forward button COMMITS: it runs `onComplete` (via `finish`) instead\n * of just advancing. Without the flag the last step commits, as it always has.\n *\n * Flag a step before the last and every step after it becomes a post-commit step —\n * an import's \"here is what happened, set up recurring rules?\" page:\n * - a successful `onComplete` advances onto the next step (a failed one stays, with\n * `wizard.error`, exactly like a last-step Finish);\n * - from there Back, the indicator and `goBack`/`goToStep` cannot cross back over\n * the commit, and Cancel is hidden — there is nothing left to discard;\n * - the last post-commit step's button is Done, which calls `onDone` (and is not\n * shown when there is no `onDone`: the step then carries its own way out).\n *\n * Only the FIRST flagged step counts. Steps after it may still validate, skip and\n * use `nextLabel` like any other.\n */\n commits?: boolean;\n}\n\n/**\n * Where the active step lives. `true` (the default) mirrors it to `?step=N` through\n * react-router, as every wizard before 0.8 did; `{ param }` renames that param (two\n * wizards on one page, or a page that already uses `step`); `false` keeps it in\n * memory only and needs no router at all.\n *\n * Read once, at mount: switching it on a mounted wizard has no effect.\n */\nexport type WizardUrlSync = boolean | { param?: string };\n\nexport interface UseWizardOptions<TData extends Record<string, unknown>> {\n steps: WizardStepConfig[];\n initialData?: Partial<TData>;\n onComplete?: (data: TData) => void | Promise<void>;\n onCancel?: () => void;\n /**\n * Leave the wizard BACKWARDS: when given, Back is shown on the first step too and\n * calls this — keksdose's import panel, whose first step's Back returns to the\n * dropzone the file came from. No confirm dialog: going back is not discarding.\n */\n onExit?: () => void;\n /**\n * The Done button on the last step after a committing step (see\n * {@link WizardStepConfig.commits}). Without it no Done button is rendered, and the\n * post-commit step is expected to carry its own way out.\n */\n onDone?: () => void;\n /**\n * Whether the chrome offers Cancel at all. Default `true`. `false` hides the button\n * (`wizard.canCancel` is false); `wizard.cancel()` still works if the app calls it.\n */\n cancellable?: boolean;\n /**\n * Whether Cancel asks first (\"Discard this form?\"). Default `true`. `false` makes\n * `cancel()` call `onCancel` straight away — for a wizard that has nothing to lose\n * yet, or one whose data is kept elsewhere.\n */\n confirmCancel?: boolean;\n /**\n * Mirror the active step to the URL. Default `true` (`?step=N`, needs a router).\n * See {@link WizardUrlSync}.\n *\n * The param is WRITTEN only, never read back (see the note in `use-wizard.ts`). It\n * is removed again when the wizard is left through the kit: a confirmed Cancel,\n * `onExit` from the first step's Back, and Done — each before the app's callback\n * runs, so an app that navigates away navigates from a clean URL. It is NOT removed\n * after `onComplete` on a last step: an app navigating away there would otherwise\n * be pulled back by the kit's own URL write. An app that stays on the page after a\n * final Finish drops the param itself, or uses `urlSync: false`.\n */\n urlSync?: WizardUrlSync;\n /**\n * Shown when a step blocks with no per-field detail — an RHF step surfacing\n * its own inline messages, say — so the user learns why the wizard did not\n * advance. Falls back to `wizard.missingRequired` from the {@link UiKitProvider},\n * then to English.\n */\n missingRequiredMessage?: string;\n /**\n * How to surface {@link missingRequiredMessage}. Defaults to a `sonner` toast\n * (an optional peer, imported only when this actually fires). Pass your own to\n * route it somewhere else, or to a no-op to silence it.\n */\n onValidationFailed?: (message: string) => void;\n}\n\nexport type StepStatus = \"upcoming\" | \"active\" | \"completed\" | \"skipped\";\n\nexport interface UseWizardReturn<TData extends Record<string, unknown>> {\n // Navigation\n currentStepIndex: number;\n currentStep: WizardStepConfig;\n steps: WizardStepConfig[];\n isFirstStep: boolean;\n isLastStep: boolean;\n /** True once every step before the committing one (the last, unless a step is\n * flagged `commits`) has been completed or skipped. */\n canFinish: boolean;\n /** The step whose forward button commits: the first `commits` step, else the last. */\n commitStepIndex: number;\n /** The active step is {@link commitStepIndex}: the chrome shows Finish, not Next. */\n isCommitStep: boolean;\n /** `onComplete` has resolved. Once the active step is past the committing one, Back\n * cannot cross it and Cancel is hidden. */\n committed: boolean;\n /** Whether Back is offered here: not on the first step unless `onExit` was given,\n * and never back across a commit. (Disabled, not hidden, while `isSubmitting`.) */\n canGoBack: boolean;\n /** Whether Cancel is offered: `cancellable`, and not after a commit. */\n canCancel: boolean;\n /** Whether Done is offered: the last step, past a commit, with an `onDone`. */\n canDone: boolean;\n /** Whether the indicator may jump to `index` — a completed/skipped/active step on\n * the right side of any commit. `goToStep` refuses anything this rejects. */\n canGoToStep: (index: number) => boolean;\n goNext: () => Promise<void>;\n /** One step back — or, on the first step, `onExit`. Refused while submitting. */\n goBack: () => void;\n goToStep: (index: number) => void;\n skip: () => void;\n cancel: () => void;\n finish: () => Promise<void>;\n /** Leave after the post-commit steps: drops the URL param, then calls `onDone`. */\n done: () => void;\n /** Register a validator for the mounted step (used by useRhfWizardStep). */\n registerStepValidate: (\n fn: () => ValidateResult | Promise<ValidateResult>,\n ) => () => void;\n /** True when the mounted step has blocked forward navigation (Next/Skip disabled). */\n nextBlocked: boolean;\n /** Set by the active step (via useWizardNextGate) to disable/enable Next+Skip. */\n setNextBlocked: (blocked: boolean) => void;\n\n // Step state\n stepStatus: (index: number) => StepStatus;\n completedSteps: Set<number>;\n\n // Data\n data: TData;\n updateData: (partial: Partial<TData>) => void;\n\n // Loading / error\n isSubmitting: boolean;\n /** A `goNext`/`finish` is running the step's validators (an async server check,\n * say). The chrome shows Next pending — spinner, `aria-busy`, disabled. */\n isValidating: boolean;\n error: string | null;\n clearError: () => void;\n\n // Per-field validation errors (populated by the last failed goNext)\n fieldErrors: FieldErrors;\n clearFieldErrors: () => void;\n\n // Cancel dialog\n showCancelDialog: boolean;\n confirmCancel: () => void;\n dismissCancel: () => void;\n}\n\nexport interface SummaryItem {\n label: ReactNode;\n value: ReactNode;\n}\n\nexport interface SummarySection {\n label: ReactNode;\n /**\n * Which step the section's edit button jumps back to. Leave it out for a section\n * that belongs to no step — file-level counts, a total — and the card has no edit\n * button, instead of borrowing one from whichever step it was parked under.\n */\n stepIndex?: number;\n items: SummaryItem[];\n}\n\n/**\n * Every string the wizard chrome shows. All optional with English defaults, so a\n * monolingual app can mount `StepperNav` bare; a translated app passes the whole\n * object once (typically from a thin app-side wrapper).\n */\nexport interface WizardLabels {\n cancel: string;\n back: string;\n next: string;\n skip: string;\n finish: string;\n /** Shown on Finish while `isSubmitting`. */\n submitting: string;\n /** `aria-label` for the step indicator's `<nav>`. */\n steps: string;\n /** The mobile \"Step 2 of 5\" line. */\n step: (current: number, total: number) => string;\n /** Heading of the cancel-confirmation dialog. */\n cancelTitle: string;\n /** Body of the cancel-confirmation dialog. */\n confirmCancel: string;\n /** Confirm button in the cancel dialog. */\n cancelConfirmLabel: string;\n /** Dismiss button in the cancel dialog (\"keep editing\"). */\n cancelDismissLabel: string;\n /** Heading above a {@link WizardSummary}. */\n reviewTitle: string;\n /** `aria-label` on a summary section's edit button. */\n edit: string;\n /**\n * Toasted (or handed to `onValidationFailed`) when a step blocks Next without\n * saying which field is at fault. `useWizard`'s `missingRequiredMessage` wins.\n *\n * This and `genericError` are OPTIONAL, unlike every key above, for the reason\n * `TwoFactorSettingLabels.qrAlt` is: apps annotate a full translation as\n * `const DE: WizardLabels = {…}`, and a new required key would be a compile error\n * in each of them on the next update. The defaults below always carry both.\n */\n missingRequired?: string;\n /** `wizard.error` when `onComplete` rejects with nothing readable — a thrown\n * non-Error, or an Error whose message is empty. Optional: see above. */\n genericError?: string;\n /** The button on the last step after a committing step (`onDone`). Optional: see\n * `missingRequired`. */\n done?: string;\n}\n\n/** Typed with the two optional keys made required, so code reading the defaults\n * directly (the fallback in `useWizard`) needs no `!`. */\nexport const DEFAULT_WIZARD_LABELS: WizardLabels &\n Required<Pick<WizardLabels, \"missingRequired\" | \"genericError\" | \"done\">> = {\n cancel: \"Cancel\",\n back: \"Back\",\n next: \"Next\",\n skip: \"Skip\",\n finish: \"Finish\",\n submitting: \"Creating…\",\n steps: \"Steps\",\n step: (current, total) => `Step ${current} of ${total}`,\n cancelTitle: \"Discard this form?\",\n confirmCancel: \"Your entries will be lost.\",\n cancelConfirmLabel: \"Discard\",\n cancelDismissLabel: \"Keep editing\",\n reviewTitle: \"Review\",\n edit: \"Edit\",\n missingRequired: \"Please fill in all required fields.\",\n genericError: \"An error occurred\",\n done: \"Done\",\n};\n\nexport function resolveWizardLabels(labels?: Partial<WizardLabels>): WizardLabels {\n return labels ? { ...DEFAULT_WIZARD_LABELS, ...labels } : DEFAULT_WIZARD_LABELS;\n}\n"],"mappings":";AAgQO,MAAM,wBACiE;AAAA,EAC5E,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,MAAM,CAAC,SAAS,UAAU,QAAQ,OAAO,OAAO,KAAK;AAAA,EACrD,aAAa;AAAA,EACb,eAAe;AAAA,EACf,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,aAAa;AAAA,EACb,MAAM;AAAA,EACN,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,MAAM;AACR;AAEO,SAAS,oBAAoB,QAA8C;AAChF,SAAO,SAAS,EAAE,GAAG,uBAAuB,GAAG,OAAO,IAAI;AAC5D;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/wizard/types.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nexport type FieldErrors = Record<string, string>;\n\nexport type ValidateResult = boolean | { ok: boolean; errors?: FieldErrors };\n\nexport interface WizardStepConfig {\n /** Unique step identifier. */\n id: string;\n /**\n * What the step is called in the indicator.\n *\n * A `ReactNode`, not a translation key: the engine never resolves strings.\n * An app with i18n maps its own keys at the `useWizard` call — the same split\n * `TourStep` and `DataTableLabels` already use.\n */\n label: ReactNode;\n /** Whether the step can be skipped. */\n optional?: boolean;\n /**\n * Validation gate before advancing.\n * - Return `true` / `false` to just block.\n * - Return `{ ok: false, errors: { fieldName: \"message\" } }` to also surface\n * per-field error messages via `wizard.fieldErrors`.\n */\n validate?: () => ValidateResult | Promise<ValidateResult>;\n /**\n * The forward button's label on THIS step, in place of `labels.next` — \"Analyse\",\n * \"Import\", \"Send code\". On the committing step (see {@link commits}) it labels\n * Finish instead of `labels.finish`; `labels.submitting` still replaces it while\n * the commit runs. Like `label`, a node the app has already translated.\n */\n nextLabel?: ReactNode;\n /**\n * This step's forward button COMMITS: it runs `onComplete` (via `finish`) instead\n * of just advancing. Without the flag the last step commits, as it always has.\n *\n * Flag a step before the last and every step after it becomes a post-commit step —\n * an import's \"here is what happened, set up recurring rules?\" page:\n * - a successful `onComplete` advances onto the next step (a failed one stays, with\n * `wizard.error`, exactly like a last-step Finish);\n * - from there Back, the indicator and `goBack`/`goToStep` cannot cross back over\n * the commit, and Cancel is hidden — there is nothing left to discard;\n * - the last post-commit step's button is Done, which calls `onDone` (and is not\n * shown when there is no `onDone`: the step then carries its own way out).\n *\n * Only the FIRST flagged step counts. Steps after it may still validate, skip and\n * use `nextLabel` like any other.\n */\n commits?: boolean;\n}\n\n/**\n * Where the active step lives. `true` (the default) mirrors it to `?step=N` through\n * react-router, as every wizard before 0.8 did; `{ param }` renames that param (two\n * wizards on one page, or a page that already uses `step`); `false` keeps it in\n * memory only and needs no router at all.\n *\n * Read once, at mount: switching it on a mounted wizard has no effect.\n */\nexport type WizardUrlSync = boolean | { param?: string };\n\nexport interface UseWizardOptions<TData extends Record<string, unknown>> {\n steps: WizardStepConfig[];\n initialData?: Partial<TData>;\n onComplete?: (data: TData) => void | Promise<void>;\n onCancel?: () => void;\n /**\n * Leave the wizard BACKWARDS: when given, Back is shown on the first step too and\n * calls this — keksdose's import panel, whose first step's Back returns to the\n * dropzone the file came from. No confirm dialog: going back is not discarding.\n */\n onExit?: () => void;\n /**\n * The Done button on the last step after a committing step (see\n * {@link WizardStepConfig.commits}). Without it no Done button is rendered, and the\n * post-commit step is expected to carry its own way out.\n */\n onDone?: () => void;\n /**\n * Whether the chrome offers Cancel at all. Default `true`. `false` hides the button\n * (`wizard.canCancel` is false); `wizard.cancel()` still works if the app calls it.\n */\n cancellable?: boolean;\n /**\n * Whether Cancel asks first (\"Discard this form?\"). Default `true`. `false` makes\n * `cancel()` call `onCancel` straight away — for a wizard that has nothing to lose\n * yet, or one whose data is kept elsewhere.\n */\n confirmCancel?: boolean;\n /**\n * Mirror the active step to the URL. Default `true` (`?step=N`, needs a router).\n * See {@link WizardUrlSync}.\n *\n * The param is WRITTEN only, never read back (see the note in `use-wizard.ts`). It\n * is removed again when the wizard is left through the kit: a confirmed Cancel,\n * `onExit` from the first step's Back, and Done — each before the app's callback\n * runs, so an app that navigates away navigates from a clean URL. It is NOT removed\n * after `onComplete` on a last step: an app navigating away there would otherwise\n * be pulled back by the kit's own URL write. An app that stays on the page after a\n * final Finish drops the param itself, or uses `urlSync: false`.\n */\n urlSync?: WizardUrlSync;\n /**\n * Shown when a step blocks with no per-field detail — an RHF step surfacing\n * its own inline messages, say — so the user learns why the wizard did not\n * advance. Falls back to `wizard.missingRequired` from the {@link UiKitProvider},\n * then to English.\n */\n missingRequiredMessage?: string;\n /**\n * How to surface {@link missingRequiredMessage}. Defaults to the kit's `toast.error`\n * (sonner, an optional peer, is loaded only when a toast is shown). Pass your own to\n * route it somewhere else, or to a no-op to silence it.\n */\n onValidationFailed?: (message: string) => void;\n}\n\nexport type StepStatus = \"upcoming\" | \"active\" | \"completed\" | \"skipped\";\n\nexport interface UseWizardReturn<TData extends Record<string, unknown>> {\n // Navigation\n currentStepIndex: number;\n currentStep: WizardStepConfig;\n steps: WizardStepConfig[];\n isFirstStep: boolean;\n isLastStep: boolean;\n /** True once every step before the committing one (the last, unless a step is\n * flagged `commits`) has been completed or skipped. */\n canFinish: boolean;\n /** The step whose forward button commits: the first `commits` step, else the last. */\n commitStepIndex: number;\n /** The active step is {@link commitStepIndex}: the chrome shows Finish, not Next. */\n isCommitStep: boolean;\n /** `onComplete` has resolved. Once the active step is past the committing one, Back\n * cannot cross it and Cancel is hidden. */\n committed: boolean;\n /** Whether Back is offered here: not on the first step unless `onExit` was given,\n * and never back across a commit. (Disabled, not hidden, while `isSubmitting`.) */\n canGoBack: boolean;\n /** Whether Cancel is offered: `cancellable`, and not after a commit. */\n canCancel: boolean;\n /** Whether Done is offered: the last step, past a commit, with an `onDone`. */\n canDone: boolean;\n /** Whether the indicator may jump to `index` — a completed/skipped/active step on\n * the right side of any commit. `goToStep` refuses anything this rejects. */\n canGoToStep: (index: number) => boolean;\n goNext: () => Promise<void>;\n /** One step back — or, on the first step, `onExit`. Refused while submitting. */\n goBack: () => void;\n goToStep: (index: number) => void;\n skip: () => void;\n cancel: () => void;\n finish: () => Promise<void>;\n /** Leave after the post-commit steps: drops the URL param, then calls `onDone`. */\n done: () => void;\n /** Register a validator for the mounted step (used by useRhfWizardStep). */\n registerStepValidate: (\n fn: () => ValidateResult | Promise<ValidateResult>,\n ) => () => void;\n /** True when the mounted step has blocked forward navigation (Next/Skip disabled). */\n nextBlocked: boolean;\n /** Set by the active step (via useWizardNextGate) to disable/enable Next+Skip. */\n setNextBlocked: (blocked: boolean) => void;\n\n // Step state\n stepStatus: (index: number) => StepStatus;\n completedSteps: Set<number>;\n\n // Data\n data: TData;\n updateData: (partial: Partial<TData>) => void;\n\n // Loading / error\n isSubmitting: boolean;\n /** A `goNext`/`finish` is running the step's validators (an async server check,\n * say). The chrome shows Next pending — spinner, `aria-busy`, disabled. */\n isValidating: boolean;\n error: string | null;\n clearError: () => void;\n\n // Per-field validation errors (populated by the last failed goNext)\n fieldErrors: FieldErrors;\n clearFieldErrors: () => void;\n\n // Cancel dialog\n showCancelDialog: boolean;\n confirmCancel: () => void;\n dismissCancel: () => void;\n}\n\nexport interface SummaryItem {\n label: ReactNode;\n value: ReactNode;\n}\n\nexport interface SummarySection {\n label: ReactNode;\n /**\n * Which step the section's edit button jumps back to. Leave it out for a section\n * that belongs to no step — file-level counts, a total — and the card has no edit\n * button, instead of borrowing one from whichever step it was parked under.\n */\n stepIndex?: number;\n items: SummaryItem[];\n}\n\n/**\n * Every string the wizard chrome shows. All optional with English defaults, so a\n * monolingual app can mount `StepperNav` bare; a translated app passes the whole\n * object once (typically from a thin app-side wrapper).\n */\nexport interface WizardLabels {\n cancel: string;\n back: string;\n next: string;\n skip: string;\n finish: string;\n /** Shown on Finish while `isSubmitting`. */\n submitting: string;\n /** `aria-label` for the step indicator's `<nav>`. */\n steps: string;\n /** The mobile \"Step 2 of 5\" line. */\n step: (current: number, total: number) => string;\n /** Heading of the cancel-confirmation dialog. */\n cancelTitle: string;\n /** Body of the cancel-confirmation dialog. */\n confirmCancel: string;\n /** Confirm button in the cancel dialog. */\n cancelConfirmLabel: string;\n /** Dismiss button in the cancel dialog (\"keep editing\"). */\n cancelDismissLabel: string;\n /** Heading above a {@link WizardSummary}. */\n reviewTitle: string;\n /** `aria-label` on a summary section's edit button. */\n edit: string;\n /**\n * Toasted (or handed to `onValidationFailed`) when a step blocks Next without\n * saying which field is at fault. `useWizard`'s `missingRequiredMessage` wins.\n *\n * This and `genericError` are OPTIONAL, unlike every key above, for the reason\n * `TwoFactorSettingLabels.qrAlt` is: apps annotate a full translation as\n * `const DE: WizardLabels = {…}`, and a new required key would be a compile error\n * in each of them on the next update. The defaults below always carry both.\n */\n missingRequired?: string;\n /** `wizard.error` when `onComplete` rejects with nothing readable — a thrown\n * non-Error, or an Error whose message is empty. Optional: see above. */\n genericError?: string;\n /** The button on the last step after a committing step (`onDone`). Optional: see\n * `missingRequired`. */\n done?: string;\n}\n\n/** Typed with the two optional keys made required, so code reading the defaults\n * directly (the fallback in `useWizard`) needs no `!`. */\nexport const DEFAULT_WIZARD_LABELS: WizardLabels &\n Required<Pick<WizardLabels, \"missingRequired\" | \"genericError\" | \"done\">> = {\n cancel: \"Cancel\",\n back: \"Back\",\n next: \"Next\",\n skip: \"Skip\",\n finish: \"Finish\",\n submitting: \"Creating…\",\n steps: \"Steps\",\n step: (current, total) => `Step ${current} of ${total}`,\n cancelTitle: \"Discard this form?\",\n confirmCancel: \"Your entries will be lost.\",\n cancelConfirmLabel: \"Discard\",\n cancelDismissLabel: \"Keep editing\",\n reviewTitle: \"Review\",\n edit: \"Edit\",\n missingRequired: \"Please fill in all required fields.\",\n genericError: \"An error occurred\",\n done: \"Done\",\n};\n\nexport function resolveWizardLabels(labels?: Partial<WizardLabels>): WizardLabels {\n return labels ? { ...DEFAULT_WIZARD_LABELS, ...labels } : DEFAULT_WIZARD_LABELS;\n}\n"],"mappings":";AAgQO,MAAM,wBACiE;AAAA,EAC5E,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,MAAM,CAAC,SAAS,UAAU,QAAQ,OAAO,OAAO,KAAK;AAAA,EACrD,aAAa;AAAA,EACb,eAAe;AAAA,EACf,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,aAAa;AAAA,EACb,MAAM;AAAA,EACN,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,MAAM;AACR;AAEO,SAAS,oBAAoB,QAA8C;AAChF,SAAO,SAAS,EAAE,GAAG,uBAAuB,GAAG,OAAO,IAAI;AAC5D;","names":[]}
|
|
@@ -3,6 +3,7 @@ import { useCallback, useEffect, useReducer, useRef, useState } from "react";
|
|
|
3
3
|
import { useSearchParams } from "react-router";
|
|
4
4
|
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
5
5
|
import { DEFAULT_WIZARD_LABELS } from "./types.js";
|
|
6
|
+
import { toast } from "../components/toast.js";
|
|
6
7
|
function isAfterCommit(state) {
|
|
7
8
|
return state.committedStepIndex !== null && state.currentStepIndex > state.committedStepIndex;
|
|
8
9
|
}
|
|
@@ -175,7 +176,6 @@ function useWizard(options) {
|
|
|
175
176
|
if (onValidationFailed) {
|
|
176
177
|
onValidationFailed(message);
|
|
177
178
|
} else {
|
|
178
|
-
const { toast } = await import("sonner");
|
|
179
179
|
toast.error(message);
|
|
180
180
|
}
|
|
181
181
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/wizard/use-wizard.ts"],"sourcesContent":["/**\n * The multi-step wizard state machine: step index, completed/skipped sets,\n * per-step async validators, collected data, field errors, submit and the\n * cancel-confirm gate.\n *\n * Domain-free, and router-free on request. By default it mirrors the active step\n * to `?step=N`, so it must be mounted inside a react-router context — the same\n * requirement `DataTable` already carries. `urlSync: false` keeps the step in\n * memory and needs no router; `urlSync: { param }` renames the param. It only ever\n * WRITES that param (see the note on `initialStep` below for why it deliberately\n * does not read it back), and removes it when the wizard is left through the kit\n * (see `UseWizardOptions.urlSync`).\n *\n * The committing step is the last one unless a step says `commits: true`; the\n * steps after that one are post-commit steps (see `WizardStepConfig.commits`).\n */\nimport { useCallback, useEffect, useReducer, useRef, useState } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { DEFAULT_WIZARD_LABELS } from \"./types\";\nimport type {\n FieldErrors,\n StepStatus,\n UseWizardOptions,\n UseWizardReturn,\n ValidateResult,\n WizardStepConfig,\n} from \"./types\";\n\ninterface WizardState<TData> {\n currentStepIndex: number;\n completedSteps: Set<number>;\n skippedSteps: Set<number>;\n data: TData;\n isSubmitting: boolean;\n isValidating: boolean;\n error: string | null;\n fieldErrors: FieldErrors;\n showCancelDialog: boolean;\n /** The step whose `onComplete` resolved, or null before any commit. */\n committedStepIndex: number | null;\n}\n\n/** Past the commit: the active step comes after the step that committed. Moves to\n * that step or any before it are refused from here. */\nfunction isAfterCommit(state: { currentStepIndex: number; committedStepIndex: number | null }) {\n return state.committedStepIndex !== null && state.currentStepIndex > state.committedStepIndex;\n}\n\n/** Whether `index` is on the far side of a commit the wizard has already moved past. */\nfunction crossesCommit(\n state: { currentStepIndex: number; committedStepIndex: number | null },\n index: number,\n) {\n return isAfterCommit(state) && index <= (state.committedStepIndex as number);\n}\n\ntype WizardAction<TData> =\n // `from` is the step the move was decided ON. A move whose step is no longer the\n // current one is stale — see `goNext` — and is dropped rather than applied relative\n // to wherever the wizard has got to since.\n | { type: \"GO_NEXT\"; from: number }\n | { type: \"GO_BACK\" }\n | { type: \"GO_TO_STEP\"; index: number }\n | { type: \"SKIP\"; from: number }\n | { type: \"UPDATE_DATA\"; partial: Partial<TData> }\n | { type: \"COMMITTED\"; from: number }\n | { type: \"SET_SUBMITTING\"; value: boolean }\n | { type: \"SET_VALIDATING\"; value: boolean }\n | { type: \"SET_ERROR\"; error: string | null }\n | { type: \"SET_FIELD_ERRORS\"; errors: FieldErrors }\n | { type: \"SHOW_CANCEL_DIALOG\"; show: boolean };\n\nfunction createReducer<TData>(stepCount: number) {\n return function reducer(\n state: WizardState<TData>,\n action: WizardAction<TData>,\n ): WizardState<TData> {\n switch (action.type) {\n case \"GO_NEXT\": {\n if (action.from !== state.currentStepIndex) return state;\n const completed = new Set(state.completedSteps);\n completed.add(state.currentStepIndex);\n const nextIndex = Math.min(state.currentStepIndex + 1, stepCount - 1);\n return { ...state, currentStepIndex: nextIndex, completedSteps: completed, fieldErrors: {} };\n }\n case \"GO_BACK\": {\n const prevIndex = Math.max(state.currentStepIndex - 1, 0);\n if (crossesCommit(state, prevIndex)) return state;\n return { ...state, currentStepIndex: prevIndex, fieldErrors: {} };\n }\n case \"GO_TO_STEP\": {\n if (action.index < 0 || action.index >= stepCount) return state;\n if (crossesCommit(state, action.index)) return state;\n if (!state.completedSteps.has(action.index) && action.index > state.currentStepIndex) {\n return state;\n }\n return { ...state, currentStepIndex: action.index };\n }\n case \"SKIP\": {\n if (action.from !== state.currentStepIndex) return state;\n const skipped = new Set(state.skippedSteps);\n skipped.add(state.currentStepIndex);\n const nextIndex = Math.min(state.currentStepIndex + 1, stepCount - 1);\n return { ...state, currentStepIndex: nextIndex, skippedSteps: skipped };\n }\n case \"UPDATE_DATA\": {\n // Clear error for any field that is now being edited (so typing makes\n // the red message disappear).\n const nextErrors = { ...state.fieldErrors };\n for (const key of Object.keys(action.partial)) {\n delete nextErrors[key];\n }\n return { ...state, data: { ...state.data, ...action.partial }, fieldErrors: nextErrors };\n }\n case \"COMMITTED\": {\n // Recorded whatever step committed, so `wizard.committed` reads true after any\n // successful Finish. Only a commit with steps after it MOVES: the default\n // last-step commit stays where it is and looks exactly as it did before 0.8.\n const committed = { ...state, committedStepIndex: action.from };\n if (action.from !== state.currentStepIndex || action.from >= stepCount - 1) {\n return committed;\n }\n const completed = new Set(state.completedSteps);\n completed.add(action.from);\n return {\n ...committed,\n currentStepIndex: action.from + 1,\n completedSteps: completed,\n fieldErrors: {},\n };\n }\n case \"SET_SUBMITTING\":\n return { ...state, isSubmitting: action.value };\n case \"SET_VALIDATING\":\n return { ...state, isValidating: action.value };\n case \"SET_ERROR\":\n return { ...state, error: action.error };\n case \"SET_FIELD_ERRORS\":\n return { ...state, fieldErrors: action.errors };\n case \"SHOW_CANCEL_DIALOG\":\n return { ...state, showCancelDialog: action.show };\n default:\n return state;\n }\n };\n}\n\nexport function useWizard<TData extends Record<string, unknown>>(\n options: UseWizardOptions<TData>,\n): UseWizardReturn<TData> {\n const {\n steps,\n initialData,\n onComplete,\n onCancel,\n onExit,\n onDone,\n cancellable = true,\n confirmCancel: askBeforeCancel = true,\n urlSync = true,\n missingRequiredMessage,\n onValidationFailed,\n } = options;\n const stepsRef = useRef<WizardStepConfig[]>(steps);\n stepsRef.current = steps;\n // A hook, not a component, but it produces two sentences of its own — the\n // \"fill in the required fields\" toast and the fallback submit error — and both\n // reach the user. So it reads the provider like any component does; only the two\n // strings are depended on below, so the callbacks stay stable across renders.\n // The `??` is for the type only: both keys are optional on `WizardLabels` (see\n // there), and the merge never replaces a default with `undefined`.\n const wizardLabels = useKitLabels(\"wizard\", DEFAULT_WIZARD_LABELS, {\n missingRequired: missingRequiredMessage,\n });\n const missingRequired = wizardLabels.missingRequired ?? DEFAULT_WIZARD_LABELS.missingRequired;\n const genericError = wizardLabels.genericError ?? DEFAULT_WIZARD_LABELS.genericError;\n\n // Validators registered by the CURRENT step's mounted components (RHF steps\n // register via `useRhfWizardStep` through the wizard context). Only the active\n // step's validators are present — each unregisters on unmount. `goNext` and\n // `finish` run them (via `runStepValidators`) alongside the step config's own\n // `validate`.\n const validatorsRef = useRef(\n new Map<symbol, () => ValidateResult | Promise<ValidateResult>>(),\n );\n const registerStepValidate = useCallback(\n (fn: () => ValidateResult | Promise<ValidateResult>) => {\n const token = Symbol();\n validatorsRef.current.set(token, fn);\n return () => {\n validatorsRef.current.delete(token);\n };\n },\n [],\n );\n\n // Chosen once, at mount: a hook may not be called conditionally, but it may be\n // chosen once and then called unconditionally on every render — which is what\n // makes `urlSync: false` work with no router above the wizard at all.\n const [useStepUrl] = useState(() => (urlSync === false ? useNoStepUrl : useSearchParamStepUrl));\n const [urlParam] = useState(() =>\n typeof urlSync === \"object\" && urlSync.param ? urlSync.param : \"step\",\n );\n // The wizard's entered `data` and `completedSteps` are NOT persisted across a\n // reload / deep-link (the reducer re-inits to empty defaults on every mount).\n // Restoring a later step index from `?step=N` would therefore leave the wizard\n // on (or past) the last step with an EMPTY data set, letting Finish submit an\n // empty payload. Always start at step 0 so a reload re-runs the wizard from a\n // valid state instead of losing the entered data silently.\n const initialStep = 0;\n\n const reducer = createReducer<TData>(steps.length);\n const [state, dispatch] = useReducer(reducer, {\n currentStepIndex: initialStep,\n completedSteps: new Set<number>(),\n skippedSteps: new Set<number>(),\n data: (initialData ?? {}) as TData,\n isSubmitting: false,\n isValidating: false,\n error: null,\n fieldErrors: {},\n showCancelDialog: false,\n committedStepIndex: null,\n });\n\n // A mounted step can DISABLE forward navigation (Next/Skip) while its own\n // state is invalid (e.g. tenant shares != 100%). Defaults to false, so\n // wizards that never call setNextBlocked behave exactly as before.\n const [nextBlocked, setNextBlockedState] = useState(false);\n const setNextBlocked = useCallback(\n (blocked: boolean) => setNextBlockedState(blocked),\n [],\n );\n\n // Sync step index to URL (a no-op under `urlSync: false`). `clearStepUrl` drops\n // the param when the wizard is left through the kit, and stops the sync writing\n // it back.\n const clearStepUrl = useStepUrl(urlParam, state.currentStepIndex);\n\n // `steps`, not `stepsRef.current`: during render the two are the same array, and\n // reading a ref in render is what the hooks linter (rightly) objects to.\n const currentStep = steps[state.currentStepIndex];\n const isFirstStep = state.currentStepIndex === 0;\n const isLastStep = state.currentStepIndex === steps.length - 1;\n // The step whose forward button commits: the first one flagged `commits`, else the\n // last — which is every wizard written before the flag existed.\n const flagged = steps.findIndex((s) => s.commits);\n const commitStepIndex = flagged === -1 ? steps.length - 1 : flagged;\n const isCommitStep = state.currentStepIndex === commitStepIndex;\n const afterCommit = isAfterCommit(state);\n // Finish is only valid once every step before the committing one has actually\n // been walked through (completed or skipped). Guards against a restored/last-step\n // index submitting a payload built from empty defaults.\n const canFinish = steps.every(\n (_, index) =>\n index >= commitStepIndex ||\n state.completedSteps.has(index) ||\n state.skippedSteps.has(index),\n );\n const canGoBack =\n (!isFirstStep || onExit !== undefined) &&\n !crossesCommit(state, state.currentStepIndex - 1);\n const canCancel = cancellable && !afterCommit;\n const canDone = isLastStep && afterCommit && onDone !== undefined;\n\n // Runs the active step's config `validate` plus every validator its mounted\n // components registered (RHF steps via useRhfWizardStep), AND-combining the\n // results and merging any per-field errors. Returns `true` when the step\n // passes (or registered no validators). Shared by `goNext` and `finish` so a\n // form-bearing last step is validated (and its values flushed) on Finish too.\n const runStepValidators = useCallback(async (): Promise<boolean> => {\n const step = stepsRef.current[state.currentStepIndex];\n const validators = [...validatorsRef.current.values()];\n if (step.validate) validators.push(step.validate);\n if (validators.length === 0) return true;\n let ok = true;\n let errors: FieldErrors = {};\n try {\n for (const validate of validators) {\n const result = await validate();\n const vOk = typeof result === \"boolean\" ? result : result.ok;\n if (!vOk) {\n ok = false;\n if (typeof result !== \"boolean\" && result.errors) {\n errors = { ...errors, ...result.errors };\n }\n }\n }\n } catch (err) {\n // FALL THROUGH, do not return. A bare `return false` here blocked Next while\n // skipping the `if (!ok)` block below — the only thing that shows a message,\n // and whose own comment is \"say something anyway, so the user is not left with\n // a Next button that silently does nothing\". So a validator that threw (an\n // async one whose request rejected, a null dereference in a field rule)\n // produced exactly the outcome that block was written to prevent: no field\n // errors, no toast, `onValidationFailed` never called, nothing in the console.\n //\n // The step still must not advance; `ok = false` is what says so. Whatever\n // partial `errors` the loop had collected before the throw are kept — they are\n // real, and an empty set is what routes this to the generic message.\n console.error(\"[wizard] a step validator threw\", err);\n ok = false;\n }\n if (!ok) {\n dispatch({ type: \"SET_FIELD_ERRORS\", errors });\n if (Object.keys(errors).length === 0) {\n // No per-field detail (e.g. an RHF step surfacing its own inline\n // message) — say something anyway, so the user is not left with a Next\n // button that silently does nothing.\n const message = missingRequired;\n if (onValidationFailed) {\n onValidationFailed(message);\n } else {\n // sonner is an optional peer: imported here, on the failure path only,\n // so an app that never trips this never has to install it.\n const { toast } = await import(\"sonner\");\n toast.error(message);\n }\n }\n return false;\n }\n return true;\n }, [state.currentStepIndex, missingRequired, onValidationFailed]);\n\n /**\n * One forward move at a time. A double-click on Next used to validate the CURRENT\n * step twice and then advance twice — the second GO_NEXT landing on a step whose\n * own validators never ran, so a required field two steps in could be walked past.\n * Two layers, because either alone leaves a gap:\n *\n * - this ref refuses a second `goNext`/`skip`/`finish` while one is in flight,\n * which covers an async validator (a server check) of any length;\n * - the `from` on each action drops a move decided on a step that is no longer\n * current, which covers the click that arrives after the first move resolved\n * but before React re-rendered — its closure still holds the old step index.\n */\n const advancingRef = useRef(false);\n\n const goNext = useCallback(async () => {\n if (advancingRef.current) return;\n advancingRef.current = true;\n const from = state.currentStepIndex;\n // Pending state for the chrome (spinner, aria-busy). The ref above is still what\n // refuses the second click: state would only be seen after a re-render.\n dispatch({ type: \"SET_VALIDATING\", value: true });\n try {\n if (await runStepValidators()) {\n dispatch({ type: \"GO_NEXT\", from });\n }\n } finally {\n dispatch({ type: \"SET_VALIDATING\", value: false });\n advancingRef.current = false;\n }\n }, [runStepValidators, state.currentStepIndex]);\n\n // Read by `goBack`, which must not move while `onComplete` runs: the commit was\n // decided on the data of the step the user is on, and Back mid-commit would show a\n // step whose edits can no longer reach it. The chrome disables the button too; this\n // covers an app calling `goBack` itself.\n const submittingRef = useRef(false);\n\n const goBack = useCallback(() => {\n if (submittingRef.current) return;\n if (state.currentStepIndex === 0 && onExit) {\n clearStepUrl();\n onExit();\n return;\n }\n // On step 0 with no `onExit` this stays the no-op move (clearing field errors) it\n // always was.\n dispatch({ type: \"GO_BACK\" });\n }, [state.currentStepIndex, onExit, clearStepUrl]);\n\n const goToStep = useCallback((index: number) => {\n dispatch({ type: \"GO_TO_STEP\", index });\n }, []);\n\n const canGoToStep = useCallback(\n (index: number) =>\n index >= 0 &&\n index < steps.length &&\n (index <= state.currentStepIndex || state.completedSteps.has(index)) &&\n !crossesCommit(state, index),\n [steps.length, state],\n );\n\n const skip = useCallback(() => {\n // Not while Next is validating: the skip would land first and the pending GO_NEXT\n // would then be stale anyway — but the user asked for one move, not a race.\n if (advancingRef.current) return;\n dispatch({ type: \"SKIP\", from: state.currentStepIndex });\n }, [state.currentStepIndex]);\n\n const confirmCancel = useCallback(() => {\n dispatch({ type: \"SHOW_CANCEL_DIALOG\", show: false });\n clearStepUrl();\n onCancel?.();\n }, [onCancel, clearStepUrl]);\n\n const cancel = useCallback(() => {\n if (!askBeforeCancel) {\n confirmCancel();\n return;\n }\n dispatch({ type: \"SHOW_CANCEL_DIALOG\", show: true });\n }, [askBeforeCancel, confirmCancel]);\n\n const done = useCallback(() => {\n clearStepUrl();\n onDone?.();\n }, [onDone, clearStepUrl]);\n\n const dismissCancel = useCallback(() => {\n dispatch({ type: \"SHOW_CANCEL_DIALOG\", show: false });\n }, []);\n\n const updateData = useCallback((partial: Partial<TData>) => {\n dispatch({ type: \"UPDATE_DATA\", partial });\n }, []);\n\n const clearError = useCallback(() => {\n dispatch({ type: \"SET_ERROR\", error: null });\n }, []);\n\n const clearFieldErrors = useCallback(() => {\n dispatch({ type: \"SET_FIELD_ERRORS\", errors: {} });\n }, []);\n\n const finish = useCallback(async () => {\n if (!onComplete || !canFinish) return;\n // A commit with steps after it happens once: those steps exist because the\n // data has been written. (A last-step commit keeps its old behaviour — the app\n // may retry Finish after `onComplete` resolved, as it always could.)\n if (commitStepIndex < steps.length - 1 && state.committedStepIndex !== null) return;\n // The same re-entry guard as `goNext`: a double-click on Finish would otherwise\n // validate twice and call `onComplete` twice — `isSubmitting` is only set once\n // validation has passed, too late to stop the second click.\n if (advancingRef.current) return;\n advancingRef.current = true;\n const from = state.currentStepIndex;\n try {\n // Validate the (possibly form-bearing) committing step before submitting,\n // matching the Next gate — review-only steps register no validators and pass\n // through.\n dispatch({ type: \"SET_VALIDATING\", value: true });\n let valid: boolean;\n try {\n valid = await runStepValidators();\n } finally {\n dispatch({ type: \"SET_VALIDATING\", value: false });\n }\n if (!valid) return;\n submittingRef.current = true;\n dispatch({ type: \"SET_SUBMITTING\", value: true });\n dispatch({ type: \"SET_ERROR\", error: null });\n try {\n await onComplete(state.data);\n dispatch({ type: \"COMMITTED\", from });\n } catch (err) {\n const message = err instanceof Error && err.message ? err.message : genericError;\n dispatch({ type: \"SET_ERROR\", error: message });\n } finally {\n submittingRef.current = false;\n dispatch({ type: \"SET_SUBMITTING\", value: false });\n }\n } finally {\n advancingRef.current = false;\n }\n }, [\n onComplete,\n canFinish,\n commitStepIndex,\n steps.length,\n state.committedStepIndex,\n state.currentStepIndex,\n state.data,\n runStepValidators,\n genericError,\n ]);\n\n const stepStatus = useCallback(\n (index: number): StepStatus => {\n if (index === state.currentStepIndex) return \"active\";\n if (state.skippedSteps.has(index)) return \"skipped\";\n if (state.completedSteps.has(index)) return \"completed\";\n return \"upcoming\";\n },\n [state.currentStepIndex, state.completedSteps, state.skippedSteps],\n );\n\n return {\n currentStepIndex: state.currentStepIndex,\n currentStep,\n steps,\n isFirstStep,\n isLastStep,\n canFinish,\n commitStepIndex,\n isCommitStep,\n committed: state.committedStepIndex !== null,\n canGoBack,\n canCancel,\n canDone,\n canGoToStep,\n goNext,\n goBack,\n goToStep,\n skip,\n cancel,\n finish,\n done,\n registerStepValidate,\n nextBlocked,\n setNextBlocked,\n stepStatus,\n completedSteps: state.completedSteps,\n data: state.data,\n updateData,\n isSubmitting: state.isSubmitting,\n isValidating: state.isValidating,\n error: state.error,\n clearError,\n fieldErrors: state.fieldErrors,\n clearFieldErrors,\n showCancelDialog: state.showCancelDialog,\n confirmCancel,\n dismissCancel,\n };\n}\n\n/**\n * `urlSync: true` / `{ param }`: mirror the active step to `?<param>=N`, and hand back\n * the function that removes it again. Once removed it stays removed — the sync effect\n * re-runs whenever react-router hands out a new setter (every search change), and\n * without the latch it would write the param straight back after the exit cleared it.\n */\nfunction useSearchParamStepUrl(param: string, index: number): () => void {\n const [, setUrlParams] = useSearchParams();\n const leftRef = useRef(false);\n useEffect(() => {\n if (leftRef.current) return;\n setUrlParams((prev) => {\n prev.set(param, String(index));\n return prev;\n }, { replace: true });\n }, [param, index, setUrlParams]);\n return useCallback(() => {\n if (leftRef.current) return;\n leftRef.current = true;\n setUrlParams((prev) => {\n prev.delete(param);\n return prev;\n }, { replace: true });\n }, [param, setUrlParams]);\n}\n\n/** `urlSync: false`: the step lives in the reducer only. Touches no router context. */\nfunction useNoStepUrl(_param: string, _index: number): () => void {\n return noop;\n}\n\nfunction noop() {}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/wizard/use-wizard.ts"],"sourcesContent":["/**\n * The multi-step wizard state machine: step index, completed/skipped sets,\n * per-step async validators, collected data, field errors, submit and the\n * cancel-confirm gate.\n *\n * Domain-free, and router-free on request. By default it mirrors the active step\n * to `?step=N`, so it must be mounted inside a react-router context — the same\n * requirement `DataTable` already carries. `urlSync: false` keeps the step in\n * memory and needs no router; `urlSync: { param }` renames the param. It only ever\n * WRITES that param (see the note on `initialStep` below for why it deliberately\n * does not read it back), and removes it when the wizard is left through the kit\n * (see `UseWizardOptions.urlSync`).\n *\n * The committing step is the last one unless a step says `commits: true`; the\n * steps after that one are post-commit steps (see `WizardStepConfig.commits`).\n */\nimport { useCallback, useEffect, useReducer, useRef, useState } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { DEFAULT_WIZARD_LABELS } from \"./types\";\nimport { toast } from \"../components/toast\";\nimport type {\n FieldErrors,\n StepStatus,\n UseWizardOptions,\n UseWizardReturn,\n ValidateResult,\n WizardStepConfig,\n} from \"./types\";\n\ninterface WizardState<TData> {\n currentStepIndex: number;\n completedSteps: Set<number>;\n skippedSteps: Set<number>;\n data: TData;\n isSubmitting: boolean;\n isValidating: boolean;\n error: string | null;\n fieldErrors: FieldErrors;\n showCancelDialog: boolean;\n /** The step whose `onComplete` resolved, or null before any commit. */\n committedStepIndex: number | null;\n}\n\n/** Past the commit: the active step comes after the step that committed. Moves to\n * that step or any before it are refused from here. */\nfunction isAfterCommit(state: { currentStepIndex: number; committedStepIndex: number | null }) {\n return state.committedStepIndex !== null && state.currentStepIndex > state.committedStepIndex;\n}\n\n/** Whether `index` is on the far side of a commit the wizard has already moved past. */\nfunction crossesCommit(\n state: { currentStepIndex: number; committedStepIndex: number | null },\n index: number,\n) {\n return isAfterCommit(state) && index <= (state.committedStepIndex as number);\n}\n\ntype WizardAction<TData> =\n // `from` is the step the move was decided ON. A move whose step is no longer the\n // current one is stale — see `goNext` — and is dropped rather than applied relative\n // to wherever the wizard has got to since.\n | { type: \"GO_NEXT\"; from: number }\n | { type: \"GO_BACK\" }\n | { type: \"GO_TO_STEP\"; index: number }\n | { type: \"SKIP\"; from: number }\n | { type: \"UPDATE_DATA\"; partial: Partial<TData> }\n | { type: \"COMMITTED\"; from: number }\n | { type: \"SET_SUBMITTING\"; value: boolean }\n | { type: \"SET_VALIDATING\"; value: boolean }\n | { type: \"SET_ERROR\"; error: string | null }\n | { type: \"SET_FIELD_ERRORS\"; errors: FieldErrors }\n | { type: \"SHOW_CANCEL_DIALOG\"; show: boolean };\n\nfunction createReducer<TData>(stepCount: number) {\n return function reducer(\n state: WizardState<TData>,\n action: WizardAction<TData>,\n ): WizardState<TData> {\n switch (action.type) {\n case \"GO_NEXT\": {\n if (action.from !== state.currentStepIndex) return state;\n const completed = new Set(state.completedSteps);\n completed.add(state.currentStepIndex);\n const nextIndex = Math.min(state.currentStepIndex + 1, stepCount - 1);\n return { ...state, currentStepIndex: nextIndex, completedSteps: completed, fieldErrors: {} };\n }\n case \"GO_BACK\": {\n const prevIndex = Math.max(state.currentStepIndex - 1, 0);\n if (crossesCommit(state, prevIndex)) return state;\n return { ...state, currentStepIndex: prevIndex, fieldErrors: {} };\n }\n case \"GO_TO_STEP\": {\n if (action.index < 0 || action.index >= stepCount) return state;\n if (crossesCommit(state, action.index)) return state;\n if (!state.completedSteps.has(action.index) && action.index > state.currentStepIndex) {\n return state;\n }\n return { ...state, currentStepIndex: action.index };\n }\n case \"SKIP\": {\n if (action.from !== state.currentStepIndex) return state;\n const skipped = new Set(state.skippedSteps);\n skipped.add(state.currentStepIndex);\n const nextIndex = Math.min(state.currentStepIndex + 1, stepCount - 1);\n return { ...state, currentStepIndex: nextIndex, skippedSteps: skipped };\n }\n case \"UPDATE_DATA\": {\n // Clear error for any field that is now being edited (so typing makes\n // the red message disappear).\n const nextErrors = { ...state.fieldErrors };\n for (const key of Object.keys(action.partial)) {\n delete nextErrors[key];\n }\n return { ...state, data: { ...state.data, ...action.partial }, fieldErrors: nextErrors };\n }\n case \"COMMITTED\": {\n // Recorded whatever step committed, so `wizard.committed` reads true after any\n // successful Finish. Only a commit with steps after it MOVES: the default\n // last-step commit stays where it is and looks exactly as it did before 0.8.\n const committed = { ...state, committedStepIndex: action.from };\n if (action.from !== state.currentStepIndex || action.from >= stepCount - 1) {\n return committed;\n }\n const completed = new Set(state.completedSteps);\n completed.add(action.from);\n return {\n ...committed,\n currentStepIndex: action.from + 1,\n completedSteps: completed,\n fieldErrors: {},\n };\n }\n case \"SET_SUBMITTING\":\n return { ...state, isSubmitting: action.value };\n case \"SET_VALIDATING\":\n return { ...state, isValidating: action.value };\n case \"SET_ERROR\":\n return { ...state, error: action.error };\n case \"SET_FIELD_ERRORS\":\n return { ...state, fieldErrors: action.errors };\n case \"SHOW_CANCEL_DIALOG\":\n return { ...state, showCancelDialog: action.show };\n default:\n return state;\n }\n };\n}\n\nexport function useWizard<TData extends Record<string, unknown>>(\n options: UseWizardOptions<TData>,\n): UseWizardReturn<TData> {\n const {\n steps,\n initialData,\n onComplete,\n onCancel,\n onExit,\n onDone,\n cancellable = true,\n confirmCancel: askBeforeCancel = true,\n urlSync = true,\n missingRequiredMessage,\n onValidationFailed,\n } = options;\n const stepsRef = useRef<WizardStepConfig[]>(steps);\n stepsRef.current = steps;\n // A hook, not a component, but it produces two sentences of its own — the\n // \"fill in the required fields\" toast and the fallback submit error — and both\n // reach the user. So it reads the provider like any component does; only the two\n // strings are depended on below, so the callbacks stay stable across renders.\n // The `??` is for the type only: both keys are optional on `WizardLabels` (see\n // there), and the merge never replaces a default with `undefined`.\n const wizardLabels = useKitLabels(\"wizard\", DEFAULT_WIZARD_LABELS, {\n missingRequired: missingRequiredMessage,\n });\n const missingRequired = wizardLabels.missingRequired ?? DEFAULT_WIZARD_LABELS.missingRequired;\n const genericError = wizardLabels.genericError ?? DEFAULT_WIZARD_LABELS.genericError;\n\n // Validators registered by the CURRENT step's mounted components (RHF steps\n // register via `useRhfWizardStep` through the wizard context). Only the active\n // step's validators are present — each unregisters on unmount. `goNext` and\n // `finish` run them (via `runStepValidators`) alongside the step config's own\n // `validate`.\n const validatorsRef = useRef(\n new Map<symbol, () => ValidateResult | Promise<ValidateResult>>(),\n );\n const registerStepValidate = useCallback(\n (fn: () => ValidateResult | Promise<ValidateResult>) => {\n const token = Symbol();\n validatorsRef.current.set(token, fn);\n return () => {\n validatorsRef.current.delete(token);\n };\n },\n [],\n );\n\n // Chosen once, at mount: a hook may not be called conditionally, but it may be\n // chosen once and then called unconditionally on every render — which is what\n // makes `urlSync: false` work with no router above the wizard at all.\n const [useStepUrl] = useState(() => (urlSync === false ? useNoStepUrl : useSearchParamStepUrl));\n const [urlParam] = useState(() =>\n typeof urlSync === \"object\" && urlSync.param ? urlSync.param : \"step\",\n );\n // The wizard's entered `data` and `completedSteps` are NOT persisted across a\n // reload / deep-link (the reducer re-inits to empty defaults on every mount).\n // Restoring a later step index from `?step=N` would therefore leave the wizard\n // on (or past) the last step with an EMPTY data set, letting Finish submit an\n // empty payload. Always start at step 0 so a reload re-runs the wizard from a\n // valid state instead of losing the entered data silently.\n const initialStep = 0;\n\n const reducer = createReducer<TData>(steps.length);\n const [state, dispatch] = useReducer(reducer, {\n currentStepIndex: initialStep,\n completedSteps: new Set<number>(),\n skippedSteps: new Set<number>(),\n data: (initialData ?? {}) as TData,\n isSubmitting: false,\n isValidating: false,\n error: null,\n fieldErrors: {},\n showCancelDialog: false,\n committedStepIndex: null,\n });\n\n // A mounted step can DISABLE forward navigation (Next/Skip) while its own\n // state is invalid (e.g. tenant shares != 100%). Defaults to false, so\n // wizards that never call setNextBlocked behave exactly as before.\n const [nextBlocked, setNextBlockedState] = useState(false);\n const setNextBlocked = useCallback(\n (blocked: boolean) => setNextBlockedState(blocked),\n [],\n );\n\n // Sync step index to URL (a no-op under `urlSync: false`). `clearStepUrl` drops\n // the param when the wizard is left through the kit, and stops the sync writing\n // it back.\n const clearStepUrl = useStepUrl(urlParam, state.currentStepIndex);\n\n // `steps`, not `stepsRef.current`: during render the two are the same array, and\n // reading a ref in render is what the hooks linter (rightly) objects to.\n const currentStep = steps[state.currentStepIndex];\n const isFirstStep = state.currentStepIndex === 0;\n const isLastStep = state.currentStepIndex === steps.length - 1;\n // The step whose forward button commits: the first one flagged `commits`, else the\n // last — which is every wizard written before the flag existed.\n const flagged = steps.findIndex((s) => s.commits);\n const commitStepIndex = flagged === -1 ? steps.length - 1 : flagged;\n const isCommitStep = state.currentStepIndex === commitStepIndex;\n const afterCommit = isAfterCommit(state);\n // Finish is only valid once every step before the committing one has actually\n // been walked through (completed or skipped). Guards against a restored/last-step\n // index submitting a payload built from empty defaults.\n const canFinish = steps.every(\n (_, index) =>\n index >= commitStepIndex ||\n state.completedSteps.has(index) ||\n state.skippedSteps.has(index),\n );\n const canGoBack =\n (!isFirstStep || onExit !== undefined) &&\n !crossesCommit(state, state.currentStepIndex - 1);\n const canCancel = cancellable && !afterCommit;\n const canDone = isLastStep && afterCommit && onDone !== undefined;\n\n // Runs the active step's config `validate` plus every validator its mounted\n // components registered (RHF steps via useRhfWizardStep), AND-combining the\n // results and merging any per-field errors. Returns `true` when the step\n // passes (or registered no validators). Shared by `goNext` and `finish` so a\n // form-bearing last step is validated (and its values flushed) on Finish too.\n const runStepValidators = useCallback(async (): Promise<boolean> => {\n const step = stepsRef.current[state.currentStepIndex];\n const validators = [...validatorsRef.current.values()];\n if (step.validate) validators.push(step.validate);\n if (validators.length === 0) return true;\n let ok = true;\n let errors: FieldErrors = {};\n try {\n for (const validate of validators) {\n const result = await validate();\n const vOk = typeof result === \"boolean\" ? result : result.ok;\n if (!vOk) {\n ok = false;\n if (typeof result !== \"boolean\" && result.errors) {\n errors = { ...errors, ...result.errors };\n }\n }\n }\n } catch (err) {\n // FALL THROUGH, do not return. A bare `return false` here blocked Next while\n // skipping the `if (!ok)` block below — the only thing that shows a message,\n // and whose own comment is \"say something anyway, so the user is not left with\n // a Next button that silently does nothing\". So a validator that threw (an\n // async one whose request rejected, a null dereference in a field rule)\n // produced exactly the outcome that block was written to prevent: no field\n // errors, no toast, `onValidationFailed` never called, nothing in the console.\n //\n // The step still must not advance; `ok = false` is what says so. Whatever\n // partial `errors` the loop had collected before the throw are kept — they are\n // real, and an empty set is what routes this to the generic message.\n console.error(\"[wizard] a step validator threw\", err);\n ok = false;\n }\n if (!ok) {\n dispatch({ type: \"SET_FIELD_ERRORS\", errors });\n if (Object.keys(errors).length === 0) {\n // No per-field detail (e.g. an RHF step surfacing its own inline\n // message) — say something anyway, so the user is not left with a Next\n // button that silently does nothing.\n const message = missingRequired;\n if (onValidationFailed) {\n onValidationFailed(message);\n } else {\n // The kit's `toast` loads sonner (an optional peer) lazily, so an app\n // that never trips this never has to install it.\n toast.error(message);\n }\n }\n return false;\n }\n return true;\n }, [state.currentStepIndex, missingRequired, onValidationFailed]);\n\n /**\n * One forward move at a time. A double-click on Next used to validate the CURRENT\n * step twice and then advance twice — the second GO_NEXT landing on a step whose\n * own validators never ran, so a required field two steps in could be walked past.\n * Two layers, because either alone leaves a gap:\n *\n * - this ref refuses a second `goNext`/`skip`/`finish` while one is in flight,\n * which covers an async validator (a server check) of any length;\n * - the `from` on each action drops a move decided on a step that is no longer\n * current, which covers the click that arrives after the first move resolved\n * but before React re-rendered — its closure still holds the old step index.\n */\n const advancingRef = useRef(false);\n\n const goNext = useCallback(async () => {\n if (advancingRef.current) return;\n advancingRef.current = true;\n const from = state.currentStepIndex;\n // Pending state for the chrome (spinner, aria-busy). The ref above is still what\n // refuses the second click: state would only be seen after a re-render.\n dispatch({ type: \"SET_VALIDATING\", value: true });\n try {\n if (await runStepValidators()) {\n dispatch({ type: \"GO_NEXT\", from });\n }\n } finally {\n dispatch({ type: \"SET_VALIDATING\", value: false });\n advancingRef.current = false;\n }\n }, [runStepValidators, state.currentStepIndex]);\n\n // Read by `goBack`, which must not move while `onComplete` runs: the commit was\n // decided on the data of the step the user is on, and Back mid-commit would show a\n // step whose edits can no longer reach it. The chrome disables the button too; this\n // covers an app calling `goBack` itself.\n const submittingRef = useRef(false);\n\n const goBack = useCallback(() => {\n if (submittingRef.current) return;\n if (state.currentStepIndex === 0 && onExit) {\n clearStepUrl();\n onExit();\n return;\n }\n // On step 0 with no `onExit` this stays the no-op move (clearing field errors) it\n // always was.\n dispatch({ type: \"GO_BACK\" });\n }, [state.currentStepIndex, onExit, clearStepUrl]);\n\n const goToStep = useCallback((index: number) => {\n dispatch({ type: \"GO_TO_STEP\", index });\n }, []);\n\n const canGoToStep = useCallback(\n (index: number) =>\n index >= 0 &&\n index < steps.length &&\n (index <= state.currentStepIndex || state.completedSteps.has(index)) &&\n !crossesCommit(state, index),\n [steps.length, state],\n );\n\n const skip = useCallback(() => {\n // Not while Next is validating: the skip would land first and the pending GO_NEXT\n // would then be stale anyway — but the user asked for one move, not a race.\n if (advancingRef.current) return;\n dispatch({ type: \"SKIP\", from: state.currentStepIndex });\n }, [state.currentStepIndex]);\n\n const confirmCancel = useCallback(() => {\n dispatch({ type: \"SHOW_CANCEL_DIALOG\", show: false });\n clearStepUrl();\n onCancel?.();\n }, [onCancel, clearStepUrl]);\n\n const cancel = useCallback(() => {\n if (!askBeforeCancel) {\n confirmCancel();\n return;\n }\n dispatch({ type: \"SHOW_CANCEL_DIALOG\", show: true });\n }, [askBeforeCancel, confirmCancel]);\n\n const done = useCallback(() => {\n clearStepUrl();\n onDone?.();\n }, [onDone, clearStepUrl]);\n\n const dismissCancel = useCallback(() => {\n dispatch({ type: \"SHOW_CANCEL_DIALOG\", show: false });\n }, []);\n\n const updateData = useCallback((partial: Partial<TData>) => {\n dispatch({ type: \"UPDATE_DATA\", partial });\n }, []);\n\n const clearError = useCallback(() => {\n dispatch({ type: \"SET_ERROR\", error: null });\n }, []);\n\n const clearFieldErrors = useCallback(() => {\n dispatch({ type: \"SET_FIELD_ERRORS\", errors: {} });\n }, []);\n\n const finish = useCallback(async () => {\n if (!onComplete || !canFinish) return;\n // A commit with steps after it happens once: those steps exist because the\n // data has been written. (A last-step commit keeps its old behaviour — the app\n // may retry Finish after `onComplete` resolved, as it always could.)\n if (commitStepIndex < steps.length - 1 && state.committedStepIndex !== null) return;\n // The same re-entry guard as `goNext`: a double-click on Finish would otherwise\n // validate twice and call `onComplete` twice — `isSubmitting` is only set once\n // validation has passed, too late to stop the second click.\n if (advancingRef.current) return;\n advancingRef.current = true;\n const from = state.currentStepIndex;\n try {\n // Validate the (possibly form-bearing) committing step before submitting,\n // matching the Next gate — review-only steps register no validators and pass\n // through.\n dispatch({ type: \"SET_VALIDATING\", value: true });\n let valid: boolean;\n try {\n valid = await runStepValidators();\n } finally {\n dispatch({ type: \"SET_VALIDATING\", value: false });\n }\n if (!valid) return;\n submittingRef.current = true;\n dispatch({ type: \"SET_SUBMITTING\", value: true });\n dispatch({ type: \"SET_ERROR\", error: null });\n try {\n await onComplete(state.data);\n dispatch({ type: \"COMMITTED\", from });\n } catch (err) {\n const message = err instanceof Error && err.message ? err.message : genericError;\n dispatch({ type: \"SET_ERROR\", error: message });\n } finally {\n submittingRef.current = false;\n dispatch({ type: \"SET_SUBMITTING\", value: false });\n }\n } finally {\n advancingRef.current = false;\n }\n }, [\n onComplete,\n canFinish,\n commitStepIndex,\n steps.length,\n state.committedStepIndex,\n state.currentStepIndex,\n state.data,\n runStepValidators,\n genericError,\n ]);\n\n const stepStatus = useCallback(\n (index: number): StepStatus => {\n if (index === state.currentStepIndex) return \"active\";\n if (state.skippedSteps.has(index)) return \"skipped\";\n if (state.completedSteps.has(index)) return \"completed\";\n return \"upcoming\";\n },\n [state.currentStepIndex, state.completedSteps, state.skippedSteps],\n );\n\n return {\n currentStepIndex: state.currentStepIndex,\n currentStep,\n steps,\n isFirstStep,\n isLastStep,\n canFinish,\n commitStepIndex,\n isCommitStep,\n committed: state.committedStepIndex !== null,\n canGoBack,\n canCancel,\n canDone,\n canGoToStep,\n goNext,\n goBack,\n goToStep,\n skip,\n cancel,\n finish,\n done,\n registerStepValidate,\n nextBlocked,\n setNextBlocked,\n stepStatus,\n completedSteps: state.completedSteps,\n data: state.data,\n updateData,\n isSubmitting: state.isSubmitting,\n isValidating: state.isValidating,\n error: state.error,\n clearError,\n fieldErrors: state.fieldErrors,\n clearFieldErrors,\n showCancelDialog: state.showCancelDialog,\n confirmCancel,\n dismissCancel,\n };\n}\n\n/**\n * `urlSync: true` / `{ param }`: mirror the active step to `?<param>=N`, and hand back\n * the function that removes it again. Once removed it stays removed — the sync effect\n * re-runs whenever react-router hands out a new setter (every search change), and\n * without the latch it would write the param straight back after the exit cleared it.\n */\nfunction useSearchParamStepUrl(param: string, index: number): () => void {\n const [, setUrlParams] = useSearchParams();\n const leftRef = useRef(false);\n useEffect(() => {\n if (leftRef.current) return;\n setUrlParams((prev) => {\n prev.set(param, String(index));\n return prev;\n }, { replace: true });\n }, [param, index, setUrlParams]);\n return useCallback(() => {\n if (leftRef.current) return;\n leftRef.current = true;\n setUrlParams((prev) => {\n prev.delete(param);\n return prev;\n }, { replace: true });\n }, [param, setUrlParams]);\n}\n\n/** `urlSync: false`: the step lives in the reducer only. Touches no router context. */\nfunction useNoStepUrl(_param: string, _index: number): () => void {\n return noop;\n}\n\nfunction noop() {}\n"],"mappings":"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","names":[]}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Pencil } from "lucide-react";
|
|
4
4
|
import { Card, CardAction, CardContent, CardHeader, CardTitle, IconButton } from "../components/ui.js";
|
|
5
|
+
import { Tooltip } from "../components/tooltip.js";
|
|
5
6
|
import { DEFAULT_WIZARD_LABELS } from "./types.js";
|
|
6
7
|
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
7
8
|
function WizardSummary({
|
|
@@ -16,18 +17,17 @@ function WizardSummary({
|
|
|
16
17
|
sections.map((section, sIdx) => /* @__PURE__ */ jsxs(Card, { children: [
|
|
17
18
|
/* @__PURE__ */ jsxs(CardHeader, { className: "pb-2", children: [
|
|
18
19
|
/* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: section.label }),
|
|
19
|
-
section.stepIndex !== void 0 && onEditStep && /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(
|
|
20
|
+
section.stepIndex !== void 0 && onEditStep && /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Tooltip, { label: l.edit, children: /* @__PURE__ */ jsx(
|
|
20
21
|
IconButton,
|
|
21
22
|
{
|
|
22
23
|
size: "sm",
|
|
23
24
|
variant: "ghost",
|
|
24
25
|
"aria-label": l.edit,
|
|
25
|
-
title: l.edit,
|
|
26
26
|
disabled,
|
|
27
27
|
onClick: () => onEditStep(section.stepIndex),
|
|
28
28
|
children: /* @__PURE__ */ jsx(Pencil, {})
|
|
29
29
|
}
|
|
30
|
-
) })
|
|
30
|
+
) }) })
|
|
31
31
|
] }),
|
|
32
32
|
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("dl", { className: "grid gap-2 text-sm", children: section.items.map((item, iIdx) => /* @__PURE__ */ jsxs("div", { children: [
|
|
33
33
|
iIdx > 0 && /* @__PURE__ */ jsx("div", { className: "mb-2 border-t border-[var(--border)]", role: "presentation" }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/wizard/wizard-summary.tsx"],"sourcesContent":["import { Pencil } from \"lucide-react\";\nimport { Card, CardAction, CardContent, CardHeader, CardTitle, IconButton } from \"../components/ui\";\nimport type { SummarySection, WizardLabels } from \"./types\";\nimport { DEFAULT_WIZARD_LABELS } from \"./types\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\n\n/**\n * The review step: one card per section, each row a label/value pair, each card\n * carrying an edit button back to the step it came from.\n *\n * Sections are data, not children, because \"jump back to step N\" is the whole\n * point of the component and a caller assembling its own cards invariably drops\n * it. Labels and values are `ReactNode`, so a value can be a badge or a link.\n *\n * A section with no `stepIndex` (file-level counts, a total) is a card without an\n * edit button. `disabled` greys every edit button at once — while the commit runs,\n * when jumping back would show a step whose edits can no longer reach the request.\n */\nexport function WizardSummary({\n sections,\n onEditStep,\n disabled = false,\n labels,\n}: {\n sections: SummarySection[];\n /** Called with the section's `stepIndex`. Optional only for a summary whose\n * sections name no step at all. */\n onEditStep?: (stepIndex: number) => void;\n /** Disable every edit button (e.g. `wizard.isSubmitting`). */\n disabled?: boolean;\n labels?: Partial<WizardLabels>;\n}) {\n const l = useKitLabels(\"wizard\", DEFAULT_WIZARD_LABELS, labels);\n\n return (\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold text-[var(--text-primary)]\">{l.reviewTitle}</h2>\n {sections.map((section, sIdx) => (\n <Card key={sIdx}>\n {/* CardHeader is a grid, and CardAction is the top-right column it\n reserves — the edit button has to go through it or it stacks under\n the title instead of sitting beside it. */}\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-base\">{section.label}</CardTitle>\n {section.stepIndex !== undefined && onEditStep && (\n <CardAction>\n <IconButton\n
|
|
1
|
+
{"version":3,"sources":["../../src/wizard/wizard-summary.tsx"],"sourcesContent":["import { Pencil } from \"lucide-react\";\nimport { Card, CardAction, CardContent, CardHeader, CardTitle, IconButton } from \"../components/ui\";\nimport { Tooltip } from \"../components/tooltip\";\nimport type { SummarySection, WizardLabels } from \"./types\";\nimport { DEFAULT_WIZARD_LABELS } from \"./types\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\n\n/**\n * The review step: one card per section, each row a label/value pair, each card\n * carrying an edit button back to the step it came from.\n *\n * Sections are data, not children, because \"jump back to step N\" is the whole\n * point of the component and a caller assembling its own cards invariably drops\n * it. Labels and values are `ReactNode`, so a value can be a badge or a link.\n *\n * A section with no `stepIndex` (file-level counts, a total) is a card without an\n * edit button. `disabled` greys every edit button at once — while the commit runs,\n * when jumping back would show a step whose edits can no longer reach the request.\n */\nexport function WizardSummary({\n sections,\n onEditStep,\n disabled = false,\n labels,\n}: {\n sections: SummarySection[];\n /** Called with the section's `stepIndex`. Optional only for a summary whose\n * sections name no step at all. */\n onEditStep?: (stepIndex: number) => void;\n /** Disable every edit button (e.g. `wizard.isSubmitting`). */\n disabled?: boolean;\n labels?: Partial<WizardLabels>;\n}) {\n const l = useKitLabels(\"wizard\", DEFAULT_WIZARD_LABELS, labels);\n\n return (\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold text-[var(--text-primary)]\">{l.reviewTitle}</h2>\n {sections.map((section, sIdx) => (\n <Card key={sIdx}>\n {/* CardHeader is a grid, and CardAction is the top-right column it\n reserves — the edit button has to go through it or it stacks under\n the title instead of sitting beside it. */}\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-base\">{section.label}</CardTitle>\n {section.stepIndex !== undefined && onEditStep && (\n <CardAction>\n {/* The kit Tooltip, not a native `title`: it shows on keyboard focus too,\n and an app with a one-tooltip rule (keksdose dev#523) sees one. */}\n <Tooltip label={l.edit}>\n <IconButton\n size=\"sm\"\n variant=\"ghost\"\n aria-label={l.edit}\n disabled={disabled}\n onClick={() => onEditStep(section.stepIndex as number)}\n >\n <Pencil />\n </IconButton>\n </Tooltip>\n </CardAction>\n )}\n </CardHeader>\n <CardContent>\n <dl className=\"grid gap-2 text-sm\">\n {section.items.map((item, iIdx) => (\n <div key={iIdx}>\n {iIdx > 0 && (\n <div className=\"mb-2 border-t border-[var(--border)]\" role=\"presentation\" />\n )}\n <div className=\"flex items-start justify-between gap-4\">\n <dt className=\"text-[var(--text-muted)]\">{item.label}</dt>\n <dd className=\"text-end font-medium text-[var(--text-primary)]\">\n {item.value}\n </dd>\n </div>\n </div>\n ))}\n </dl>\n </CardContent>\n </Card>\n ))}\n </div>\n );\n}\n"],"mappings":";AAqCM,cAMI,YANJ;AArCN,SAAS,cAAc;AACvB,SAAS,MAAM,YAAY,aAAa,YAAY,WAAW,kBAAkB;AACjF,SAAS,eAAe;AAExB,SAAS,6BAA6B;AACtC,SAAS,oBAAoB;AActB,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AACF,GAQG;AACD,QAAM,IAAI,aAAa,UAAU,uBAAuB,MAAM;AAE9D,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,wBAAC,QAAG,WAAU,oDAAoD,YAAE,aAAY;AAAA,IAC/E,SAAS,IAAI,CAAC,SAAS,SACtB,qBAAC,QAIC;AAAA,2BAAC,cAAW,WAAU,QACpB;AAAA,4BAAC,aAAU,WAAU,aAAa,kBAAQ,OAAM;AAAA,QAC/C,QAAQ,cAAc,UAAa,cAClC,oBAAC,cAGC,8BAAC,WAAQ,OAAO,EAAE,MAChB;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,cAAY,EAAE;AAAA,YACd;AAAA,YACA,SAAS,MAAM,WAAW,QAAQ,SAAmB;AAAA,YAErD,8BAAC,UAAO;AAAA;AAAA,QACV,GACF,GACF;AAAA,SAEJ;AAAA,MACA,oBAAC,eACC,8BAAC,QAAG,WAAU,sBACX,kBAAQ,MAAM,IAAI,CAAC,MAAM,SACxB,qBAAC,SACE;AAAA,eAAO,KACN,oBAAC,SAAI,WAAU,wCAAuC,MAAK,gBAAe;AAAA,QAE5E,qBAAC,SAAI,WAAU,0CACb;AAAA,8BAAC,QAAG,WAAU,4BAA4B,eAAK,OAAM;AAAA,UACrD,oBAAC,QAAG,WAAU,mDACX,eAAK,OACR;AAAA,WACF;AAAA,WATQ,IAUV,CACD,GACH,GACF;AAAA,SAxCS,IAyCX,CACD;AAAA,KACH;AAEJ;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eifi1/ui-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "App-agnostic shared design system: domain-free React components, theme and tokens.",
|
|
6
6
|
"_sideEffects": "The barrel (src/index.ts) re-exports EVERY component, including components/chart, which statically imports recharts. Without this field a bundler must assume each of those modules has side effects, so it cannot drop the unused ones: importing a Button from '@eifi1/ui-kit' dragged all 433KB of recharts into the consumer's ENTRY chunk, and Keksdose was preloading it on every page load, login included. Nothing in src/ imports for side effects (no bare imports, no CSS imports from JS, no module-level global writes) — the token stylesheet is its own './tokens.css' export — so this is safe. CSS is still listed so any future stylesheet survives. Re-checked when `flag-icons` left `dependencies` and tsup started emitting a `\"use client\"` banner: neither touches this. The flag stylesheet was never imported from JS in the first place (that was the finding), and a directive prologue is a string literal every bundler lifts out before it decides whether a module is inert.",
|
|
@@ -89,7 +89,8 @@
|
|
|
89
89
|
"prepublishOnly": "npm run build",
|
|
90
90
|
"release": "commit-and-tag-version",
|
|
91
91
|
"release:dry": "commit-and-tag-version --dry-run",
|
|
92
|
-
"prepare": "husky || true"
|
|
92
|
+
"prepare": "husky || true",
|
|
93
|
+
"deps:outdated": "npm outdated || true"
|
|
93
94
|
},
|
|
94
95
|
"peerDependencies": {
|
|
95
96
|
"lucide-react": ">=0.400.0 <2",
|
|
@@ -115,18 +116,18 @@
|
|
|
115
116
|
"zustand": "^5.0.5"
|
|
116
117
|
},
|
|
117
118
|
"devDependencies": {
|
|
118
|
-
"@eslint/js": "^
|
|
119
|
+
"@eslint/js": "^10.0.1",
|
|
119
120
|
"@tailwindcss/vite": "^4.3.3",
|
|
120
|
-
"@testing-library/jest-dom": "^
|
|
121
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
121
122
|
"@testing-library/react": "^16.3.0",
|
|
122
123
|
"@testing-library/user-event": "^14.6.7",
|
|
123
124
|
"@types/node": "^24.13.6",
|
|
124
125
|
"@types/react": "^19.2.14",
|
|
125
126
|
"@types/react-dom": "^19.2.3",
|
|
126
|
-
"@vitejs/plugin-react": "^
|
|
127
|
-
"@vitest/coverage-v8": "^
|
|
127
|
+
"@vitejs/plugin-react": "^6.1.1",
|
|
128
|
+
"@vitest/coverage-v8": "^5.0.2",
|
|
128
129
|
"commit-and-tag-version": "^13.2.1",
|
|
129
|
-
"eslint": "^
|
|
130
|
+
"eslint": "^10.11.0",
|
|
130
131
|
"eslint-import-resolver-typescript": "^4.4.5",
|
|
131
132
|
"eslint-plugin-import-x": "^4.17.1",
|
|
132
133
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
@@ -135,7 +136,7 @@
|
|
|
135
136
|
"globals": "^17.12.0",
|
|
136
137
|
"husky": "^9.1.7",
|
|
137
138
|
"jsdom": "^30.0.1",
|
|
138
|
-
"lucide-react": "^
|
|
139
|
+
"lucide-react": "^1.47.0",
|
|
139
140
|
"react-hook-form": "^7.88.0",
|
|
140
141
|
"react-router": "^8.3.0",
|
|
141
142
|
"recharts": "^3.8.1",
|
|
@@ -144,8 +145,8 @@
|
|
|
144
145
|
"tsup": "^8.5.0",
|
|
145
146
|
"typescript": "~5.9.3",
|
|
146
147
|
"typescript-eslint": "^8.70.1",
|
|
147
|
-
"vite": "^
|
|
148
|
-
"vitest": "^
|
|
148
|
+
"vite": "^8.3.1",
|
|
149
|
+
"vitest": "^5.0.2"
|
|
149
150
|
},
|
|
150
151
|
"license": "MIT",
|
|
151
152
|
"repository": {
|
|
@@ -165,8 +166,11 @@
|
|
|
165
166
|
"engines": {
|
|
166
167
|
"node": ">=20.18"
|
|
167
168
|
},
|
|
168
|
-
"_overrides": "esbuild is forced to ^0.28.2 for GHSA-g7r4-m6w7-qqqr (dev-server file read on Windows, fixed in 0.28.1). vite already allows ^0.28; tsup 8.5.1 still pins ^0.27, and `npm run check` (build + package verification) proves it works on 0.28. Drop this once tsup allows 0.28 itself.",
|
|
169
|
+
"_overrides": "esbuild is forced to ^0.28.2 for GHSA-g7r4-m6w7-qqqr (dev-server file read on Windows, fixed in 0.28.1). vite already allows ^0.28; tsup 8.5.1 still pins ^0.27, and `npm run check` (build + package verification) proves it works on 0.28. Drop this once tsup allows 0.28 itself. eslint-plugin-jsx-a11y 6.10.2 (its latest) declares eslint peers up to ^9; it runs unchanged on eslint 10 — every a11y rule still fires, same counts — so its eslint peer is pointed at ours. Drop that entry once jsx-a11y declares ^10.",
|
|
169
170
|
"overrides": {
|
|
170
|
-
"esbuild": "^0.28.2"
|
|
171
|
+
"esbuild": "^0.28.2",
|
|
172
|
+
"eslint-plugin-jsx-a11y": {
|
|
173
|
+
"eslint": "$eslint"
|
|
174
|
+
}
|
|
171
175
|
}
|
|
172
176
|
}
|