torch-glare 2.5.0 → 2.5.2

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.
Files changed (79) hide show
  1. package/apps/lib/components/DataViews/filters/filters.tsx +7 -11
  2. package/apps/lib/components/FormBuilder/context.ts +8 -25
  3. package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +1 -1
  4. package/apps/lib/components/FormBuilder/form-builder.tsx +32 -172
  5. package/apps/lib/components/FormBuilder/index.ts +0 -4
  6. package/apps/lib/components/FormBuilder/types.ts +6 -16
  7. package/apps/lib/components/FormRenderer/FormDrawer.tsx +2 -2
  8. package/apps/lib/components/FormRenderer/detail.tsx +6 -6
  9. package/apps/lib/components/FormRenderer/form-renderer.tsx +155 -35
  10. package/apps/lib/components/{FormBuilder → FormRenderer}/header.tsx +12 -25
  11. package/apps/lib/components/FormRenderer/index.ts +4 -0
  12. package/apps/lib/components/FormRenderer/section.tsx +39 -0
  13. package/apps/lib/components/{FormBuilder → FormRenderer}/stepper.tsx +47 -19
  14. package/apps/lib/components/FormRenderer/types.ts +6 -6
  15. package/apps/lib/registry.json +62 -3
  16. package/apps/lib/tsconfig.tsbuildinfo +1 -1
  17. package/dist/bin/index.js +8 -4
  18. package/dist/bin/index.js.map +1 -1
  19. package/dist/src/commands/add.d.ts +3 -2
  20. package/dist/src/commands/add.d.ts.map +1 -1
  21. package/dist/src/commands/add.js +27 -35
  22. package/dist/src/commands/add.js.map +1 -1
  23. package/dist/src/commands/hook.d.ts.map +1 -1
  24. package/dist/src/commands/hook.js +23 -13
  25. package/dist/src/commands/hook.js.map +1 -1
  26. package/dist/src/commands/init.d.ts.map +1 -1
  27. package/dist/src/commands/init.js +6 -2
  28. package/dist/src/commands/init.js.map +1 -1
  29. package/dist/src/commands/layout.d.ts.map +1 -1
  30. package/dist/src/commands/layout.js +23 -13
  31. package/dist/src/commands/layout.js.map +1 -1
  32. package/dist/src/commands/provider.d.ts.map +1 -1
  33. package/dist/src/commands/provider.js +22 -12
  34. package/dist/src/commands/provider.js.map +1 -1
  35. package/dist/src/commands/utils.d.ts.map +1 -1
  36. package/dist/src/commands/utils.js +21 -28
  37. package/dist/src/commands/utils.js.map +1 -1
  38. package/dist/src/shared/copyComponentsRecursively.d.ts +5 -3
  39. package/dist/src/shared/copyComponentsRecursively.d.ts.map +1 -1
  40. package/dist/src/shared/copyComponentsRecursively.js +5 -6
  41. package/dist/src/shared/copyComponentsRecursively.js.map +1 -1
  42. package/dist/src/shared/getDependenciesAndInstallNestedComponents.js +1 -1
  43. package/dist/src/shared/getDependenciesAndInstallNestedComponents.js.map +1 -1
  44. package/dist/src/shared/installDependencies.d.ts +16 -1
  45. package/dist/src/shared/installDependencies.d.ts.map +1 -1
  46. package/dist/src/shared/installDependencies.js +41 -21
  47. package/dist/src/shared/installDependencies.js.map +1 -1
  48. package/dist/src/shared/installFromPlan.d.ts +23 -0
  49. package/dist/src/shared/installFromPlan.d.ts.map +1 -0
  50. package/dist/src/shared/installFromPlan.js +77 -0
  51. package/dist/src/shared/installFromPlan.js.map +1 -0
  52. package/dist/src/shared/resolveEntry.d.ts +21 -0
  53. package/dist/src/shared/resolveEntry.d.ts.map +1 -0
  54. package/dist/src/shared/resolveEntry.js +54 -0
  55. package/dist/src/shared/resolveEntry.js.map +1 -0
  56. package/dist/src/shared/suggestOtherCommand.d.ts +8 -0
  57. package/dist/src/shared/suggestOtherCommand.d.ts.map +1 -0
  58. package/dist/src/shared/suggestOtherCommand.js +34 -0
  59. package/dist/src/shared/suggestOtherCommand.js.map +1 -0
  60. package/dist/src/shared/tailwindInit.d.ts +3 -1
  61. package/dist/src/shared/tailwindInit.d.ts.map +1 -1
  62. package/dist/src/shared/tailwindInit.js +3 -1
  63. package/dist/src/shared/tailwindInit.js.map +1 -1
  64. package/dist/src/shared/wireStylesheet.d.ts +32 -0
  65. package/dist/src/shared/wireStylesheet.d.ts.map +1 -0
  66. package/dist/src/shared/wireStylesheet.js +92 -0
  67. package/dist/src/shared/wireStylesheet.js.map +1 -0
  68. package/dist/src/types/main.d.ts +6 -0
  69. package/dist/src/types/main.d.ts.map +1 -1
  70. package/docs/components/form-builder.md +62 -88
  71. package/docs/components/form-renderer.md +61 -25
  72. package/docs/components/form-summary.md +18 -3
  73. package/docs/components/section-block.md +1 -1
  74. package/docs/how-to/forms-with-form-builder.md +44 -41
  75. package/docs/migration/changelog.md +3 -0
  76. package/docs/migration/form-builder-2.5.2.md +113 -0
  77. package/docs/reference/cli.md +28 -3
  78. package/docs/tutorials/getting-started.md +7 -0
  79. package/package.json +1 -1
@@ -1,11 +1,11 @@
1
1
  "use client";
2
2
 
3
- import { Children, useId } from "react";
4
- import { FieldValues } from "react-hook-form";
3
+ import { Children, useId, type ReactElement, type ReactNode } from "react";
4
+ import { useForm, type Control, type FieldValues } from "react-hook-form";
5
5
 
6
+ import { cn } from "../../utils/cn";
6
7
  import { FormBuilder } from "../FormBuilder";
7
8
  import { FormIdContext, LoadingContext } from "../FormBuilder/context";
8
- import { StepperActions } from "../FormBuilder/stepper";
9
9
  import {
10
10
  DetailSidebar,
11
11
  DetailTab,
@@ -16,20 +16,39 @@ import {
16
16
  isDetailTabElement,
17
17
  } from "./detail";
18
18
  import { FormDrawer } from "./FormDrawer";
19
+ import { FormHeaderBar } from "./header";
20
+ import { Section } from "./section";
21
+ import {
22
+ Back,
23
+ Next,
24
+ Step,
25
+ StepSlot,
26
+ Stepper,
27
+ StepperActions,
28
+ StepperContext,
29
+ StepperNav,
30
+ isStepElement,
31
+ isStepperElement,
32
+ useStepperState,
33
+ type StepProps,
34
+ } from "./stepper";
19
35
  import type { FormRendererProps } from "./types";
20
36
 
21
37
  /**
22
- * FormRenderer — a thin wrapper around the compound `FormBuilder`. Author the
23
- * fields as JSX children; FormRenderer owns page-vs-drawer display, the absolute
24
- * title header, and drawer field direction.
38
+ * FormRenderer — the chrome around a `FormBuilder`.
39
+ *
40
+ * `FormBuilder` is the fields and nothing else: a `<form>`, its react-hook-form context, and the
41
+ * field components. Every pixel drawn *around* those fields is here — page-vs-drawer display, the
42
+ * absolute title header, the `actions` bar, titled `FormRenderer.Section` cards, the page gutters,
43
+ * the scroll shell, the wizard rail (`FormRenderer.Stepper`), and the `summary` column.
25
44
  *
26
45
  * FormRenderer never manufactures a Submit — you compose it and hand it to `actions`
27
- * (`actions={<FormBuilder.Submit>Save</FormBuilder.Submit>}`). It renders in the form's
28
- * header action pill (page) or the drawer header (drawer), and auto-targets this form.
46
+ * (`actions={<FormBuilder.Submit>Save</FormBuilder.Submit>}`). It renders in the form's header
47
+ * action pill (page) or the drawer header (drawer), and auto-targets this form.
29
48
  *
30
49
  * Give it `FormRenderer.Sidebar` + `FormRenderer.Tab` children instead of fields and it switches to
31
- * a **detail-tabs** view: a display-only page (no `<form>`) whose sidebar swaps `FormBuilder.Section`
32
- * panels — the sidebar sits where a stepper's rail would.
50
+ * a **detail-tabs** view: a display-only page (no `<form>`) whose sidebar swaps
51
+ * `FormRenderer.Section` panels — the sidebar sits where a stepper's rail would.
33
52
  */
34
53
  function FormRendererRoot<T extends FieldValues = FieldValues>({
35
54
  children,
@@ -65,12 +84,35 @@ function FormRendererRoot<T extends FieldValues = FieldValues>({
65
84
  const autoId = useId();
66
85
  const formId = id ?? autoId;
67
86
 
87
+ // The form instance is created HERE, not in FormBuilder, because the stepper needs `trigger` to
88
+ // validate a step before advancing — and the rail that calls it renders outside the `<form>`.
89
+ // Same pattern FormBuilder uses: one is always created (hooks can't be conditional), the
90
+ // caller's hoisted instance wins when given.
91
+ const ownForm = useForm<T>({ resolver, defaultValues, values });
92
+ const formInstance = form ?? ownForm;
93
+
94
+ const childArray = Children.toArray(children);
95
+
68
96
  // Detail-tabs mode: a `FormRenderer.Sidebar` + `FormRenderer.Tab` children mean a display-only
69
97
  // detail page (no `<form>`) — the sidebar swaps Section panels via Radix Tabs. Detected here so
70
98
  // the form props below are simply unused.
71
- const childArray = Children.toArray(children);
72
99
  const detailSidebar = childArray.find(isDetailSidebarElement);
73
100
  const detailTabs = childArray.filter(isDetailTabElement);
101
+
102
+ // Split out a `FormRenderer.Stepper`: its Steps become the visibility-toggled slots inside the
103
+ // `<form>`, its non-Step children a footer, and its state drives the rail beside them.
104
+ const stepperEl = childArray.find(isStepperElement);
105
+ const stepChildren = stepperEl ? Children.toArray(stepperEl.props.children) : [];
106
+ const steps = stepChildren.filter(isStepElement) as ReactElement<StepProps>[];
107
+ const stepExtras = stepChildren.filter((n) => !isStepElement(n));
108
+ const isStepper = !!stepperEl;
109
+
110
+ // Called unconditionally (inert when there are no steps) to keep hooks order stable.
111
+ const stepper = useStepperState(
112
+ steps,
113
+ formInstance.trigger as Parameters<typeof useStepperState>[1],
114
+ );
115
+
74
116
  if (detailSidebar && detailTabs.length > 0) {
75
117
  return (
76
118
  <DetailTabsView
@@ -83,35 +125,106 @@ function FormRendererRoot<T extends FieldValues = FieldValues>({
83
125
  );
84
126
  }
85
127
 
86
- const inner = (
128
+ // The fields the `<form>` wraps: the stepper's steps (+ any custom footer extras), or the
129
+ // children as authored. Every step stays mounted so the whole form is registered at all times.
130
+ const fields: ReactNode = isStepper ? (
131
+ <>
132
+ {steps.map((step, i) => (
133
+ <StepSlot key={i} index={i} active={i === stepper.currentStep}>
134
+ {step.props.children}
135
+ </StepSlot>
136
+ ))}
137
+ {stepExtras}
138
+ </>
139
+ ) : (
140
+ children
141
+ );
142
+
143
+ const formEl = (
87
144
  <FormBuilder
88
145
  id={formId}
89
- form={form}
146
+ form={formInstance}
90
147
  onSubmit={onSubmit ?? (() => {})}
91
148
  onInvalid={onInvalid}
92
- resolver={resolver}
93
- defaultValues={defaultValues}
94
- values={values}
95
149
  loading={loading}
96
150
  fieldDirection={effectiveDirection}
97
151
  resetOnSuccess={resetOnSuccess}
98
- // Page display only — in a drawer the panel owns the sizing.
99
- className={!isDrawer ? className : undefined}
100
- // Page display: FormBuilder lays the conclusion as the grid's right column (a stepper adds
101
- // the left nav column too). In a drawer the summary goes to FormDrawer's tray instead.
102
- conclusion={!isDrawer ? summary : undefined}
103
152
  >
104
- {useHeader && (
105
- <FormBuilder.Header title={header!.title} label={header!.label} variant={header!.variant}>
106
- {/* A stepper form prepends chevron Back/Next + a divider before the Submit. */}
107
- {actions && <StepperActions>{actions}</StepperActions>}
108
- </FormBuilder.Header>
109
- )}
110
-
111
- {children}
153
+ {fields}
112
154
  </FormBuilder>
113
155
  );
114
156
 
157
+ // The fields column caps at 1100px and centres — standalone, and as the middle column of the
158
+ // stepper grid. This is page framing, which is why it wraps FormBuilder rather than living
159
+ // inside it: a form embedded in a 260px rail has no room to give up 96px to gutters.
160
+ const fieldsColumn = <div className="mx-auto w-full max-w-[1100px] px-[48px]">{formEl}</div>;
161
+
162
+ // Inside the form surface: the stepper rail beside the fields. Columns never wrap — the
163
+ // layout stays side-by-side at every screen size (the fields column shrinks instead).
164
+ const bodyInner = isStepper ? (
165
+ <div className="grid w-full grid-cols-[1fr_minmax(0,1100px)_1fr] gap-8">
166
+ <StepperNav control={formInstance.control as Control<FieldValues>} />
167
+ {fieldsColumn}
168
+ {/* Empty third column — balances the rail's gutter so the middle column is centred. */}
169
+ <div />
170
+ </div>
171
+ ) : (
172
+ fieldsColumn
173
+ );
174
+
175
+ const surface = useHeader ? (
176
+ // Scroll shell: the absolute header floats over the scrollable body. No fixed height — it
177
+ // fills whatever the parent gives it (like the drawer's panel fills its tray) and the body
178
+ // scrolls internally.
179
+ <div className="relative isolate flex h-full w-full flex-col overflow-hidden rounded-2xl bg-background-presentation-body-primary">
180
+ <FormHeaderBar title={header!.title} label={header!.label} variant={header!.variant}>
181
+ {/* A stepper form prepends chevron Back/Next + a divider before the Submit. */}
182
+ {actions && <StepperActions>{actions}</StepperActions>}
183
+ </FormHeaderBar>
184
+ <div className="relative z-[1] flex min-h-0 w-full flex-1 flex-col overflow-y-auto px-6 py-6 pt-[72px] scrollbar-hide">
185
+ {bodyInner}
186
+ </div>
187
+ </div>
188
+ ) : (
189
+ bodyInner
190
+ );
191
+
192
+ // The summary (right) lives OUTSIDE the scroll surface — its own panel beside it (mirroring the
193
+ // drawer's tray, a 6px gutter). Only the surface's body scrolls; the summary stays put. In a
194
+ // drawer it goes to FormDrawer's tray instead.
195
+ const pageBody =
196
+ summary && !isDrawer ? (
197
+ <div className="flex h-full flex-row items-stretch">
198
+ <div className="min-h-0 min-w-0 flex-1">{surface}</div>
199
+ <div className="ml-[6px] flex min-h-0">{summary}</div>
200
+ </div>
201
+ ) : (
202
+ surface
203
+ );
204
+
205
+ // `className` lands on the OUTERMOST element — the one a parent lays out (e.g. `flex-1 min-h-0`
206
+ // to fill a flex column). `h-full` fills a parent that has a definite height.
207
+ //
208
+ // The form-id and loading contexts are supplied HERE as well as inside FormBuilder, because the
209
+ // header's action pill renders outside the `<FormBuilder>` subtree: a bare `FormBuilder.Submit`
210
+ // in `actions` reads the id from context to target the `<form>` by native form-association.
211
+ // Same reason the drawer branch below re-provides them around its own header.
212
+ const inner = (
213
+ <LoadingContext.Provider value={!!loading}>
214
+ <FormIdContext.Provider value={formId}>
215
+ <div className={cn("h-full w-full", !isDrawer && className)}>{pageBody}</div>
216
+ </FormIdContext.Provider>
217
+ </LoadingContext.Provider>
218
+ );
219
+
220
+ // One provider around the whole tree: the rail renders outside the `<form>`, the step slots
221
+ // inside it, and both read the same state.
222
+ const tree = isStepper ? (
223
+ <StepperContext.Provider value={stepper}>{inner}</StepperContext.Provider>
224
+ ) : (
225
+ inner
226
+ );
227
+
115
228
  if (isDrawer) {
116
229
  return (
117
230
  <FormDrawer
@@ -132,21 +245,28 @@ function FormRendererRoot<T extends FieldValues = FieldValues>({
132
245
  }
133
246
  onOpenInNewTab={onOpenInNewTab}
134
247
  >
135
- {inner}
248
+ {tree}
136
249
  </FormDrawer>
137
250
  );
138
251
  }
139
252
 
140
- // Page display: FormBuilder already placed `summary` as the grid's conclusion column.
141
- return inner;
253
+ return tree;
142
254
  }
143
255
 
144
256
  /**
145
- * FormRenderer — see {@link FormRendererRoot}. The compound statics drive the display-only
146
- * **detail-tabs** view: `FormRenderer.Sidebar` (the rail) + `FormRenderer.Sidebar.Item` (a tab) +
147
- * `FormRenderer.Tab` (a `FormBuilder.Section` panel).
257
+ * FormRenderer — see {@link FormRendererRoot}. The compound statics are the form's chrome:
258
+ *
259
+ * - `FormRenderer.Section` a titled card grouping fields (or display rows).
260
+ * - `FormRenderer.Stepper` / `.Step` — the wizard; `.Back` / `.Next` are the chevron controls,
261
+ * which the header's action bar already prepends for you.
262
+ * - `FormRenderer.Sidebar` / `.Tab` / `.Grid` / `.Row` — the display-only **detail-tabs** view.
148
263
  */
149
264
  export const FormRenderer = Object.assign(FormRendererRoot, {
265
+ Section,
266
+ Stepper,
267
+ Step,
268
+ Back,
269
+ Next,
150
270
  Sidebar: DetailSidebar,
151
271
  Tab: DetailTab,
152
272
  Grid: DetailGrid,
@@ -29,8 +29,10 @@ export interface FormHeaderBarProps {
29
29
  * The floating form header: a `HeaderBar` title pill on the left and a dark action
30
30
  * pill on the right, absolutely positioned over the scrollable body.
31
31
  *
32
- * Shared by **both** form surfaces — `FormBuilder.Header` (page) and `FormDrawer`
33
- * (drawer) — so a form's title looks identical wherever it is rendered.
32
+ * Shared by all three FormRenderer surfaces — the page form, `FormDrawer` and the
33
+ * detail view — so a form's title looks identical wherever it is rendered. This is
34
+ * chrome, so it lives here rather than in `FormBuilder`: a bare `<FormBuilder>` is
35
+ * fields and nothing else.
34
36
  *
35
37
  * Per the design both pills are 44px tall, reached differently: the title pill is
36
38
  * 6px padding + 32px content, the action pill 8px + 28px.
@@ -70,32 +72,17 @@ export function FormHeaderBar({
70
72
  );
71
73
  }
72
74
 
73
- export interface HeaderProps {
75
+ /**
76
+ * The `header` prop's shape — `header={{ title, label, variant }}` on `FormRenderer`.
77
+ * There is no `FormRenderer.Header` component: the header is a prop, not a child, so a
78
+ * child element can never reconfigure the surrounding layout the way the old
79
+ * `FormBuilder.Header` did.
80
+ */
81
+ export interface HeaderConfig {
74
82
  /** Plain title text (uppercased), e.g. the entity label or SKU. */
75
83
  title: string;
76
84
  /** Badge text — defaults from `variant` (New / Edit / View). */
77
85
  label?: string;
78
- /** Colored badge variant. Defaults from the form `mode` (view → detail). */
86
+ /** Colored badge variant. */
79
87
  variant?: HeaderVariant;
80
- /** Action buttons shown in the right-hand action pill (e.g. FormBuilder.Submit). */
81
- children?: ReactNode;
82
- }
83
-
84
- /**
85
- * `FormBuilder.Header` — the page form's title + action bar. A thin, mode-aware
86
- * wrapper over `FormHeaderBar`: it defaults the badge variant from the form `mode`
87
- * (view → detail).
88
- *
89
- * Place it as a direct child of `<FormBuilder>`; the root then switches to the
90
- * scroll-shell layout that reserves space beneath the floating header.
91
- */
92
- export function Header({ title, label, variant = "new", children }: HeaderProps) {
93
- return (
94
- <FormHeaderBar title={title} label={label} variant={variant}>
95
- {children}
96
- </FormHeaderBar>
97
- );
98
88
  }
99
-
100
- // Marker so the FormBuilder root can detect the header among its children.
101
- (Header as unknown as { __isFormHeader: boolean }).__isFormHeader = true;
@@ -1,6 +1,10 @@
1
1
  export { FormRenderer } from "./form-renderer";
2
2
  export { FormDrawer } from "./FormDrawer";
3
3
  export type { FormDrawerProps } from "./FormDrawer";
4
+ export { FormHeaderBar } from "./header";
5
+ export type { HeaderConfig, HeaderVariant, FormHeaderBarProps } from "./header";
6
+ export type { SectionProps } from "./section";
7
+ export type { StepProps, StepperProps } from "./stepper";
4
8
  export type { FormRendererProps, FormRendererDisplay, FieldDirection } from "./types";
5
9
  export type {
6
10
  DetailSidebarProps,
@@ -0,0 +1,39 @@
1
+ "use client";
2
+
3
+ import { ReactNode } from "react";
4
+
5
+ import { SectionBlock, type SectionColor, type SectionVariant } from "../SectionBlock";
6
+
7
+ export interface SectionProps {
8
+ title?: ReactNode;
9
+ color?: SectionColor;
10
+ icon?: ReactNode;
11
+ /**
12
+ * `"Table"` switches to the full-bleed table shell — no body padding, a rule under
13
+ * the header, and the card clipped to its radius. `FormBuilder.Table` uses it; pass
14
+ * it here only when hand-composing a table inside a section.
15
+ */
16
+ variant?: SectionVariant;
17
+ /** Right-aligned content on the title row — e.g. action buttons. */
18
+ action?: ReactNode;
19
+ children: ReactNode;
20
+ }
21
+
22
+ /**
23
+ * `FormRenderer.Section` — a titled Glare `SectionBlock` grouping fields.
24
+ *
25
+ * It lives here, not on `FormBuilder`, because it is presentation: a coloured card with a title
26
+ * row and an action slot. It holds no form state and reads no form context, so it works just as
27
+ * well around the read-only `FormRenderer.Grid` rows of a detail page as it does around fields.
28
+ *
29
+ * The per-field row layout — label, required marker, hint, the two-column split — is a different
30
+ * thing entirely: that is `FieldSection`, applied by every field itself, and it stays inside
31
+ * FormBuilder where the fields are.
32
+ */
33
+ export function Section({ title, color, icon, variant, action, children }: SectionProps) {
34
+ return (
35
+ <SectionBlock title={title} color={color} icon={icon} variant={variant} action={action}>
36
+ {children}
37
+ </SectionBlock>
38
+ );
39
+ }
@@ -1,18 +1,42 @@
1
1
  "use client";
2
2
 
3
3
  import * as React from "react";
4
- import { useFormState, type FieldPath, type FieldValues } from "react-hook-form";
4
+ import { useFormState, type Control, type FieldPath, type FieldValues } from "react-hook-form";
5
5
 
6
6
  import { cn } from "../../utils/cn";
7
7
  import { Button } from "../Button";
8
+ // The step *registry* stays with the fields: `FieldShell` registers each field name into it, so
9
+ // it must ship with FormBuilder (which is installable on its own). Everything else about the
10
+ // stepper — the rail, the state, Back/Next — is chrome and lives here. The dependency only ever
11
+ // points this way: FormRenderer → FormBuilder, never back.
12
+ import { StepContext, type StepRegistry } from "../FormBuilder/context";
8
13
  import { FormStepper, FormStep, FormStepIndicator, FormStepLabel } from "../FormStepper";
9
- import {
10
- StepContext,
11
- StepperContext,
12
- useStepper,
13
- type StepperContextValue,
14
- type StepRegistry,
15
- } from "./context";
14
+
15
+ // ─── Stepper state context ───────────────────────────────────────────────────
16
+
17
+ /** Stepper state shared by the nav, the step slots and the Back/Next buttons. */
18
+ export interface StepperContextValue {
19
+ currentStep: number;
20
+ totalSteps: number;
21
+ titles: string[];
22
+ isFirstStep: boolean;
23
+ isLastStep: boolean;
24
+ goToNext: () => void | Promise<void>;
25
+ goToPrevious: () => void;
26
+ goToStep: (index: number) => void;
27
+ /** Field names registered per step, for per-step validation. */
28
+ stepFields: Record<number, Set<string>>;
29
+ /** Steps that have passed validation — stay checked even after navigating back. */
30
+ completedSteps: Set<number>;
31
+ }
32
+
33
+ export const StepperContext = React.createContext<StepperContextValue | null>(null);
34
+
35
+ export const useStepper = () => {
36
+ const ctx = React.useContext(StepperContext);
37
+ if (!ctx) throw new Error("FormRenderer.Step/Back/Next must be used within FormRenderer.Stepper");
38
+ return ctx;
39
+ };
16
40
 
17
41
  // ─── Step (declaration only — the Stepper reads its props) ───────────────────
18
42
 
@@ -23,7 +47,7 @@ export interface StepProps {
23
47
  }
24
48
 
25
49
  /**
26
- * Declares a wizard step. It renders nothing itself — `FormBuilder.Stepper`
50
+ * Declares a wizard step. It renders nothing itself — `FormRenderer.Stepper`
27
51
  * collects Step elements as an array and renders every step's fields (so the
28
52
  * **whole form is registered**), toggling visibility per the active step.
29
53
  */
@@ -76,10 +100,14 @@ function StepSlot({
76
100
  /**
77
101
  * Vertical step rail (matches `Tabs.svg`): the numbered state badges stacked top
78
102
  * to bottom, joined by a short vertical connector between consecutive steps.
103
+ *
104
+ * `control` is passed in rather than read from context: the rail renders in its own grid column
105
+ * **outside** the `<form>`, so there is no `FormProvider` above it to read. Same reason
106
+ * `useStepperState` takes `trigger` as an argument.
79
107
  */
80
- function StepperNav() {
108
+ function StepperNav({ control }: { control: Control<FieldValues> }) {
81
109
  const { titles, currentStep, goToStep, stepFields, completedSteps } = useStepper();
82
- const { errors } = useFormState();
110
+ const { errors } = useFormState({ control });
83
111
 
84
112
  const stepHasError = (index: number) =>
85
113
  [...(stepFields[index] ?? [])].some((name) => name in errors);
@@ -150,13 +178,13 @@ function StepNavButton({
150
178
  );
151
179
  }
152
180
 
153
- /** `FormBuilder.Back` — chevron to the previous step; disabled on the first. */
181
+ /** `FormRenderer.Back` — chevron to the previous step; disabled on the first. */
154
182
  export function Back() {
155
183
  const { goToPrevious, isFirstStep } = useStepper();
156
184
  return <StepNavButton dir="left" onClick={goToPrevious} disabled={isFirstStep} />;
157
185
  }
158
186
 
159
- /** `FormBuilder.Next` — chevron to the next step (validates first); disabled on the last. */
187
+ /** `FormRenderer.Next` — chevron to the next step (validates first); disabled on the last. */
160
188
  export function Next() {
161
189
  const { goToNext, isLastStep } = useStepper();
162
190
  return <StepNavButton dir="right" onClick={() => void goToNext()} disabled={isLastStep} />;
@@ -165,7 +193,7 @@ export function Next() {
165
193
  // ─── Stepper action bar (Back/Next + divider, then the Submit) ────────────────
166
194
 
167
195
  /**
168
- * `FormRenderer` wraps its `actions` in this. When the form is a stepper it prepends the
196
+ * The FormRenderer root wraps its `actions` in this. When the form is a stepper it prepends the
169
197
  * chevron `Back`/`Next` controls + a divider before the (user-provided) Submit — the Figma
170
198
  * `Body-HeaderBar-1.0` layout. Outside a stepper there's no `StepperContext`, so it renders
171
199
  * the actions untouched.
@@ -191,10 +219,10 @@ export interface StepperProps {
191
219
  }
192
220
 
193
221
  /**
194
- * `FormBuilder.Stepper` — declares a wizard. It renders **nothing itself**: the
195
- * FormBuilder root detects it, lifts its state (so the nav can live in its own
222
+ * `FormRenderer.Stepper` — declares a wizard. It renders **nothing itself**: the
223
+ * FormRenderer root detects it, lifts its state (so the nav can live in its own
196
224
  * grid column *outside* the `<form>`), and renders `[nav | active step's fields]`.
197
- * Children are `FormBuilder.Step`s (plus an optional custom footer).
225
+ * Children are `FormRenderer.Step`s (plus an optional custom footer).
198
226
  *
199
227
  * Every step's fields stay registered with react-hook-form at all times — the
200
228
  * stepper only controls which step is *visible*. **Navigation is the step buttons
@@ -202,7 +230,7 @@ export interface StepperProps {
202
230
  * validates every step in between and stops at the first with errors (which shows a
203
231
  * red indicator). Only the last step shows the Submit button.
204
232
  */
205
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- children are read by the FormBuilder root
233
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- children are read by the FormRenderer root
206
234
  export function Stepper(_props: StepperProps) {
207
235
  return null;
208
236
  }
@@ -218,7 +246,7 @@ export function isStepperElement(node: React.ReactNode): node is React.ReactElem
218
246
  type TriggerFn = (names?: FieldPath<FieldValues>[]) => Promise<boolean>;
219
247
 
220
248
  /**
221
- * Stepper state — lifted to the FormBuilder root so the nav (rail) and the step
249
+ * Stepper state — lifted to the FormRenderer root so the nav (rail) and the step
222
250
  * fields can render in separate grid columns while sharing one state. `trigger` is
223
251
  * the form's `trigger` (passed in — no `useFormContext` needed). Inert when
224
252
  * `steps` is empty (a form without a stepper still calls this, for hooks order).
@@ -8,18 +8,18 @@ import type {
8
8
  } from "react-hook-form";
9
9
 
10
10
  /**
11
- * FormRenderer — a thin wrapper around the compound `FormBuilder`. You author the
12
- * fields as **JSX children** (`FormBuilder.Section`, `FormBuilder.Text`, …); the
13
- * renderer owns the surrounding concerns: page-vs-drawer display, the absolute
14
- * title header, vertical field layout inside a drawer, and an `actions` slot (the
15
- * form's header / drawer action bar) where you place the Save.
11
+ * FormRenderer — the chrome around a `FormBuilder`. You author the fields as **JSX children**
12
+ * (`FormRenderer.Section`, `FormBuilder.Text`, …); the renderer owns everything drawn around
13
+ * them: page-vs-drawer display, the absolute title header, the `actions` bar, the titled section
14
+ * cards, the page gutters and scroll shell, the wizard rail, the `summary` column, and vertical
15
+ * field layout inside a drawer.
16
16
  */
17
17
 
18
18
  export type FormRendererDisplay = "page" | "drawer";
19
19
  export type FieldDirection = "horizontal" | "vertical";
20
20
 
21
21
  export interface FormRendererProps<T extends FieldValues = FieldValues> {
22
- /** The form body — `FormBuilder.Section` / field / `FormBuilder.Stepper` JSX. */
22
+ /** The form body — `FormRenderer.Section` / field / `FormRenderer.Stepper` JSX. */
23
23
  children: ReactNode;
24
24
 
25
25
  // --- react-hook-form root (forwarded to FormBuilder) ---
@@ -1,6 +1,63 @@
1
1
  {
2
- "version": "2.5.0",
2
+ "version": "2.5.2",
3
3
  "generatedBy": "scripts/bin/generateRegistry",
4
+ "npmVersions": {
5
+ "@dnd-kit/core": "^6.3.1",
6
+ "@dnd-kit/sortable": "^10.0.0",
7
+ "@dnd-kit/utilities": "^3.2.2",
8
+ "@editorjs/checklist": "^1.6.0",
9
+ "@editorjs/code": "^2.9.4",
10
+ "@editorjs/delimiter": "^1.4.2",
11
+ "@editorjs/editorjs": "^2.31.1",
12
+ "@editorjs/embed": "^2.8.0",
13
+ "@editorjs/header": "^2.8.8",
14
+ "@editorjs/inline-code": "^1.5.2",
15
+ "@editorjs/link": "^2.6.2",
16
+ "@editorjs/list": "^2.0.9",
17
+ "@editorjs/marker": "^1.4.0",
18
+ "@editorjs/nested-list": "^1.4.3",
19
+ "@editorjs/quote": "^2.7.6",
20
+ "@editorjs/raw": "^2.5.1",
21
+ "@editorjs/simple-image": "^1.6.0",
22
+ "@editorjs/table": "^2.4.5",
23
+ "@editorjs/text-variant-tune": "^1.0.3",
24
+ "@editorjs/underline": "^1.2.1",
25
+ "@editorjs/warning": "^1.4.1",
26
+ "@radix-ui/react-alert-dialog": "^1.1.6",
27
+ "@radix-ui/react-avatar": "^1.1.3",
28
+ "@radix-ui/react-checkbox": "^1.1.4",
29
+ "@radix-ui/react-context-menu": "^2.3.2",
30
+ "@radix-ui/react-dialog": "^1.1.6",
31
+ "@radix-ui/react-dropdown-menu": "^2.1.6",
32
+ "@radix-ui/react-label": "^2.1.2",
33
+ "@radix-ui/react-popover": "^1.1.6",
34
+ "@radix-ui/react-radio-group": "^1.2.3",
35
+ "@radix-ui/react-scroll-area": "^1.2.3",
36
+ "@radix-ui/react-select": "^2.1.6",
37
+ "@radix-ui/react-slider": "^1.4.2",
38
+ "@radix-ui/react-slot": "^1.1.2",
39
+ "@radix-ui/react-switch": "^1.1.3",
40
+ "@radix-ui/react-tabs": "^1.1.13",
41
+ "@radix-ui/react-toggle": "^1.1.2",
42
+ "@radix-ui/react-toggle-group": "^1.1.11",
43
+ "@radix-ui/react-tooltip": "^1.1.8",
44
+ "@tanstack/react-table": "^8.21.3",
45
+ "@tanstack/react-virtual": "^3.14.9",
46
+ "chart.js": "^4.5.1",
47
+ "class-variance-authority": "^0.7.1",
48
+ "clsx": "^2.1.1",
49
+ "date-fns": "^4.1.0",
50
+ "editorjs-undo": "^2.0.28",
51
+ "input-otp": "^1.4.2",
52
+ "lucide-react": "^0.507.0",
53
+ "next": "15.2.1",
54
+ "react-day-picker": "^9.6.7",
55
+ "react-hook-form": "^7.54.2",
56
+ "react-hot-toast": "^2.5.2",
57
+ "tailwind-merge": "^3.0.2",
58
+ "torch-react-mobile-picker": "^1.1.0",
59
+ "vaul": "^1.1.2"
60
+ },
4
61
  "items": [
5
62
  {
6
63
  "name": "ActionButton",
@@ -365,8 +422,6 @@
365
422
  "components/DatePicker",
366
423
  "components/FieldHint",
367
424
  "components/Form",
368
- "components/FormStepper",
369
- "components/HeaderBar",
370
425
  "components/ImageAttachment",
371
426
  "components/Input",
372
427
  "components/InputField",
@@ -399,8 +454,12 @@
399
454
  "react-hook-form"
400
455
  ],
401
456
  "registryDependencies": [
457
+ "components/Button",
402
458
  "components/Drawer",
403
459
  "components/FormBuilder",
460
+ "components/FormStepper",
461
+ "components/HeaderBar",
462
+ "components/SectionBlock",
404
463
  "components/TabFormItem",
405
464
  "utils/cn"
406
465
  ]