@payfit/unity-components 2.55.14 → 2.55.16

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.
@@ -4,9 +4,10 @@ description: >
4
4
  Load when building or migrating Unity forms. Use useTanstackUnityForm with
5
5
  schema validation and prefer Tanstack Form field APIs for composed fields,
6
6
  custom layouts, and validation behavior.
7
- type: core
8
- library: '@payfit/unity-components'
9
- library_version: '2.x'
7
+ metadata:
8
+ type: core
9
+ library: '@payfit/unity-components'
10
+ library_version: '2.x'
10
11
  sources:
11
12
  - 'PayFit/hr-apps:libs/shared/unity/components/src/hooks/use-tanstack-form.tsx'
12
13
  - 'PayFit/hr-apps:libs/shared/unity/components/src/hooks/use-form.tsx'
@@ -61,103 +62,14 @@ Wrapping order is load-bearing: `form.AppForm` provides the form context, `form.
61
62
 
62
63
  ## Core Patterns
63
64
 
64
- ### Composed API (default)
65
+ - Prefer composed `field.*Field` components for ordinary fields.
66
+ - Use the atomic field parts only when custom layout or control composition is
67
+ required.
68
+ - Default validation to blur; use dynamic validation for fields that need
69
+ blur-then-change behavior.
70
+ - Narrow every `form.Subscribe` with a selector.
65
71
 
66
- Drop a single bound field component inside `<form.AppField>`. It bundles label, input, helper text, feedback, and a11y wiring.
67
-
68
- ```tsx
69
- <form.AppField name="firstName">
70
- {field => (
71
- <field.TextField
72
- label="First name"
73
- helperText="As it appears on your ID"
74
- isRequired
75
- />
76
- )}
77
- </form.AppField>
78
-
79
- <form.AppField name="country">
80
- {field => (
81
- <field.SelectField
82
- label="Country"
83
- options={[
84
- { value: 'fr', label: 'France' },
85
- { value: 'es', label: 'Spain' },
86
- ]}
87
- />
88
- )}
89
- </form.AppField>
90
- ```
91
-
92
- ### Atomic API (only when customizing layout/parts)
93
-
94
- Reach for Atomic when you need to interleave custom content between the label and the input, or swap an input for a non-standard control. Wraps every part in `field.Field`.
95
-
96
- ```tsx
97
- <form.AppField name="password">
98
- {field => (
99
- <field.Field>
100
- <field.FieldLabel isRequired>Password</field.FieldLabel>
101
- <field.FieldHelperText>Enter a strong password</field.FieldHelperText>
102
- <field.TextInput type="password" />
103
- <form.Subscribe selector={s => s.values.password}>
104
- {password => (
105
- <Text variant="bodySmallStrong">Length: {password.length}</Text>
106
- )}
107
- </form.Subscribe>
108
- <field.FieldFeedbackText />
109
- </field.Field>
110
- )}
111
- </form.AppField>
112
- ```
113
-
114
- ### Validation timing
115
-
116
- `validators.onBlur` is the default; use `onChange` only for fields that need live feedback (password strength meter, search-as-you-type). `fieldRevalidateLogic` gives "blur until first error, then change" UX without polluting form-level validators.
117
-
118
- ```tsx
119
- import { fieldRevalidateLogic, useTanstackUnityForm } from '@payfit/unity-components'
120
-
121
- const form = useTanstackUnityForm({
122
- defaultValues: { email: '', password: '' },
123
- validators: { onBlur: z.object({ email: z.email() }) },
124
- validationLogic: fieldRevalidateLogic({
125
- whenPristine: 'blur',
126
- whenDirty: 'change',
127
- fields: ['password'],
128
- }),
129
- })
130
-
131
- <form.AppField
132
- name="password"
133
- validators={{
134
- onDynamic: ({ value }) =>
135
- value.length < 8 ? 'Password too short' : undefined,
136
- }}
137
- >
138
- {field => <field.PasswordField label="Password" />}
139
- </form.AppField>
140
- ```
141
-
142
- Fields listed in `fieldRevalidateLogic.fields` MUST use `onDynamic`/`onDynamicAsync` as their sole validator and MUST NOT also appear in a form-level schema, or stale errors will linger.
143
-
144
- ### Optimal subscription with form.Subscribe + selector
145
-
146
- Always pass a `selector` to `form.Subscribe`. A bare children-only subscription re-renders on every keystroke anywhere in the form.
147
-
148
- ```tsx
149
- <form.Subscribe selector={s => s.values.password}>
150
- {password => <Text>Length: {password.length}</Text>}
151
- </form.Subscribe>
152
-
153
- <form.Subscribe selector={s => [s.canSubmit, s.isSubmitting] as const}>
154
- {([canSubmit, isSubmitting]) => (
155
- <Button type="submit" isDisabled={!canSubmit} isLoading={isSubmitting}>
156
- Submit
157
- </Button>
158
- )}
159
- </form.Subscribe>
160
- ```
72
+ Read [references/patterns.md](references/patterns.md) for complete examples.
161
73
 
162
74
  ## Common Mistakes
163
75
 
@@ -181,9 +93,10 @@ const form = useTanstackUnityForm({ validators: { onBlur: schema } })
181
93
 
182
94
  The legacy `use-form` hook is `@deprecated` but still exported; agents trained on older code reach for it and end up mixing RHF Controller with Tanstack field components, which breaks at runtime.
183
95
 
184
- Fixed-but-legacy-risk: the legacy hook is still exported but will be removed in the next few weeks (after or alongside the rebrand). Never author new code with it.
96
+ The legacy hook remains exported for compatibility but is deprecated. Do not
97
+ author new code with it.
185
98
 
186
- Source: libs/shared/unity/components/src/hooks/use-form.tsx:79 (@deprecated JSDoc); maintainer interview
99
+ Source: libs/shared/unity/components/src/hooks/use-form.tsx:79 (@deprecated JSDoc)
187
100
 
188
101
  ### CRITICAL Omit form.AppForm or form.AppField wrapping
189
102
 
@@ -329,7 +242,7 @@ Correct:
329
242
 
330
243
  `field.TextField` is the Composed API and already includes label/input/feedback; wrapping it in `field.Field` + `field.FieldLabel` double-wraps and breaks layout + a11y. Default to Composed; reach for Atomic only when you must customize the field's layout or swap a part — never as the standard pattern.
331
244
 
332
- Source: TanstackTextField.tsx vs TanstackFormField.tsx + parts; maintainer interview (Composed is default)
245
+ Source: TanstackTextField.tsx; TanstackFormField.tsx and its parts
333
246
 
334
247
  ## References
335
248
 
@@ -0,0 +1,102 @@
1
+ # Unity TanStack Form patterns
2
+
3
+ ### Composed API (default)
4
+
5
+ Drop a single bound field component inside `<form.AppField>`. It bundles label, input, helper text, feedback, and a11y wiring.
6
+
7
+ ```tsx
8
+ <form.AppField name="firstName">
9
+ {field => (
10
+ <field.TextField
11
+ label="First name"
12
+ helperText="As it appears on your ID"
13
+ isRequired
14
+ />
15
+ )}
16
+ </form.AppField>
17
+
18
+ <form.AppField name="country">
19
+ {field => (
20
+ <field.SelectField
21
+ label="Country"
22
+ options={[
23
+ { value: 'fr', label: 'France' },
24
+ { value: 'es', label: 'Spain' },
25
+ ]}
26
+ />
27
+ )}
28
+ </form.AppField>
29
+ ```
30
+
31
+ ### Atomic API (only when customizing layout/parts)
32
+
33
+ Reach for Atomic when you need to interleave custom content between the label and the input, or swap an input for a non-standard control. Wraps every part in `field.Field`.
34
+
35
+ ```tsx
36
+ <form.AppField name="password">
37
+ {field => (
38
+ <field.Field>
39
+ <field.FieldLabel isRequired>Password</field.FieldLabel>
40
+ <field.FieldHelperText>Enter a strong password</field.FieldHelperText>
41
+ <field.TextInput type="password" />
42
+ <form.Subscribe selector={s => s.values.password}>
43
+ {password => (
44
+ <Text variant="bodySmallStrong">Length: {password.length}</Text>
45
+ )}
46
+ </form.Subscribe>
47
+ <field.FieldFeedbackText />
48
+ </field.Field>
49
+ )}
50
+ </form.AppField>
51
+ ```
52
+
53
+ ### Validation timing
54
+
55
+ `validators.onBlur` is the default; use `onChange` only for fields that need live feedback (password strength meter, search-as-you-type). `fieldRevalidateLogic` gives "blur until first error, then change" UX without polluting form-level validators.
56
+
57
+ ```tsx
58
+ import { fieldRevalidateLogic, useTanstackUnityForm } from '@payfit/unity-components'
59
+
60
+ const form = useTanstackUnityForm({
61
+ defaultValues: { email: '', password: '' },
62
+ validators: { onBlur: z.object({ email: z.email() }) },
63
+ validationLogic: fieldRevalidateLogic({
64
+ whenPristine: 'blur',
65
+ whenDirty: 'change',
66
+ fields: ['password'],
67
+ }),
68
+ })
69
+
70
+ <form.AppField
71
+ name="password"
72
+ validators={{
73
+ onDynamic: ({ value }) =>
74
+ value.length < 8 ? 'Password too short' : undefined,
75
+ }}
76
+ >
77
+ {field => <field.PasswordField label="Password" />}
78
+ </form.AppField>
79
+ ```
80
+
81
+ For fields listed in `fieldRevalidateLogic.fields`, use
82
+ `onDynamic`/`onDynamicAsync` as the sole validator and omit those fields from
83
+ the form-level schema; combining both leaves stale errors.
84
+
85
+ ### Optimal subscription with form.Subscribe + selector
86
+
87
+ Pass a `selector` to `form.Subscribe` so it observes only the state needed by
88
+ its children. A bare subscription re-renders on every form update.
89
+
90
+ ```tsx
91
+ <form.Subscribe selector={s => s.values.password}>
92
+ {password => <Text>Length: {password.length}</Text>}
93
+ </form.Subscribe>
94
+
95
+ <form.Subscribe selector={s => [s.canSubmit, s.isSubmitting] as const}>
96
+ {([canSubmit, isSubmitting]) => (
97
+ <Button type="submit" isDisabled={!canSubmit} isLoading={isSubmitting}>
98
+ Submit
99
+ </Button>
100
+ )}
101
+ </form.Subscribe>
102
+ ```