torch-glare 2.4.2 → 2.4.3

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 (46) hide show
  1. package/apps/lib/components/FormBuilder/context.ts +2 -5
  2. package/apps/lib/components/FormBuilder/fields/ChoiceFields.tsx +2 -7
  3. package/apps/lib/components/FormBuilder/fields/ColorField.tsx +1 -18
  4. package/apps/lib/components/FormBuilder/fields/CustomField.tsx +1 -6
  5. package/apps/lib/components/FormBuilder/fields/DateField.tsx +1 -3
  6. package/apps/lib/components/FormBuilder/fields/FieldArray.tsx +10 -15
  7. package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +6 -32
  8. package/apps/lib/components/FormBuilder/fields/FileField.tsx +1 -2
  9. package/apps/lib/components/FormBuilder/fields/OptionListFields.tsx +2 -9
  10. package/apps/lib/components/FormBuilder/fields/OtpField.tsx +1 -2
  11. package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +24 -23
  12. package/apps/lib/components/FormBuilder/fields/RichTextEditorField.tsx +1 -7
  13. package/apps/lib/components/FormBuilder/fields/SelectField.tsx +3 -13
  14. package/apps/lib/components/FormBuilder/fields/SignatureField.tsx +1 -19
  15. package/apps/lib/components/FormBuilder/fields/SliderField.tsx +1 -6
  16. package/apps/lib/components/FormBuilder/fields/SwitchBoxField.tsx +1 -2
  17. package/apps/lib/components/FormBuilder/fields/TableField.tsx +22 -26
  18. package/apps/lib/components/FormBuilder/fields/TextField.tsx +5 -14
  19. package/apps/lib/components/FormBuilder/fields/TreeSelectField.tsx +1 -7
  20. package/apps/lib/components/FormBuilder/form-builder.tsx +15 -28
  21. package/apps/lib/components/FormBuilder/header.tsx +2 -6
  22. package/apps/lib/components/FormBuilder/index.ts +1 -5
  23. package/apps/lib/components/FormBuilder/stepper.tsx +88 -15
  24. package/apps/lib/components/FormBuilder/submit.tsx +1 -4
  25. package/apps/lib/components/FormBuilder/types.ts +2 -5
  26. package/apps/lib/components/FormRenderer/FormDrawer.tsx +9 -2
  27. package/apps/lib/components/FormRenderer/form-renderer.tsx +9 -11
  28. package/apps/lib/components/FormRenderer/index.ts +1 -6
  29. package/apps/lib/components/FormRenderer/types.ts +0 -2
  30. package/apps/lib/components/Popover.tsx +6 -2
  31. package/apps/lib/components/SearchableSelect.tsx +7 -2
  32. package/apps/lib/layouts/FieldSection.tsx +25 -22
  33. package/apps/lib/registry.json +1 -1
  34. package/apps/lib/tsconfig.tsbuildinfo +1 -1
  35. package/dist/src/shared/tailwindInit.d.ts.map +1 -1
  36. package/dist/src/shared/tailwindInit.js +3 -0
  37. package/dist/src/shared/tailwindInit.js.map +1 -1
  38. package/docs/components/form-builder.md +19 -29
  39. package/docs/components/form-renderer.md +16 -10
  40. package/docs/components/searchable-select.md +50 -46
  41. package/docs/how-to/forms-with-form-builder.md +14 -11
  42. package/docs/reference/tailwind-plugins.md +11 -1
  43. package/docs/tutorials/getting-started.md +9 -0
  44. package/package.json +1 -1
  45. package/apps/lib/components/FormBuilder/DisplayField.tsx +0 -40
  46. package/apps/lib/components/FormBuilder/viewFormat.tsx +0 -137
@@ -7,7 +7,6 @@ import { InputField } from "../../InputField";
7
7
  import { Textarea } from "../../Textarea";
8
8
  import { PasswordLevel } from "../../PasswordLevel";
9
9
  import { useLoading, useCell } from "../context";
10
- import { formatFieldView } from "../viewFormat";
11
10
  import { formatNumber } from "../numberFormat";
12
11
  import type { BaseFieldProps, CurrencyFieldProps, PasswordFieldProps } from "../types";
13
12
  import { FieldShell } from "./FieldShell";
@@ -110,7 +109,7 @@ function TextLike({ props, type }: { props: BaseFieldProps; type: "text" | "emai
110
109
  const loading = useLoading();
111
110
  const cell = useCell();
112
111
  return (
113
- <FieldShell {...props} view={(v) => formatFieldView({ kind: "text", value: v })}>
112
+ <FieldShell {...props}>
114
113
  {(field) => (
115
114
  <InputField
116
115
  {...field}
@@ -136,10 +135,7 @@ export function PasswordField(props: PasswordFieldProps) {
136
135
  const loading = useLoading();
137
136
  const cell = useCell();
138
137
  return (
139
- <FieldShell
140
- {...props}
141
- view={(v) => formatFieldView({ kind: "text", value: v ? "••••••••" : "" })}
142
- >
138
+ <FieldShell {...props}>
143
139
  {(field) => (
144
140
  <div className="flex w-full flex-col gap-2">
145
141
  <InputField
@@ -162,7 +158,7 @@ export function NumberField(props: BaseFieldProps) {
162
158
  const loading = useLoading();
163
159
  const cell = useCell();
164
160
  return (
165
- <FieldShell {...props} view={(v) => formatFieldView({ kind: "number", value: v })}>
161
+ <FieldShell {...props}>
166
162
  {(field) => (
167
163
  <NumberInput
168
164
  field={field}
@@ -179,12 +175,7 @@ export function CurrencyField(props: CurrencyFieldProps) {
179
175
  const loading = useLoading();
180
176
  const cell = useCell();
181
177
  return (
182
- <FieldShell
183
- {...props}
184
- view={(v) =>
185
- formatFieldView({ kind: "currency", value: v, currencySymbol: props.currencySymbol })
186
- }
187
- >
178
+ <FieldShell {...props}>
188
179
  {(field) => (
189
180
  <NumberInput
190
181
  field={field}
@@ -205,7 +196,7 @@ export function CurrencyField(props: CurrencyFieldProps) {
205
196
  export function TextareaField(props: BaseFieldProps & { rows?: number }) {
206
197
  const loading = useLoading();
207
198
  return (
208
- <FieldShell {...props} view={(v) => formatFieldView({ kind: "text", value: v })}>
199
+ <FieldShell {...props}>
209
200
  {(field, fieldState) => (
210
201
  <Textarea
211
202
  {...field}
@@ -25,13 +25,7 @@ export function TreeSelectField<T>(props: TreeSelectFieldProps<T>) {
25
25
  };
26
26
 
27
27
  return (
28
- <FieldShell
29
- {...props}
30
- view={(v) => {
31
- const node = findById(v);
32
- return { value: node ? String(props.getNodeLabel(node)) : "" };
33
- }}
34
- >
28
+ <FieldShell {...props}>
35
29
  {(field) => (
36
30
  <SearchableTree
37
31
  nodes={props.nodes}
@@ -7,13 +7,7 @@ import { useForm, type FieldValues } from "react-hook-form";
7
7
  import { cn } from "../../utils/cn";
8
8
  import { Form } from "../Form";
9
9
  import { SectionBlock, type SectionColor } from "../SectionBlock";
10
- import {
11
- LoadingContext,
12
- ModeContext,
13
- DirectionContext,
14
- StepperContext,
15
- FormIdContext,
16
- } from "./context";
10
+ import { LoadingContext, DirectionContext, StepperContext, FormIdContext } from "./context";
17
11
  import { Header } from "./header";
18
12
  import type { FormBuilderRootProps } from "./types";
19
13
  import {
@@ -94,7 +88,6 @@ function FormBuilderRoot<T extends FieldValues = FieldValues>({
94
88
  defaultValues,
95
89
  values,
96
90
  loading = false,
97
- mode = "edit",
98
91
  fieldDirection,
99
92
  resetOnSuccess,
100
93
  conclusion,
@@ -119,12 +112,11 @@ function FormBuilderRoot<T extends FieldValues = FieldValues>({
119
112
  const header = childArray.find(isHeaderElement);
120
113
  const rest = childArray.filter((n) => !isHeaderElement(n));
121
114
 
122
- const isView = mode === "view";
123
115
  const stepperEl = rest.find(isStepperElement);
124
116
  const stepChildren = stepperEl ? React.Children.toArray(stepperEl.props.children) : [];
125
117
  const steps = stepChildren.filter(isStepElement);
126
118
  const stepExtras = stepChildren.filter((n) => !isStepElement(n));
127
- const isStepper = !!stepperEl && !isView;
119
+ const isStepper = !!stepperEl;
128
120
 
129
121
  // Stepper state is lifted HERE so the nav can live in its own grid column, outside the
130
122
  // `<form>`. Called unconditionally (inert when there are no steps) to keep hooks order stable.
@@ -151,12 +143,10 @@ function FormBuilderRoot<T extends FieldValues = FieldValues>({
151
143
  // The fields column caps at 1200px and centers — as the middle column of the grid, and
152
144
  // standalone.
153
145
  const fieldsInner = (
154
- <div className="mx-auto flex w-full max-w-[1100px] flex-col gap-4">{fields}</div>
146
+ <div className="mx-auto flex w-full max-w-[1100px] flex-col gap-4 px-[48px]">{fields}</div>
155
147
  );
156
148
 
157
- const formEl = isView ? (
158
- <div className="w-full min-w-0">{fieldsInner}</div>
159
- ) : (
149
+ const formEl = (
160
150
  <form id={id} className="w-full min-w-0" onSubmit={form.handleSubmit(handleValid, onInvalid)}>
161
151
  {fieldsInner}
162
152
  </form>
@@ -203,7 +193,7 @@ function FormBuilderRoot<T extends FieldValues = FieldValues>({
203
193
 
204
194
  // `className` lands on the OUTERMOST element — the one a parent lays out (e.g. `flex-1 min-h-0`
205
195
  // to fill a flex column). `h-full` fills a parent that has a definite height.
206
- const outerClassName = cn("h-full w-full", className);
196
+ const outerClassName = cn("h-full w-full @container", className);
207
197
 
208
198
  const tree = (
209
199
  <Form {...form}>
@@ -214,15 +204,13 @@ function FormBuilderRoot<T extends FieldValues = FieldValues>({
214
204
  return (
215
205
  <FormIdContext.Provider value={id}>
216
206
  <LoadingContext.Provider value={loading}>
217
- <ModeContext.Provider value={mode}>
218
- <DirectionContext.Provider value={direction}>
219
- {isStepper ? (
220
- <StepperContext.Provider value={stepper}>{tree}</StepperContext.Provider>
221
- ) : (
222
- tree
223
- )}
224
- </DirectionContext.Provider>
225
- </ModeContext.Provider>
207
+ <DirectionContext.Provider value={direction}>
208
+ {isStepper ? (
209
+ <StepperContext.Provider value={stepper}>{tree}</StepperContext.Provider>
210
+ ) : (
211
+ tree
212
+ )}
213
+ </DirectionContext.Provider>
226
214
  </LoadingContext.Provider>
227
215
  </FormIdContext.Provider>
228
216
  );
@@ -240,10 +228,9 @@ function FormBuilderRoot<T extends FieldValues = FieldValues>({
240
228
  * </FormBuilder>
241
229
  * ```
242
230
  *
243
- * Steps are components (`FormBuilder.Stepper` + `FormBuilder.Step`) and
244
- * `mode="view"` renders read-only. FormBuilder is drawer-unaware — to show a form
245
- * in a drawer, wrap it in `FormRenderer`'s `FormDrawer` (or use `FormRenderer`
246
- * with `display: "drawer"`).
231
+ * Steps are components (`FormBuilder.Stepper` + `FormBuilder.Step`). FormBuilder is
232
+ * drawer-unaware — to show a form in a drawer, wrap it in `FormRenderer`'s `FormDrawer`
233
+ * (or use `FormRenderer` with `display: "drawer"`).
247
234
  */
248
235
  export const FormBuilder = Object.assign(FormBuilderRoot, {
249
236
  // fields
@@ -4,7 +4,6 @@ import { ReactNode } from "react";
4
4
 
5
5
  import { cn } from "../../utils/cn";
6
6
  import { HeaderBar } from "../HeaderBar";
7
- import { useMode } from "./context";
8
7
 
9
8
  export type HeaderVariant = "new" | "edit" | "detail";
10
9
 
@@ -90,12 +89,9 @@ export interface HeaderProps {
90
89
  * Place it as a direct child of `<FormBuilder>`; the root then switches to the
91
90
  * scroll-shell layout that reserves space beneath the floating header.
92
91
  */
93
- export function Header({ title, label, variant, children }: HeaderProps) {
94
- const mode = useMode();
95
- const v: HeaderVariant = variant ?? (mode === "view" ? "detail" : "new");
96
-
92
+ export function Header({ title, label, variant = "new", children }: HeaderProps) {
97
93
  return (
98
- <FormHeaderBar title={title} label={label} variant={v}>
94
+ <FormHeaderBar title={title} label={label} variant={variant}>
99
95
  {children}
100
96
  </FormHeaderBar>
101
97
  );
@@ -2,13 +2,9 @@ export { FormBuilder } from "./form-builder";
2
2
  export type { SectionProps } from "./form-builder";
3
3
  export { FormHeaderBar } from "./header";
4
4
  export type { HeaderProps, HeaderVariant, FormHeaderBarProps } from "./header";
5
- export { DisplayField } from "./DisplayField";
6
- export type { DisplayFieldProps } from "./DisplayField";
7
5
  export { RichTextField } from "../TextEditor/RichTextField";
8
- export { formatFieldView } from "./viewFormat";
9
- export type { FieldView, ViewKind, ViewFormatOptions } from "./viewFormat";
10
6
  export type { StepProps } from "./stepper";
11
- export type { FormBuilderMode, FieldDirection } from "./context";
7
+ export type { FieldDirection } from "./context";
12
8
  export type {
13
9
  BaseFieldProps,
14
10
  OptionItem,
@@ -6,7 +6,13 @@ import { useFormState, type FieldPath, type FieldValues } from "react-hook-form"
6
6
  import { cn } from "../../utils/cn";
7
7
  import { Button } from "../Button";
8
8
  import { FormStepper, FormStep, FormStepIndicator, FormStepLabel } from "../FormStepper";
9
- import { StepContext, useStepper, type StepperContextValue, type StepRegistry } from "./context";
9
+ import {
10
+ StepContext,
11
+ StepperContext,
12
+ useStepper,
13
+ type StepperContextValue,
14
+ type StepRegistry,
15
+ } from "./context";
10
16
 
11
17
  // ─── Step (declaration only — the Stepper reads its props) ───────────────────
12
18
 
@@ -72,7 +78,7 @@ function StepSlot({
72
78
  * to bottom, joined by a short vertical connector between consecutive steps.
73
79
  */
74
80
  function StepperNav() {
75
- const { titles, currentStep, goToStep, stepFields } = useStepper();
81
+ const { titles, currentStep, goToStep, stepFields, completedSteps } = useStepper();
76
82
  const { errors } = useFormState();
77
83
 
78
84
  const stepHasError = (index: number) =>
@@ -83,8 +89,13 @@ function StepperNav() {
83
89
  {titles.map((title, index) => {
84
90
  // The step buttons ARE the navigation: click to move. Backward is free;
85
91
  // clicking forward validates the steps in between (goToStep) and stops at
86
- // the first one with errors. Errored steps show a red indicator.
87
- const type = stepHasError(index) ? "negative" : index < currentStep ? "success" : "default";
92
+ // the first one with errors. A live error shows red; a step that has passed
93
+ // validation stays checked (success) even after navigating back to it.
94
+ const type = stepHasError(index)
95
+ ? "negative"
96
+ : completedSteps.has(index)
97
+ ? "success"
98
+ : "default";
88
99
  return (
89
100
  <React.Fragment key={title}>
90
101
  <FormStep index={index} type={type} onClick={() => void goToStep(index)}>
@@ -105,24 +116,71 @@ function StepperNav() {
105
116
  );
106
117
  }
107
118
 
108
- // ─── Back / Next / default footer ────────────────────────────────────────────
119
+ // ─── Back / Next chevron nav (the Figma header action bar) ───────────────────
109
120
 
110
- export function Back({ children }: { children?: React.ReactNode }) {
111
- const { goToPrevious, isFirstStep } = useStepper();
121
+ /**
122
+ * Shared chevron nav button, matching the Figma header action bar (Body-HeaderBar-1.0) — the
123
+ * Glare `Button` icon variant, the same control Select/SearchableSelect use for their chevrons.
124
+ */
125
+ function StepNavButton({
126
+ dir,
127
+ onClick,
128
+ disabled,
129
+ }: {
130
+ dir: "left" | "right";
131
+ onClick: () => void;
132
+ disabled: boolean;
133
+ }) {
112
134
  return (
113
- <Button type="button" variant="BorderStyle" disabled={isFirstStep} onClick={goToPrevious}>
114
- {children ?? "Back"}
135
+ <Button
136
+ type="button"
137
+ buttonType="icon"
138
+ size="M"
139
+ onClick={onClick}
140
+ disabled={disabled}
141
+ aria-label={dir === "left" ? "Previous step" : "Next step"}
142
+ >
143
+ <i
144
+ className={cn(
145
+ "text-[18px]",
146
+ dir === "left" ? "ri-arrow-left-s-line" : "ri-arrow-right-s-line",
147
+ )}
148
+ />
115
149
  </Button>
116
150
  );
117
151
  }
118
152
 
119
- export function Next({ children }: { children?: React.ReactNode }) {
153
+ /** `FormBuilder.Back` chevron to the previous step; disabled on the first. */
154
+ export function Back() {
155
+ const { goToPrevious, isFirstStep } = useStepper();
156
+ return <StepNavButton dir="left" onClick={goToPrevious} disabled={isFirstStep} />;
157
+ }
158
+
159
+ /** `FormBuilder.Next` — chevron to the next step (validates first); disabled on the last. */
160
+ export function Next() {
120
161
  const { goToNext, isLastStep } = useStepper();
121
- if (isLastStep) return null;
162
+ return <StepNavButton dir="right" onClick={() => void goToNext()} disabled={isLastStep} />;
163
+ }
164
+
165
+ // ─── Stepper action bar (Back/Next + divider, then the Submit) ────────────────
166
+
167
+ /**
168
+ * `FormRenderer` wraps its `actions` in this. When the form is a stepper it prepends the
169
+ * chevron `Back`/`Next` controls + a divider before the (user-provided) Submit — the Figma
170
+ * `Body-HeaderBar-1.0` layout. Outside a stepper there's no `StepperContext`, so it renders
171
+ * the actions untouched.
172
+ */
173
+ export function StepperActions({ children }: { children?: React.ReactNode }) {
174
+ const stepper = React.useContext(StepperContext);
175
+ if (!stepper) return <>{children}</>;
122
176
  return (
123
- <Button type="button" variant="PrimeStyle" onClick={() => void goToNext()}>
124
- {children ?? "Next"}
125
- </Button>
177
+ <div className="flex items-center gap-2">
178
+ <Back />
179
+ <Next />
180
+ {/* Divider — white-alpha hairline between the nav and the Submit. */}
181
+ <span aria-hidden className="mx-1 h-5 w-px rounded-[2px] bg-white-alpha-20" />
182
+ {children}
183
+ </div>
126
184
  );
127
185
  }
128
186
 
@@ -170,13 +228,27 @@ export function useStepperState(
170
228
  trigger: TriggerFn,
171
229
  ): StepperContextValue {
172
230
  const [currentStep, setCurrentStep] = React.useState(0);
231
+ // Steps that have passed their last validation — kept so their checkmark persists when the
232
+ // user navigates back to an earlier step.
233
+ const [completedSteps, setCompletedSteps] = React.useState<Set<number>>(new Set());
173
234
  const stepFieldsRef = React.useRef<Record<number, Set<string>>>({});
174
235
  const titles = steps.map((s) => s.props.title);
175
236
  const lastIndex = steps.length - 1;
176
237
 
238
+ const markStep = (step: number, passed: boolean) =>
239
+ setCompletedSteps((prev) => {
240
+ if (passed === prev.has(step)) return prev; // no change
241
+ const next = new Set(prev);
242
+ if (passed) next.add(step);
243
+ else next.delete(step);
244
+ return next;
245
+ });
246
+
177
247
  const validateStep = async (step: number) => {
178
248
  const names = [...(stepFieldsRef.current[step] ?? [])] as FieldPath<FieldValues>[];
179
- return names.length === 0 ? true : trigger(names);
249
+ const passed = names.length === 0 ? true : await trigger(names);
250
+ markStep(step, passed);
251
+ return passed;
180
252
  };
181
253
 
182
254
  // Navigation runs through the step buttons. Backward is free; going forward
@@ -211,6 +283,7 @@ export function useStepperState(
211
283
  goToPrevious: () => setCurrentStep((s) => Math.max(s - 1, 0)),
212
284
  goToStep,
213
285
  stepFields: stepFieldsRef.current,
286
+ completedSteps,
214
287
  };
215
288
  }
216
289
 
@@ -3,7 +3,7 @@
3
3
  import { ReactNode } from "react";
4
4
 
5
5
  import { Button } from "../Button";
6
- import { useFormId, useLoading, useMode } from "./context";
6
+ import { useFormId, useLoading } from "./context";
7
7
 
8
8
  export interface SubmitButtonProps {
9
9
  children?: ReactNode;
@@ -21,9 +21,6 @@ export interface SubmitButtonProps {
21
21
  export function SubmitButton({ children, className, loadingText, form }: SubmitButtonProps) {
22
22
  const loading = useLoading();
23
23
  const ctxFormId = useFormId();
24
- const mode = useMode();
25
-
26
- if (mode === "view") return null;
27
24
 
28
25
  return (
29
26
  <Button
@@ -8,7 +8,7 @@ import type {
8
8
  Resolver,
9
9
  UseFormReturn,
10
10
  } from "react-hook-form";
11
- import type { FieldDirection, FormBuilderMode } from "./context";
11
+ import type { FieldDirection } from "./context";
12
12
  import type { SectionColor } from "../SectionBlock";
13
13
 
14
14
  /** Props shared by every `FormBuilder.*` field. `name` is the RHF path. */
@@ -69,13 +69,12 @@ export interface FileFieldProps extends BaseFieldProps {
69
69
  multiple?: boolean;
70
70
  }
71
71
 
72
- /** `FormBuilder.Custom` — bring your own control + optional read-only render. */
72
+ /** `FormBuilder.Custom` — bring your own control, keeping the FieldSection + validation wiring. */
73
73
  export interface CustomFieldProps extends BaseFieldProps {
74
74
  render: (args: {
75
75
  field: ControllerRenderProps<FieldValues, string>;
76
76
  fieldState: ControllerFieldState;
77
77
  }) => ReactNode;
78
- formatView?: (value: unknown) => ReactNode;
79
78
  }
80
79
 
81
80
  /** `FormBuilder.Date` / `.DateRange` / `.DateMultiple` / `.DateTime`. */
@@ -248,8 +247,6 @@ export interface FormBuilderRootProps<T extends FieldValues = FieldValues> {
248
247
  values?: T;
249
248
  /** Loading flag — Submit shows a spinner and inputs disable. */
250
249
  loading?: boolean;
251
- /** `"edit"` (default) or read-only `"view"`. */
252
- mode?: FormBuilderMode;
253
250
  /** Field row direction. Defaults to horizontal (vertical inside a drawer). */
254
251
  fieldDirection?: FieldDirection;
255
252
  /** Reset to defaults after a successful submit. */
@@ -106,8 +106,15 @@ export function FormDrawer({
106
106
  {actions}
107
107
  </FormHeaderBar>
108
108
 
109
- {/* pt-[72px] clears the 44px header pill (inset 4px) — same as the page shell. */}
110
- <div className="h-full overflow-y-auto px-3 pb-[20px] pt-[72px]">{children}</div>
109
+ {/* pt-[72px] clears the 44px header pill (inset 4px) — same as the page shell. The
110
+ 48px bottom breathing-room goes on an inner wrapper, not the scroll container:
111
+ FormBuilder's outer element is `h-full`, which would pin it to the content box and
112
+ swallow the container's own `pb`. On a plain wrapper that `h-full` resolves to the
113
+ content height, so the padding actually lengthens the scroll. The conclusion panel
114
+ is a separate sibling, so it keeps its own spacing. */}
115
+ <div className="h-full overflow-y-auto px-3 pt-[72px]">
116
+ <div className="pb-[48px]">{children}</div>
117
+ </div>
111
118
  </div>
112
119
  </DrawerPanel>
113
120
 
@@ -4,7 +4,8 @@ import { useId } from "react";
4
4
  import { FieldValues } from "react-hook-form";
5
5
 
6
6
  import { FormBuilder } from "../FormBuilder";
7
- import { FormIdContext, LoadingContext, ModeContext } from "../FormBuilder/context";
7
+ import { FormIdContext, LoadingContext } from "../FormBuilder/context";
8
+ import { StepperActions } from "../FormBuilder/stepper";
8
9
  import { FormDrawer } from "./FormDrawer";
9
10
  import type { FormRendererProps } from "./types";
10
11
 
@@ -25,7 +26,6 @@ export function FormRenderer<T extends FieldValues = FieldValues>({
25
26
  resolver,
26
27
  defaultValues,
27
28
  values,
28
- mode = "edit",
29
29
  loading,
30
30
  resetOnSuccess,
31
31
  fieldDirection,
@@ -61,7 +61,6 @@ export function FormRenderer<T extends FieldValues = FieldValues>({
61
61
  resolver={resolver}
62
62
  defaultValues={defaultValues}
63
63
  values={values}
64
- mode={mode}
65
64
  loading={loading}
66
65
  fieldDirection={effectiveDirection}
67
66
  resetOnSuccess={resetOnSuccess}
@@ -73,7 +72,8 @@ export function FormRenderer<T extends FieldValues = FieldValues>({
73
72
  >
74
73
  {useHeader && (
75
74
  <FormBuilder.Header title={header!.title} label={header!.label} variant={header!.variant}>
76
- {actions}
75
+ {/* A stepper form prepends chevron Back/Next + a divider before the Submit. */}
76
+ {actions && <StepperActions>{actions}</StepperActions>}
77
77
  </FormBuilder.Header>
78
78
  )}
79
79
 
@@ -90,15 +90,13 @@ export function FormRenderer<T extends FieldValues = FieldValues>({
90
90
  badge={badge ?? header?.label}
91
91
  variant={header?.variant}
92
92
  summary={summary}
93
- // The drawer header sits outside the `<form>`, so re-supply the form/loading/mode context
94
- // a bare `FormBuilder.Submit` reads (on the page it gets these from the FormBuilder tree).
93
+ // The drawer header sits outside the `<form>`, so re-supply the form-id and loading
94
+ // context a bare `FormBuilder.Submit` reads (on the page it gets these from the tree).
95
95
  actions={
96
96
  actions ? (
97
- <ModeContext.Provider value={mode}>
98
- <LoadingContext.Provider value={!!loading}>
99
- <FormIdContext.Provider value={formId}>{actions}</FormIdContext.Provider>
100
- </LoadingContext.Provider>
101
- </ModeContext.Provider>
97
+ <LoadingContext.Provider value={!!loading}>
98
+ <FormIdContext.Provider value={formId}>{actions}</FormIdContext.Provider>
99
+ </LoadingContext.Provider>
102
100
  ) : undefined
103
101
  }
104
102
  onOpenInNewTab={onOpenInNewTab}
@@ -1,9 +1,4 @@
1
1
  export { FormRenderer } from "./form-renderer";
2
2
  export { FormDrawer } from "./FormDrawer";
3
3
  export type { FormDrawerProps } from "./FormDrawer";
4
- export type {
5
- FormRendererProps,
6
- FormRendererMode,
7
- FormRendererDisplay,
8
- FieldDirection,
9
- } from "./types";
4
+ export type { FormRendererProps, FormRendererDisplay, FieldDirection } from "./types";
@@ -15,7 +15,6 @@ import type {
15
15
  * form's header / drawer action bar) where you place the Save.
16
16
  */
17
17
 
18
- export type FormRendererMode = "edit" | "view";
19
18
  export type FormRendererDisplay = "page" | "drawer";
20
19
  export type FieldDirection = "horizontal" | "vertical";
21
20
 
@@ -29,7 +28,6 @@ export interface FormRendererProps<T extends FieldValues = FieldValues> {
29
28
  resolver?: Resolver<T>;
30
29
  defaultValues?: DefaultValues<T>;
31
30
  values?: T;
32
- mode?: FormRendererMode;
33
31
  loading?: boolean;
34
32
  resetOnSuccess?: boolean;
35
33
  /** Row layout; defaults to vertical inside a drawer. */
@@ -239,10 +239,14 @@ const popoverStyles = cva(
239
239
  "bg-background-system-body-primary",
240
240
  "shadow-[0px_0px_18px_0px_rgba(0,0,0,0.75)]",
241
241
  ],
242
+ // Adopts the DropdownMenu surface (`menuContentStyles`): backdrop-blurred, borderless
243
+ // rounded-14 panel with a soft ambient shadow — keeping the original background color.
242
244
  PresentationStyle: [
243
- "border-border-presentation-global-primary",
245
+ "border-transparent",
246
+ "rounded-[14px]",
247
+ "backdrop-blur-[21px]",
244
248
  "bg-background-presentation-form-base",
245
- "shadow-[0px_0px_10px_0px_rgba(0,0,0,0.4),0px_4px_4px_0px_rgba(0,0,0,0.2)]",
249
+ "shadow-[0_0_32px_2px_rgba(0,0,0,0.20),0_0_48px_2px_rgba(0,0,0,0.05)]",
246
250
  ],
247
251
  },
248
252
  overlayBlur: {
@@ -24,6 +24,9 @@ export interface SearchableSelectOption {
24
24
  value: string;
25
25
  label: string;
26
26
  icon?: ReactNode;
27
+ /** Text used for client-side filtering. Defaults to `label` — set it to match on a subset
28
+ * (e.g. a country name only, when the label also shows a dial code). */
29
+ searchText?: string;
27
30
  }
28
31
 
29
32
  interface Props {
@@ -38,6 +41,7 @@ interface Props {
38
41
  theme?: Themes;
39
42
  dir?: string;
40
43
  className?: string;
44
+ inputClassName?: string;
41
45
  /** Transparent border/background so the trigger blends into a table cell. */
42
46
  onTable?: boolean;
43
47
 
@@ -107,6 +111,7 @@ export function SearchableSelect({
107
111
  theme,
108
112
  dir,
109
113
  className,
114
+ inputClassName,
110
115
  onTable,
111
116
  filterClientSide = true,
112
117
  onSearchChange,
@@ -155,7 +160,7 @@ export function SearchableSelect({
155
160
  if (!filterClientSide) return options;
156
161
  const q = search.trim().toLowerCase();
157
162
  if (!q) return options;
158
- return options.filter((o) => o.label.toLowerCase().includes(q));
163
+ return options.filter((o) => (o.searchText ?? o.label).toLowerCase().includes(q));
159
164
  }, [options, search, filterClientSide]);
160
165
 
161
166
  const selectedOption = options.find((o) => o.value === value) ?? null;
@@ -201,7 +206,7 @@ export function SearchableSelect({
201
206
  setOpen(true);
202
207
  }}
203
208
  onBlur={() => setSearching(false)}
204
- className={cn("min-w-[100px] flex-1", {
209
+ className={cn("min-w-[100px] flex-1", inputClassName, {
205
210
  "!h-[18px]": size === "XS",
206
211
  "!h-[22px]": size === "S",
207
212
  "!h-[24px]": size === "M",
@@ -31,32 +31,35 @@ export function FieldSection({
31
31
  <section
32
32
  {...props}
33
33
  data-theme={theme}
34
- className={cn(
35
- "grid py-[16px] px-[12px] w-full max-w-[1200px] min-w-[0px] ",
36
- direction === "vertical" && "grid-rows-[auto_1fr] gap-[12px]",
37
- direction === "horizontal" && "grid-cols-[350px_1fr] gap-[24px]",
38
- direction === "flexible" &&
39
- "grid-rows-[auto_1fr] gap-[12px] lg:grid-cols-[350px_1fr] lg:grid-rows-[1fr] lg:gap-[24px]",
40
- className,
41
- )}
34
+ className={cn("w-full max-w-[1200px] min-w-[0px] @container", className)}
42
35
  >
43
- {/* Fixed width section for labels */}
44
- <div className="flex flex-col gap-[12px]">
45
- {label && (
46
- <Label
47
- size={size}
48
- label={label}
49
- requiredLabel={requiredLabel}
50
- labelDirections={"horizontal"}
51
- />
36
+ <div
37
+ className={cn(
38
+ "grid py-[16px] px-[12px] w-full min-w-[0px]",
39
+ direction === "vertical" && "grid-rows-[auto_1fr] gap-[12px]",
40
+ direction === "horizontal" && "grid-cols-[350px_1fr] gap-[24px]",
41
+ direction === "flexible" &&
42
+ "grid-rows-[auto_1fr] gap-[12px] @md:grid-cols-[350px_1fr] @md:grid-rows-[1fr] @md:gap-[24px]",
52
43
  )}
44
+ >
45
+ {/* Fixed width section for labels */}
46
+ <div className="flex flex-col gap-[12px]">
47
+ {label && (
48
+ <Label
49
+ size={size}
50
+ label={label}
51
+ requiredLabel={requiredLabel}
52
+ labelDirections={"horizontal"}
53
+ />
54
+ )}
53
55
 
54
- {secondaryLabel && <Label size={size} secondaryLabel={secondaryLabel} />}
55
- {childrenUnderLabel}
56
- </div>
56
+ {secondaryLabel && <Label size={size} secondaryLabel={secondaryLabel} />}
57
+ {childrenUnderLabel}
58
+ </div>
57
59
 
58
- {/* Flexible section that takes up the remaining space */}
59
- <div className="grid grid-cols-1 place-items-end gap-[12px]">{children}</div>
60
+ {/* Flexible section that takes up the remaining space */}
61
+ <div className="grid grid-cols-1 place-items-end gap-[12px]">{children}</div>
62
+ </div>
60
63
  </section>
61
64
  );
62
65
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.4.2",
2
+ "version": "2.4.3",
3
3
  "generatedBy": "scripts/bin/generateRegistry",
4
4
  "items": [
5
5
  {