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
@@ -2,17 +2,12 @@
2
2
 
3
3
  import { createContext, useContext } from "react";
4
4
 
5
- export type FormBuilderMode = "edit" | "view";
6
5
  export type FieldDirection = "horizontal" | "vertical";
7
6
 
8
7
  /** Loading flag — drives the Submit spinner and disables inputs. */
9
8
  export const LoadingContext = createContext<boolean>(false);
10
9
  export const useLoading = () => useContext(LoadingContext);
11
10
 
12
- /** Edit vs read-only. Fields render `DisplayField` in `"view"`. */
13
- export const ModeContext = createContext<FormBuilderMode>("edit");
14
- export const useMode = () => useContext(ModeContext);
15
-
16
11
  /**
17
12
  * The `<form>`'s `id`. Provided by the FormBuilder root so a `FormBuilder.Submit` rendered
18
13
  * **outside** the `<form>` element — the absolute header's action pill, or the drawer header —
@@ -62,6 +57,8 @@ export interface StepperContextValue {
62
57
  goToStep: (index: number) => void;
63
58
  /** Field names registered per step, for per-step validation. */
64
59
  stepFields: Record<number, Set<string>>;
60
+ /** Steps that have passed validation — stay checked even after navigating back. */
61
+ completedSteps: Set<number>;
65
62
  }
66
63
  export const StepperContext = createContext<StepperContextValue | null>(null);
67
64
  export const useStepper = () => {
@@ -4,7 +4,6 @@ import { LabeledCheckBox } from "../../LabeledCheckBox";
4
4
  import { RadioGroup } from "../../Radio";
5
5
  import { RadioCard } from "../../RadioCard";
6
6
  import { useLoading } from "../context";
7
- import { formatFieldView } from "../viewFormat";
8
7
  import type { CheckboxFieldProps, RadioCardsFieldProps } from "../types";
9
8
  import { FieldShell } from "./FieldShell";
10
9
 
@@ -16,7 +15,7 @@ import { FieldShell } from "./FieldShell";
16
15
  export function CheckboxField({ subLabel, ...props }: CheckboxFieldProps) {
17
16
  const loading = useLoading();
18
17
  return (
19
- <FieldShell {...props} view={(v) => formatFieldView({ kind: "boolean", value: v })}>
18
+ <FieldShell {...props}>
20
19
  {(field) => (
21
20
  <div className="flex w-full items-center ps-2.5">
22
21
  <LabeledCheckBox
@@ -36,12 +35,8 @@ export function CheckboxField({ subLabel, ...props }: CheckboxFieldProps) {
36
35
  /** `FormBuilder.RadioCards` — radio options rendered as cards (Glare `RadioCard`). */
37
36
  export function RadioCardsField(props: RadioCardsFieldProps) {
38
37
  const loading = useLoading();
39
- const options = props.options.map((o) => ({
40
- label: typeof o.label === "string" ? o.label : String(o.value),
41
- value: o.value,
42
- }));
43
38
  return (
44
- <FieldShell {...props} view={(v) => formatFieldView({ kind: "option", value: v, options })}>
39
+ <FieldShell {...props}>
45
40
  {(field) => (
46
41
  <RadioGroup
47
42
  value={field.value ?? ""}
@@ -15,24 +15,7 @@ export function ColorField(props: ColorFieldProps) {
15
15
  const loading = useLoading();
16
16
 
17
17
  return (
18
- <FieldShell
19
- {...props}
20
- view={(v) =>
21
- v
22
- ? {
23
- valueNode: (
24
- <span className="inline-flex items-center gap-2">
25
- <span
26
- className="h-4 w-4 rounded-[4px] border border-border-presentation-action-primary"
27
- style={{ background: String(v) }}
28
- />
29
- {String(v)}
30
- </span>
31
- ),
32
- }
33
- : { value: "" }
34
- }
35
- >
18
+ <FieldShell {...props}>
36
19
  {(field) => {
37
20
  const value = typeof field.value === "string" ? field.value : "";
38
21
  const disabled = props.disabled || loading;
@@ -6,11 +6,6 @@ import { FieldShell } from "./FieldShell";
6
6
  /** `FormBuilder.Custom` — render any control, keeping the FieldSection + validation wiring. */
7
7
  export function CustomField(props: CustomFieldProps) {
8
8
  return (
9
- <FieldShell
10
- {...props}
11
- view={(v) => ({ valueNode: props.formatView ? props.formatView(v) : String(v ?? "—") })}
12
- >
13
- {(field, fieldState) => props.render({ field, fieldState })}
14
- </FieldShell>
9
+ <FieldShell {...props}>{(field, fieldState) => props.render({ field, fieldState })}</FieldShell>
15
10
  );
16
11
  }
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { DatePicker } from "../../DatePicker";
4
4
  import { useCell } from "../context";
5
- import { formatFieldView } from "../viewFormat";
6
5
  import type { DateFieldProps } from "../types";
7
6
  import { FieldShell } from "./FieldShell";
8
7
 
@@ -12,11 +11,10 @@ import { FieldShell } from "./FieldShell";
12
11
  * `.DateTime` (date + time). All back onto the one Glare `DatePicker`.
13
12
  */
14
13
  export function DateField(props: DateFieldProps) {
15
- const kind = props.mode === "range" ? "daterange" : props.mode === "multiple" ? "dates" : "date";
16
14
  const cell = useCell();
17
15
 
18
16
  return (
19
- <FieldShell {...props} view={(v) => formatFieldView({ kind, value: v })}>
17
+ <FieldShell {...props}>
20
18
  {(field) => (
21
19
  <DatePicker
22
20
  mode={props.mode ?? "single"}
@@ -4,17 +4,14 @@ import { useFormContext, useFieldArray, type FieldValues, type ArrayPath } from
4
4
 
5
5
  import { Button } from "../../Button";
6
6
  import { Card } from "../../Card";
7
- import { useMode } from "../context";
8
7
  import type { FieldArrayProps } from "../types";
9
8
 
10
9
  /**
11
10
  * `FormBuilder.FieldArray` — a repeating list of sub-fields (RHF `useFieldArray`).
12
- * `children` renders one row's fields; name them `${rowName}.field`. Add/Remove is
13
- * hidden in view mode.
11
+ * `children` renders one row's fields; name them `${rowName}.field`.
14
12
  */
15
13
  export function FieldArray(props: FieldArrayProps) {
16
14
  const form = useFormContext<FieldValues>();
17
- const mode = useMode();
18
15
  const { fields, append, remove } = useFieldArray({
19
16
  control: form.control,
20
17
  name: props.name as ArrayPath<FieldValues>,
@@ -22,7 +19,7 @@ export function FieldArray(props: FieldArrayProps) {
22
19
 
23
20
  if (props.hidden) return null;
24
21
 
25
- const addButton = mode !== "view" && (
22
+ const addButton = (
26
23
  <Button
27
24
  type="button"
28
25
  size="S"
@@ -55,16 +52,14 @@ export function FieldArray(props: FieldArrayProps) {
55
52
  <div className="flex flex-col gap-3">
56
53
  {fields.map((f, index) => (
57
54
  <Card key={f.id} className="relative gap-3 pr-8">
58
- {mode !== "view" && (
59
- <button
60
- type="button"
61
- aria-label="Remove"
62
- onClick={() => remove(index)}
63
- className="absolute right-2 top-2 inline-flex h-[22px] w-[22px] items-center justify-center rounded-full text-content-presentation-state-negative transition-colors hover:bg-background-presentation-action-hover"
64
- >
65
- <i className="ri-close-line text-[16px]" />
66
- </button>
67
- )}
55
+ <button
56
+ type="button"
57
+ aria-label="Remove"
58
+ onClick={() => remove(index)}
59
+ className="absolute right-2 top-2 inline-flex h-[22px] w-[22px] items-center justify-center rounded-full text-content-presentation-state-negative transition-colors hover:bg-background-presentation-action-hover"
60
+ >
61
+ <i className="ri-close-line text-[16px]" />
62
+ </button>
68
63
  {props.children(`${props.name}.${index}`, index, () => remove(index))}
69
64
  </Card>
70
65
  ))}
@@ -15,9 +15,7 @@ import { FieldSection } from "../../../layouts/FieldSection";
15
15
  import { FormField, FormItem, FormControl } from "../../Form";
16
16
  import { FieldHint } from "../../FieldHint";
17
17
  import { Tooltip } from "../../Tooltip";
18
- import { DisplayField } from "../DisplayField";
19
- import { useMode, useDirection, useStepRegistry, useCell } from "../context";
20
- import type { FieldView } from "../viewFormat";
18
+ import { useDirection, useStepRegistry, useCell } from "../context";
21
19
 
22
20
  export interface FieldShellProps {
23
21
  name: string;
@@ -28,20 +26,18 @@ export interface FieldShellProps {
28
26
  hidden?: boolean;
29
27
  /** Force the field's layout direction, overriding the form's `useDirection()` context. */
30
28
  direction?: "horizontal" | "vertical" | "flexible";
31
- /** Edit-mode input, wired to the react-hook-form field. */
29
+ /** The input, wired to the react-hook-form field. */
32
30
  children: (
33
31
  field: ControllerRenderProps<FieldValues, string>,
34
32
  fieldState: ControllerFieldState,
35
33
  ) => ReactNode;
36
- /** Read-only rendering (view mode) computed from the current value. */
37
- view?: (value: unknown) => FieldView;
38
34
  }
39
35
 
40
36
  /**
41
37
  * Shared wrapper for every `FormBuilder.*` field: the FieldSection row + the RHF
42
- * `FormField`/`FormItem`/`FormControl`/`FormMessage` scaffolding in edit mode, or
43
- * `DisplayField` in view mode. Also registers the field name into the enclosing
44
- * `FormBuilder.Step` (if any) so the stepper can validate per step.
38
+ * `FormField`/`FormItem`/`FormControl`/`FormMessage` scaffolding. Also registers the
39
+ * field name into the enclosing `FormBuilder.Step` (if any) so the stepper can
40
+ * validate per step.
45
41
  */
46
42
  export function FieldShell({
47
43
  name,
@@ -52,10 +48,8 @@ export function FieldShell({
52
48
  hidden,
53
49
  direction: directionProp,
54
50
  children,
55
- view,
56
51
  }: FieldShellProps) {
57
52
  const form = useFormContext();
58
- const mode = useMode();
59
53
  const cell = useCell();
60
54
  const ctxDirection = useDirection();
61
55
  // A field may pin its own direction (e.g. RichText forces vertical), else the form's. When
@@ -82,13 +76,8 @@ export function FieldShell({
82
76
 
83
77
  // Cell mode (inside FormBuilder.Table): render just the control — no FieldSection label/row.
84
78
  // Errors surface as a tooltip on the control rather than a stacked FieldHint, so a row stays
85
- // one line tall. Step registration + view formatting above still apply.
79
+ // one line tall. Step registration above still applies.
86
80
  if (cell) {
87
- if (mode === "view") {
88
- const value = form.getValues(name);
89
- const v = view ? view(value) : { value: value == null ? "" : String(value) };
90
- return <DisplayField value={v.value} valueNode={v.valueNode} />;
91
- }
92
81
  return (
93
82
  <FormField
94
83
  control={form.control}
@@ -111,21 +100,6 @@ export function FieldShell({
111
100
  );
112
101
  }
113
102
 
114
- if (mode === "view") {
115
- const value = form.getValues(name);
116
- const v = view ? view(value) : { value: value == null ? "" : String(value) };
117
- return (
118
- <FieldSection
119
- label={label}
120
- secondaryLabel={description}
121
- direction={direction}
122
- className={fullWidth ? "max-w-full" : undefined}
123
- >
124
- <DisplayField value={v.value} valueNode={v.valueNode} />
125
- </FieldSection>
126
- );
127
- }
128
-
129
103
  return (
130
104
  <FieldSection
131
105
  label={label}
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { ImageAttachment } from "../../ImageAttachment";
4
4
  import { useLoading } from "../context";
5
- import { formatFieldView } from "../viewFormat";
6
5
  import type { FileFieldProps } from "../types";
7
6
  import { FieldShell } from "./FieldShell";
8
7
 
@@ -10,7 +9,7 @@ import { FieldShell } from "./FieldShell";
10
9
  export function FileField(props: FileFieldProps & { image?: boolean }) {
11
10
  const loading = useLoading();
12
11
  return (
13
- <FieldShell {...props} view={(v) => formatFieldView({ kind: "file", value: v })}>
12
+ <FieldShell {...props}>
14
13
  {(field) => {
15
14
  const current: unknown = field.value;
16
15
  const fileName = Array.isArray(current)
@@ -7,7 +7,6 @@ import { Label } from "../../Label";
7
7
  import { RadioGroup, Radio } from "../../Radio";
8
8
  import { Checkbox } from "../../Checkbox";
9
9
  import { useLoading } from "../context";
10
- import { formatFieldView } from "../viewFormat";
11
10
  import type { OptionItem, OptionsFieldProps } from "../types";
12
11
  import { FieldShell } from "./FieldShell";
13
12
 
@@ -66,10 +65,7 @@ function OptionRow({
66
65
  export function RadioListField(props: OptionsFieldProps) {
67
66
  const loading = useLoading();
68
67
  return (
69
- <FieldShell
70
- {...props}
71
- view={(v) => formatFieldView({ kind: "option", value: v, options: props.options })}
72
- >
68
+ <FieldShell {...props}>
73
69
  {(field) => (
74
70
  <RadioGroup value={field.value ?? ""} onValueChange={field.onChange} className="w-full">
75
71
  <OptionBox>
@@ -102,10 +98,7 @@ export function RadioListField(props: OptionsFieldProps) {
102
98
  export function CheckboxGroupField(props: OptionsFieldProps) {
103
99
  const loading = useLoading();
104
100
  return (
105
- <FieldShell
106
- {...props}
107
- view={(v) => formatFieldView({ kind: "multi", value: v, options: props.options })}
108
- >
101
+ <FieldShell {...props}>
109
102
  {(field) => {
110
103
  const selected: string[] = Array.isArray(field.value) ? field.value : [];
111
104
  const toggle = (value: string, checked: boolean) => {
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { InputOTP, InputOTPGroup, InputOTPSlot } from "../../InputOTP";
4
4
  import { useLoading } from "../context";
5
- import { formatFieldView } from "../viewFormat";
6
5
  import type { OtpFieldProps } from "../types";
7
6
  import { FieldShell } from "./FieldShell";
8
7
 
@@ -12,7 +11,7 @@ export function OtpField(props: OtpFieldProps) {
12
11
  const length = props.length ?? 6;
13
12
 
14
13
  return (
15
- <FieldShell {...props} view={(v) => formatFieldView({ kind: "text", value: v })}>
14
+ <FieldShell {...props}>
16
15
  {(field) => (
17
16
  <InputOTP
18
17
  maxLength={length}
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
 
3
- import { Select, SelectTrigger, SelectContent, SelectItem } from "../../Select";
3
+ import { SearchableSelect } from "../../SearchableSelect";
4
4
  import { InputField } from "../../InputField";
5
+ import { cn } from "../../../utils/cn";
5
6
  import { useLoading, useCell } from "../context";
6
- import { formatFieldView } from "../viewFormat";
7
7
  import type { PhoneFieldProps } from "../types";
8
8
  import { FieldShell } from "./FieldShell";
9
9
  import { COUNTRIES, countryByCode, countryByDial, flagEmoji } from "./countries";
@@ -11,6 +11,14 @@ import { COUNTRIES, countryByCode, countryByDial, flagEmoji } from "./countries"
11
11
  // Dial codes longest-first, so "+1" doesn't shadow "+1…"-style codes when prefix-matching.
12
12
  const DIALS = [...new Set(COUNTRIES.map((c) => c.dial))].sort((a, b) => b.length - a.length);
13
13
 
14
+ // Country picker rows. Keyed by the unique ISO code; the row shows flag + dial, but `searchText`
15
+ // makes filtering match the country NAME (typing a dial code won't match).
16
+ const COUNTRY_OPTIONS = COUNTRIES.map((c) => ({
17
+ value: c.code,
18
+ label: `${flagEmoji(c.code)} ${c.dial}`,
19
+ searchText: c.name,
20
+ }));
21
+
14
22
  function splitPhone(value: unknown, defaultDial: string): { dial: string; number: string } {
15
23
  const s = typeof value === "string" ? value.trim() : "";
16
24
  if (!s) return { dial: defaultDial, number: "" };
@@ -26,8 +34,9 @@ function splitPhone(value: unknown, defaultDial: string): { dial: string; number
26
34
  }
27
35
 
28
36
  /**
29
- * `FormBuilder.Phone` — country dial-code (every country) + number. The picker is keyed by the
30
- * unique ISO code (dial codes repeat); the value is stored as a `"+<dial> <number>"` string.
37
+ * `FormBuilder.Phone` — country dial-code (every country) + number. The picker is a
38
+ * `SearchableSelect` keyed by the unique ISO code (dial codes repeat) type to filter by
39
+ * country name or dial. The value is stored as a `"+<dial> <number>"` string.
31
40
  */
32
41
  export function PhoneField(props: PhoneFieldProps) {
33
42
  const loading = useLoading();
@@ -35,7 +44,7 @@ export function PhoneField(props: PhoneFieldProps) {
35
44
  const defaultDial = props.defaultCountry ?? "+964"; // Iraq
36
45
 
37
46
  return (
38
- <FieldShell {...props} view={(v) => formatFieldView({ kind: "text", value: v })}>
47
+ <FieldShell {...props}>
39
48
  {(field) => {
40
49
  const { dial, number } = splitPhone(field.value, defaultDial);
41
50
  const country = countryByDial(dial);
@@ -43,25 +52,17 @@ export function PhoneField(props: PhoneFieldProps) {
43
52
  const commit = (d: string, n: string) => field.onChange(n ? `${d} ${n}` : d);
44
53
  return (
45
54
  <div className="flex w-full min-w-0 items-center gap-2">
46
- <Select
47
- value={country?.code ?? ""}
55
+ {/* Searchable country picker — filter by name or dial; rows carry the full country name. */}
56
+ <SearchableSelect
57
+ options={COUNTRY_OPTIONS}
58
+ value={country?.code ?? null}
48
59
  onValueChange={(code) => commit(countryByCode(code)?.dial ?? dial, number)}
49
- disabled={disabled}
50
- >
51
- {/* Compact trigger — flag + dial. The dropdown rows carry the full country name. */}
52
- <SelectTrigger size="XL" onTable={cell} className="shrink-0">
53
- <span className="whitespace-nowrap">
54
- {country ? `${flagEmoji(country.code)} ${country.dial}` : dial}
55
- </span>
56
- </SelectTrigger>
57
- <SelectContent>
58
- {COUNTRIES.map((c) => (
59
- <SelectItem key={c.code} value={c.code}>
60
- {`${flagEmoji(c.code)} ${c.name} ${c.dial}`}
61
- </SelectItem>
62
- ))}
63
- </SelectContent>
64
- </Select>
60
+ placeholder="+964"
61
+ size="M"
62
+ onTable={cell}
63
+ inputClassName="w-[100px] min-w-[0px]"
64
+ className={cn("w-[113px] shrink-0", disabled && "pointer-events-none opacity-60")}
65
+ />
65
66
  <div className="min-w-0 flex-1">
66
67
  <InputField
67
68
  type="tel"
@@ -6,7 +6,6 @@ import type { OutputData } from "@editorjs/editorjs";
6
6
  // statically re-exports `TextEditor` (which loads EditorJS at module init) and would drag
7
7
  // it into the SSR bundle, crashing server rendering. This path stays SSR-safe (lazy inside).
8
8
  import { RichTextField as RichTextEditor } from "../../TextEditor/RichTextField";
9
- import { formatFieldView } from "../viewFormat";
10
9
  import type { BaseFieldProps } from "../types";
11
10
  import { FieldShell } from "./FieldShell";
12
11
 
@@ -18,12 +17,7 @@ import { FieldShell } from "./FieldShell";
18
17
  */
19
18
  export function RichTextField(props: BaseFieldProps) {
20
19
  return (
21
- <FieldShell
22
- {...props}
23
- direction="vertical"
24
- fullWidth
25
- view={(v) => formatFieldView({ kind: "richtext", value: v })}
26
- >
20
+ <FieldShell {...props} direction="vertical" fullWidth>
27
21
  {(field) => (
28
22
  <RichTextEditor
29
23
  data={field.value as OutputData | undefined}
@@ -5,7 +5,6 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ".
5
5
  import { BadgeField } from "../../BadgeField";
6
6
  import type { Tag } from "../../../hooks/useTagSelection";
7
7
  import { useLoading, useCell } from "../context";
8
- import { formatFieldView } from "../viewFormat";
9
8
  import type { SelectFieldProps, SearchableSelectFieldProps, OptionsFieldProps } from "../types";
10
9
  import { FieldShell } from "./FieldShell";
11
10
 
@@ -15,10 +14,7 @@ export function SelectField(props: SelectFieldProps) {
15
14
  const cell = useCell();
16
15
 
17
16
  return (
18
- <FieldShell
19
- {...props}
20
- view={(v) => formatFieldView({ kind: "option", value: v, options: props.options })}
21
- >
17
+ <FieldShell {...props}>
22
18
  {(field) => (
23
19
  <Select
24
20
  value={typeof field.value === "string" ? field.value : ""}
@@ -48,10 +44,7 @@ export function SelectField(props: SelectFieldProps) {
48
44
  export function SearchableSelectField(props: SearchableSelectFieldProps) {
49
45
  const cell = useCell();
50
46
  return (
51
- <FieldShell
52
- {...props}
53
- view={(v) => formatFieldView({ kind: "option", value: v, options: props.options })}
54
- >
47
+ <FieldShell {...props}>
55
48
  {(field) => (
56
49
  <SearchableSelect
57
50
  options={props.options}
@@ -75,10 +68,7 @@ export function SearchableSelectField(props: SearchableSelectFieldProps) {
75
68
  export function MultiSelectField(props: OptionsFieldProps) {
76
69
  const cell = useCell();
77
70
  return (
78
- <FieldShell
79
- {...props}
80
- view={(v) => formatFieldView({ kind: "multi", value: v, options: props.options })}
81
- >
71
+ <FieldShell {...props}>
82
72
  {(field) => {
83
73
  const selected = new Set<string>(Array.isArray(field.value) ? field.value : []);
84
74
  const tags: Tag[] = props.options.map((opt) => ({
@@ -22,25 +22,7 @@ export function SignatureField(props: SignatureFieldProps) {
22
22
  const penColor = props.penColor ?? "#111827";
23
23
 
24
24
  return (
25
- <FieldShell
26
- {...props}
27
- direction="vertical"
28
- fullWidth
29
- view={(v) =>
30
- typeof v === "string" && v
31
- ? {
32
- valueNode: (
33
- // eslint-disable-next-line @next/next/no-img-element -- data-URL preview, no optimization applies
34
- <img
35
- src={v}
36
- alt="Signature"
37
- className="max-h-[120px] rounded-[8px] border border-border-presentation-action-primary bg-white"
38
- />
39
- ),
40
- }
41
- : { value: "" }
42
- }
43
- >
25
+ <FieldShell {...props} direction="vertical" fullWidth>
44
26
  {(field) => (
45
27
  <SignaturePad
46
28
  value={typeof field.value === "string" ? field.value : ""}
@@ -29,12 +29,7 @@ export function SliderField(props: SliderFieldProps) {
29
29
  const fmt = (n: number) => `${n}${suffix}`;
30
30
 
31
31
  return (
32
- <FieldShell
33
- {...props}
34
- view={(v) => ({
35
- value: Array.isArray(v) ? `${fmt(v[0])} – ${fmt(v[1])}` : v == null ? "" : fmt(v as number),
36
- })}
37
- >
32
+ <FieldShell {...props}>
38
33
  {(field) => {
39
34
  const raw = field.value;
40
35
  const values: number[] = props.range
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { Switch } from "../../Switch";
4
4
  import { useLoading } from "../context";
5
- import { formatFieldView } from "../viewFormat";
6
5
  import type { SwitchBoxFieldProps } from "../types";
7
6
  import { FieldShell } from "./FieldShell";
8
7
 
@@ -18,7 +17,7 @@ export function SwitchBoxField({ subLabel, ...props }: SwitchBoxFieldProps) {
18
17
  const loading = useLoading();
19
18
  const hasSubLabel = subLabel != null && subLabel !== "";
20
19
  return (
21
- <FieldShell {...props} view={(v) => formatFieldView({ kind: "boolean", value: v })}>
20
+ <FieldShell {...props}>
22
21
  {(field) => (
23
22
  <div className="flex w-full items-center justify-end gap-[10px] rounded-[8px] bg-background-presentation-form-field-primary px-[10px] py-[6px]">
24
23
  {hasSubLabel && (
@@ -24,7 +24,7 @@ import { SectionBlock } from "../../SectionBlock";
24
24
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../Table";
25
25
  import { Checkbox } from "../../Checkbox";
26
26
  import { Button } from "../../Button";
27
- import { CellContext, useLoading, useMode } from "../context";
27
+ import { CellContext, useLoading } from "../context";
28
28
  import type { TableColumn, TableFieldProps } from "../types";
29
29
 
30
30
  const ALIGN: Record<NonNullable<TableColumn["align"]>, string> = {
@@ -41,9 +41,7 @@ const ALIGN: Record<NonNullable<TableColumn["align"]>, string> = {
41
41
  export function TableField(props: TableFieldProps) {
42
42
  const { name, columns, selectable = true, reorderable = true } = props;
43
43
  const form = useFormContext<FieldValues>();
44
- const mode = useMode();
45
44
  const loading = useLoading();
46
- const isView = mode === "view";
47
45
 
48
46
  const { fields, append, remove, move, replace } = useFieldArray({
49
47
  control: form.control,
@@ -60,8 +58,8 @@ export function TableField(props: TableFieldProps) {
60
58
 
61
59
  if (props.hidden) return null;
62
60
 
63
- const showHandle = reorderable && !isView;
64
- const showSelect = selectable && !isView;
61
+ const showHandle = reorderable;
62
+ const showSelect = selectable;
65
63
 
66
64
  const toggleRow = (id: string) =>
67
65
  setSelected((prev) => {
@@ -193,31 +191,29 @@ export function TableField(props: TableFieldProps) {
193
191
  </TableRow>
194
192
  )}
195
193
 
196
- {!isView && (
197
- <TableRow className="h-[40px]">
198
- <TableCell
199
- colSpan={totalCols}
200
- className="border-t-2 border-transparent hover:border-border-presentation-table-action-hover hover:bg-background-presentation-table-acton-hover"
194
+ <TableRow className="h-[40px]">
195
+ <TableCell
196
+ colSpan={totalCols}
197
+ className="border-t-2 border-transparent hover:border-border-presentation-table-action-hover hover:bg-background-presentation-table-acton-hover"
198
+ >
199
+ <button
200
+ type="button"
201
+ disabled={loading}
202
+ onClick={() => append((props.defaultItem ?? {}) as never)}
203
+ className="flex w-full items-center justify-start gap-2 typography-body-medium-semibold text-content-presentation-action-light-primary [&_i]:text-[20px]"
201
204
  >
202
- <button
203
- type="button"
204
- disabled={loading}
205
- onClick={() => append((props.defaultItem ?? {}) as never)}
206
- className="flex w-full items-center justify-start gap-2 typography-body-medium-semibold text-content-presentation-action-light-primary [&_i]:text-[20px]"
207
- >
208
- <i className="ri-add-line" />
209
- {props.addLabel ?? "Add New"}
210
- </button>
211
- </TableCell>
212
- </TableRow>
213
- )}
205
+ <i className="ri-add-line" />
206
+ {props.addLabel ?? "Add New"}
207
+ </button>
208
+ </TableCell>
209
+ </TableRow>
214
210
  </TableBody>
215
211
  </Table>
216
212
  );
217
213
 
218
- // Header actions on the right of the SectionBlock title row (edit mode only): a "Delete Row"
219
- // that's disabled until rows are selected, and a primary "Add New".
220
- const headerActions = !isView ? (
214
+ // Header actions on the right of the SectionBlock title row: a "Delete Row" that's disabled
215
+ // until rows are selected, and a primary "Add New".
216
+ const headerActions = (
221
217
  <>
222
218
  {selectable && (
223
219
  <Button
@@ -242,7 +238,7 @@ export function TableField(props: TableFieldProps) {
242
238
  {props.addLabel ?? "Add New"}
243
239
  </Button>
244
240
  </>
245
- ) : undefined;
241
+ );
246
242
 
247
243
  return (
248
244
  <SectionBlock