@dxos/react-ui-form 0.8.2-main.fbd8ed0 → 0.8.2-staging.42af850

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 (54) hide show
  1. package/dist/lib/browser/index.mjs +1445 -1245
  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 +1431 -1231
  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 +1445 -1245
  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/FieldEditor/FieldEditor.d.ts.map +1 -1
  13. package/dist/types/src/components/FieldEditor/FieldEditor.test.d.ts +2 -0
  14. package/dist/types/src/components/FieldEditor/FieldEditor.test.d.ts.map +1 -0
  15. package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
  16. package/dist/types/src/components/Form/Defaults.d.ts +4 -4
  17. package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
  18. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/Form.stories.d.ts +1 -2
  20. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  21. package/dist/types/src/components/Form/FormActions.d.ts +1 -1
  22. package/dist/types/src/components/Form/FormActions.d.ts.map +1 -1
  23. package/dist/types/src/components/Form/FormContent.d.ts +6 -5
  24. package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
  25. package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
  26. package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
  27. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  28. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +5 -3
  29. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  30. package/dist/types/src/components/testing.d.ts.map +1 -1
  31. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +1 -1
  32. package/dist/types/src/translations.d.ts +5 -0
  33. package/dist/types/src/translations.d.ts.map +1 -1
  34. package/dist/types/src/vitest-setup.d.ts +2 -0
  35. package/dist/types/src/vitest-setup.d.ts.map +1 -0
  36. package/package.json +27 -25
  37. package/src/components/ControlSection/ControlSection.tsx +2 -2
  38. package/src/components/FieldEditor/FieldEditor.test.tsx +49 -0
  39. package/src/components/FieldEditor/FieldEditor.tsx +3 -2
  40. package/src/components/Form/ArrayField.tsx +51 -34
  41. package/src/components/Form/Defaults.tsx +74 -48
  42. package/src/components/Form/Form.stories.tsx +77 -101
  43. package/src/components/Form/Form.tsx +1 -2
  44. package/src/components/Form/FormActions.tsx +15 -18
  45. package/src/components/Form/FormContent.tsx +34 -35
  46. package/src/components/Form/FormContext.tsx +11 -7
  47. package/src/components/Form/Input.tsx +1 -1
  48. package/src/components/Form/RefField.tsx +30 -10
  49. package/src/components/ViewEditor/ViewEditor.stories.tsx +14 -5
  50. package/src/components/ViewEditor/ViewEditor.tsx +51 -24
  51. package/src/components/testing.tsx +5 -9
  52. package/src/hooks/useRefQueryLookupHandler.ts +1 -3
  53. package/src/translations.ts +9 -2
  54. package/src/vitest-setup.ts +11 -0
@@ -15,11 +15,13 @@ import { useAsyncEffect } from '@dxos/react-ui';
15
15
  import { ViewProjection, ViewType, createView } from '@dxos/schema';
16
16
  import { withTheme, withLayout } from '@dxos/storybook-utils';
17
17
 
18
- import { ViewEditor } from './ViewEditor';
18
+ import { ViewEditor, type ViewEditorProps } from './ViewEditor';
19
19
  import translations from '../../translations';
20
20
  import { TestLayout, TestPanel } from '../testing';
21
21
 
22
- const DefaultStory = () => {
22
+ type StoryProps = Pick<ViewEditorProps, 'readonly'>;
23
+
24
+ const DefaultStory = (props: StoryProps) => {
23
25
  const space = useSpace();
24
26
  const [schema, setSchema] = useState<EchoSchema>();
25
27
  const [view, setView] = useState<ViewType>();
@@ -57,6 +59,7 @@ const DefaultStory = () => {
57
59
  },
58
60
  [views, schema],
59
61
  );
62
+
60
63
  const handleDelete = useCallback((property: string) => projection?.deleteFieldProjection(property), [projection]);
61
64
 
62
65
  if (!schema || !view || !projection) {
@@ -70,6 +73,7 @@ const DefaultStory = () => {
70
73
  schema={schema}
71
74
  view={view}
72
75
  registry={space?.db.schemaRegistry}
76
+ readonly={props.readonly}
73
77
  onTypenameChanged={updateViewTypename}
74
78
  onDelete={handleDelete}
75
79
  />
@@ -78,9 +82,8 @@ const DefaultStory = () => {
78
82
  );
79
83
  };
80
84
 
81
- const meta: Meta<typeof ViewEditor> = {
85
+ const meta: Meta<StoryProps> = {
82
86
  title: 'ui/react-ui-form/ViewEditor',
83
- component: ViewEditor,
84
87
  render: DefaultStory,
85
88
  decorators: [withClientProvider({ createSpace: true }), withLayout({ fullscreen: true }), withTheme],
86
89
  parameters: {
@@ -90,6 +93,12 @@ const meta: Meta<typeof ViewEditor> = {
90
93
 
91
94
  export default meta;
92
95
 
93
- type Story = StoryObj;
96
+ type Story = StoryObj<StoryProps>;
94
97
 
95
98
  export const Default: Story = {};
99
+
100
+ export const Readonly: Story = {
101
+ args: {
102
+ readonly: true,
103
+ },
104
+ };
@@ -2,13 +2,13 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Schema, SchemaAST } from 'effect';
5
+ import { Schema } from 'effect';
6
6
  import React, { useCallback, useMemo, useState } from 'react';
7
7
 
8
8
  import { type SchemaRegistry } from '@dxos/echo-db';
9
- import { Format, type JsonProp, isMutable, toJsonSchema } from '@dxos/echo-schema';
9
+ import { EchoSchema, Format, type JsonProp, isMutable, toJsonSchema } from '@dxos/echo-schema';
10
10
  import { invariant } from '@dxos/invariant';
11
- import { IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';
11
+ import { Icon, IconButton, Message, type ThemedClassName, useTranslation } from '@dxos/react-ui';
12
12
  import { List } from '@dxos/react-ui-list';
13
13
  import { ghostHover, inputTextLabel, mx } from '@dxos/react-ui-theme';
14
14
  import { FieldSchema, type FieldType, type ViewType, ViewProjection, VIEW_FIELD_LIMIT } from '@dxos/schema';
@@ -21,10 +21,10 @@ const grid = 'grid grid-cols-[32px_1fr_32px] min-bs-[2.5rem]';
21
21
 
22
22
  const ViewMetaSchema = Schema.Struct({
23
23
  name: Schema.String.annotations({
24
- [SchemaAST.TitleAnnotationId]: 'View',
24
+ title: 'View',
25
25
  }),
26
26
  typename: Format.URL.annotations({
27
- [SchemaAST.TitleAnnotationId]: 'Typename',
27
+ title: 'Typename',
28
28
  }),
29
29
  }).pipe(Schema.mutable);
30
30
 
@@ -54,7 +54,11 @@ export const ViewEditor = ({
54
54
  onDelete,
55
55
  }: ViewEditorProps) => {
56
56
  const { t } = useTranslation(translationKey);
57
- const projection = useMemo(() => new ViewProjection(toJsonSchema(schema), view), [schema, view]);
57
+ const projection = useMemo(() => {
58
+ // Use reactive and mutable version of json schema when schema is mutable.
59
+ const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
60
+ return new ViewProjection(jsonSchema, view);
61
+ }, [schema, view]);
58
62
  const [field, setField] = useState<FieldType>();
59
63
  const immutable = readonly || !isMutable(schema);
60
64
 
@@ -68,9 +72,15 @@ export const ViewEditor = ({
68
72
  };
69
73
  }, [view]);
70
74
 
71
- const handleSelect = useCallback((field: FieldType) => {
72
- setField((f) => (f === field ? undefined : field));
73
- }, []);
75
+ const handleSelect = useCallback(
76
+ (field: FieldType) => {
77
+ if (immutable) {
78
+ return;
79
+ }
80
+ setField((f) => (f === field ? undefined : field));
81
+ },
82
+ [immutable],
83
+ );
74
84
 
75
85
  // TODO(burdon): Check if mutable; variant of useCallback that return undefined if readonly?
76
86
 
@@ -88,12 +98,12 @@ export const ViewEditor = ({
88
98
  view.name = name;
89
99
  }
90
100
 
91
- if (view.query.typename !== typename) {
101
+ if (view.query.typename !== typename && !immutable) {
92
102
  onTypenameChanged?.(typename);
93
103
  }
94
104
  });
95
105
  },
96
- [schema, view, onTypenameChanged],
106
+ [schema, view, onTypenameChanged, immutable],
97
107
  );
98
108
 
99
109
  const handleDelete = useCallback(
@@ -141,16 +151,28 @@ export const ViewEditor = ({
141
151
  );
142
152
 
143
153
  return (
144
- <div role='none' className={mx('grid grid-rows-[1fr_1fr] min-bs-0', classNames)}>
145
- <Form<ViewMetaType>
146
- autoSave
147
- schema={ViewMetaSchema}
148
- values={viewValues}
149
- onSave={immutable ? undefined : handleUpdate}
150
- classNames='min-bs-0 overflow-y-auto'
151
- />
152
-
153
- <div role='none' className='border-bs border-separator min-bs-0 overflow-y-auto'>
154
+ <div role='none' className={mx('overflow-y-auto', classNames)}>
155
+ <div role='none' className='p-2'>
156
+ <div role='none' className='mbe-2'>
157
+ {immutable && (
158
+ <Message.Root valence='neutral' className='rounded'>
159
+ <Message.Title>
160
+ <Icon icon='ph--info--regular' size={5} classNames='inline' /> {t('system schema title')}
161
+ </Message.Title>
162
+ <Message.Body>{t('system schema description')}</Message.Body>
163
+ </Message.Root>
164
+ )}
165
+ </div>
166
+ <Form<ViewMetaType>
167
+ autoSave
168
+ schema={ViewMetaSchema}
169
+ values={viewValues}
170
+ onSave={handleUpdate}
171
+ classNames='min-bs-0 overflow-y-auto'
172
+ />
173
+ </div>
174
+
175
+ <div role='none' className='min-bs-0 overflow-y-auto'>
154
176
  {/* TODO(burdon): Clean up common form ux. */}
155
177
  <div role='none' className='p-2'>
156
178
  <label className={mx(inputTextLabel)}>{t('fields label')}</label>
@@ -173,7 +195,11 @@ export const ViewEditor = ({
173
195
 
174
196
  <div role='list' className='flex flex-col w-full'>
175
197
  {fields?.map((field) => (
176
- <List.Item<FieldType> key={field.id} item={field} classNames={mx(grid, ghostHover, 'cursor-pointer')}>
198
+ <List.Item<FieldType>
199
+ key={field.id}
200
+ item={field}
201
+ classNames={mx(grid, ghostHover, !immutable && 'cursor-pointer')}
202
+ >
177
203
  <List.ItemDragHandle />
178
204
  <List.ItemTitle onClick={() => handleSelect(field)}>{field.path}</List.ItemTitle>
179
205
  <div className='flex items-center gap-2 -ml-4'>
@@ -214,7 +240,7 @@ export const ViewEditor = ({
214
240
  <List.Item<string>
215
241
  key={property}
216
242
  item={property}
217
- classNames={mx(grid, ghostHover, 'cursor-pointer')}
243
+ classNames={mx(grid, ghostHover, !immutable && 'cursor-pointer')}
218
244
  >
219
245
  <div />
220
246
  <List.ItemTitle>{property}</List.ItemTitle>
@@ -240,13 +266,14 @@ export const ViewEditor = ({
240
266
  )}
241
267
 
242
268
  {!readonly && !field && (
243
- <div className='flex p-2 justify-center'>
269
+ <div role='none' className='p-2'>
244
270
  <IconButton
245
271
  icon='ph--plus--regular'
246
272
  label={t('button add property')}
247
273
  onClick={immutable ? undefined : handleAdd}
248
274
  // TODO(burdon): Show field limit in ux (not tooltip).
249
275
  disabled={view.fields.length >= VIEW_FIELD_LIMIT}
276
+ classNames='flex is-full'
250
277
  />
251
278
  </div>
252
279
  )}
@@ -5,20 +5,16 @@
5
5
  import React, { type PropsWithChildren } from 'react';
6
6
 
7
7
  import { type ThemedClassName } from '@dxos/react-ui';
8
- import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
8
+ import { JsonFilter } from '@dxos/react-ui-syntax-highlighter';
9
9
  import { mx } from '@dxos/react-ui-theme';
10
10
 
11
11
  export const TestPanel = ({ classNames, children }: ThemedClassName<PropsWithChildren>) => (
12
- <div className='flex flex-col p-4'>
13
- <div className={mx('flex flex-col border border-separator rounded w-[300px]', classNames)}>{children}</div>
14
- </div>
12
+ <div className={mx('w-full max-w-[30rem] border border-separator p-2 rounded', classNames)}>{children}</div>
15
13
  );
16
14
 
17
15
  export const TestLayout = ({ classNames, json, children }: ThemedClassName<PropsWithChildren<{ json?: any }>>) => (
18
- <div className='w-full h-full grid grid-cols-3'>
19
- <div className={mx('flex col-span-2 w-full justify-center overflow-y-auto', classNames)}>{children}</div>
20
- <SyntaxHighlighter language='json' className='w-full text-xs font-thin'>
21
- {JSON.stringify(json, null, 2)}
22
- </SyntaxHighlighter>
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>
18
+ <JsonFilter data={json} classNames='w-full text-xs' testId='debug' />
23
19
  </div>
24
20
  );
@@ -13,7 +13,7 @@ import { type QueryRefOptions } from './useQueryRefOptions';
13
13
  type UseRefQueryLookupProps = { space?: Space };
14
14
 
15
15
  export const useRefQueryLookupHandler = ({ space }: UseRefQueryLookupProps): QueryRefOptions => {
16
- const handleRefQueryLookup = useCallback(
16
+ return useCallback(
17
17
  async (typeInfo: TypeAnnotation) => {
18
18
  if (!space) {
19
19
  return [];
@@ -36,6 +36,4 @@ export const useRefQueryLookupHandler = ({ space }: UseRefQueryLookupProps): Que
36
36
  },
37
37
  [space],
38
38
  );
39
-
40
- return handleRefQueryLookup;
41
39
  };
@@ -10,6 +10,7 @@ export default [
10
10
  [translationKey]: {
11
11
  'fields label': 'Fields',
12
12
  'hidden fields label': 'Hidden Fields',
13
+ 'empty readonly ref field label': '(none)',
13
14
 
14
15
  // TODO(burdon): Standardize field/property.
15
16
  'button add property': 'Add property',
@@ -20,6 +21,8 @@ export default [
20
21
  'add field': 'Add field',
21
22
  'delete field': 'Delete field',
22
23
  'field limit reached': 'Maximum number of fields reached',
24
+ 'boolean input true value': 'Yes',
25
+ 'boolean input false value': 'No',
23
26
 
24
27
  // TODO(burdon): Factor out?
25
28
  'button cancel': 'Cancel',
@@ -52,16 +55,20 @@ export default [
52
55
  'format uri': 'URI',
53
56
  'format uuid': 'UUID',
54
57
 
55
- // Placeholders
58
+ // Placeholders.
56
59
  'placeholder latitude': 'Latitude',
57
60
  'placeholder longitude': 'Longitude',
58
61
 
59
- // SelectOptionsInput
62
+ // SelectOptionsInput.
60
63
  'select option label': 'Label',
61
64
  'select option label placeholder': 'Option label',
62
65
  'select option color': 'Color',
63
66
  'select option delete': 'Delete',
64
67
  'select option add': 'Add option',
68
+
69
+ // System schema message.
70
+ 'system schema title': 'System Schema',
71
+ 'system schema description': 'This schema is managed by the system and cannot be modified.',
65
72
  },
66
73
  },
67
74
  },
@@ -0,0 +1,11 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ // https://github.com/testing-library/jest-dom#with-vitest
6
+ import '@testing-library/jest-dom/vitest';
7
+
8
+ // https://github.com/jsdom/jsdom/issues/3368#issuecomment-1396749033
9
+ import ResizeObserver from 'resize-observer-polyfill';
10
+
11
+ global.ResizeObserver = ResizeObserver;