@dxos/react-ui-form 0.8.3-main.672df60 → 0.8.3-staging.0fa589b

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 (63) hide show
  1. package/dist/lib/browser/index.mjs +190 -173
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +150 -133
  5. package/dist/lib/node/index.cjs.map +3 -3
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/lib/node-esm/index.mjs +190 -173
  8. package/dist/lib/node-esm/index.mjs.map +3 -3
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/types/src/components/ControlSection/ControlSection.d.ts +1 -1
  11. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +1 -1
  12. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +3 -2
  14. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  15. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +5 -0
  16. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
  18. package/dist/types/src/components/Form/Form.d.ts +6 -14
  19. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  20. package/dist/types/src/components/Form/Form.stories.d.ts +2 -2
  21. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  22. package/dist/types/src/components/Form/FormActions.d.ts +3 -1
  23. package/dist/types/src/components/Form/FormActions.d.ts.map +1 -1
  24. package/dist/types/src/components/Form/FormContent.d.ts +30 -3
  25. package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
  26. package/dist/types/src/components/Form/Input.d.ts +6 -4
  27. package/dist/types/src/components/Form/Input.d.ts.map +1 -1
  28. package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
  29. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -1
  30. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -1
  31. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
  32. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +1 -1
  33. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +3 -2
  34. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  35. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +7 -0
  36. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/testing.d.ts +3 -1
  38. package/dist/types/src/components/testing.d.ts.map +1 -1
  39. package/dist/types/src/translations.d.ts +3 -0
  40. package/dist/types/src/translations.d.ts.map +1 -1
  41. package/package.json +28 -26
  42. package/src/components/ControlSection/ControlSection.stories.tsx +15 -3
  43. package/src/components/ControlSection/ControlSection.tsx +11 -9
  44. package/src/components/FieldEditor/FieldEditor.stories.tsx +25 -5
  45. package/src/components/FieldEditor/FieldEditor.test.tsx +21 -13
  46. package/src/components/FieldEditor/FieldEditor.tsx +11 -2
  47. package/src/components/Form/ArrayField.tsx +1 -1
  48. package/src/components/Form/Defaults.tsx +5 -25
  49. package/src/components/Form/Form.stories.tsx +5 -5
  50. package/src/components/Form/Form.tsx +44 -49
  51. package/src/components/Form/FormActions.tsx +17 -2
  52. package/src/components/Form/FormContent.tsx +46 -46
  53. package/src/components/Form/Input.tsx +13 -8
  54. package/src/components/Form/RefField.tsx +1 -5
  55. package/src/components/Form/custom/GeoPointInput.tsx +1 -3
  56. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +7 -5
  57. package/src/components/Form/custom/SelectOptionsInput.tsx +1 -3
  58. package/src/components/Form/custom/TupleInput.tsx +1 -3
  59. package/src/components/ViewEditor/ViewEditor.stories.tsx +25 -9
  60. package/src/components/ViewEditor/ViewEditor.test.tsx +80 -42
  61. package/src/components/ViewEditor/ViewEditor.tsx +66 -66
  62. package/src/components/testing.tsx +12 -7
  63. package/src/translations.ts +4 -1
@@ -8,16 +8,17 @@ import React, { useCallback, useMemo, useState } from 'react';
8
8
  import { type SchemaRegistry } from '@dxos/echo-db';
9
9
  import { EchoSchema, Format, type JsonProp, isMutable, toJsonSchema } from '@dxos/echo-schema';
10
10
  import { invariant } from '@dxos/invariant';
11
- import { Icon, IconButton, Message, type ThemedClassName, useTranslation } from '@dxos/react-ui';
11
+ import { Callout, IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';
12
12
  import { List } from '@dxos/react-ui-list';
13
+ import { cardSpacing } from '@dxos/react-ui-stack';
13
14
  import { ghostHover, inputTextLabel, mx } from '@dxos/react-ui-theme';
14
15
  import { FieldSchema, type FieldType, type ViewType, ViewProjection, VIEW_FIELD_LIMIT } from '@dxos/schema';
15
16
 
16
17
  import { translationKey } from '../../translations';
17
18
  import { FieldEditor } from '../FieldEditor';
18
- import { Form } from '../Form';
19
+ import { Form, type FormProps } from '../Form';
19
20
 
20
- const grid = 'grid grid-cols-[32px_1fr_32px] min-bs-[2.5rem]';
21
+ const grid = 'grid grid-cols-[32px_1fr_32px_32px] min-bs-[2.5rem]';
21
22
 
22
23
  const ViewMetaSchema = Schema.Struct({
23
24
  name: Schema.String.annotations({
@@ -30,15 +31,17 @@ const ViewMetaSchema = Schema.Struct({
30
31
 
31
32
  type ViewMetaType = Schema.Schema.Type<typeof ViewMetaSchema>;
32
33
 
33
- export type ViewEditorProps = ThemedClassName<{
34
- schema: Schema.Schema.AnyNoContext;
35
- view: ViewType;
36
- registry?: SchemaRegistry;
37
- readonly?: boolean;
38
- showHeading?: boolean;
39
- onTypenameChanged?: (typename: string) => void;
40
- onDelete?: (fieldId: string) => void;
41
- }>;
34
+ export type ViewEditorProps = ThemedClassName<
35
+ {
36
+ schema: Schema.Schema.AnyNoContext;
37
+ view: ViewType;
38
+ registry?: SchemaRegistry;
39
+ readonly?: boolean;
40
+ showHeading?: boolean;
41
+ onTypenameChanged?: (typename: string) => void;
42
+ onDelete?: (fieldId: string) => void;
43
+ } & Pick<FormProps<any>, 'outerSpacing'>
44
+ >;
42
45
 
43
46
  /**
44
47
  * Schema-based object form.
@@ -48,10 +51,11 @@ export const ViewEditor = ({
48
51
  schema,
49
52
  view,
50
53
  registry,
51
- readonly,
54
+ readonly: _readonly,
52
55
  showHeading = false,
53
56
  onTypenameChanged,
54
57
  onDelete,
58
+ outerSpacing = true,
55
59
  }: ViewEditorProps) => {
56
60
  const { t } = useTranslation(translationKey);
57
61
  const projection = useMemo(() => {
@@ -60,7 +64,7 @@ export const ViewEditor = ({
60
64
  return new ViewProjection(jsonSchema, view);
61
65
  }, [schema, view]);
62
66
  const [field, setField] = useState<FieldType>();
63
- const immutable = readonly || !isMutable(schema);
67
+ const readonly = _readonly || !isMutable(schema);
64
68
 
65
69
  // TODO(burdon): Should be reactive.
66
70
  const viewValues = useMemo(() => {
@@ -74,60 +78,60 @@ export const ViewEditor = ({
74
78
 
75
79
  const handleSelect = useCallback(
76
80
  (field: FieldType) => {
77
- if (immutable) {
81
+ if (readonly) {
78
82
  return;
79
83
  }
80
84
  setField((f) => (f === field ? undefined : field));
81
85
  },
82
- [immutable],
86
+ [readonly],
83
87
  );
84
88
 
85
89
  // TODO(burdon): Check if mutable; variant of useCallback that return undefined if readonly?
86
90
 
87
91
  const handleAdd = useCallback(() => {
88
- invariant(!immutable);
92
+ invariant(!readonly);
89
93
  const field = projection.createFieldProjection();
90
94
  setField(field);
91
- }, [schema, view]);
95
+ }, [schema, view, readonly]);
92
96
 
93
97
  const handleUpdate = useCallback(
94
98
  ({ name, typename }: ViewMetaType) => {
95
- invariant(!immutable);
99
+ invariant(!readonly);
96
100
  requestAnimationFrame(() => {
97
101
  if (view.name !== name) {
98
102
  view.name = name;
99
103
  }
100
104
 
101
- if (view.query.typename !== typename && !immutable) {
105
+ if (view.query.typename !== typename && !readonly) {
102
106
  onTypenameChanged?.(typename);
103
107
  }
104
108
  });
105
109
  },
106
- [schema, view, onTypenameChanged, immutable],
110
+ [schema, view, onTypenameChanged, readonly],
107
111
  );
108
112
 
109
113
  const handleDelete = useCallback(
110
114
  (fieldId: string) => {
111
- invariant(!immutable);
115
+ invariant(!readonly);
112
116
  if (fieldId === field?.id) {
113
117
  setField(undefined);
114
118
  }
115
119
 
116
120
  onDelete?.(fieldId);
117
121
  },
118
- [schema, field, onDelete],
122
+ [schema, field, onDelete, readonly],
119
123
  );
120
124
 
121
125
  const handleMove = useCallback(
122
126
  (fromIndex: number, toIndex: number) => {
123
- invariant(!immutable);
127
+ invariant(!readonly);
124
128
  // NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
125
129
  const fields = [...view.fields];
126
130
  const [moved] = fields.splice(fromIndex, 1);
127
131
  fields.splice(toIndex, 0, moved);
128
132
  view.fields = fields;
129
133
  },
130
- [schema, view.fields],
134
+ [schema, view.fields, readonly],
131
135
  );
132
136
 
133
137
  const handleClose = useCallback(() => setField(undefined), []);
@@ -151,36 +155,35 @@ export const ViewEditor = ({
151
155
  );
152
156
 
153
157
  return (
154
- <div role='none' className={mx('overflow-y-auto', classNames)}>
155
- {immutable && (
156
- <div role='none' className='mbe-2'>
157
- <Message.Root valence='neutral' className='rounded'>
158
- <Message.Title>
159
- <Icon icon='ph--info--regular' size={5} classNames='inline' /> {t('system schema title')}
160
- </Message.Title>
161
- <Message.Body>{t('system schema description')}</Message.Body>
162
- </Message.Root>
158
+ <div role='none' className={mx(classNames)}>
159
+ {readonly && (
160
+ <div role='none' className={mx('is-full', outerSpacing && 'plb-cardSpacingBlock pli-cardSpacingInline')}>
161
+ <Callout.Root valence='info'>
162
+ <Callout.Title>{t('system schema description')}</Callout.Title>
163
+ </Callout.Root>
163
164
  </div>
164
165
  )}
166
+
167
+ {/* TODO(burdon): Is the form read-only or just the schema? */}
168
+ {/* TODO(burdon): Readonly fields should take up the same space as editable fields (just be ghosted). */}
165
169
  <Form<ViewMetaType>
166
170
  autoSave
167
171
  schema={ViewMetaSchema}
168
172
  values={viewValues}
173
+ readonly={readonly}
169
174
  onSave={handleUpdate}
170
- classNames='min-bs-0 overflow-y-auto'
175
+ outerSpacing={outerSpacing}
171
176
  />
172
177
 
173
- <div role='none' className='min-bs-0 overflow-y-auto'>
174
- {/* TODO(burdon): Clean up common form ux. */}
175
- <div role='none' className='p-2'>
176
- <label className={mx(inputTextLabel)}>{t('fields label')}</label>
177
- </div>
178
+ <div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
179
+ <label className={mx(inputTextLabel)}>{t('fields label')}</label>
178
180
 
179
181
  <List.Root<FieldType>
180
182
  items={view.fields}
181
183
  isItem={Schema.is(FieldSchema)}
182
184
  getId={(field) => field.id}
183
- onMove={immutable ? undefined : handleMove}
185
+ readonly={readonly}
186
+ onMove={readonly ? undefined : handleMove}
184
187
  >
185
188
  {({ items: fields }) => (
186
189
  <>
@@ -191,30 +194,29 @@ export const ViewEditor = ({
191
194
  </div>
192
195
  )}
193
196
 
194
- <div role='list' className='flex flex-col w-full'>
197
+ <div role='list' className='flex flex-col is-full'>
195
198
  {fields?.map((field) => (
196
199
  <List.Item<FieldType>
197
200
  key={field.id}
198
201
  item={field}
199
- classNames={mx(grid, ghostHover, !immutable && 'cursor-pointer')}
202
+ classNames={mx(grid, ghostHover, 'overflow-hidden', !readonly && 'cursor-pointer')}
200
203
  >
201
204
  <List.ItemDragHandle />
202
205
  <List.ItemTitle onClick={() => handleSelect(field)}>{field.path}</List.ItemTitle>
203
- <div className='flex items-center gap-2 -ml-4'>
204
- <List.ItemButton
205
- icon='ph--eye-slash--regular'
206
+ <List.ItemButton
207
+ data-testid='hide-field-button'
208
+ icon='ph--eye-slash--regular'
209
+ // TDOO(burdon): Is this the correct test?
210
+ disabled={view.fields.length <= 1}
211
+ onClick={() => handleHide(field.id)}
212
+ />
213
+ {/* TODO(burdon): Remove unless implement undo. */}
214
+ {!readonly && (
215
+ <List.ItemDeleteButton
206
216
  disabled={view.fields.length <= 1}
207
- onClick={() => handleHide(field.id)}
208
- data-testid='hide-field-button'
217
+ onClick={() => handleDelete(field.id)}
209
218
  />
210
- {!immutable && (
211
- <List.ItemDeleteButton
212
- icon='ph--trash--regular'
213
- disabled={view.fields.length <= 1}
214
- onClick={() => handleDelete(field.id)}
215
- />
216
- )}
217
- </div>
219
+ )}
218
220
  </List.Item>
219
221
  ))}
220
222
  </div>
@@ -223,10 +225,8 @@ export const ViewEditor = ({
223
225
  </List.Root>
224
226
 
225
227
  {hiddenProperties.length > 0 && (
226
- <div>
227
- <div role='none' className='p-2'>
228
- <label className={mx(inputTextLabel)}>{t('hidden fields label')}</label>
229
- </div>
228
+ <>
229
+ <label className={mx(inputTextLabel)}>{t('hidden fields label')}</label>
230
230
 
231
231
  <List.Root<string>
232
232
  items={hiddenProperties}
@@ -234,14 +234,14 @@ export const ViewEditor = ({
234
234
  getId={(property) => property}
235
235
  >
236
236
  {({ items: properties }) => (
237
- <div role='list' className='flex flex-col w-full'>
237
+ <div role='list' className='flex flex-col is-full'>
238
238
  {properties?.map((property) => (
239
239
  <List.Item<string>
240
240
  key={property}
241
241
  item={property}
242
- classNames={mx(grid, ghostHover, !immutable && 'cursor-pointer')}
242
+ classNames={mx(grid, ghostHover, !readonly && 'cursor-pointer')}
243
243
  >
244
- <div />
244
+ <List.ItemDragHandle disabled />
245
245
  <List.ItemTitle>{property}</List.ItemTitle>
246
246
  <List.ItemButton
247
247
  icon='ph--eye--regular'
@@ -253,7 +253,7 @@ export const ViewEditor = ({
253
253
  </div>
254
254
  )}
255
255
  </List.Root>
256
- </div>
256
+ </>
257
257
  )}
258
258
  </div>
259
259
 
@@ -269,11 +269,11 @@ export const ViewEditor = ({
269
269
  )}
270
270
 
271
271
  {!readonly && !field && (
272
- <div role='none' className='p-2'>
272
+ <div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
273
273
  <IconButton
274
274
  icon='ph--plus--regular'
275
275
  label={t('button add property')}
276
- onClick={immutable ? undefined : handleAdd}
276
+ onClick={readonly ? undefined : handleAdd}
277
277
  // TODO(burdon): Show field limit in ux (not tooltip).
278
278
  disabled={view.fields.length >= VIEW_FIELD_LIMIT}
279
279
  classNames='flex is-full'
@@ -5,16 +5,21 @@
5
5
  import React, { type PropsWithChildren } from 'react';
6
6
 
7
7
  import { type ThemedClassName } from '@dxos/react-ui';
8
+ import { Card } from '@dxos/react-ui-stack';
8
9
  import { JsonFilter } from '@dxos/react-ui-syntax-highlighter';
9
- import { mx } from '@dxos/react-ui-theme';
10
-
11
- export const TestPanel = ({ classNames, children }: ThemedClassName<PropsWithChildren>) => (
12
- <div className={mx('w-full max-w-[30rem] border border-separator p-2 rounded', classNames)}>{children}</div>
13
- );
10
+ import { mx, textBlockWidth } from '@dxos/react-ui-theme';
14
11
 
15
12
  export const TestLayout = ({ classNames, json, children }: ThemedClassName<PropsWithChildren<{ json?: any }>>) => (
16
- <div className='w-full h-full grid grid-cols-[1fr_1fr]'>
17
- <div className={mx('m-2 justify-center overflow-y-auto', classNames)}>{children}</div>
13
+ <div className='w-full h-full grid grid-cols-[1fr_1fr] gap-2 overflow-hidden'>
14
+ <div className={mx('p-4 justify-center overflow-y-auto', classNames)}>{children}</div>
18
15
  <JsonFilter data={json} classNames='w-full text-xs' testId='debug' />
19
16
  </div>
20
17
  );
18
+
19
+ export const TestPanel = ({ classNames, children }: ThemedClassName<PropsWithChildren>) => (
20
+ <Card.Content classNames={[textBlockWidth, classNames]}>{children}</Card.Content>
21
+ );
22
+
23
+ // Symbol for accessing debug objects in tests.
24
+ export const VIEW_EDITOR_DEBUG_SYMBOL = Symbol('viewEditorDebug');
25
+ export const FIELD_EDITOR_DEBUG_SYMBOL = Symbol('fieldEditorDebug');
@@ -35,6 +35,7 @@ export default [
35
35
  'format date-time': 'DateTime',
36
36
  'format did': 'DID',
37
37
  'format duration': 'Duration',
38
+ 'format dxn': 'DXN',
38
39
  'format email': 'Email',
39
40
  'format formula': 'Formula',
40
41
  'format hostname': 'Hostname',
@@ -47,12 +48,14 @@ export default [
47
48
  'format regex': 'RegExp',
48
49
  'format string': 'String',
49
50
  'format single-select': 'Select',
51
+ 'format multi-select': 'Multi-select',
50
52
  'format text': 'Long text',
51
53
  'format time': 'Time',
52
54
  'format timestamp': 'Timestamp',
53
55
  'format user': 'User',
54
56
  'format number': 'Number',
55
57
  'format uri': 'URI',
58
+ 'format url': 'URL',
56
59
  'format uuid': 'UUID',
57
60
 
58
61
  // Placeholders.
@@ -68,7 +71,7 @@ export default [
68
71
 
69
72
  // System schema message.
70
73
  'system schema title': 'System Schema',
71
- 'system schema description': 'This schema is managed by the system and cannot be modified.',
74
+ 'system schema description': 'This is a system schema and cannot be modified.',
72
75
  },
73
76
  },
74
77
  },