@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.
- package/CHANGELOG.md +10 -0
- package/README.md +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/features/bottom-sheet/bottomSheetMeta.d.ts +1 -1
- package/dist/features/card/cardMeta.d.ts +1 -1
- package/dist/features/card/mediaCardMeta.d.ts +1 -1
- package/dist/features/card/postCardMeta.d.ts +1 -1
- package/dist/features/form/adapters/inbound/Form.d.ts +5 -0
- package/dist/features/form/adapters/inbound/Form.d.ts.map +1 -0
- package/dist/features/form/adapters/inbound/Form.js +18 -0
- package/dist/features/form/adapters/inbound/Form.js.map +1 -0
- package/dist/{components/form → features/form/adapters/inbound}/FormActions.d.ts +1 -1
- package/dist/features/form/adapters/inbound/FormActions.d.ts.map +1 -0
- package/dist/{components/form → features/form/adapters/inbound}/FormActions.js +3 -3
- package/dist/features/form/adapters/inbound/FormActions.js.map +1 -0
- package/dist/features/form/adapters/inbound/FormError.d.ts +5 -0
- package/dist/features/form/adapters/inbound/FormError.d.ts.map +1 -0
- package/dist/features/form/adapters/inbound/FormError.js +18 -0
- package/dist/features/form/adapters/inbound/FormError.js.map +1 -0
- package/dist/features/form/adapters/inbound/FormField.d.ts +5 -0
- package/dist/features/form/adapters/inbound/FormField.d.ts.map +1 -0
- package/dist/{patterns/form-field → features/form/adapters/inbound}/FormField.js +5 -4
- package/dist/features/form/adapters/inbound/FormField.js.map +1 -0
- package/dist/features/form/formActionsMeta.d.ts +9 -0
- package/dist/features/form/formActionsMeta.d.ts.map +1 -0
- package/dist/features/form/formActionsMeta.js +9 -0
- package/dist/features/form/formActionsMeta.js.map +1 -0
- package/dist/features/form/formErrorMeta.d.ts +32 -0
- package/dist/features/form/formErrorMeta.d.ts.map +1 -0
- package/dist/features/form/formErrorMeta.js +19 -0
- package/dist/features/form/formErrorMeta.js.map +1 -0
- package/dist/{components/form/meta.d.ts → features/form/formFieldMeta.d.ts} +28 -39
- package/dist/features/form/formFieldMeta.d.ts.map +1 -0
- package/dist/features/form/formFieldMeta.js +35 -0
- package/dist/features/form/formFieldMeta.js.map +1 -0
- package/dist/features/form/formMeta.d.ts +61 -0
- package/dist/features/form/formMeta.d.ts.map +1 -0
- package/dist/features/form/formMeta.js +34 -0
- package/dist/features/form/formMeta.js.map +1 -0
- package/dist/features/form/public.d.ts +8 -0
- package/dist/features/form/public.d.ts.map +1 -0
- package/dist/features/form/public.js +7 -0
- package/dist/features/form/public.js.map +1 -0
- package/dist/{components → features}/form/useFormController.d.ts +1 -1
- package/dist/features/form/useFormController.d.ts.map +1 -0
- package/dist/{components → features}/form/useFormController.js +1 -1
- package/dist/features/form/useFormController.js.map +1 -0
- package/dist/{components/form → features/form/utils}/validation.d.ts +1 -1
- package/dist/features/form/utils/validation.d.ts.map +1 -0
- package/dist/{components/form → features/form/utils}/validation.js +2 -4
- package/dist/features/form/utils/validation.js.map +1 -0
- package/dist/features/keyboard-avoiding-view/keyboardAvoidingViewMeta.d.ts +1 -1
- package/dist/features/layout/boxMeta.d.ts +1 -1
- package/dist/features/layout/containerMeta.d.ts +1 -1
- package/dist/features/layout/gridMeta.d.ts +1 -1
- package/dist/features/layout/stackMeta.d.ts +1 -1
- package/dist/features/list/flatListMeta.d.ts +1 -1
- package/dist/features/list/sectionListMeta.d.ts +1 -1
- package/dist/features/surface/surfaceMeta.d.ts +1 -1
- package/dist/features/uploader/adapters/inbound/Uploader.d.ts.map +1 -1
- package/dist/features/uploader/adapters/inbound/Uploader.js +1 -1
- package/dist/features/uploader/adapters/inbound/Uploader.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/layout/screen/meta.d.ts +1 -1
- package/dist/layout/screen-section/meta.d.ts +1 -1
- package/dist/metadata/allowedChildren.d.ts +3 -3
- package/dist/metadata/allowedChildren.d.ts.map +1 -1
- package/dist/metadata/allowedChildren.js +1 -1
- package/dist/metadata/allowedChildren.js.map +1 -1
- package/dist/metadata/bindableComponentMeta.d.ts +2 -2
- package/dist/metadata/componentMeta.d.ts.map +1 -1
- package/dist/metadata/componentMeta.js +4 -1
- package/dist/metadata/componentMeta.js.map +1 -1
- package/dist/patterns/auth/AuthForm.d.ts +21 -0
- package/dist/patterns/auth/AuthForm.d.ts.map +1 -0
- package/dist/patterns/auth/AuthForm.js +19 -0
- package/dist/patterns/auth/AuthForm.js.map +1 -0
- package/dist/patterns/auth/AuthFormField.d.ts +16 -0
- package/dist/patterns/auth/AuthFormField.d.ts.map +1 -0
- package/dist/patterns/auth/AuthFormField.js +38 -0
- package/dist/patterns/auth/AuthFormField.js.map +1 -0
- package/dist/patterns/auth/ForgotPasswordForm.d.ts.map +1 -1
- package/dist/patterns/auth/ForgotPasswordForm.js +2 -2
- package/dist/patterns/auth/ForgotPasswordForm.js.map +1 -1
- package/dist/patterns/auth/OtpForm.d.ts.map +1 -1
- package/dist/patterns/auth/OtpForm.js +2 -2
- package/dist/patterns/auth/OtpForm.js.map +1 -1
- package/dist/patterns/auth/SignInForm.d.ts.map +1 -1
- package/dist/patterns/auth/SignInForm.js +2 -2
- package/dist/patterns/auth/SignInForm.js.map +1 -1
- package/dist/patterns/auth/SignUpForm.d.ts.map +1 -1
- package/dist/patterns/auth/SignUpForm.js +2 -2
- package/dist/patterns/auth/SignUpForm.js.map +1 -1
- package/dist/patterns/auth/types.d.ts +1 -1
- package/dist/patterns/auth/types.d.ts.map +1 -1
- package/dist/patterns/auth/types.js.map +1 -1
- package/dist/patterns/auth/utils.d.ts +1 -1
- package/dist/patterns/auth/utils.d.ts.map +1 -1
- package/dist/patterns/auth/utils.js.map +1 -1
- package/dist/patterns/inspector-field/InspectorField.js +1 -1
- package/dist/patterns/inspector-field/InspectorField.js.map +1 -1
- package/dist/patterns/message-bubble/meta.d.ts +1 -1
- package/dist/patterns/notice/meta.d.ts +1 -1
- package/dist/patterns/panel/meta.d.ts +1 -1
- package/dist/patterns/reader/meta.d.ts +2 -2
- package/dist/patterns/scanner/meta.d.ts +2 -2
- package/dist/registry.js +1 -1
- package/dist/registry.js.map +1 -1
- package/dist/{components/form/types.d.ts → types/form.d.ts} +12 -28
- package/dist/types/form.d.ts.map +1 -0
- package/dist/types/form.js +2 -0
- package/dist/types/form.js.map +1 -0
- package/package.json +2 -1
- package/src/audit.test.ts +2 -4
- package/src/constants.ts +1 -0
- package/src/featureManifest.test.ts +7 -0
- package/src/features/form/adapters/inbound/Form.tsx +39 -0
- package/src/{components/form → features/form/adapters/inbound}/FormActions.tsx +4 -4
- package/src/features/form/adapters/inbound/FormError.tsx +22 -0
- package/src/{patterns/form-field → features/form/adapters/inbound}/FormField.tsx +6 -4
- package/src/features/form/formActionsMeta.ts +9 -0
- package/src/features/form/formErrorMeta.ts +19 -0
- package/src/features/form/formFieldMeta.ts +35 -0
- package/src/features/form/formMeta.ts +34 -0
- package/src/{components/form/index.ts → features/form/public.ts} +6 -7
- package/src/{components → features}/form/useFormController.ts +2 -2
- package/src/{components/form → features/form/utils}/validation.test.ts +4 -1
- package/src/{components/form → features/form/utils}/validation.ts +4 -8
- package/src/features/uploader/adapters/inbound/Uploader.tsx +1 -1
- package/src/index.ts +26 -27
- package/src/metadata/allowedChildren.ts +1 -1
- package/src/metadata/componentMeta.test.ts +18 -1
- package/src/metadata/componentMeta.ts +4 -1
- package/src/patterns/auth/AuthForm.tsx +68 -0
- package/src/patterns/auth/AuthFormField.tsx +81 -0
- package/src/patterns/auth/ForgotPasswordForm.tsx +3 -2
- package/src/patterns/auth/OtpForm.tsx +3 -2
- package/src/patterns/auth/SignInForm.tsx +3 -2
- package/src/patterns/auth/SignUpForm.tsx +3 -2
- package/src/patterns/auth/types.ts +1 -1
- package/src/patterns/auth/utils.ts +1 -1
- package/src/patterns/inspector-field/InspectorField.tsx +1 -1
- package/src/registry.ts +1 -1
- package/src/theme/themeScopeStructure.test.ts +5 -7
- package/src/{components/form/types.ts → types/form.ts} +12 -36
- package/dist/components/form/Form.d.ts +0 -7
- package/dist/components/form/Form.d.ts.map +0 -1
- package/dist/components/form/Form.js +0 -34
- package/dist/components/form/Form.js.map +0 -1
- package/dist/components/form/FormActions.d.ts.map +0 -1
- package/dist/components/form/FormActions.js.map +0 -1
- package/dist/components/form/FormError.d.ts +0 -7
- package/dist/components/form/FormError.d.ts.map +0 -1
- package/dist/components/form/FormError.js +0 -21
- package/dist/components/form/FormError.js.map +0 -1
- package/dist/components/form/FormField.d.ts +0 -7
- package/dist/components/form/FormField.d.ts.map +0 -1
- package/dist/components/form/FormField.js +0 -81
- package/dist/components/form/FormField.js.map +0 -1
- package/dist/components/form/index.d.ts +0 -8
- package/dist/components/form/index.d.ts.map +0 -1
- package/dist/components/form/index.js +0 -7
- package/dist/components/form/index.js.map +0 -1
- package/dist/components/form/meta.d.ts.map +0 -1
- package/dist/components/form/meta.js +0 -94
- package/dist/components/form/meta.js.map +0 -1
- package/dist/components/form/types.d.ts.map +0 -1
- package/dist/components/form/types.js +0 -2
- package/dist/components/form/types.js.map +0 -1
- package/dist/components/form/useFormController.d.ts.map +0 -1
- package/dist/components/form/useFormController.js.map +0 -1
- package/dist/components/form/validation.d.ts.map +0 -1
- package/dist/components/form/validation.js.map +0 -1
- package/dist/patterns/form-field/FormField.d.ts +0 -4
- package/dist/patterns/form-field/FormField.d.ts.map +0 -1
- package/dist/patterns/form-field/FormField.js.map +0 -1
- package/dist/patterns/form-field/index.d.ts +0 -2
- package/dist/patterns/form-field/index.d.ts.map +0 -1
- package/dist/patterns/form-field/index.js +0 -2
- package/dist/patterns/form-field/index.js.map +0 -1
- package/dist/patterns/form-field/types.d.ts +0 -9
- package/dist/patterns/form-field/types.d.ts.map +0 -1
- package/dist/patterns/form-field/types.js +0 -2
- package/dist/patterns/form-field/types.js.map +0 -1
- package/src/components/form/Form.tsx +0 -74
- package/src/components/form/FormError.tsx +0 -34
- package/src/components/form/FormField.tsx +0 -157
- package/src/components/form/meta.ts +0 -98
- package/src/patterns/form-field/index.ts +0 -1
- 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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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,
|
|
@@ -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
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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 '../../
|
|
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, '
|
|
69
|
-
join(srcDir, '
|
|
70
|
-
join(srcDir, '
|
|
71
|
-
join(srcDir, '
|
|
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, '
|
|
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'),
|