@dxos/react-ui-form 0.8.2-staging.7ac8446 → 0.8.2

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 (112) hide show
  1. package/dist/lib/browser/index.mjs +1818 -1309
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +1774 -1273
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/lib/node-esm/index.mjs +1818 -1309
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/types/src/components/{ControlSection.d.ts → ControlSection/ControlSection.d.ts} +9 -6
  11. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +1 -0
  12. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +8 -0
  13. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -0
  14. package/dist/types/src/components/ControlSection/index.d.ts +2 -0
  15. package/dist/types/src/components/ControlSection/index.d.ts.map +1 -0
  16. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  17. package/dist/types/src/components/FieldEditor/FieldEditor.test.d.ts +2 -0
  18. package/dist/types/src/components/FieldEditor/FieldEditor.test.d.ts.map +1 -0
  19. package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
  20. package/dist/types/src/components/Form/Defaults.d.ts +4 -4
  21. package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
  22. package/dist/types/src/components/Form/Form.d.ts +6 -4
  23. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  24. package/dist/types/src/components/Form/Form.stories.d.ts +38 -36
  25. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  26. package/dist/types/src/components/Form/FormActions.d.ts +1 -1
  27. package/dist/types/src/components/Form/FormActions.d.ts.map +1 -1
  28. package/dist/types/src/components/Form/FormContent.d.ts +16 -12
  29. package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
  30. package/dist/types/src/components/Form/FormContext.d.ts +2 -2
  31. package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
  32. package/dist/types/src/components/Form/Input.d.ts.map +1 -1
  33. package/dist/types/src/components/Form/RefField.d.ts +12 -0
  34. package/dist/types/src/components/Form/RefField.d.ts.map +1 -0
  35. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -1
  36. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -1
  37. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
  38. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +1 -1
  39. package/dist/types/src/components/Form/factory.d.ts +1 -1
  40. package/dist/types/src/components/Form/factory.d.ts.map +1 -1
  41. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  42. package/dist/types/src/components/ViewEditor/Test.stories.d.ts.map +1 -1
  43. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +2 -2
  44. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  45. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +5 -3
  46. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  47. package/dist/types/src/components/deprecated/Form.d.ts +2 -2
  48. package/dist/types/src/components/deprecated/Form.d.ts.map +1 -1
  49. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +1 -1
  50. package/dist/types/src/components/deprecated/FormInput.d.ts.map +1 -1
  51. package/dist/types/src/components/index.d.ts.map +1 -1
  52. package/dist/types/src/components/testing.d.ts.map +1 -1
  53. package/dist/types/src/hooks/index.d.ts +2 -0
  54. package/dist/types/src/hooks/index.d.ts.map +1 -1
  55. package/dist/types/src/hooks/useForm.d.ts +2 -2
  56. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  57. package/dist/types/src/hooks/useQueryRefOptions.d.ts +23 -0
  58. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +1 -0
  59. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +8 -0
  60. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +1 -0
  61. package/dist/types/src/translations.d.ts +5 -0
  62. package/dist/types/src/translations.d.ts.map +1 -1
  63. package/dist/types/src/util/field.d.ts.map +1 -1
  64. package/dist/types/src/util/formatting.d.ts.map +1 -1
  65. package/dist/types/src/util/index.d.ts +2 -0
  66. package/dist/types/src/util/index.d.ts.map +1 -1
  67. package/dist/types/src/util/refs.d.ts +9 -0
  68. package/dist/types/src/util/refs.d.ts.map +1 -0
  69. package/dist/types/src/util/schema.d.ts +3 -0
  70. package/dist/types/src/util/schema.d.ts.map +1 -0
  71. package/dist/types/src/vitest-setup.d.ts +2 -0
  72. package/dist/types/src/vitest-setup.d.ts.map +1 -0
  73. package/dist/types/tsconfig.tsbuildinfo +1 -1
  74. package/package.json +28 -25
  75. package/src/components/ControlSection/ControlSection.stories.tsx +29 -0
  76. package/src/components/{ControlSection.tsx → ControlSection/ControlSection.tsx} +40 -16
  77. package/src/components/ControlSection/index.ts +5 -0
  78. package/src/components/FieldEditor/FieldEditor.stories.tsx +1 -1
  79. package/src/components/FieldEditor/FieldEditor.test.tsx +49 -0
  80. package/src/components/FieldEditor/FieldEditor.tsx +3 -2
  81. package/src/components/Form/ArrayField.tsx +63 -50
  82. package/src/components/Form/Defaults.tsx +77 -48
  83. package/src/components/Form/Form.stories.tsx +134 -109
  84. package/src/components/Form/Form.tsx +8 -5
  85. package/src/components/Form/FormActions.tsx +15 -18
  86. package/src/components/Form/FormContent.tsx +116 -46
  87. package/src/components/Form/FormContext.tsx +24 -10
  88. package/src/components/Form/Input.tsx +4 -10
  89. package/src/components/Form/RefField.tsx +217 -0
  90. package/src/components/Form/custom/GeoPointInput.tsx +2 -2
  91. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +6 -5
  92. package/src/components/Form/custom/SelectOptionsInput.tsx +2 -2
  93. package/src/components/Form/factory.tsx +1 -1
  94. package/src/components/Form/index.ts +0 -1
  95. package/src/components/ViewEditor/ViewEditor.stories.tsx +19 -9
  96. package/src/components/ViewEditor/ViewEditor.tsx +65 -35
  97. package/src/components/deprecated/Form.stories.tsx +6 -6
  98. package/src/components/deprecated/Form.tsx +6 -6
  99. package/src/components/deprecated/FormInput.tsx +1 -1
  100. package/src/components/index.ts +1 -0
  101. package/src/components/testing.tsx +5 -9
  102. package/src/hooks/index.ts +3 -1
  103. package/src/hooks/useForm.ts +4 -3
  104. package/src/hooks/useQueryRefOptions.ts +53 -0
  105. package/src/hooks/useRefQueryLookupHandler.ts +39 -0
  106. package/src/translations.ts +9 -2
  107. package/src/util/formatting.ts +15 -3
  108. package/src/util/index.ts +3 -1
  109. package/src/util/refs.ts +38 -0
  110. package/src/util/schema.ts +14 -0
  111. package/src/vitest-setup.ts +11 -0
  112. package/dist/types/src/components/ControlSection.d.ts.map +0 -1
@@ -5,36 +5,40 @@
5
5
  import '@dxos-theme';
6
6
 
7
7
  import { type Meta, type StoryObj } from '@storybook/react';
8
+ import { Schema } from 'effect';
8
9
  import React, { useCallback, useState } from 'react';
9
10
 
10
- import { AST, type BaseObject, Format, S } from '@dxos/echo-schema';
11
+ import { ContactType } from '@dxos/client/testing';
12
+ import { type BaseObject, Expando, Format, getDXN, Ref, type TypeAnnotation } from '@dxos/echo-schema';
13
+ import { live } from '@dxos/live-object';
14
+ import { withSurfaceVariantsLayout } from '@dxos/react-ui/testing';
11
15
  import { Testing } from '@dxos/schema/testing';
12
- import { withLayout, withTheme } from '@dxos/storybook-utils';
16
+ import { withTheme } from '@dxos/storybook-utils';
13
17
 
14
18
  import { SelectInput } from './Defaults';
15
19
  import { Form, type FormProps } from './Form';
16
20
  import translations from '../../translations';
17
21
  import { TestLayout, TestPanel } from '../testing';
18
22
 
19
- const AddressSchema = S.Struct({
20
- street: S.optional(S.String.annotations({ [AST.TitleAnnotationId]: 'Street' })),
21
- city: S.optional(S.String.annotations({ [AST.TitleAnnotationId]: 'City' })),
22
- zip: S.optional(S.String.pipe(S.pattern(/^\d{5}(-\d{4})?$/)).annotations({ [AST.TitleAnnotationId]: 'ZIP' })),
23
- location: S.optional(Format.GeoPoint.annotations({ [AST.TitleAnnotationId]: 'Location' })),
24
- }).annotations({ [AST.TitleAnnotationId]: 'Address' });
23
+ const AddressSchema = Schema.Struct({
24
+ street: Schema.optional(Schema.String.annotations({ title: 'Street' })),
25
+ city: Schema.optional(Schema.String.annotations({ title: 'City' })),
26
+ zip: Schema.optional(Schema.String.pipe(Schema.pattern(/^\d{5}(-\d{4})?$/)).annotations({ title: 'ZIP' })),
27
+ location: Schema.optional(Format.GeoPoint.annotations({ title: 'Location' })),
28
+ }).annotations({ title: 'Address' });
25
29
 
26
30
  // TODO(burdon): Translations?
27
- const TestSchema = S.Struct({
28
- name: S.optional(S.String.annotations({ [AST.TitleAnnotationId]: 'Name' })),
29
- active: S.optional(S.Boolean.annotations({ [AST.TitleAnnotationId]: 'Active' })),
30
- rank: S.optional(S.Number.annotations({ [AST.TitleAnnotationId]: 'Rank' })),
31
- website: S.optional(Format.URL.annotations({ [AST.TitleAnnotationId]: 'Website' })),
32
- address: S.optional(AddressSchema),
33
- }).pipe(S.mutable);
31
+ const TestSchema = Schema.Struct({
32
+ name: Schema.optional(Schema.String.annotations({ title: 'Name' })),
33
+ active: Schema.optional(Schema.Boolean.annotations({ title: 'Active' })),
34
+ rank: Schema.optional(Schema.Number.annotations({ title: 'Rank' })),
35
+ website: Schema.optional(Format.URL.annotations({ title: 'Website' })),
36
+ address: Schema.optional(AddressSchema),
37
+ }).pipe(Schema.mutable);
34
38
 
35
- type TestType = S.Schema.Type<typeof TestSchema>;
39
+ type TestType = Schema.Schema.Type<typeof TestSchema>;
36
40
 
37
- type StoryProps<T extends BaseObject> = { schema: S.Schema<T> } & FormProps<T>;
41
+ type StoryProps<T extends BaseObject> = { schema: Schema.Schema<T> } & FormProps<T>;
38
42
 
39
43
  const DefaultStory = <T extends BaseObject>({ schema, values: initialValues, ...props }: StoryProps<T>) => {
40
44
  const [values, setValues] = useState(initialValues);
@@ -42,6 +46,15 @@ const DefaultStory = <T extends BaseObject>({ schema, values: initialValues, ...
42
46
  setValues(values);
43
47
  }, []);
44
48
 
49
+ return <Form<T> schema={schema} values={values} onSave={handleSave} {...props} />;
50
+ };
51
+
52
+ const DebugStory = <T extends BaseObject>({ schema, values: initialValues, ...props }: StoryProps<T>) => {
53
+ const [values, setValues] = useState(initialValues);
54
+ const handleSave = useCallback<NonNullable<FormProps<T>['onSave']>>((values) => {
55
+ setValues(values);
56
+ }, []);
57
+
45
58
  return (
46
59
  <TestLayout json={{ values, schema: schema.ast.toJSON() }}>
47
60
  <TestPanel>
@@ -54,11 +67,17 @@ const DefaultStory = <T extends BaseObject>({ schema, values: initialValues, ...
54
67
  const meta: Meta<StoryProps<any>> = {
55
68
  title: 'ui/react-ui-form/Form',
56
69
  component: Form,
57
- render: DefaultStory,
58
- decorators: [withLayout({ fullscreen: true, tooltips: true }), withTheme],
70
+ render: DebugStory,
71
+ decorators: [withTheme],
59
72
  parameters: {
60
73
  translations,
61
74
  },
75
+ argTypes: {
76
+ readonly: {
77
+ control: 'boolean',
78
+ description: 'Readonly',
79
+ },
80
+ },
62
81
  };
63
82
 
64
83
  export default meta;
@@ -66,6 +85,8 @@ export default meta;
66
85
  type Story<T extends BaseObject> = StoryObj<StoryProps<T>>;
67
86
 
68
87
  export const Default: Story<TestType> = {
88
+ render: DefaultStory,
89
+ decorators: [withSurfaceVariantsLayout(), withTheme],
69
90
  args: {
70
91
  schema: TestSchema,
71
92
  values: {
@@ -75,27 +96,30 @@ export const Default: Story<TestType> = {
75
96
  zip: '11205',
76
97
  },
77
98
  },
99
+ readonly: false,
78
100
  },
79
101
  };
80
102
 
81
- export const Org: Story<Testing.Org> = {
103
+ export const Organization: Story<Testing.Organization> = {
82
104
  args: {
83
- schema: Testing.OrgSchema,
105
+ schema: Testing.OrganizationSchema,
84
106
  values: {
85
107
  name: 'DXOS',
86
- // website: 'https://dxos.org',
108
+ website: 'https://dxos.org',
87
109
  },
110
+ readonly: false,
88
111
  },
89
112
  };
90
113
 
91
- export const OrgAutoSave: Story<Testing.Org> = {
114
+ export const OrganizationAutoSave: Story<Testing.Organization> = {
92
115
  args: {
93
- schema: Testing.OrgSchema,
116
+ schema: Testing.OrganizationSchema,
94
117
  values: {
95
118
  name: 'DXOS',
96
- // website: 'https://dxos.org',
119
+ website: 'https://dxos.org',
97
120
  },
98
121
  autoSave: true,
122
+ readonly: false,
99
123
  },
100
124
  };
101
125
 
@@ -111,96 +135,58 @@ export const OrgAutoSave: Story<Testing.Org> = {
111
135
  // },
112
136
  // };
113
137
 
114
- //
115
- // TODO(burdon): Move into separate storybook and use test types.
116
- //
117
-
118
- const ShapeSchema = S.Struct({
119
- shape: S.optional(
120
- S.Union(
121
- S.Struct({
122
- type: S.Literal('circle').annotations({ [AST.TitleAnnotationId]: 'Type' }),
123
- radius: S.optional(S.Number.annotations({ [AST.TitleAnnotationId]: 'Radius' })),
138
+ const ShapeSchema = Schema.Struct({
139
+ shape: Schema.optional(
140
+ Schema.Union(
141
+ Schema.Struct({
142
+ type: Schema.Literal('circle').annotations({ title: 'Type' }),
143
+ radius: Schema.optional(Schema.Number.annotations({ title: 'Radius' })),
124
144
  }),
125
- S.Struct({
126
- type: S.Literal('square').annotations({ [AST.TitleAnnotationId]: 'Type' }),
127
- size: S.optional(S.Number.pipe(S.nonNegative()).annotations({ [AST.TitleAnnotationId]: 'Size' })),
145
+ Schema.Struct({
146
+ type: Schema.Literal('square').annotations({ title: 'Type' }),
147
+ size: Schema.optional(Schema.Number.pipe(Schema.nonNegative()).annotations({ title: 'Size' })),
128
148
  }),
129
- ).annotations({ [AST.TitleAnnotationId]: 'Shape' }),
149
+ ).annotations({ title: 'Shape' }),
130
150
  ),
131
- }).pipe(S.mutable);
132
-
133
- type ShapeType = S.Schema.Type<typeof ShapeSchema>;
151
+ }).pipe(Schema.mutable);
134
152
 
135
- type DiscriminatedUnionStoryProps = FormProps<ShapeType>;
153
+ type ShapeType = Schema.Schema.Type<typeof ShapeSchema>;
136
154
 
137
- const DiscriminatedUnionStory = ({ values: initialValues }: DiscriminatedUnionStoryProps) => {
138
- const [values, setValues] = useState(initialValues);
139
- const handleSave = useCallback<NonNullable<FormProps<ShapeType>['onSave']>>((values) => {
140
- setValues(values);
141
- }, []);
142
-
143
- return (
144
- <TestLayout json={{ values, schema: ShapeSchema.ast.toJSON() }}>
145
- <TestPanel>
146
- <Form<ShapeType>
147
- schema={ShapeSchema}
148
- values={values}
149
- onSave={handleSave}
150
- Custom={{
151
- ['shape.type' as const]: (props) => (
152
- <SelectInput
153
- {...props}
154
- options={['circle', 'square'].map((value) => ({
155
- value,
156
- label: value,
157
- }))}
158
- />
159
- ),
160
- }}
161
- />
162
- </TestPanel>
163
- </TestLayout>
164
- );
165
- };
166
-
167
- export const DiscriminatedShape: StoryObj<DiscriminatedUnionStoryProps> = {
168
- render: DiscriminatedUnionStory,
155
+ export const DiscriminatedShape: Story<ShapeType> = {
169
156
  args: {
157
+ schema: ShapeSchema,
158
+ readonly: false,
170
159
  values: {
171
160
  shape: {
172
161
  type: 'circle',
173
162
  radius: 5,
174
163
  },
175
164
  },
165
+ Custom: {
166
+ ['shape.type' as const]: (props) => (
167
+ <SelectInput
168
+ {...props}
169
+ options={['circle', 'square'].map((value) => ({
170
+ value,
171
+ label: value,
172
+ }))}
173
+ />
174
+ ),
175
+ },
176
176
  },
177
177
  };
178
178
 
179
- const ArraysSchema = S.Struct({
180
- names: S.Array(S.String.pipe(S.nonEmptyString())),
181
- addresses: S.Array(AddressSchema),
182
- }).pipe(S.mutable);
183
-
184
- type ArraysType = S.Schema.Type<typeof ArraysSchema>;
179
+ const ArraysSchema = Schema.Struct({
180
+ names: Schema.Array(Schema.String.pipe(Schema.nonEmptyString())),
181
+ addresses: Schema.Array(AddressSchema),
182
+ }).pipe(Schema.mutable);
185
183
 
186
- const ArraysStory = ({ values: initialValues }: FormProps<ArraysType>) => {
187
- const [values, setValues] = useState(initialValues);
188
- const handleSave = useCallback<NonNullable<FormProps<ArraysType>['onSave']>>((values) => {
189
- setValues(values);
190
- }, []);
191
-
192
- return (
193
- <TestLayout json={{ values, schema: ArraysSchema.ast.toJSON() }}>
194
- <TestPanel>
195
- <Form<ArraysType> schema={ArraysSchema} values={values} onSave={handleSave} />
196
- </TestPanel>
197
- </TestLayout>
198
- );
199
- };
184
+ type ArraysType = Schema.Schema.Type<typeof ArraysSchema>;
200
185
 
201
186
  export const Arrays: StoryObj<FormProps<ArraysType>> = {
202
- render: ArraysStory,
203
187
  args: {
188
+ schema: ArraysSchema,
189
+ readonly: false,
204
190
  values: {
205
191
  names: ['Alice', 'Bob'],
206
192
  addresses: [],
@@ -208,34 +194,73 @@ export const Arrays: StoryObj<FormProps<ArraysType>> = {
208
194
  },
209
195
  };
210
196
 
211
- const ColorSchema = S.Struct({
212
- color: S.Union(S.Literal('red'), S.Literal('green'), S.Literal('blue')).annotations({
213
- [AST.TitleAnnotationId]: 'Color',
197
+ const ColorSchema = Schema.Struct({
198
+ color: Schema.Union(Schema.Literal('red'), Schema.Literal('green'), Schema.Literal('blue')).annotations({
199
+ title: 'Color',
214
200
  }),
215
- }).pipe(S.mutable);
201
+ }).pipe(Schema.mutable);
202
+
203
+ type ColorType = Schema.Schema.Type<typeof ColorSchema>;
204
+
205
+ export const Enum: StoryObj<FormProps<ColorType>> = {
206
+ args: {
207
+ schema: ColorSchema,
208
+ readonly: false,
209
+ values: {
210
+ color: 'red',
211
+ },
212
+ },
213
+ };
216
214
 
217
- type ColorType = S.Schema.Type<typeof ColorSchema>;
215
+ const RefSchema = Schema.Struct({
216
+ contact: Ref(ContactType).annotations({ title: 'Contact Reference' }),
217
+ optionalContact: Schema.optional(Ref(ContactType).annotations({ title: 'Optional Contact Reference' })),
218
+ refArray: Schema.optional(Schema.Array(Ref(ContactType))),
219
+ unknownExpando: Schema.optional(Ref(Expando).annotations({ title: 'Optional Ref to an Expando (DXN Input)' })),
220
+ });
218
221
 
219
- const EnumStory = ({ values: initialValues }: FormProps<ColorType>) => {
222
+ const contact1 = live(ContactType, { identifiers: [] });
223
+ const contact2 = live(ContactType, { identifiers: [] });
224
+
225
+ const RefStory = ({ values: initialValues, readonly }: FormProps<any>) => {
220
226
  const [values, setValues] = useState(initialValues);
221
- const handleSave = useCallback<NonNullable<FormProps<ColorType>['onSave']>>((values) => {
227
+ const handleSave = useCallback<NonNullable<FormProps<any>['onSave']>>((values) => {
222
228
  setValues(values);
223
229
  }, []);
224
230
 
231
+ const onQueryRefOptions = useCallback((typeInfo: TypeAnnotation) => {
232
+ switch (typeInfo.typename) {
233
+ case ContactType.typename:
234
+ return [
235
+ { dxn: getDXN(contact1)!, label: 'John Coltraine' },
236
+ { dxn: getDXN(contact2)!, label: 'Erykah Badu' },
237
+ ];
238
+ default:
239
+ return [];
240
+ }
241
+ }, []);
242
+
225
243
  return (
226
- <TestLayout json={{ values, schema: ColorSchema.ast.toJSON() }}>
244
+ <TestLayout json={{ values, schema: RefSchema.ast.toJSON() }}>
227
245
  <TestPanel>
228
- <Form<ColorType> schema={ColorSchema} values={values} onSave={handleSave} />
246
+ <Form
247
+ schema={RefSchema}
248
+ values={values}
249
+ readonly={readonly}
250
+ onSave={handleSave}
251
+ onQueryRefOptions={onQueryRefOptions}
252
+ />
229
253
  </TestPanel>
230
254
  </TestLayout>
231
255
  );
232
256
  };
233
257
 
234
- export const Enum: StoryObj<FormProps<ColorType>> = {
235
- render: EnumStory,
258
+ export const Refs: StoryObj<FormProps<ContactType>> = {
259
+ render: RefStory,
236
260
  args: {
261
+ readonly: false,
237
262
  values: {
238
- color: 'red',
239
- },
263
+ refArray: [Ref.make(contact1), Ref.make(contact2)],
264
+ } as any,
240
265
  },
241
266
  };
@@ -2,9 +2,10 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { type Schema } from 'effect';
5
6
  import React, { type ReactElement, useEffect, useMemo, useRef } from 'react';
6
7
 
7
- import { type BaseObject, type S, type PropertyKey } from '@dxos/echo-schema';
8
+ import { type BaseObject, type PropertyKey } from '@dxos/echo-schema';
8
9
  import { type ThemedClassName } from '@dxos/react-ui';
9
10
  import { mx } from '@dxos/react-ui-theme';
10
11
  import { type SchemaProperty } from '@dxos/schema';
@@ -13,13 +14,13 @@ import { FormActions } from './FormActions';
13
14
  import { FormFields } from './FormContent';
14
15
  import { FormProvider } from './FormContext';
15
16
  import { type InputProps, type InputComponent } from './Input';
16
- import { type FormOptions } from '../../hooks';
17
+ import { type FormOptions, type QueryRefOptions } from '../../hooks';
17
18
 
18
19
  export type PropsFilter<T extends BaseObject> = (props: SchemaProperty<T>[]) => SchemaProperty<T>[];
19
20
 
20
21
  export type ComponentLookup = (args: {
21
22
  prop: string;
22
- schema: S.Schema<any>;
23
+ schema: Schema.Schema<any>;
23
24
  inputProps: InputProps;
24
25
  }) => ReactElement | undefined;
25
26
 
@@ -41,6 +42,7 @@ export type FormProps<T extends BaseObject> = ThemedClassName<
41
42
  autoSave?: boolean;
42
43
  testId?: string;
43
44
  onCancel?: () => void;
45
+ onQueryRefOptions?: QueryRefOptions;
44
46
  lookupComponent?: ComponentLookup;
45
47
  /**
46
48
  * Map of custom renderers for specific properties.
@@ -65,6 +67,7 @@ export const Form = <T extends BaseObject>({
65
67
  onValidate,
66
68
  onSave,
67
69
  onCancel,
70
+ onQueryRefOptions,
68
71
  lookupComponent,
69
72
  Custom,
70
73
  }: FormProps<T>) => {
@@ -92,14 +95,14 @@ export const Form = <T extends BaseObject>({
92
95
  onValid={onValid}
93
96
  onSave={onSave}
94
97
  >
95
- {/* TODO(burdon): Remove padding. */}
96
- <div ref={formRef} role='none' className={mx('p-2', classNames)} data-testid={testId}>
98
+ <div ref={formRef} role='none' className={mx('flex flex-col gap-4', classNames)} data-testid={testId}>
97
99
  <FormFields
98
100
  schema={schema}
99
101
  path={path}
100
102
  readonly={readonly}
101
103
  filter={filter}
102
104
  sort={sort}
105
+ onQueryRefOptions={onQueryRefOptions}
103
106
  lookupComponent={lookupComponent}
104
107
  Custom={Custom}
105
108
  />
@@ -5,7 +5,6 @@
5
5
  import React from 'react';
6
6
 
7
7
  import { IconButton, useTranslation } from '@dxos/react-ui';
8
- import { mx } from '@dxos/react-ui-theme';
9
8
 
10
9
  import { useFormContext } from './FormContext';
11
10
  import { translationKey } from '../../translations';
@@ -15,27 +14,25 @@ export type FormActionsProps = {
15
14
  onCancel?: () => void;
16
15
  };
17
16
 
18
- export const FormActions = ({ onCancel, readonly }: FormActionsProps) => {
17
+ export const FormActions = ({ readonly, onCancel }: FormActionsProps) => {
19
18
  const { t } = useTranslation(translationKey);
20
19
  const { canSave, handleSave } = useFormContext();
21
20
 
22
21
  return (
23
- <div role='none' className='mt-2 flex justify-center'>
24
- <div role='none' className={mx(onCancel && !readonly && 'grid grid-cols-2 gap-2')}>
25
- {onCancel && !readonly && (
26
- <IconButton data-testid='cancel-button' icon='ph--x--regular' label={t('button cancel')} onClick={onCancel} />
27
- )}
28
- {handleSave && (
29
- <IconButton
30
- type='submit'
31
- data-testid='save-button'
32
- disabled={!canSave}
33
- icon='ph--check--regular'
34
- label={t('button save')}
35
- onClick={handleSave}
36
- />
37
- )}
38
- </div>
22
+ <div role='none' className='flex [&_button]:grow gap-1'>
23
+ {onCancel && !readonly && (
24
+ <IconButton data-testid='cancel-button' icon='ph--x--regular' label={t('button cancel')} onClick={onCancel} />
25
+ )}
26
+ {handleSave && (
27
+ <IconButton
28
+ type='submit'
29
+ data-testid='save-button'
30
+ disabled={!canSave}
31
+ icon='ph--check--regular'
32
+ label={t('button save')}
33
+ onClick={handleSave}
34
+ />
35
+ )}
39
36
  </div>
40
37
  );
41
38
  };