@dxos/react-ui-form 0.8.4-main.bc674ce → 0.8.4-main.bcb3aa67d6

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 (99) hide show
  1. package/README.md +0 -1
  2. package/dist/lib/browser/index.mjs +620 -583
  3. package/dist/lib/browser/index.mjs.map +4 -4
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/node-esm/index.mjs +620 -583
  6. package/dist/lib/node-esm/index.mjs.map +4 -4
  7. package/dist/lib/node-esm/meta.json +1 -1
  8. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +3 -3
  9. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  10. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +61 -61
  11. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  12. package/dist/types/src/components/Form/Form.d.ts +18 -15
  13. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  14. package/dist/types/src/components/Form/Form.stories.d.ts +67 -67
  15. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/Form/FormField.d.ts +7 -1
  17. package/dist/types/src/components/Form/FormField.d.ts.map +1 -1
  18. package/dist/types/src/components/Form/FormFieldComponent.d.ts +0 -1
  19. package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -1
  20. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +2 -2
  21. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -1
  22. package/dist/types/src/components/Form/FormFieldSet.d.ts +9 -10
  23. package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -1
  24. package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -1
  25. package/dist/types/src/components/Form/fields/RefField.d.ts +7 -5
  26. package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -1
  27. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -1
  28. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +61 -61
  29. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +10 -2
  30. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
  31. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +61 -61
  32. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
  33. package/dist/types/src/components/Settings/Settings.d.ts +39 -0
  34. package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
  35. package/dist/types/src/components/Settings/Settings.stories.d.ts +81 -0
  36. package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
  37. package/dist/types/src/components/Settings/index.d.ts +2 -0
  38. package/dist/types/src/components/Settings/index.d.ts.map +1 -0
  39. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +5 -5
  40. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  41. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +65 -64
  42. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  43. package/dist/types/src/components/index.d.ts +1 -1
  44. package/dist/types/src/components/index.d.ts.map +1 -1
  45. package/dist/types/src/components/testing.d.ts.map +1 -1
  46. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
  47. package/dist/types/src/translations.d.ts +61 -61
  48. package/dist/types/src/translations.d.ts.map +1 -1
  49. package/dist/types/src/util/index.d.ts +0 -1
  50. package/dist/types/src/util/index.d.ts.map +1 -1
  51. package/dist/types/src/util/refs.d.ts +1 -0
  52. package/dist/types/src/util/refs.d.ts.map +1 -1
  53. package/dist/types/tsconfig.tsbuildinfo +1 -1
  54. package/package.json +36 -38
  55. package/src/components/FieldEditor/FieldEditor.stories.tsx +4 -6
  56. package/src/components/FieldEditor/FieldEditor.tsx +3 -4
  57. package/src/components/Form/AUDIT.md +17 -0
  58. package/src/components/Form/Form.stories.tsx +23 -24
  59. package/src/components/Form/Form.tsx +58 -65
  60. package/src/components/Form/FormField.tsx +27 -8
  61. package/src/components/Form/FormFieldComponent.stories.tsx +5 -5
  62. package/src/components/Form/FormFieldComponent.tsx +17 -20
  63. package/src/components/Form/FormFieldSet.tsx +108 -104
  64. package/src/components/Form/fields/ArrayField.tsx +11 -8
  65. package/src/components/Form/fields/GeoPointField.tsx +6 -6
  66. package/src/components/Form/fields/MarkdownField.tsx +1 -1
  67. package/src/components/Form/fields/RefField.tsx +35 -17
  68. package/src/components/Form/fields/SelectField.tsx +1 -1
  69. package/src/components/Form/fields/SelectOptionField.stories.tsx +2 -2
  70. package/src/components/Form/fields/SelectOptionField.tsx +9 -10
  71. package/src/components/Form/fields/TupleField.tsx +1 -1
  72. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +10 -14
  73. package/src/components/ObjectPicker/ObjectPicker.tsx +71 -60
  74. package/src/components/Settings/Settings.stories.tsx +56 -0
  75. package/src/components/Settings/Settings.tsx +129 -0
  76. package/src/components/{ControlSection → Settings}/index.ts +1 -1
  77. package/src/components/ViewEditor/ViewEditor.stories.tsx +22 -20
  78. package/src/components/ViewEditor/ViewEditor.test.tsx +3 -6
  79. package/src/components/ViewEditor/ViewEditor.tsx +99 -70
  80. package/src/components/index.ts +1 -1
  81. package/src/components/testing.tsx +20 -8
  82. package/src/hooks/useFormHandler.ts +42 -24
  83. package/src/translations.ts +62 -63
  84. package/src/util/index.ts +0 -1
  85. package/src/util/refs.ts +7 -3
  86. package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
  87. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
  88. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +0 -87
  89. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
  90. package/dist/types/src/components/ControlSection/index.d.ts +0 -2
  91. package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
  92. package/dist/types/src/components/Form/Form-old.stories.d.ts +0 -131
  93. package/dist/types/src/components/Form/Form-old.stories.d.ts.map +0 -1
  94. package/dist/types/src/util/echo.d.ts +0 -6
  95. package/dist/types/src/util/echo.d.ts.map +0 -1
  96. package/src/components/ControlSection/ControlSection.stories.tsx +0 -38
  97. package/src/components/ControlSection/ControlSection.tsx +0 -153
  98. package/src/components/Form/Form-old.stories.tsx +0 -314
  99. package/src/util/echo.ts +0 -25
@@ -78,12 +78,19 @@ export type FormFieldProps = {
78
78
  * Function to lookup custom renderers for specific properties.
79
79
  */
80
80
  fieldProvider?: FormFieldProvider;
81
+ /**
82
+ * Typename of the ref type that the create props apply to.
83
+ * When set, createOptionLabel/createOptionIcon/createInitialValuePath/createFieldMap/onCreate
84
+ * are only passed to ref fields whose typename matches.
85
+ */
86
+ createTypename?: string;
81
87
  } & Pick<FormFieldComponentProps, 'autoFocus' | 'readonly' | 'layout'> &
82
88
  Pick<
83
89
  RefFieldProps,
84
90
  | 'createOptionLabel'
85
91
  | 'createOptionIcon'
86
92
  | 'createInitialValuePath'
93
+ | 'createFieldMap'
87
94
  | 'db'
88
95
  | 'schemaHook'
89
96
  | 'getOptions'
@@ -102,9 +109,11 @@ export const FormField = (props: FormFieldProps) => {
102
109
  layout,
103
110
 
104
111
  // RefFieldProps
112
+ createTypename,
105
113
  createOptionLabel,
106
114
  createOptionIcon,
107
115
  createInitialValuePath,
116
+ createFieldMap,
108
117
  db,
109
118
  schemaHook,
110
119
  getOptions: getRefOptions,
@@ -117,7 +126,7 @@ export const FormField = (props: FormFieldProps) => {
117
126
 
118
127
  const label = useMemo(() => title ?? String.capitalize(name), [title, name]);
119
128
  const placeholder = useMemo(
120
- () => (examples?.length ? `${t('example placeholder')}: ${examples[0]}` : (description ?? label)),
129
+ () => (examples?.length ? `${t('example.placeholder')}: ${examples[0]}` : (description ?? label)),
121
130
  [examples, description, label],
122
131
  );
123
132
 
@@ -171,13 +180,21 @@ export const FormField = (props: FormFieldProps) => {
171
180
 
172
181
  const options = getOptions(type);
173
182
  if (options) {
183
+ // Resolve labels from projection metadata when available.
184
+ const fieldProjections = projection?.getFieldProjections();
185
+ const fieldProjection = fieldProjections?.find((fp) => fp.field.path === name);
186
+ const selectOptions = fieldProjection?.props.options;
187
+
174
188
  return (
175
189
  <SelectField
176
190
  {...fieldProps}
177
- options={options.map((option) => ({
178
- value: option,
179
- label: option.toString(),
180
- }))}
191
+ options={options.map((option) => {
192
+ const selectOption = selectOptions?.find((so) => so.id === globalThis.String(option));
193
+ return {
194
+ value: option,
195
+ label: selectOption?.title ?? option.toString(),
196
+ };
197
+ })}
181
198
  />
182
199
  );
183
200
  }
@@ -188,13 +205,15 @@ export const FormField = (props: FormFieldProps) => {
188
205
 
189
206
  const refProps = getRefProps(type);
190
207
  if (refProps) {
208
+ const isCreateTarget = !createTypename || refProps.typename === createTypename;
191
209
  return (
192
210
  <RefField
193
211
  {...fieldProps}
194
212
  {...refProps}
195
- createOptionLabel={createOptionLabel}
196
- createOptionIcon={createOptionIcon}
197
- createInitialValuePath={createInitialValuePath}
213
+ createOptionLabel={isCreateTarget ? createOptionLabel : undefined}
214
+ createOptionIcon={isCreateTarget ? createOptionIcon : undefined}
215
+ createInitialValuePath={isCreateTarget ? createInitialValuePath : undefined}
216
+ createFieldMap={isCreateTarget ? createFieldMap : undefined}
198
217
  db={db}
199
218
  schemaHook={schemaHook}
200
219
  getOptions={getRefOptions}
@@ -10,7 +10,7 @@ import { withLayout, withTheme } from '@dxos/react-ui/testing';
10
10
 
11
11
  import { type FormFieldComponentProps, FormFieldWrapper } from './FormFieldComponent';
12
12
 
13
- const Component = ({
13
+ const DefaultStory = ({
14
14
  type,
15
15
  placeholder,
16
16
  readonly,
@@ -24,7 +24,7 @@ const Component = ({
24
24
  );
25
25
 
26
26
  return (
27
- <div className='plb-cardSpacingBlock pli-cardSpacingInline'>
27
+ <div className='p-form-padding'>
28
28
  <FormFieldWrapper<string> {...props}>
29
29
  {({ value = '' }) => (
30
30
  <Input.TextInput
@@ -41,10 +41,10 @@ const Component = ({
41
41
  );
42
42
  };
43
43
 
44
- const meta: Meta<typeof Component> = {
44
+ const meta: Meta<typeof DefaultStory> = {
45
45
  title: 'ui/react-ui-form/FormFieldComponent',
46
- component: Component,
47
- decorators: [withTheme, withLayout({ layout: 'column' })],
46
+ component: DefaultStory,
47
+ decorators: [withTheme(), withLayout({ layout: 'column' })],
48
48
  parameters: {
49
49
  layout: 'fullscreen',
50
50
  },
@@ -15,12 +15,10 @@ import React, {
15
15
 
16
16
  import { type Format } from '@dxos/echo/internal';
17
17
  import { Icon, Input, Tooltip } from '@dxos/react-ui';
18
- import { errorText, mx } from '@dxos/ui-theme';
18
+ import { inputTextLabel } from '@dxos/ui-theme';
19
19
 
20
20
  import { type FormFieldStatus } from '../../hooks';
21
21
 
22
- const labelSpacing = 'mbs-inputSpacingBlock mbe-labelSpacingBlock';
23
-
24
22
  /**
25
23
  * Dynamic props passed to input components.
26
24
  */
@@ -33,7 +31,6 @@ export type FormFieldStateProps<T = any> = {
33
31
 
34
32
  /**
35
33
  * Presentation mode.
36
- *
37
34
  * - full: Show label, control, and status.
38
35
  * - compact: Show label and control.
39
36
  * - inline: Control only.
@@ -79,14 +76,12 @@ export type FormFieldLabelProps = {
79
76
 
80
77
  export const FormFieldLabel = ({ label, error, readonly, asChild }: FormFieldLabelProps) => {
81
78
  const Label = readonly || asChild ? 'span' : Input.Label;
82
- const labelProps = readonly || asChild ? { className: 'text-description text-sm' } : { classNames: '!mlb-0 text-sm' };
83
-
84
79
  return (
85
- <div role='none' className={mx('flex justify-between items-center', labelSpacing)}>
86
- <Label {...labelProps}>{label}</Label>
80
+ <div role='none' className='flex items-center justify-between'>
81
+ <Label className={inputTextLabel}>{label}</Label>
87
82
  {error && (
88
83
  <Tooltip.Trigger asChild content={error} side='bottom'>
89
- <Icon icon='ph--warning--regular' size={4} classNames={errorText} />
84
+ <Icon icon='ph--warning--regular' size={4} classNames='text-error-text' />
90
85
  </Tooltip.Trigger>
91
86
  )}
92
87
  </div>
@@ -118,15 +113,17 @@ export const FormFieldWrapper = <T,>(props: FormFieldWrapperProps<T>) => {
118
113
  const str = String(value ?? '');
119
114
 
120
115
  return (
121
- <Input.Root validationValence={status}>
122
- {layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} />}
123
- {layout === 'static' ? <p>{str}</p> : children ? children({ value }) : null}
124
- {layout === 'full' && (
125
- <Input.DescriptionAndValidation>
126
- <Input.Validation>{error}</Input.Validation>
127
- </Input.DescriptionAndValidation>
128
- )}
129
- </Input.Root>
116
+ <div role='none' className='contents'>
117
+ <Input.Root validationValence={status}>
118
+ {layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} />}
119
+ {layout === 'static' ? <p>{str}</p> : children ? children({ value }) : null}
120
+ {layout === 'full' && (
121
+ <Input.DescriptionAndValidation>
122
+ <Input.Validation>{error}</Input.Validation>
123
+ </Input.DescriptionAndValidation>
124
+ )}
125
+ </Input.Root>
126
+ </div>
130
127
  );
131
128
  };
132
129
 
@@ -158,8 +155,8 @@ export class FormFieldErrorBoundary extends Component<FormFieldErrorBoundaryProp
158
155
  override render() {
159
156
  if (this.state.error) {
160
157
  return (
161
- <div className='flex gap-2 border border-roseFill font-mono text-sm'>
162
- <span className='bg-roseFill text-surfaceText pli-1 font-thin'>ERROR</span>
158
+ <div className='flex gap-2 border border-rose-fill font-mono text-sm'>
159
+ <span className='bg-rose-fill text-surface-text px-1 font-thin'>ERROR</span>
163
160
  {String(this.props.path?.join('.'))}
164
161
  </div>
165
162
  );
@@ -2,13 +2,10 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React, { forwardRef, useMemo } from 'react';
5
+ import React, { useMemo } from 'react';
6
6
 
7
7
  import { type AnyProperties } from '@dxos/echo/internal';
8
8
  import { type SchemaProperty } from '@dxos/effect';
9
- import { type ThemedClassName } from '@dxos/react-ui';
10
- import { mx } from '@dxos/ui-theme';
11
- import { isTruthy } from '@dxos/util';
12
9
 
13
10
  import { type FormHandlerProps } from '../../hooks';
14
11
  import { getFormProperties } from '../../util';
@@ -17,109 +14,116 @@ import { useFormValues } from './Form';
17
14
  import { FormField, type FormFieldProps } from './FormField';
18
15
  import { FormFieldErrorBoundary, FormFieldLabel } from './FormFieldComponent';
19
16
 
20
- export type FormFieldSetProps<T extends AnyProperties> = ThemedClassName<
21
- {
22
- label?: string;
23
- exclude?: (props: SchemaProperty[]) => SchemaProperty[];
24
- sort?: string[];
25
- } & Pick<FormHandlerProps<T>, 'schema'> &
26
- Pick<
27
- FormFieldProps,
28
- | 'path'
29
- | 'autoFocus'
30
- | 'readonly'
31
- | 'layout'
32
- | 'projection'
33
- | 'fieldMap'
34
- | 'fieldProvider'
35
- | 'createOptionLabel'
36
- | 'createOptionIcon'
37
- | 'createInitialValuePath'
38
- | 'db'
39
- | 'schemaHook'
40
- | 'getOptions'
41
- | 'onCreate'
42
- >
43
- >;
44
-
45
- export const FormFieldSet = forwardRef<HTMLDivElement, FormFieldSetProps<any>>(
46
- ({ classNames, label, schema, readonly, path, exclude, sort, projection, layout, ...props }, forwardRef) => {
47
- const values = useFormValues(FormFieldSet.displayName!, path);
48
-
49
- // TODO(burdon): Updates on every value change.
50
- // Remove values dep if can remove from getSchemaProperties.
51
- const properties = useMemo(() => {
52
- if (!schema) {
53
- return [];
54
- }
17
+ const FORM_FIELDSET_NAME = 'Form.FieldSet';
18
+
19
+ export type FormFieldSetProps<T extends AnyProperties> = {
20
+ label?: string;
21
+ sort?: string[];
22
+ exclude?: (props: SchemaProperty[]) => SchemaProperty[];
23
+ } & Pick<FormHandlerProps<T>, 'schema'> &
24
+ Pick<
25
+ FormFieldProps,
26
+ | 'path'
27
+ | 'autoFocus'
28
+ | 'readonly'
29
+ | 'layout'
30
+ | 'projection'
31
+ | 'fieldMap'
32
+ | 'fieldProvider'
33
+ | 'createTypename'
34
+ | 'createOptionLabel'
35
+ | 'createOptionIcon'
36
+ | 'createInitialValuePath'
37
+ | 'createFieldMap'
38
+ | 'db'
39
+ | 'schemaHook'
40
+ | 'getOptions'
41
+ | 'onCreate'
42
+ >;
43
+
44
+ /**
45
+ * Renders a set of form fields derived from a schema object.
46
+ */
47
+ export const FormFieldSet = ({
48
+ label,
49
+ schema,
50
+ readonly,
51
+ path,
52
+ sort,
53
+ exclude,
54
+ projection,
55
+ layout,
56
+ ...props
57
+ }: FormFieldSetProps<any>) => {
58
+ const values = useFormValues(FORM_FIELDSET_NAME, path);
59
+
60
+ // TODO(burdon): Updates on every value change.
61
+ // Remove values dep if can remove from getSchemaProperties.
62
+ const properties = useMemo(() => {
63
+ if (!schema) {
64
+ return [];
65
+ }
55
66
 
56
- // TODO(wittjosiah): Reconcile FormInputAnnotation with projection hidden properties & exclude function.
57
- const props = getFormProperties(schema.ast);
58
-
59
- // Use projection-based field management when view and projection are available.
60
- if (projection) {
61
- const fieldProjections = projection.getFieldProjections();
62
- const hiddenProperties = new Set(projection.getHiddenProperties());
63
-
64
- // Filter properties to only include visible ones and order by projection.
65
- const visibleProps = props.filter((prop) => !hiddenProperties.has(prop.name.toString()));
66
- const orderedProps: SchemaProperty[] = [];
67
-
68
- // Add properties in projection field order.
69
- for (const fieldProjection of fieldProjections) {
70
- const fieldPath = String(fieldProjection.field.path);
71
- const prop = visibleProps.find((p) => p.name === fieldPath);
72
- if (prop) {
73
- orderedProps.push(prop);
74
- }
67
+ // TODO(wittjosiah): Reconcile FormInputAnnotation with projection hidden properties & exclude function.
68
+ const schemaProps = getFormProperties(schema.ast);
69
+ const filteredProps = exclude ? exclude(schemaProps) : schemaProps;
70
+
71
+ // Use projection-based field management when view and projection are available.
72
+ if (projection) {
73
+ const fieldProjections = projection.getFieldProjections();
74
+ const hiddenProperties = new Set(projection.getHiddenProperties());
75
+
76
+ // Filter properties to only include visible ones and order by projection.
77
+ const visibleProps = filteredProps.filter((prop) => !hiddenProperties.has(prop.name.toString()));
78
+ const orderedProps: SchemaProperty[] = [];
79
+
80
+ // Add properties in projection field order.
81
+ for (const fieldProjection of fieldProjections) {
82
+ const fieldPath = String(fieldProjection.field.path);
83
+ const prop = visibleProps.find((p) => p.name === fieldPath);
84
+ if (prop) {
85
+ orderedProps.push(prop);
75
86
  }
76
-
77
- // Add any remaining properties not in projection.
78
- const projectionPaths = new Set(fieldProjections.map((projection) => String(projection.field.path)));
79
- const remainingProps = visibleProps.filter((prop) => !projectionPaths.has(prop.name.toString()));
80
- orderedProps.push(...remainingProps);
81
- return orderedProps;
82
87
  }
83
88
 
84
- // Fallback to legacy filter/sort behavior.
85
- const filtered = exclude ? exclude(props) : props;
86
- return sort
87
- ? filtered.sort(({ name: a }, { name: b }) => sort.indexOf(a.toString()) - sort.indexOf(b.toString()))
88
- : filtered;
89
- }, [schema, values, exclude, sort, projection?.fields]);
90
-
91
- if ((readonly || layout === 'static') && values == null) {
92
- return null;
89
+ // Add any remaining properties not in projection.
90
+ const projectionPaths = new Set(fieldProjections.map((projection) => String(projection.field.path)));
91
+ const remainingProps = visibleProps.filter((prop) => !projectionPaths.has(prop.name.toString()));
92
+ orderedProps.push(...remainingProps);
93
+ return orderedProps;
93
94
  }
94
95
 
95
- return (
96
- <div
97
- role='form'
98
- className={mx('is-full', layout === 'inline' && 'flex flex-col gap-2', classNames)}
99
- ref={forwardRef}
100
- >
101
- {layout !== 'inline' && label && <FormFieldLabel label={label} asChild />}
102
- {properties
103
- .map((property) => {
104
- const name = property.name.toString();
105
- return (
106
- <FormFieldErrorBoundary key={name} path={[...(path ?? []), name]}>
107
- <FormField
108
- type={property.type}
109
- name={name}
110
- path={[...(path ?? []), name]}
111
- readonly={readonly}
112
- layout={layout}
113
- projection={projection}
114
- {...props}
115
- />
116
- </FormFieldErrorBoundary>
117
- );
118
- })
119
- .filter(isTruthy)}
120
- </div>
121
- );
122
- },
123
- );
124
-
125
- FormFieldSet.displayName = 'Form.Fields';
96
+ // Fallback to legacy filter/sort behavior.
97
+ return sort
98
+ ? [...filteredProps].sort(({ name: a }, { name: b }) => sort.indexOf(a.toString()) - sort.indexOf(b.toString()))
99
+ : filteredProps;
100
+ }, [schema, values, exclude, sort, projection]);
101
+
102
+ if ((readonly || layout === 'static') && values == null) {
103
+ return null;
104
+ }
105
+
106
+ return (
107
+ <>
108
+ {layout !== 'inline' && label && <FormFieldLabel label={label} asChild />}
109
+ {properties.map((property) => {
110
+ const name = property.name.toString();
111
+ return (
112
+ <FormFieldErrorBoundary key={name} path={[...(path ?? []), name]}>
113
+ <FormField
114
+ type={property.type}
115
+ name={name}
116
+ path={[...(path ?? []), name]}
117
+ readonly={readonly}
118
+ layout={layout}
119
+ projection={projection}
120
+ {...props}
121
+ />
122
+ </FormFieldErrorBoundary>
123
+ );
124
+ })}
125
+ </>
126
+ );
127
+ };
128
+
129
+ FormFieldSet.displayName = FORM_FIELDSET_NAME;
@@ -4,7 +4,7 @@
4
4
 
5
5
  import * as Option from 'effect/Option';
6
6
  import * as SchemaAST from 'effect/SchemaAST';
7
- import React, { Fragment, useCallback } from 'react';
7
+ import React, { useCallback } from 'react';
8
8
 
9
9
  import { findNode, getArrayElementType, getDiscriminatedType, isDiscriminatedUnion, isNestedType } from '@dxos/effect';
10
10
  import { IconButton, useTranslation } from '@dxos/react-ui';
@@ -77,10 +77,10 @@ export const ArrayField = ({
77
77
  <FormFieldLabel readonly={readonly} label={label} asChild />
78
78
  )}
79
79
 
80
- <div role='none' className='flex flex-col gap-2'>
80
+ <div role='none' className='flex flex-col gap-form-gap'>
81
81
  {values?.map((_, index) => {
82
82
  return (
83
- <div role='none' key={index} className='grid grid-cols-[1fr_min-content] gap-2 last:mb-3'>
83
+ <div role='none' key={index} className='flex gap-form-gap last:mb-form-gap'>
84
84
  <FormField
85
85
  autoFocus={index === values.length - 1}
86
86
  type={elementType}
@@ -91,13 +91,13 @@ export const ArrayField = ({
91
91
  />
92
92
 
93
93
  {!readonly && layout !== 'static' && (
94
- <div role='none' className='flex flex-col bs-full justify-end'>
94
+ <div role='none' className='flex flex-col h-full justify-end'>
95
95
  {/* NOTE: Aligns with center of last field if multi-field object. */}
96
- <div role='none' className='flex items-center bs-[var(--line-height)]'>
96
+ <div role='none' className='flex items-center h-[2rem]'>
97
97
  <IconButton
98
98
  icon='ph--x--regular'
99
99
  iconOnly
100
- label={t('button remove')}
100
+ label={t('remove.button')}
101
101
  onClick={() => handleDelete(index)}
102
102
  classNames='self-center'
103
103
  />
@@ -112,9 +112,9 @@ export const ArrayField = ({
112
112
  {/* TODO(burdon): Get label from schema. */}
113
113
  {!readonly && layout !== 'static' && (
114
114
  <IconButton
115
- classNames='flex is-full _mlb-cardSpacingBlock'
115
+ classNames='flex w-full'
116
116
  icon='ph--plus--regular'
117
- label={t('add field')}
117
+ label={t('add-field.button')}
118
118
  onClick={handleAdd}
119
119
  />
120
120
  )}
@@ -140,6 +140,9 @@ export const getDefaultValue = (ast?: SchemaAST.AST): any => {
140
140
  case 'BooleanKeyword': {
141
141
  return false;
142
142
  }
143
+ case 'Suspend': {
144
+ return getDefaultValue(ast.f());
145
+ }
143
146
  default: {
144
147
  if (ast && isNestedType(ast)) {
145
148
  return {};
@@ -62,17 +62,17 @@ export const GeoPointField = ({
62
62
  {layout === 'static' ? (
63
63
  <LatLng {...value} />
64
64
  ) : (
65
- <div role='none' className='grid grid-cols-2 gap-2'>
65
+ <div role='none' className='grid grid-cols-2 gap-form-gap'>
66
66
  <div>
67
67
  <Input.Root>
68
- {layout !== 'inline' && <Input.Label>{t('latitude label')}</Input.Label>}
68
+ {layout !== 'inline' && <Input.Label>{t('latitude.label')}</Input.Label>}
69
69
  <Input.TextInput
70
70
  type='number'
71
71
  step='0.00001'
72
72
  min='-90'
73
73
  max='90'
74
74
  disabled={!!readonly}
75
- placeholder={t('latitude placeholder')}
75
+ placeholder={t('latitude.placeholder')}
76
76
  value={latitudeText ?? ''}
77
77
  onChange={handleChange('latitude', setLatitudeText)}
78
78
  onBlur={onBlur}
@@ -81,14 +81,14 @@ export const GeoPointField = ({
81
81
  </div>
82
82
  <div>
83
83
  <Input.Root>
84
- {layout !== 'inline' && <Input.Label>{t('longitude label')}</Input.Label>}
84
+ {layout !== 'inline' && <Input.Label>{t('longitude.label')}</Input.Label>}
85
85
  <Input.TextInput
86
86
  type='number'
87
87
  step='0.00001'
88
88
  min='-180'
89
89
  max='180'
90
90
  disabled={!!readonly}
91
- placeholder={t('longitude placeholder')}
91
+ placeholder={t('longitude.placeholder')}
92
92
  value={longitudeText ?? ''}
93
93
  onChange={handleChange('longitude', setLongitudeText)}
94
94
  onBlur={onBlur}
@@ -106,7 +106,7 @@ const LatLng = ({ latitude = 0, longitude = 0 }: GeoLocation) => {
106
106
  const lngHem = longitude >= 0 ? 'E' : 'W';
107
107
 
108
108
  return (
109
- <span className='inline-flex items-center gap-1'>
109
+ <span className='inline-flex items-center gap-form-gap'>
110
110
  <span>
111
111
  <span>{Math.abs(latitude).toFixed(5)}</span>
112
112
  <span className='text-subdued'>°{latHem}</span>
@@ -65,7 +65,7 @@ export const MarkdownField = ({
65
65
  disabled={!!readonly}
66
66
  placeholder={placeholder}
67
67
  value={value ?? ''}
68
- classNames={'min-bs-auto max-bs-40 overflow-auto'}
68
+ classNames={'min-h-auto max-h-40 overflow-auto'}
69
69
  onChange={handleChange}
70
70
  onBlur={onBlur}
71
71
  style={{ resize: 'none' }}