@ankhorage/zora 10.0.0 → 11.0.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.
Files changed (196) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +1 -1
  3. package/dist/constants.d.ts +1 -0
  4. package/dist/constants.d.ts.map +1 -1
  5. package/dist/constants.js +1 -0
  6. package/dist/constants.js.map +1 -1
  7. package/dist/features/bottom-sheet/bottomSheetMeta.d.ts +1 -1
  8. package/dist/features/card/cardMeta.d.ts +1 -1
  9. package/dist/features/card/mediaCardMeta.d.ts +1 -1
  10. package/dist/features/card/postCardMeta.d.ts +1 -1
  11. package/dist/features/form/adapters/inbound/Form.d.ts +5 -0
  12. package/dist/features/form/adapters/inbound/Form.d.ts.map +1 -0
  13. package/dist/features/form/adapters/inbound/Form.js +18 -0
  14. package/dist/features/form/adapters/inbound/Form.js.map +1 -0
  15. package/dist/{components/form → features/form/adapters/inbound}/FormActions.d.ts +1 -1
  16. package/dist/features/form/adapters/inbound/FormActions.d.ts.map +1 -0
  17. package/dist/{components/form → features/form/adapters/inbound}/FormActions.js +3 -3
  18. package/dist/features/form/adapters/inbound/FormActions.js.map +1 -0
  19. package/dist/features/form/adapters/inbound/FormError.d.ts +5 -0
  20. package/dist/features/form/adapters/inbound/FormError.d.ts.map +1 -0
  21. package/dist/features/form/adapters/inbound/FormError.js +18 -0
  22. package/dist/features/form/adapters/inbound/FormError.js.map +1 -0
  23. package/dist/features/form/adapters/inbound/FormField.d.ts +5 -0
  24. package/dist/features/form/adapters/inbound/FormField.d.ts.map +1 -0
  25. package/dist/{patterns/form-field → features/form/adapters/inbound}/FormField.js +5 -4
  26. package/dist/features/form/adapters/inbound/FormField.js.map +1 -0
  27. package/dist/features/form/formActionsMeta.d.ts +9 -0
  28. package/dist/features/form/formActionsMeta.d.ts.map +1 -0
  29. package/dist/features/form/formActionsMeta.js +9 -0
  30. package/dist/features/form/formActionsMeta.js.map +1 -0
  31. package/dist/features/form/formErrorMeta.d.ts +32 -0
  32. package/dist/features/form/formErrorMeta.d.ts.map +1 -0
  33. package/dist/features/form/formErrorMeta.js +19 -0
  34. package/dist/features/form/formErrorMeta.js.map +1 -0
  35. package/dist/{components/form/meta.d.ts → features/form/formFieldMeta.d.ts} +28 -39
  36. package/dist/features/form/formFieldMeta.d.ts.map +1 -0
  37. package/dist/features/form/formFieldMeta.js +35 -0
  38. package/dist/features/form/formFieldMeta.js.map +1 -0
  39. package/dist/features/form/formMeta.d.ts +61 -0
  40. package/dist/features/form/formMeta.d.ts.map +1 -0
  41. package/dist/features/form/formMeta.js +34 -0
  42. package/dist/features/form/formMeta.js.map +1 -0
  43. package/dist/features/form/public.d.ts +8 -0
  44. package/dist/features/form/public.d.ts.map +1 -0
  45. package/dist/features/form/public.js +7 -0
  46. package/dist/features/form/public.js.map +1 -0
  47. package/dist/{components → features}/form/useFormController.d.ts +1 -1
  48. package/dist/features/form/useFormController.d.ts.map +1 -0
  49. package/dist/{components → features}/form/useFormController.js +1 -1
  50. package/dist/features/form/useFormController.js.map +1 -0
  51. package/dist/{components/form → features/form/utils}/validation.d.ts +1 -1
  52. package/dist/features/form/utils/validation.d.ts.map +1 -0
  53. package/dist/{components/form → features/form/utils}/validation.js +2 -4
  54. package/dist/features/form/utils/validation.js.map +1 -0
  55. package/dist/features/keyboard-avoiding-view/keyboardAvoidingViewMeta.d.ts +1 -1
  56. package/dist/features/layout/boxMeta.d.ts +1 -1
  57. package/dist/features/layout/containerMeta.d.ts +1 -1
  58. package/dist/features/layout/gridMeta.d.ts +1 -1
  59. package/dist/features/layout/stackMeta.d.ts +1 -1
  60. package/dist/features/list/flatListMeta.d.ts +1 -1
  61. package/dist/features/list/sectionListMeta.d.ts +1 -1
  62. package/dist/features/surface/surfaceMeta.d.ts +1 -1
  63. package/dist/features/uploader/adapters/inbound/Uploader.d.ts.map +1 -1
  64. package/dist/features/uploader/adapters/inbound/Uploader.js +1 -1
  65. package/dist/features/uploader/adapters/inbound/Uploader.js.map +1 -1
  66. package/dist/index.d.ts +2 -2
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +1 -1
  69. package/dist/index.js.map +1 -1
  70. package/dist/layout/screen/meta.d.ts +1 -1
  71. package/dist/layout/screen-section/meta.d.ts +1 -1
  72. package/dist/metadata/allowedChildren.d.ts +3 -3
  73. package/dist/metadata/allowedChildren.d.ts.map +1 -1
  74. package/dist/metadata/allowedChildren.js +1 -1
  75. package/dist/metadata/allowedChildren.js.map +1 -1
  76. package/dist/metadata/bindableComponentMeta.d.ts +2 -2
  77. package/dist/metadata/componentMeta.d.ts.map +1 -1
  78. package/dist/metadata/componentMeta.js +4 -1
  79. package/dist/metadata/componentMeta.js.map +1 -1
  80. package/dist/patterns/auth/AuthForm.d.ts +21 -0
  81. package/dist/patterns/auth/AuthForm.d.ts.map +1 -0
  82. package/dist/patterns/auth/AuthForm.js +19 -0
  83. package/dist/patterns/auth/AuthForm.js.map +1 -0
  84. package/dist/patterns/auth/AuthFormField.d.ts +16 -0
  85. package/dist/patterns/auth/AuthFormField.d.ts.map +1 -0
  86. package/dist/patterns/auth/AuthFormField.js +38 -0
  87. package/dist/patterns/auth/AuthFormField.js.map +1 -0
  88. package/dist/patterns/auth/ForgotPasswordForm.d.ts.map +1 -1
  89. package/dist/patterns/auth/ForgotPasswordForm.js +2 -2
  90. package/dist/patterns/auth/ForgotPasswordForm.js.map +1 -1
  91. package/dist/patterns/auth/OtpForm.d.ts.map +1 -1
  92. package/dist/patterns/auth/OtpForm.js +2 -2
  93. package/dist/patterns/auth/OtpForm.js.map +1 -1
  94. package/dist/patterns/auth/SignInForm.d.ts.map +1 -1
  95. package/dist/patterns/auth/SignInForm.js +2 -2
  96. package/dist/patterns/auth/SignInForm.js.map +1 -1
  97. package/dist/patterns/auth/SignUpForm.d.ts.map +1 -1
  98. package/dist/patterns/auth/SignUpForm.js +2 -2
  99. package/dist/patterns/auth/SignUpForm.js.map +1 -1
  100. package/dist/patterns/auth/types.d.ts +1 -1
  101. package/dist/patterns/auth/types.d.ts.map +1 -1
  102. package/dist/patterns/auth/types.js.map +1 -1
  103. package/dist/patterns/auth/utils.d.ts +1 -1
  104. package/dist/patterns/auth/utils.d.ts.map +1 -1
  105. package/dist/patterns/auth/utils.js.map +1 -1
  106. package/dist/patterns/inspector-field/InspectorField.js +1 -1
  107. package/dist/patterns/inspector-field/InspectorField.js.map +1 -1
  108. package/dist/patterns/message-bubble/meta.d.ts +1 -1
  109. package/dist/patterns/notice/meta.d.ts +1 -1
  110. package/dist/patterns/panel/meta.d.ts +1 -1
  111. package/dist/patterns/reader/meta.d.ts +2 -2
  112. package/dist/patterns/scanner/meta.d.ts +2 -2
  113. package/dist/registry.js +1 -1
  114. package/dist/registry.js.map +1 -1
  115. package/dist/{components/form/types.d.ts → types/form.d.ts} +12 -28
  116. package/dist/types/form.d.ts.map +1 -0
  117. package/dist/types/form.js +2 -0
  118. package/dist/types/form.js.map +1 -0
  119. package/package.json +2 -1
  120. package/src/audit.test.ts +2 -4
  121. package/src/constants.ts +1 -0
  122. package/src/featureManifest.test.ts +7 -0
  123. package/src/features/form/adapters/inbound/Form.tsx +39 -0
  124. package/src/{components/form → features/form/adapters/inbound}/FormActions.tsx +4 -4
  125. package/src/features/form/adapters/inbound/FormError.tsx +22 -0
  126. package/src/{patterns/form-field → features/form/adapters/inbound}/FormField.tsx +6 -4
  127. package/src/features/form/formActionsMeta.ts +9 -0
  128. package/src/features/form/formErrorMeta.ts +19 -0
  129. package/src/features/form/formFieldMeta.ts +35 -0
  130. package/src/features/form/formMeta.ts +34 -0
  131. package/src/{components/form/index.ts → features/form/public.ts} +6 -7
  132. package/src/{components → features}/form/useFormController.ts +2 -2
  133. package/src/{components/form → features/form/utils}/validation.test.ts +4 -1
  134. package/src/{components/form → features/form/utils}/validation.ts +4 -8
  135. package/src/features/uploader/adapters/inbound/Uploader.tsx +1 -1
  136. package/src/index.ts +26 -27
  137. package/src/metadata/allowedChildren.ts +1 -1
  138. package/src/metadata/componentMeta.test.ts +18 -1
  139. package/src/metadata/componentMeta.ts +4 -1
  140. package/src/patterns/auth/AuthForm.tsx +68 -0
  141. package/src/patterns/auth/AuthFormField.tsx +81 -0
  142. package/src/patterns/auth/ForgotPasswordForm.tsx +3 -2
  143. package/src/patterns/auth/OtpForm.tsx +3 -2
  144. package/src/patterns/auth/SignInForm.tsx +3 -2
  145. package/src/patterns/auth/SignUpForm.tsx +3 -2
  146. package/src/patterns/auth/types.ts +1 -1
  147. package/src/patterns/auth/utils.ts +1 -1
  148. package/src/patterns/inspector-field/InspectorField.tsx +1 -1
  149. package/src/registry.ts +1 -1
  150. package/src/theme/themeScopeStructure.test.ts +5 -7
  151. package/src/{components/form/types.ts → types/form.ts} +12 -36
  152. package/dist/components/form/Form.d.ts +0 -7
  153. package/dist/components/form/Form.d.ts.map +0 -1
  154. package/dist/components/form/Form.js +0 -34
  155. package/dist/components/form/Form.js.map +0 -1
  156. package/dist/components/form/FormActions.d.ts.map +0 -1
  157. package/dist/components/form/FormActions.js.map +0 -1
  158. package/dist/components/form/FormError.d.ts +0 -7
  159. package/dist/components/form/FormError.d.ts.map +0 -1
  160. package/dist/components/form/FormError.js +0 -21
  161. package/dist/components/form/FormError.js.map +0 -1
  162. package/dist/components/form/FormField.d.ts +0 -7
  163. package/dist/components/form/FormField.d.ts.map +0 -1
  164. package/dist/components/form/FormField.js +0 -81
  165. package/dist/components/form/FormField.js.map +0 -1
  166. package/dist/components/form/index.d.ts +0 -8
  167. package/dist/components/form/index.d.ts.map +0 -1
  168. package/dist/components/form/index.js +0 -7
  169. package/dist/components/form/index.js.map +0 -1
  170. package/dist/components/form/meta.d.ts.map +0 -1
  171. package/dist/components/form/meta.js +0 -94
  172. package/dist/components/form/meta.js.map +0 -1
  173. package/dist/components/form/types.d.ts.map +0 -1
  174. package/dist/components/form/types.js +0 -2
  175. package/dist/components/form/types.js.map +0 -1
  176. package/dist/components/form/useFormController.d.ts.map +0 -1
  177. package/dist/components/form/useFormController.js.map +0 -1
  178. package/dist/components/form/validation.d.ts.map +0 -1
  179. package/dist/components/form/validation.js.map +0 -1
  180. package/dist/patterns/form-field/FormField.d.ts +0 -4
  181. package/dist/patterns/form-field/FormField.d.ts.map +0 -1
  182. package/dist/patterns/form-field/FormField.js.map +0 -1
  183. package/dist/patterns/form-field/index.d.ts +0 -2
  184. package/dist/patterns/form-field/index.d.ts.map +0 -1
  185. package/dist/patterns/form-field/index.js +0 -2
  186. package/dist/patterns/form-field/index.js.map +0 -1
  187. package/dist/patterns/form-field/types.d.ts +0 -9
  188. package/dist/patterns/form-field/types.d.ts.map +0 -1
  189. package/dist/patterns/form-field/types.js +0 -2
  190. package/dist/patterns/form-field/types.js.map +0 -1
  191. package/src/components/form/Form.tsx +0 -74
  192. package/src/components/form/FormError.tsx +0 -34
  193. package/src/components/form/FormField.tsx +0 -157
  194. package/src/components/form/meta.ts +0 -98
  195. package/src/patterns/form-field/index.ts +0 -1
  196. package/src/patterns/form-field/types.ts +0 -16
@@ -0,0 +1,34 @@
1
+ import type { ZoraComponentMeta } from '../../metadata';
2
+ export const formMeta = {
3
+ name: 'Form',
4
+ category: 'component',
5
+ directManifestNode: true,
6
+ allowedChildren: ['FormError', 'FormField'],
7
+ blueprint: {
8
+ label: 'Form',
9
+ defaultProps: { submitLabel: 'Submit', loading: false, disabled: false },
10
+ },
11
+ bindings: {
12
+ events: {
13
+ submit: {
14
+ label: 'Submit',
15
+ description: 'Runs when the form is submitted.',
16
+ payload: { eventType: 'form.submit', fields: [] },
17
+ },
18
+ },
19
+ },
20
+ events: {
21
+ submit: {
22
+ label: 'Submit',
23
+ eventType: 'form.submit',
24
+ description: 'Emitted when the form is submitted.',
25
+ payloadFields: [],
26
+ },
27
+ },
28
+ slots: { actions: { label: 'Actions', allowedChildren: ['Button', 'ButtonGroup'] } },
29
+ props: {
30
+ submitLabel: { type: 'string', category: 'Content', label: 'Submit label', default: 'Submit' },
31
+ loading: { type: 'boolean', category: 'State', label: 'Loading', default: false },
32
+ disabled: { type: 'boolean', category: 'State', label: 'Disabled', default: false },
33
+ },
34
+ } as const satisfies ZoraComponentMeta;
@@ -1,13 +1,8 @@
1
- export { Form } from './Form';
2
- export { FormActions } from './FormActions';
3
- export { FormError } from './FormError';
4
- export { FormField } from './FormField';
5
1
  export type {
6
2
  FormActionsProps,
7
3
  FormErrorProps,
8
4
  FormErrors,
9
5
  FormFieldConfig,
10
- FormFieldControlProps,
11
6
  FormFieldInputType,
12
7
  FormFieldProps,
13
8
  FormProps,
@@ -17,6 +12,10 @@ export type {
17
12
  UseFormControllerOptions,
18
13
  UseFormControllerResult,
19
14
  ValidationRule,
20
- } from './types';
15
+ } from '../../types/form';
16
+ export { Form } from './adapters/inbound/Form';
17
+ export { FormActions } from './adapters/inbound/FormActions';
18
+ export { FormError } from './adapters/inbound/FormError';
19
+ export { FormField } from './adapters/inbound/FormField';
21
20
  export { useFormController } from './useFormController';
22
- export { hasRequiredRule, validateField, validateFields, validateValue } from './validation';
21
+ export { hasRequiredRule, validateField, validateFields, validateValue } from './utils/validation';
@@ -6,8 +6,8 @@ import type {
6
6
  FormValues,
7
7
  UseFormControllerOptions,
8
8
  UseFormControllerResult,
9
- } from './types';
10
- import { validateFields } from './validation';
9
+ } from '../../types/form';
10
+ import { validateFields } from './utils/validation';
11
11
 
12
12
  function createInitialValues<TName extends string>(
13
13
  fields: readonly { name: TName }[],
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
2
 
3
- import type { FormFieldConfig, FormValues } from './types';
3
+ import type { FormFieldConfig, FormValues } from '../../../types/form';
4
4
  import { validateFields, validateValue } from './validation';
5
5
 
6
6
  describe('form validation', () => {
@@ -12,6 +12,9 @@ describe('form validation', () => {
12
12
 
13
13
  test('validates email fields', () => {
14
14
  expect(validateValue('not-email', [{ kind: 'email' }])).toBe('Enter a valid email address.');
15
+ expect(validateValue('person@exam_ple.com', [{ kind: 'email' }])).toBe(
16
+ 'Enter a valid email address.',
17
+ );
15
18
  expect(validateValue('person@example.com', [{ kind: 'email' }])).toBeUndefined();
16
19
  });
17
20
 
@@ -1,12 +1,12 @@
1
+ import { isEmail } from '@ankhorage/utility/regex';
2
+
1
3
  import type {
2
4
  FormFieldConfig,
3
5
  FormValidationErrors,
4
6
  FormValidationResult,
5
7
  FormValues,
6
8
  ValidationRule,
7
- } from './types';
8
-
9
- const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
9
+ } from '../../../types/form';
10
10
 
11
11
  export function hasRequiredRule(rules: readonly ValidationRule[] | undefined): boolean {
12
12
  return rules?.some((rule) => rule.kind === 'required') ?? false;
@@ -27,11 +27,7 @@ export function validateValue(
27
27
  return rule.message ?? 'This field is required.';
28
28
  }
29
29
 
30
- if (
31
- rule.kind === 'email' &&
32
- normalizedValue.length > 0 &&
33
- !emailPattern.test(normalizedValue)
34
- ) {
30
+ if (rule.kind === 'email' && normalizedValue.length > 0 && !isEmail(normalizedValue)) {
35
31
  return rule.message ?? 'Enter a valid email address.';
36
32
  }
37
33
 
@@ -1,10 +1,10 @@
1
1
  import React from 'react';
2
2
 
3
3
  import { Modal } from '../../../../components/modal';
4
- import { FormField } from '../../../../patterns/form-field';
5
4
  import { withZoraThemeScope } from '../../../../theme/withZoraThemeScope';
6
5
  import type { UploadAsset, UploaderProps, UploadType } from '../../../../types/upload';
7
6
  import { Button } from '../../../button/public';
7
+ import { FormField } from '../../../form/public';
8
8
  import { Icon } from '../../../icon/public';
9
9
  import { Image } from '../../../image/public';
10
10
  import { Box, Stack } from '../../../layout/public';
package/src/index.ts CHANGED
@@ -4,33 +4,6 @@ export type { BreadcrumbItem, BreadcrumbsProps } from './components/breadcrumbs'
4
4
  export { Breadcrumbs } from './components/breadcrumbs';
5
5
  export type { DatePickerProps, DatePickerValue } from './components/date-picker';
6
6
  export { DatePicker } from './components/date-picker';
7
- export type {
8
- FormActionsProps,
9
- FormErrorProps,
10
- FormErrors,
11
- FormFieldConfig,
12
- FormFieldControlProps,
13
- FormFieldInputType,
14
- FormFieldProps,
15
- FormProps,
16
- FormValidationErrors,
17
- FormValidationResult,
18
- FormValues,
19
- UseFormControllerOptions,
20
- UseFormControllerResult,
21
- ValidationRule,
22
- } from './components/form';
23
- export {
24
- Form,
25
- FormActions,
26
- FormError,
27
- FormField,
28
- hasRequiredRule,
29
- useFormController,
30
- validateField,
31
- validateFields,
32
- validateValue,
33
- } from './components/form';
34
7
  export type {
35
8
  GradientColor,
36
9
  GradientColors,
@@ -139,6 +112,32 @@ export {
139
112
  type CheckboxGroupProps,
140
113
  type CheckboxProps,
141
114
  } from './features/form/checkbox/public';
115
+ export type {
116
+ FormActionsProps,
117
+ FormErrorProps,
118
+ FormErrors,
119
+ FormFieldConfig,
120
+ FormFieldInputType,
121
+ FormFieldProps,
122
+ FormProps,
123
+ FormValidationErrors,
124
+ FormValidationResult,
125
+ FormValues,
126
+ UseFormControllerOptions,
127
+ UseFormControllerResult,
128
+ ValidationRule,
129
+ } from './features/form/public';
130
+ export {
131
+ Form,
132
+ FormActions,
133
+ FormError,
134
+ FormField,
135
+ hasRequiredRule,
136
+ useFormController,
137
+ validateField,
138
+ validateFields,
139
+ validateValue,
140
+ } from './features/form/public';
142
141
  export {
143
142
  Radio,
144
143
  RadioGroup,
@@ -30,7 +30,7 @@ export const CONTAINER_ALLOWED_CHILDREN = [
30
30
  'RadioGroup',
31
31
  'ThemeModeToggle',
32
32
  'TextInput',
33
- 'FormField',
33
+ 'Form',
34
34
  'Card',
35
35
  'Panel',
36
36
  'Notice',
@@ -51,7 +51,7 @@ describe('ZORA_COMPONENT_META event metadata', () => {
51
51
  const eventType: ZoraComponentEventPayloadKind = event?.eventType ?? 'form.submit';
52
52
 
53
53
  expect(eventType).toBe('form.submit');
54
- expect(event?.payloadFields?.map((field) => field.path)).toEqual(['payload.values']);
54
+ expect(event?.payloadFields).toEqual([]);
55
55
  });
56
56
 
57
57
  test('declares button event metadata', () => {
@@ -100,6 +100,21 @@ describe('ZORA_COMPONENT_META requirement metadata', () => {
100
100
  });
101
101
 
102
102
  describe('ZORA_COMPONENT_META invariants', () => {
103
+ test('keeps form nodes in their canonical hierarchy', () => {
104
+ expect(ZORA_COMPONENT_META.Form.allowedChildren).toEqual(['FormError', 'FormField']);
105
+ expect(ZORA_COMPONENT_META.FormField.allowedChildren).toEqual([
106
+ 'Checkbox',
107
+ 'CheckboxGroup',
108
+ 'RadioGroup',
109
+ 'TextInput',
110
+ ]);
111
+ for (const [name, meta] of Object.entries(ZORA_COMPONENT_META)) {
112
+ if (name === 'Form') continue;
113
+ expect(meta.allowedChildren).not.toContain('FormError');
114
+ expect(meta.allowedChildren).not.toContain('FormField');
115
+ }
116
+ });
117
+
103
118
  test('every key matches meta.name', () => {
104
119
  for (const [key, meta] of Object.entries(ZORA_COMPONENT_META)) {
105
120
  expect(meta.name).toBe(key);
@@ -136,6 +151,7 @@ describe('ZORA_COMPONENT_META invariants', () => {
136
151
  'EmptyState',
137
152
  'Hero',
138
153
  'Button',
154
+ 'FormError',
139
155
  'Checkbox',
140
156
  'CheckboxGroup',
141
157
  'Radio',
@@ -158,6 +174,7 @@ describe('ZORA_COMPONENT_META invariants', () => {
158
174
  ]);
159
175
 
160
176
  const expectedContainerNodes = new Set([
177
+ 'Form',
161
178
  'Surface',
162
179
  'MediaCard',
163
180
  'FlatList',
@@ -1,7 +1,6 @@
1
1
  import { appBarMeta } from '../components/app-bar/meta';
2
2
  import { breadcrumbsMeta } from '../components/breadcrumbs/meta';
3
3
  import { datePickerMeta } from '../components/date-picker/meta';
4
- import { formActionsMeta, formErrorMeta, formFieldMeta, formMeta } from '../components/form/meta';
5
4
  import { gradientMeta } from '../components/gradient/meta';
6
5
  import { dropdownMenuMeta, menuMeta } from '../components/menu/meta';
7
6
  import { modalMeta } from '../components/modal/meta';
@@ -37,6 +36,10 @@ import { chipMeta } from '../features/chip/chipMeta';
37
36
  import { dataTableMeta } from '../features/data-table/dataTableMeta';
38
37
  import { emptyStateMeta } from '../features/empty-state/emptyStateMeta';
39
38
  import { checkboxGroupMeta, checkboxMeta } from '../features/form/checkbox/checkboxMeta';
39
+ import { formActionsMeta } from '../features/form/formActionsMeta';
40
+ import { formErrorMeta } from '../features/form/formErrorMeta';
41
+ import { formFieldMeta } from '../features/form/formFieldMeta';
42
+ import { formMeta } from '../features/form/formMeta';
40
43
  import { radioGroupMeta, radioMeta } from '../features/form/radio/radioMeta';
41
44
  import { textInputMeta } from '../features/form/text-input/textInputMeta';
42
45
  import { iconMeta } from '../features/icon/iconMeta';
@@ -0,0 +1,68 @@
1
+ import React from 'react';
2
+
3
+ import { FormActions, FormError, useFormController } from '../../features/form/public';
4
+ import { Stack } from '../../features/layout/public';
5
+ import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
6
+ import type { FormErrors, FormFieldConfig, FormValues } from '../../types/form';
7
+ import { AuthFormField } from './AuthFormField';
8
+
9
+ interface AuthFormProps<TName extends string> {
10
+ fields: readonly FormFieldConfig<TName>[];
11
+ values: FormValues<TName>;
12
+ onChange: (values: FormValues<TName>) => void;
13
+ onSubmit: (values: FormValues<TName>) => void | Promise<void>;
14
+ errors?: FormErrors<TName>;
15
+ error?: React.ReactNode;
16
+ loading?: boolean;
17
+ disabled?: boolean;
18
+ submitLabel?: React.ReactNode;
19
+ actions?: React.ReactNode;
20
+ testID?: string;
21
+ interactionPolicy?: ZoraBaseProps['interactionPolicy'];
22
+ }
23
+
24
+ /*** Keeps config-driven field orchestration private to the auth patterns that own it. */
25
+ export function AuthForm<TName extends string>({
26
+ fields,
27
+ values,
28
+ onChange,
29
+ onSubmit,
30
+ errors,
31
+ error,
32
+ loading = false,
33
+ disabled = false,
34
+ submitLabel = 'Submit',
35
+ actions,
36
+ testID,
37
+ interactionPolicy,
38
+ }: AuthFormProps<TName>) {
39
+ const controller = useFormController({ fields, values, errors, onChange, onSubmit });
40
+ return (
41
+ <Stack gap="m" testID={testID}>
42
+ <FormError error={error} />
43
+ {fields.map((field) => (
44
+ <AuthFormField
45
+ key={field.name}
46
+ disabled={disabled}
47
+ error={controller.errors[field.name]}
48
+ field={field}
49
+ interactionPolicy={interactionPolicy}
50
+ loading={loading}
51
+ onChange={controller.setFieldValue}
52
+ value={values[field.name]}
53
+ />
54
+ ))}
55
+ <FormActions
56
+ disabled={disabled}
57
+ interactionPolicy={interactionPolicy}
58
+ loading={loading}
59
+ onSubmit={() => {
60
+ if (interactionPolicy !== 'passive') void controller.handleSubmit();
61
+ }}
62
+ submitLabel={submitLabel}
63
+ >
64
+ {actions}
65
+ </FormActions>
66
+ </Stack>
67
+ );
68
+ }
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+
3
+ import { FormField, hasRequiredRule } from '../../features/form/public';
4
+ import { TextInput } from '../../features/form/text-input/public';
5
+ import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
6
+ import type { FormFieldConfig, FormFieldValue } from '../../types/form';
7
+
8
+ interface AuthFormFieldProps<TName extends string> {
9
+ field: FormFieldConfig<TName>;
10
+ value: FormFieldValue;
11
+ onChange: (name: TName, value: FormFieldValue) => void;
12
+ error?: React.ReactNode;
13
+ disabled?: boolean;
14
+ loading?: boolean;
15
+ interactionPolicy?: ZoraBaseProps['interactionPolicy'];
16
+ }
17
+
18
+ /*** Adapts an auth field configuration to the public compositional form primitives. */
19
+ export function AuthFormField<TName extends string>({
20
+ field,
21
+ value,
22
+ onChange,
23
+ error,
24
+ disabled = false,
25
+ loading = false,
26
+ interactionPolicy,
27
+ }: AuthFormFieldProps<TName>) {
28
+ const fieldDisabled = disabled || loading || field.disabled;
29
+ return (
30
+ <FormField
31
+ description={field.description}
32
+ disabled={fieldDisabled}
33
+ errorText={error}
34
+ helperText={field.helperText}
35
+ invalid={Boolean(error)}
36
+ label={field.label}
37
+ readOnly={field.readOnly}
38
+ required={field.required ?? hasRequiredRule(field.rules)}
39
+ testID={field.testID}
40
+ >
41
+ <TextInput
42
+ accessibilityLabel={typeof field.label === 'string' ? field.label : undefined}
43
+ autoCapitalize={
44
+ field.autoCapitalize ??
45
+ (['email', 'password', 'url'].includes(field.type ?? 'text') ? 'none' : undefined)
46
+ }
47
+ autoComplete={field.autoComplete}
48
+ disabled={fieldDisabled}
49
+ interactionPolicy={interactionPolicy}
50
+ invalid={Boolean(error)}
51
+ keyboardType={resolveKeyboardType(field)}
52
+ maxLength={field.maxLength}
53
+ onChangeText={(nextValue) => onChange(field.name, nextValue)}
54
+ placeholder={field.placeholder}
55
+ readOnly={field.readOnly}
56
+ secureTextEntry={field.secureTextEntry ?? field.type === 'password'}
57
+ textContentType={resolveTextContentType(field)}
58
+ value={value}
59
+ />
60
+ </FormField>
61
+ );
62
+ }
63
+
64
+ /*** Resolves the native keyboard for an auth field configuration. */
65
+ function resolveKeyboardType(field: FormFieldConfig) {
66
+ if (field.keyboardType) return field.keyboardType;
67
+ if (field.type === 'email') return 'email-address';
68
+ if (field.type === 'number' || field.type === 'otp') return 'number-pad';
69
+ if (field.type === 'tel') return 'phone-pad';
70
+ if (field.type === 'url') return 'url';
71
+ return undefined;
72
+ }
73
+
74
+ /*** Resolves platform text-content hints for an auth field configuration. */
75
+ function resolveTextContentType(field: FormFieldConfig) {
76
+ if (field.textContentType) return field.textContentType;
77
+ if (field.type === 'email') return 'emailAddress';
78
+ if (field.type === 'password') return 'password';
79
+ if (field.type === 'otp') return 'oneTimeCode';
80
+ return undefined;
81
+ }
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
2
 
3
- import { Form, type FormFieldConfig, type FormValues } from '../../components/form';
4
3
  import { Button } from '../../features/button/public';
5
4
  import { Stack } from '../../features/layout/public';
6
5
  import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
6
+ import type { FormFieldConfig, FormValues } from '../../types/form';
7
+ import { AuthForm } from './AuthForm';
7
8
  import type { ForgotPasswordFormProps } from './types';
8
9
  import {
9
10
  defaultIdentifiers,
@@ -56,7 +57,7 @@ function ForgotPasswordFormInner({
56
57
  );
57
58
 
58
59
  return (
59
- <Form
60
+ <AuthForm
60
61
  actions={
61
62
  onSignIn ? (
62
63
  <Stack direction="row" gap="s" wrap="wrap">
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
2
 
3
- import { Form, type FormFieldConfig, type FormValues } from '../../components/form';
4
3
  import { Button } from '../../features/button/public';
5
4
  import { Stack } from '../../features/layout/public';
6
5
  import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
6
+ import type { FormFieldConfig, FormValues } from '../../types/form';
7
+ import { AuthForm } from './AuthForm';
7
8
  import type { OtpFormProps } from './types';
8
9
 
9
10
  type OtpFieldName = 'otp';
@@ -51,7 +52,7 @@ function OtpFormInner({
51
52
  );
52
53
 
53
54
  return (
54
- <Form
55
+ <AuthForm
55
56
  actions={
56
57
  onResend ? (
57
58
  <Stack direction="row" gap="s" wrap="wrap">
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
2
 
3
- import { Form, type FormFieldConfig, type FormValues } from '../../components/form';
4
3
  import { Button } from '../../features/button/public';
5
4
  import { Stack } from '../../features/layout/public';
6
5
  import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
6
+ import type { FormFieldConfig, FormValues } from '../../types/form';
7
+ import { AuthForm } from './AuthForm';
7
8
  import type { SignInFormProps } from './types';
8
9
  import {
9
10
  defaultIdentifiers,
@@ -68,7 +69,7 @@ function SignInFormInner({
68
69
  );
69
70
 
70
71
  return (
71
- <Form
72
+ <AuthForm
72
73
  actions={
73
74
  hasActions ? (
74
75
  <Stack direction="row" gap="s" wrap="wrap">
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
2
 
3
- import { Form, type FormFieldConfig, type FormValues } from '../../components/form';
4
3
  import { Button } from '../../features/button/public';
5
4
  import { Stack } from '../../features/layout/public';
6
5
  import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
6
+ import type { FormFieldConfig, FormValues } from '../../types/form';
7
+ import { AuthForm } from './AuthForm';
7
8
  import type { SignUpFormProps } from './types';
8
9
 
9
10
  const defaultSignUpFields = [
@@ -48,7 +49,7 @@ function SignUpFormInner({
48
49
  const [values, setValues] = React.useState<FormValues>(() => createValues(fields));
49
50
 
50
51
  return (
51
- <Form
52
+ <AuthForm
52
53
  actions={
53
54
  onSignIn ? (
54
55
  <Stack direction="row" gap="s" wrap="wrap">
@@ -1,9 +1,9 @@
1
1
  import type { ButtonIconSpec } from '@ankhorage/surface';
2
2
  import type React from 'react';
3
3
 
4
- import type { FormFieldConfig, FormValues } from '../../components/form';
5
4
  import type { ButtonProps } from '../../features/button/public';
6
5
  import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
6
+ import type { FormFieldConfig, FormValues } from '../../types/form';
7
7
 
8
8
  export type AuthIdentifierKind = 'email' | 'phone' | 'username';
9
9
 
@@ -1,4 +1,4 @@
1
- import type { FormFieldConfig, ValidationRule } from '../../components/form';
1
+ import type { FormFieldConfig, ValidationRule } from '../../types/form';
2
2
  import type { AuthIdentifierKind } from './types';
3
3
 
4
4
  export const defaultIdentifiers = ['email'] as const satisfies readonly AuthIdentifierKind[];
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
2
 
3
+ import { FormField } from '../../features/form/public';
3
4
  import { Box, Stack } from '../../features/layout/public';
4
5
  import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
5
- import { FormField } from '../form-field';
6
6
  import type { InspectorFieldProps } from './types';
7
7
 
8
8
  function InspectorFieldInner({
package/src/registry.ts CHANGED
@@ -4,7 +4,6 @@ import type React from 'react';
4
4
  import { AppBar } from './components/app-bar';
5
5
  import { Breadcrumbs } from './components/breadcrumbs';
6
6
  import { DatePicker } from './components/date-picker';
7
- import { Form, FormActions, FormError, FormField } from './components/form';
8
7
  import { Gradient } from './components/gradient';
9
8
  import { DropdownMenu, Menu } from './components/menu';
10
9
  import { Modal } from './components/modal';
@@ -35,6 +34,7 @@ import { ChipGroup } from './features/chip/public';
35
34
  import { DataTable } from './features/data-table/public';
36
35
  import { EmptyState } from './features/empty-state/public';
37
36
  import { Checkbox, CheckboxGroup } from './features/form/checkbox/public';
37
+ import { Form, FormActions, FormError, FormField } from './features/form/public';
38
38
  import { Radio, RadioGroup } from './features/form/radio/public';
39
39
  import { TextInput } from './features/form/text-input/public';
40
40
  import { Icon } from './features/icon/public';
@@ -65,10 +65,10 @@ const scopedComponentFiles = [
65
65
  join(srcDir, 'features', 'card', 'adapters', 'inbound', 'MetricCard.tsx'),
66
66
  join(srcDir, 'features', 'form', 'checkbox', 'adapters', 'inbound', 'Checkbox.tsx'),
67
67
  join(srcDir, 'features', 'form', 'checkbox', 'adapters', 'inbound', 'CheckboxGroup.tsx'),
68
- join(srcDir, 'components', 'form', 'Form.tsx'),
69
- join(srcDir, 'components', 'form', 'FormActions.tsx'),
70
- join(srcDir, 'components', 'form', 'FormError.tsx'),
71
- join(srcDir, 'components', 'form', 'FormField.tsx'),
68
+ join(srcDir, 'features', 'form', 'adapters', 'inbound', 'Form.tsx'),
69
+ join(srcDir, 'features', 'form', 'adapters', 'inbound', 'FormActions.tsx'),
70
+ join(srcDir, 'features', 'form', 'adapters', 'inbound', 'FormError.tsx'),
71
+ join(srcDir, 'features', 'form', 'adapters', 'inbound', 'FormField.tsx'),
72
72
  join(srcDir, 'features', 'typography', 'adapters', 'inbound', 'Heading.tsx'),
73
73
  join(srcDir, 'features', 'icon', 'adapters', 'inbound', 'Icon.tsx'),
74
74
  join(srcDir, 'features', 'button', 'adapters', 'inbound', 'IconButton.tsx'),
@@ -99,7 +99,6 @@ const scopedComponentFiles = [
99
99
  join(srcDir, 'patterns', 'confirm-dialog', 'ConfirmDialog.tsx'),
100
100
  join(srcDir, 'patterns', 'disclosure-section', 'DisclosureSection.tsx'),
101
101
  join(srcDir, 'features', 'empty-state', 'adapters', 'inbound', 'EmptyState.tsx'),
102
- join(srcDir, 'patterns', 'form-field', 'FormField.tsx'),
103
102
  join(srcDir, 'patterns', 'inspector-field', 'InspectorField.tsx'),
104
103
  join(srcDir, 'patterns', 'list', 'List.tsx'),
105
104
  join(srcDir, 'patterns', 'list', 'ListRow.tsx'),
@@ -136,7 +135,7 @@ const scopedPropTypeFiles = [
136
135
  join(srcDir, 'types', 'media-card.ts'),
137
136
  join(srcDir, 'types', 'metric-card.ts'),
138
137
  join(srcDir, 'types', 'checkbox.ts'),
139
- join(srcDir, 'components', 'form', 'types.ts'),
138
+ join(srcDir, 'types', 'form.ts'),
140
139
  join(srcDir, 'types', 'heading.ts'),
141
140
  join(srcDir, 'types', 'icon.ts'),
142
141
  join(srcDir, 'types', 'icon-button.ts'),
@@ -162,7 +161,6 @@ const scopedPropTypeFiles = [
162
161
  join(srcDir, 'patterns', 'confirm-dialog', 'types.ts'),
163
162
  join(srcDir, 'patterns', 'disclosure-section', 'types.ts'),
164
163
  join(srcDir, 'types', 'empty-state.ts'),
165
- join(srcDir, 'patterns', 'form-field', 'types.ts'),
166
164
  join(srcDir, 'patterns', 'inspector-field', 'types.ts'),
167
165
  join(srcDir, 'patterns', 'list', 'types.ts'),
168
166
  join(srcDir, 'patterns', 'notice', 'types.ts'),