@vectoriox/iox-ui 5.28.0 → 5.29.0

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.
@@ -0,0 +1,105 @@
1
+ /**
2
+ * FORM PRESETS — a complete, working form as one insert: its fields, its submit label and the words of
3
+ * its Success and Error blocks. ONE definition (W1.1): the builder's Forms tray builds a preset from
4
+ * here (`buildFormPreset`, iox-builder), and the site composer (`generation-core`) stamps the SAME preset
5
+ * into a composed page, so a generated contact form is the real one — never a second, drifting copy.
6
+ * Data only: framework-free, like the rest of forms-core.
7
+ */
8
+
9
+ export enum FormPresetId {
10
+ Contact = 'contact',
11
+ Newsletter = 'newsletter',
12
+ Application = 'application',
13
+ Booking = 'booking',
14
+ }
15
+
16
+ export interface FormPresetField {
17
+ label: string;
18
+ control: string;
19
+ required?: boolean;
20
+ help?: string;
21
+ placeholder?: string;
22
+ /** Choices for select/radio, `value|Label` per line — the Field's `optionsText` trait. */
23
+ optionsText?: string;
24
+ rows?: number;
25
+ }
26
+
27
+ export interface FormPresetDef {
28
+ id: string;
29
+ name: string;
30
+ icon: string;
31
+ /** One line in the panel: what it collects and where it goes. */
32
+ description: string;
33
+ /** The form's author-facing name, which the submissions inbox groups by. */
34
+ formName: string;
35
+ fields: FormPresetField[];
36
+ submitLabel: string;
37
+ successText: string;
38
+ errorText: string;
39
+ }
40
+
41
+ export const FORM_PRESETS: readonly FormPresetDef[] = Object.freeze([
42
+ {
43
+ id: FormPresetId.Contact, name: 'Contact', icon: 'ph-thin ph-envelope-simple',
44
+ description: 'Name, email and a message. Stored, and emailed to you.',
45
+ formName: 'Contact form',
46
+ fields: [
47
+ { label: 'Full name', control: 'text', required: true },
48
+ { label: 'Email', control: 'email', required: true },
49
+ { label: 'Message', control: 'textarea', required: true, rows: 5 },
50
+ ],
51
+ submitLabel: 'Send message',
52
+ successText: 'Thanks — we’ll be in touch shortly.',
53
+ errorText: 'We couldn’t send your message. Please try again — your answers are still here.',
54
+ },
55
+ {
56
+ id: FormPresetId.Newsletter, name: 'Newsletter', icon: 'ph-thin ph-paper-plane-tilt',
57
+ description: 'One email field plus consent. Stored.',
58
+ formName: 'Newsletter signup',
59
+ fields: [
60
+ { label: 'Email', control: 'email', required: true, placeholder: 'you@company.com' },
61
+ // Consent is a checkbox that must be TICKED — `required` on a checkbox becomes the
62
+ // `accepted` rule, with its own message, rather than a generic "this is required".
63
+ { label: 'Email me occasional updates', control: 'checkbox', required: true },
64
+ ],
65
+ submitLabel: 'Subscribe',
66
+ successText: 'You’re on the list.',
67
+ errorText: 'We couldn’t sign you up. Please try again.',
68
+ },
69
+ {
70
+ id: FormPresetId.Application, name: 'Application', icon: 'ph-thin ph-read-cv-logo',
71
+ description: 'Details plus a CV upload. Stored, with an auto-reply.',
72
+ formName: 'Application',
73
+ fields: [
74
+ { label: 'Full name', control: 'text', required: true },
75
+ { label: 'Email', control: 'email', required: true },
76
+ { label: 'Phone', control: 'tel' },
77
+ { label: 'Role', control: 'select', required: true, optionsText: 'Design\nEngineering\nOther' },
78
+ { label: 'CV', control: 'file', required: true, help: 'PDF or Word, up to 5MB' },
79
+ { label: 'Anything else', control: 'textarea', rows: 4 },
80
+ ],
81
+ submitLabel: 'Apply',
82
+ successText: 'Application received — we’ll read it properly and reply.',
83
+ errorText: 'We couldn’t submit your application. Please try again.',
84
+ },
85
+ {
86
+ id: FormPresetId.Booking, name: 'Booking request', icon: 'ph-thin ph-calendar-check',
87
+ description: 'Date and party size. Stored, and it notifies you.',
88
+ formName: 'Booking request',
89
+ fields: [
90
+ { label: 'Name', control: 'text', required: true },
91
+ { label: 'Email', control: 'email', required: true },
92
+ { label: 'Date', control: 'date', required: true },
93
+ { label: 'People', control: 'number', required: true },
94
+ { label: 'Notes', control: 'textarea', rows: 3, help: 'Allergies, access needs, anything useful' },
95
+ ],
96
+ submitLabel: 'Request a booking',
97
+ successText: 'Request received — we’ll confirm by email.',
98
+ errorText: 'We couldn’t send your request. Please try again.',
99
+ },
100
+ ]);
101
+
102
+ /** The preset with this id, or undefined. */
103
+ export function formPresetById(id: string | null | undefined): FormPresetDef | undefined {
104
+ return FORM_PRESETS.find(p => p.id === id);
105
+ }
@@ -18,3 +18,4 @@ export * from './options';
18
18
  export * from './runtime';
19
19
  export * from './rule-catalog';
20
20
  export * from './transport';
21
+ export * from './form-presets';
@@ -0,0 +1,75 @@
1
+ import { NEUTRAL_STYLE_PROPS, NODE_BASELINES } from './node-baselines.generated';
2
+
3
+ /**
4
+ * Why each type's constructor sets the style values it sets (W1.1, Q1). Every value a type's config
5
+ * gives that differs from what the trait saves when nobody sets it (`NEUTRAL_STYLE_PROPS`) is exactly
6
+ * one of:
7
+ *
8
+ * - **Layout** — a size or spacing that exists only so an EMPTY node dropped from the palette is visible
9
+ * and grabbable (a Container's 200px, a Text's 10px padding). A composed node is never empty and its
10
+ * pattern owns its box, so the composer RELEASES these to the neutral value unless the pattern
11
+ * declares one: the nav bar was 200px tall and every lead line indented 10px (W1's screenshot).
12
+ * - **Behaviour** — what makes the type work: a button's inline-flex centring, a form's column, the 44px
13
+ * touch target, a slide's width, a spacer's height, a list's marker room. Kept.
14
+ * - **Look** — the type's pre-design-system paint, which its element style replaces. Kept (the style
15
+ * writes over it; a type with no style keeps its pre-DS2 look, D5).
16
+ *
17
+ * Saved pages are untouched: this is the composer's rule, never the builder's (a palette drop still
18
+ * gets its 200px). `baseline-roles.spec.ts` fails the moment a config gains a default this table does
19
+ * not classify — so a new default is a decision, not an accident.
20
+ */
21
+ export enum BaselineRole {
22
+ Layout = 'layout',
23
+ Behaviour = 'behaviour',
24
+ Look = 'look',
25
+ }
26
+
27
+ const { Layout, Behaviour, Look } = BaselineRole;
28
+
29
+ export const BASELINE_ROLES: Readonly<Record<string, Readonly<Record<string, BaselineRole>>>> = {
30
+ Button: {},
31
+ ButtonBlock: {
32
+ display: Behaviour, justifyContent: Behaviour, alignItems: Behaviour, gap: Behaviour, cursor: Behaviour,
33
+ padding: Look, borderRadius: Look,
34
+ },
35
+ Card: { width: Behaviour, maxWidth: Layout, padding: Look, borderWidth: Look, borderColor: Look, borderRadius: Look, backgroundColor: Look },
36
+ // `width: 100%` (DEFAULT_WIDTH_BY_TYPE) is what lets a block fill its row — a flex child collapses without it.
37
+ Container: { width: Behaviour, minHeight: Layout },
38
+ Heading: { fontSize: Look, fontWeight: Look },
39
+ Text: { width: Behaviour, minHeight: Layout, padding: Layout },
40
+ Image: { width: Behaviour, maxWidth: Layout, aspectRatio: Layout },
41
+ Link: {},
42
+ LinkedContainer: { minHeight: Layout },
43
+ Section: { minHeight: Layout },
44
+ Icon: { lineHeight: Behaviour, fontSize: Behaviour, color: Look },
45
+ Divider: { width: Behaviour, height: Look, margin: Layout, backgroundColor: Look },
46
+ Spacer: { width: Behaviour, height: Behaviour },
47
+ List: { width: Behaviour, padding: Behaviour },
48
+ ListItem: { width: Behaviour },
49
+ Repeater: { width: Behaviour },
50
+ SliderContainer: { width: Behaviour },
51
+ SliderSlide: { width: Behaviour, aspectRatio: Behaviour },
52
+ // The crossfade stacks its frames absolutely: without a box it has no height at all.
53
+ Gallery: { minHeight: Behaviour, backgroundColor: Look },
54
+ Form: { display: Behaviour, flexDirection: Behaviour, gap: Behaviour, width: Behaviour },
55
+ Field: { width: Behaviour },
56
+ FieldGroup: { display: Behaviour, gap: Behaviour, width: Behaviour },
57
+ SubmitButton: {
58
+ display: Behaviour, justifyContent: Behaviour, alignItems: Behaviour, minHeight: Behaviour, cursor: Behaviour,
59
+ color: Look, padding: Look, borderRadius: Look, backgroundColor: Look,
60
+ },
61
+ FormSuccess: { width: Behaviour, color: Look, padding: Look, borderRadius: Look, backgroundColor: Look },
62
+ FormError: { width: Behaviour, color: Look, padding: Look, borderRadius: Look, backgroundColor: Look },
63
+ Dialog: { width: Behaviour, padding: Look, borderRadius: Look, backgroundColor: Look, boxShadow: Look },
64
+ };
65
+
66
+ /** The style values a type's constructor sets for an empty palette drop, each mapped to the value the
67
+ * trait saves when nobody sets it — what a composed node of that type starts from. */
68
+ export function releasedLayoutDefaults(type: string): Record<string, unknown> {
69
+ const roles = BASELINE_ROLES[type] ?? {};
70
+ const out: Record<string, unknown> = {};
71
+ for (const [trait, role] of Object.entries(roles)) {
72
+ if (role === BaselineRole.Layout && trait in (NODE_BASELINES[type]?.styleProps ?? {})) out[trait] = NEUTRAL_STYLE_PROPS[trait];
73
+ }
74
+ return out;
75
+ }