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.
- package/apps/lib/components/FormBuilder/context.ts +2 -5
- package/apps/lib/components/FormBuilder/fields/ChoiceFields.tsx +2 -7
- package/apps/lib/components/FormBuilder/fields/ColorField.tsx +1 -18
- package/apps/lib/components/FormBuilder/fields/CustomField.tsx +1 -6
- package/apps/lib/components/FormBuilder/fields/DateField.tsx +1 -3
- package/apps/lib/components/FormBuilder/fields/FieldArray.tsx +10 -15
- package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +6 -32
- package/apps/lib/components/FormBuilder/fields/FileField.tsx +1 -2
- package/apps/lib/components/FormBuilder/fields/OptionListFields.tsx +2 -9
- package/apps/lib/components/FormBuilder/fields/OtpField.tsx +1 -2
- package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +24 -23
- package/apps/lib/components/FormBuilder/fields/RichTextEditorField.tsx +1 -7
- package/apps/lib/components/FormBuilder/fields/SelectField.tsx +3 -13
- package/apps/lib/components/FormBuilder/fields/SignatureField.tsx +1 -19
- package/apps/lib/components/FormBuilder/fields/SliderField.tsx +1 -6
- package/apps/lib/components/FormBuilder/fields/SwitchBoxField.tsx +1 -2
- package/apps/lib/components/FormBuilder/fields/TableField.tsx +22 -26
- package/apps/lib/components/FormBuilder/fields/TextField.tsx +5 -14
- package/apps/lib/components/FormBuilder/fields/TreeSelectField.tsx +1 -7
- package/apps/lib/components/FormBuilder/form-builder.tsx +15 -28
- package/apps/lib/components/FormBuilder/header.tsx +2 -6
- package/apps/lib/components/FormBuilder/index.ts +1 -5
- package/apps/lib/components/FormBuilder/stepper.tsx +88 -15
- package/apps/lib/components/FormBuilder/submit.tsx +1 -4
- package/apps/lib/components/FormBuilder/types.ts +2 -5
- package/apps/lib/components/FormRenderer/FormDrawer.tsx +9 -2
- package/apps/lib/components/FormRenderer/form-renderer.tsx +9 -11
- package/apps/lib/components/FormRenderer/index.ts +1 -6
- package/apps/lib/components/FormRenderer/types.ts +0 -2
- package/apps/lib/components/Popover.tsx +6 -2
- package/apps/lib/components/SearchableSelect.tsx +7 -2
- package/apps/lib/layouts/FieldSection.tsx +25 -22
- package/apps/lib/registry.json +1 -1
- package/apps/lib/tsconfig.tsbuildinfo +1 -1
- package/dist/src/shared/tailwindInit.d.ts.map +1 -1
- package/dist/src/shared/tailwindInit.js +3 -0
- package/dist/src/shared/tailwindInit.js.map +1 -1
- package/docs/components/form-builder.md +19 -29
- package/docs/components/form-renderer.md +16 -10
- package/docs/components/searchable-select.md +50 -46
- package/docs/how-to/forms-with-form-builder.md +14 -11
- package/docs/reference/tailwind-plugins.md +11 -1
- package/docs/tutorials/getting-started.md +9 -0
- package/package.json +1 -1
- package/apps/lib/components/FormBuilder/DisplayField.tsx +0 -40
- 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}
|
|
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}
|
|
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}
|
|
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`.
|
|
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 =
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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 {
|
|
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
|
-
/**
|
|
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
|
|
43
|
-
*
|
|
44
|
-
*
|
|
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
|
|
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}
|
|
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}
|
|
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 {
|
|
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
|
|
30
|
-
* unique ISO code (dial codes repeat)
|
|
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}
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
{
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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}
|
|
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
|
|
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
|
|
64
|
-
const showSelect = selectable
|
|
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
|
-
|
|
197
|
-
<
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
<
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
|
219
|
-
//
|
|
220
|
-
const headerActions =
|
|
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
|
-
)
|
|
241
|
+
);
|
|
246
242
|
|
|
247
243
|
return (
|
|
248
244
|
<SectionBlock
|