@timeax/form-palette 0.0.1
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/.scaffold-cache.json +537 -0
- package/package.json +42 -0
- package/src/.scaffold-cache.json +544 -0
- package/src/adapters/axios.ts +117 -0
- package/src/adapters/index.ts +91 -0
- package/src/adapters/inertia.ts +187 -0
- package/src/core/adapter-registry.ts +87 -0
- package/src/core/bound/bind-host.ts +14 -0
- package/src/core/bound/observe-bound-field.ts +172 -0
- package/src/core/bound/wait-for-bound-field.ts +57 -0
- package/src/core/context.ts +23 -0
- package/src/core/core-provider.tsx +818 -0
- package/src/core/core-root.tsx +72 -0
- package/src/core/core-shell.tsx +44 -0
- package/src/core/errors/error-strip.tsx +71 -0
- package/src/core/errors/index.ts +2 -0
- package/src/core/errors/map-error-bag.ts +51 -0
- package/src/core/errors/map-zod.ts +39 -0
- package/src/core/hooks/use-button.ts +220 -0
- package/src/core/hooks/use-core-context.ts +20 -0
- package/src/core/hooks/use-core-utility.ts +0 -0
- package/src/core/hooks/use-core.ts +13 -0
- package/src/core/hooks/use-field.ts +497 -0
- package/src/core/hooks/use-optional-field.ts +28 -0
- package/src/core/index.ts +0 -0
- package/src/core/registry/binder-registry.ts +82 -0
- package/src/core/registry/field-registry.ts +187 -0
- package/src/core/test.tsx +17 -0
- package/src/global.d.ts +14 -0
- package/src/index.ts +68 -0
- package/src/input/index.ts +4 -0
- package/src/input/input-field.tsx +854 -0
- package/src/input/input-layout-graph.ts +230 -0
- package/src/input/input-props.ts +190 -0
- package/src/lib/get-global-countries.ts +87 -0
- package/src/lib/utils.ts +6 -0
- package/src/presets/index.ts +0 -0
- package/src/presets/shadcn-preset.ts +0 -0
- package/src/presets/shadcn-variants/checkbox.tsx +849 -0
- package/src/presets/shadcn-variants/chips.tsx +756 -0
- package/src/presets/shadcn-variants/color.tsx +284 -0
- package/src/presets/shadcn-variants/custom.tsx +227 -0
- package/src/presets/shadcn-variants/date.tsx +796 -0
- package/src/presets/shadcn-variants/file.tsx +764 -0
- package/src/presets/shadcn-variants/keyvalue.tsx +556 -0
- package/src/presets/shadcn-variants/multiselect.tsx +1132 -0
- package/src/presets/shadcn-variants/number.tsx +176 -0
- package/src/presets/shadcn-variants/password.tsx +737 -0
- package/src/presets/shadcn-variants/phone.tsx +628 -0
- package/src/presets/shadcn-variants/radio.tsx +578 -0
- package/src/presets/shadcn-variants/select.tsx +956 -0
- package/src/presets/shadcn-variants/slider.tsx +622 -0
- package/src/presets/shadcn-variants/text.tsx +343 -0
- package/src/presets/shadcn-variants/textarea.tsx +66 -0
- package/src/presets/shadcn-variants/toggle.tsx +218 -0
- package/src/presets/shadcn-variants/treeselect.tsx +784 -0
- package/src/presets/ui/badge.tsx +46 -0
- package/src/presets/ui/button.tsx +60 -0
- package/src/presets/ui/calendar.tsx +214 -0
- package/src/presets/ui/checkbox.tsx +115 -0
- package/src/presets/ui/custom.tsx +0 -0
- package/src/presets/ui/dialog.tsx +141 -0
- package/src/presets/ui/field.tsx +246 -0
- package/src/presets/ui/input-mask.tsx +739 -0
- package/src/presets/ui/input-otp.tsx +77 -0
- package/src/presets/ui/input.tsx +1011 -0
- package/src/presets/ui/label.tsx +22 -0
- package/src/presets/ui/number.tsx +1370 -0
- package/src/presets/ui/popover.tsx +46 -0
- package/src/presets/ui/radio-group.tsx +43 -0
- package/src/presets/ui/scroll-area.tsx +56 -0
- package/src/presets/ui/select.tsx +190 -0
- package/src/presets/ui/separator.tsx +28 -0
- package/src/presets/ui/slider.tsx +61 -0
- package/src/presets/ui/switch.tsx +32 -0
- package/src/presets/ui/textarea.tsx +634 -0
- package/src/presets/ui/time-dropdowns.tsx +350 -0
- package/src/schema/adapter.ts +217 -0
- package/src/schema/core.ts +429 -0
- package/src/schema/field-map.ts +0 -0
- package/src/schema/field.ts +224 -0
- package/src/schema/index.ts +0 -0
- package/src/schema/input-field.ts +260 -0
- package/src/schema/presets.ts +0 -0
- package/src/schema/variant.ts +216 -0
- package/src/variants/core/checkbox.tsx +54 -0
- package/src/variants/core/chips.tsx +22 -0
- package/src/variants/core/color.tsx +16 -0
- package/src/variants/core/custom.tsx +18 -0
- package/src/variants/core/date.tsx +25 -0
- package/src/variants/core/file.tsx +9 -0
- package/src/variants/core/keyvalue.tsx +12 -0
- package/src/variants/core/multiselect.tsx +28 -0
- package/src/variants/core/number.tsx +115 -0
- package/src/variants/core/password.tsx +35 -0
- package/src/variants/core/phone.tsx +16 -0
- package/src/variants/core/radio.tsx +38 -0
- package/src/variants/core/select.tsx +15 -0
- package/src/variants/core/slider.tsx +55 -0
- package/src/variants/core/text.tsx +114 -0
- package/src/variants/core/textarea.tsx +22 -0
- package/src/variants/core/toggle.tsx +50 -0
- package/src/variants/core/treeselect.tsx +11 -0
- package/src/variants/helpers/selection-summary.tsx +236 -0
- package/src/variants/index.ts +75 -0
- package/src/variants/registry.ts +38 -0
- package/src/variants/select-shared.ts +0 -0
- package/src/variants/shared.ts +126 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// src/input/input-layout-graph.ts
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
FieldLayoutConfig,
|
|
7
|
+
FieldOrdering,
|
|
8
|
+
FieldRootId,
|
|
9
|
+
FieldSlotId,
|
|
10
|
+
RelativeRootsMap,
|
|
11
|
+
SlotPlacement,
|
|
12
|
+
} from "@/schema/input-field";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Helper slots are all non-root slots:
|
|
16
|
+
* - sublabel
|
|
17
|
+
* - description
|
|
18
|
+
* - helpText
|
|
19
|
+
* - errorText
|
|
20
|
+
*/
|
|
21
|
+
export type HelperSlotId = Exclude<FieldSlotId, FieldRootId>;
|
|
22
|
+
|
|
23
|
+
export interface HelperSlot {
|
|
24
|
+
id: HelperSlotId;
|
|
25
|
+
root: FieldRootId;
|
|
26
|
+
placement: SlotPlacement;
|
|
27
|
+
content: React.ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Accessor for a (root, placement) group.
|
|
32
|
+
*
|
|
33
|
+
* - `slots()` gives you the concrete HelperSlot[] (possibly empty).
|
|
34
|
+
* - `render(fn)` calls `fn(slots)` only if there are slots,
|
|
35
|
+
* otherwise returns null (so React renders nothing).
|
|
36
|
+
*/
|
|
37
|
+
export interface SlotAccessor {
|
|
38
|
+
root: FieldRootId;
|
|
39
|
+
placement: SlotPlacement;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Concrete list of slots for this root + placement.
|
|
43
|
+
* May be an empty array.
|
|
44
|
+
*/
|
|
45
|
+
slots(): HelperSlot[];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Render this group.
|
|
49
|
+
*
|
|
50
|
+
* If no slots are present, returns null so nothing is rendered.
|
|
51
|
+
*
|
|
52
|
+
* Example:
|
|
53
|
+
* graph
|
|
54
|
+
* .getSlotsFor("input", "below")
|
|
55
|
+
* .render((slots) =>
|
|
56
|
+
* slots.map((slot) =>
|
|
57
|
+
* renderHelperSlot("input", slot, classes)
|
|
58
|
+
* )
|
|
59
|
+
* );
|
|
60
|
+
*/
|
|
61
|
+
render(
|
|
62
|
+
renderFn: (slots: HelperSlot[]) => React.ReactNode
|
|
63
|
+
): React.ReactNode;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Layout graph for helpers.
|
|
68
|
+
*/
|
|
69
|
+
export interface LayoutGraph {
|
|
70
|
+
helperSlots: HelperSlot[];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get a slot accessor for a given root + placement.
|
|
74
|
+
*/
|
|
75
|
+
getSlotsFor(
|
|
76
|
+
root: FieldRootId,
|
|
77
|
+
placement: SlotPlacement
|
|
78
|
+
): SlotAccessor;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Default root attachment for helpers when layout.relativeRoots
|
|
83
|
+
* does not specify anything.
|
|
84
|
+
*/
|
|
85
|
+
const defaultRelativeRoots: RelativeRootsMap = {
|
|
86
|
+
sublabel: "label",
|
|
87
|
+
description: "input",
|
|
88
|
+
helpText: "input",
|
|
89
|
+
errorText: "input",
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Default relative ordering per root when layout.ordering
|
|
94
|
+
* is not provided.
|
|
95
|
+
*
|
|
96
|
+
* Only governs *priority* when multiple helpers share the same
|
|
97
|
+
* root + placement. It does not decide the placement itself.
|
|
98
|
+
*/
|
|
99
|
+
const defaultOrdering: FieldOrdering = {
|
|
100
|
+
label: ["sublabel"],
|
|
101
|
+
input: ["errorText", "description", "helpText"],
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
function defaultPlacementFor(id: HelperSlotId): SlotPlacement {
|
|
105
|
+
if (id === "sublabel") {
|
|
106
|
+
// Typical: small label text to the right of the main label
|
|
107
|
+
return "right";
|
|
108
|
+
}
|
|
109
|
+
// For description/help/error, "below" the root is the usual default
|
|
110
|
+
return "below";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface BuildLayoutGraphArgs {
|
|
114
|
+
layout: FieldLayoutConfig;
|
|
115
|
+
/**
|
|
116
|
+
* Raw contents for each helper slot.
|
|
117
|
+
* Undefined/null means "no slot".
|
|
118
|
+
*/
|
|
119
|
+
sublabel?: React.ReactNode;
|
|
120
|
+
description?: React.ReactNode;
|
|
121
|
+
helpText?: React.ReactNode;
|
|
122
|
+
errorText?: React.ReactNode;
|
|
123
|
+
tags?: React.ReactNode;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Build a layout graph for helper slots given:
|
|
128
|
+
* - the effective layout (after variant defaults + overrides)
|
|
129
|
+
* - the actual content for each slot
|
|
130
|
+
*/
|
|
131
|
+
export function buildLayoutGraph(
|
|
132
|
+
args: BuildLayoutGraphArgs
|
|
133
|
+
): LayoutGraph {
|
|
134
|
+
const { layout, sublabel, description, helpText, errorText, tags } = args;
|
|
135
|
+
|
|
136
|
+
const relativeRoots: RelativeRootsMap = {
|
|
137
|
+
...defaultRelativeRoots,
|
|
138
|
+
...(layout.relativeRoots ?? {}),
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const ordering: FieldOrdering = {
|
|
142
|
+
...defaultOrdering,
|
|
143
|
+
...(layout.ordering ?? {}),
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const helperSlots: HelperSlot[] = [];
|
|
147
|
+
|
|
148
|
+
const pushSlot = (
|
|
149
|
+
id: HelperSlotId,
|
|
150
|
+
content: React.ReactNode | undefined,
|
|
151
|
+
placement: SlotPlacement | undefined
|
|
152
|
+
) => {
|
|
153
|
+
if (content === undefined || content === null) return;
|
|
154
|
+
|
|
155
|
+
const root: FieldRootId =
|
|
156
|
+
relativeRoots[id] ??
|
|
157
|
+
(id === "sublabel" ? "label" : "input");
|
|
158
|
+
|
|
159
|
+
const effectivePlacement: SlotPlacement =
|
|
160
|
+
placement ?? defaultPlacementFor(id);
|
|
161
|
+
|
|
162
|
+
if (effectivePlacement === "hidden") return;
|
|
163
|
+
|
|
164
|
+
helperSlots.push({
|
|
165
|
+
id,
|
|
166
|
+
root,
|
|
167
|
+
placement: effectivePlacement,
|
|
168
|
+
content,
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
pushSlot("sublabel", sublabel, layout.sublabelPlacement);
|
|
173
|
+
pushSlot("description", description, layout.descriptionPlacement);
|
|
174
|
+
pushSlot("helpText", helpText, layout.helpTextPlacement);
|
|
175
|
+
pushSlot("errorText", errorText, layout.errorTextPlacement);
|
|
176
|
+
pushSlot("tags", tags, layout.tagPlacement)
|
|
177
|
+
|
|
178
|
+
function makeAccessor(
|
|
179
|
+
root: FieldRootId,
|
|
180
|
+
placement: SlotPlacement
|
|
181
|
+
): SlotAccessor {
|
|
182
|
+
// cache per accessor so multiple .slots()/.render() calls
|
|
183
|
+
// don't keep re-filtering
|
|
184
|
+
let cache: HelperSlot[] | null = null;
|
|
185
|
+
|
|
186
|
+
const compute = (): HelperSlot[] => {
|
|
187
|
+
if (cache) return cache;
|
|
188
|
+
|
|
189
|
+
const base = helperSlots.filter(
|
|
190
|
+
(s) => s.root === root && s.placement === placement
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const order = ordering[root] ?? [];
|
|
194
|
+
if (!order.length) {
|
|
195
|
+
cache = base;
|
|
196
|
+
return cache;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
cache = [...base].sort((a, b) => {
|
|
200
|
+
const ai = order.indexOf(a.id);
|
|
201
|
+
const bi = order.indexOf(b.id);
|
|
202
|
+
|
|
203
|
+
const aRank = ai === -1 ? Number.POSITIVE_INFINITY : ai;
|
|
204
|
+
const bRank = bi === -1 ? Number.POSITIVE_INFINITY : bi;
|
|
205
|
+
|
|
206
|
+
return aRank - bRank;
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return cache;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
root,
|
|
214
|
+
placement,
|
|
215
|
+
slots: () => compute(),
|
|
216
|
+
render(renderFn) {
|
|
217
|
+
const slots = compute();
|
|
218
|
+
if (!slots.length) return null; // nothing rendered
|
|
219
|
+
return renderFn(slots);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
helperSlots,
|
|
226
|
+
getSlotsFor(root, placement) {
|
|
227
|
+
return makeAccessor(root, placement);
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// src/input/input-props.ts
|
|
2
|
+
// noinspection DuplicatedCode
|
|
3
|
+
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
|
|
6
|
+
import type { CoreContext, Dict } from "@/schema/core";
|
|
7
|
+
import type { Field } from "@/schema/field";
|
|
8
|
+
import type { FieldSize, FieldDensity, ChangeDetail } from "@/variants/shared";
|
|
9
|
+
import type {
|
|
10
|
+
VariantKey,
|
|
11
|
+
VariantValueFor,
|
|
12
|
+
VariantPropsFor,
|
|
13
|
+
} from "@/schema/variant";
|
|
14
|
+
import type {
|
|
15
|
+
LabelPlacement,
|
|
16
|
+
SublabelPlacement,
|
|
17
|
+
DescriptionPlacement,
|
|
18
|
+
HelpTextPlacement,
|
|
19
|
+
ErrorTextPlacement,
|
|
20
|
+
ValidateResult,
|
|
21
|
+
SlotPlacement,
|
|
22
|
+
} from "@/schema/input-field";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Core, variant-agnostic props for InputField.
|
|
26
|
+
*
|
|
27
|
+
* @template TValue Logical value type for this field. Will be refined by
|
|
28
|
+
* variant typing (VariantValueFor<K>).
|
|
29
|
+
*/
|
|
30
|
+
export interface InputFieldBaseProps<TValue = unknown> {
|
|
31
|
+
// βββββββββββββββββββββββββββββββββββββββββββββ
|
|
32
|
+
// Identity / wiring into the core runtime
|
|
33
|
+
// βββββββββββββββββββββββββββββββββββββββββββββ
|
|
34
|
+
|
|
35
|
+
name?: string;
|
|
36
|
+
bind?: string;
|
|
37
|
+
groupId?: string;
|
|
38
|
+
shared?: string;
|
|
39
|
+
ignore?: boolean;
|
|
40
|
+
alias?: string
|
|
41
|
+
main?: boolean;
|
|
42
|
+
tags?: FieldTag[];
|
|
43
|
+
contain?: boolean
|
|
44
|
+
|
|
45
|
+
// βββββββββββββββββββββββββββββββββββββββββββββ
|
|
46
|
+
// Chrome / description
|
|
47
|
+
// βββββββββββββββββββββββββββββββββββββββββββββ
|
|
48
|
+
|
|
49
|
+
label?: React.ReactNode;
|
|
50
|
+
sublabel?: React.ReactNode;
|
|
51
|
+
description?: React.ReactNode;
|
|
52
|
+
helpText?: React.ReactNode;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Optional explicit error text to display.
|
|
56
|
+
*
|
|
57
|
+
* This is *visual* error copy. The actual validation state still
|
|
58
|
+
* lives in field.error / schema / onValidate.
|
|
59
|
+
*/
|
|
60
|
+
errorText?: React.ReactNode;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Placement hints for label / sublabel / description / helpText / errorText.
|
|
64
|
+
*
|
|
65
|
+
* These are purely layout hints; actual behaviour is implemented
|
|
66
|
+
* by the preset / host component.
|
|
67
|
+
*/
|
|
68
|
+
labelPlacement?: LabelPlacement;
|
|
69
|
+
sublabelPlacement?: SublabelPlacement;
|
|
70
|
+
descriptionPlacement?: DescriptionPlacement;
|
|
71
|
+
helpTextPlacement?: HelpTextPlacement;
|
|
72
|
+
errorTextPlacement?: ErrorTextPlacement;
|
|
73
|
+
tagPlacement?: SlotPlacement
|
|
74
|
+
|
|
75
|
+
// βββββββββββββββββββββββββββββββββββββββββββββ
|
|
76
|
+
// State flags
|
|
77
|
+
// βββββββββββββββββββββββββββββββββββββββββββββ
|
|
78
|
+
|
|
79
|
+
required?: boolean;
|
|
80
|
+
disabled?: boolean;
|
|
81
|
+
readOnly?: boolean;
|
|
82
|
+
|
|
83
|
+
size?: FieldSize;
|
|
84
|
+
density?: FieldDensity;
|
|
85
|
+
|
|
86
|
+
// βββββββββββββββββββββββββββββββββββββββββββββ
|
|
87
|
+
// Layout hooks
|
|
88
|
+
// βββββββββββββββββββββββββββββββββββββββββββββ
|
|
89
|
+
|
|
90
|
+
inline?: boolean;
|
|
91
|
+
fullWidth?: boolean;
|
|
92
|
+
|
|
93
|
+
// βββββββββββββββββββββββββββββββββββββββββββββ
|
|
94
|
+
// Validation hooks
|
|
95
|
+
// βββββββββββββββββββββββββββββββββββββββββββββ
|
|
96
|
+
|
|
97
|
+
onValidate?(
|
|
98
|
+
value: TValue | undefined,
|
|
99
|
+
field: Field,
|
|
100
|
+
form: CoreContext<Dict>
|
|
101
|
+
): ValidateResult;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Per-field change hook at the InputField level.
|
|
105
|
+
*
|
|
106
|
+
* - `value` is what the variant is trying to set.
|
|
107
|
+
* - `detail` comes from the variant (`ChangeDetail`).
|
|
108
|
+
* - If you return `undefined`, the original value is used.
|
|
109
|
+
* - If you return *anything else*, that is what will be stored
|
|
110
|
+
* in the core (and emitted to the form).
|
|
111
|
+
*/
|
|
112
|
+
onChange?(e: {
|
|
113
|
+
value: TValue | undefined,
|
|
114
|
+
preventDefault(): void;
|
|
115
|
+
event?: React.SyntheticEvent;
|
|
116
|
+
readonly isDefaultPrevented?: boolean;
|
|
117
|
+
readonly detail: ChangeDetail
|
|
118
|
+
}): void;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
export type Events<TRaw, TValue, TMeta> = {
|
|
123
|
+
onValidate?(
|
|
124
|
+
value: TValue | undefined,
|
|
125
|
+
field: Field,
|
|
126
|
+
form: CoreContext<Dict>
|
|
127
|
+
): ValidateResult;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Per-field change hook at the InputField level.
|
|
131
|
+
*
|
|
132
|
+
* - `value` is what the variant is trying to set.
|
|
133
|
+
* - `detail` comes from the variant (`ChangeDetail`).
|
|
134
|
+
* - If you return `undefined`, the original value is used.
|
|
135
|
+
* - If you return *anything else*, that is what will be stored
|
|
136
|
+
* in the core (and emitted to the form).
|
|
137
|
+
*/
|
|
138
|
+
onChange?(e: {
|
|
139
|
+
value: TValue | undefined,
|
|
140
|
+
preventDefault(): void;
|
|
141
|
+
event?: React.SyntheticEvent;
|
|
142
|
+
readonly isDefaultPrevented?: boolean;
|
|
143
|
+
readonly detail: ChangeDetail<TMeta, TRaw>
|
|
144
|
+
}): void;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Public props for <InputField />.
|
|
149
|
+
*
|
|
150
|
+
* - `variant` selects the variant module.
|
|
151
|
+
* - All variant-specific props are merged directly into the field props
|
|
152
|
+
* via `VariantPropsFor<K>`.
|
|
153
|
+
*
|
|
154
|
+
* NOTE: this is a type alias (not an interface) so we can safely intersect
|
|
155
|
+
* unions coming from VariantPropsFor<K> / VariantValueFor<K>.
|
|
156
|
+
*/
|
|
157
|
+
export type InputFieldProps<K extends VariantKey = VariantKey, H = unknown> =
|
|
158
|
+
InputFieldBaseProps<VariantValueFor<K, H>> &
|
|
159
|
+
VariantPropsFor<K, H> &
|
|
160
|
+
Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> & {
|
|
161
|
+
variant: K;
|
|
162
|
+
classes?: Partial<InputFieldClassNames>;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export interface InputFieldClassNames {
|
|
166
|
+
root?: string;
|
|
167
|
+
labelRow?: string;
|
|
168
|
+
inlineRow?: string;
|
|
169
|
+
label?: string;
|
|
170
|
+
sublabel?: string;
|
|
171
|
+
description?: string;
|
|
172
|
+
helpText?: string;
|
|
173
|
+
error?: string;
|
|
174
|
+
group?: string;
|
|
175
|
+
content?: string;
|
|
176
|
+
variant?: string;
|
|
177
|
+
inlineInputColumn?: string
|
|
178
|
+
inlineLabelColumn?: string;
|
|
179
|
+
required?: string;
|
|
180
|
+
tag?: string
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
export interface FieldTag {
|
|
185
|
+
label: React.ReactNode;
|
|
186
|
+
icon?: React.ReactNode;
|
|
187
|
+
className?: string;
|
|
188
|
+
color?: string; // text color
|
|
189
|
+
bgColor?: string; // background color
|
|
190
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { PhoneCountry } from "@/presets/shadcn-variants/phone";
|
|
2
|
+
|
|
3
|
+
// e.g. src/lib/get-global-countries.ts
|
|
4
|
+
let cachedCountries: PhoneCountry[] | null = null;
|
|
5
|
+
|
|
6
|
+
const DEFAULT_COUNTRIES: PhoneCountry[] = [
|
|
7
|
+
{
|
|
8
|
+
code: "NG",
|
|
9
|
+
label: "Nigeria",
|
|
10
|
+
dial: "234",
|
|
11
|
+
mask: "999 999 9999",
|
|
12
|
+
flag: "π³π¬",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
code: "US",
|
|
16
|
+
label: "United States",
|
|
17
|
+
dial: "1",
|
|
18
|
+
mask: "(999) 999-9999",
|
|
19
|
+
flag: "πΊπΈ",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
code: "GB",
|
|
23
|
+
label: "United Kingdom",
|
|
24
|
+
dial: "44",
|
|
25
|
+
mask: "9999 999 999",
|
|
26
|
+
flag: "π¬π§",
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
cachedCountries = DEFAULT_COUNTRIES;
|
|
31
|
+
let validatedOnce = false;
|
|
32
|
+
|
|
33
|
+
function isPhoneCountry(value: unknown): value is PhoneCountry {
|
|
34
|
+
if (!value || typeof value !== "object") return false;
|
|
35
|
+
|
|
36
|
+
const v = value as Record<string, unknown>;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
typeof v.code === "string" &&
|
|
40
|
+
typeof v.label === "string" &&
|
|
41
|
+
typeof v.dial === "string" &&
|
|
42
|
+
typeof v.mask === "string"
|
|
43
|
+
// flag is optional & can be anything React can render, so we don't
|
|
44
|
+
// validate it beyond existence.
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getGlobalCountryList(): PhoneCountry[] {
|
|
49
|
+
// If we've already validated & cached, just return it.
|
|
50
|
+
if (cachedCountries) return cachedCountries;
|
|
51
|
+
|
|
52
|
+
if (typeof window === "undefined") {
|
|
53
|
+
cachedCountries = [];
|
|
54
|
+
return cachedCountries;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const raw = window["form-palette"]?.countries;
|
|
58
|
+
|
|
59
|
+
if (!Array.isArray(raw)) {
|
|
60
|
+
if (!validatedOnce && process.env.NODE_ENV !== "production") {
|
|
61
|
+
console.warn(
|
|
62
|
+
"['form-palette'] window.'form-palette'.countries is not an array:",
|
|
63
|
+
raw,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
validatedOnce = true;
|
|
67
|
+
cachedCountries = [];
|
|
68
|
+
return cachedCountries;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const result: PhoneCountry[] = [];
|
|
72
|
+
|
|
73
|
+
for (const item of raw) {
|
|
74
|
+
if (isPhoneCountry(item)) {
|
|
75
|
+
result.push(item);
|
|
76
|
+
} else if (process.env.NODE_ENV !== "production") {
|
|
77
|
+
console.warn(
|
|
78
|
+
"['form-palette'] Ignoring invalid PhoneCountry entry:",
|
|
79
|
+
item,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
validatedOnce = true;
|
|
85
|
+
cachedCountries = result;
|
|
86
|
+
return result;
|
|
87
|
+
}
|
package/src/lib/utils.ts
ADDED
|
File without changes
|
|
File without changes
|