@dxos/react-ui-form 0.8.1-staging.391c573 → 0.8.1-staging.97aedb1

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 (39) hide show
  1. package/dist/lib/browser/index.mjs +635 -511
  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 +710 -580
  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 +635 -511
  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 +20 -0
  11. package/dist/types/src/components/ControlSection.d.ts.map +1 -0
  12. package/dist/types/src/components/Form/Form.stories.d.ts +2 -2
  13. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  14. package/dist/types/src/components/Form/Input.d.ts +1 -1
  15. package/dist/types/src/components/Form/Input.d.ts.map +1 -1
  16. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/index.d.ts +1 -2
  18. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  19. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +4 -4
  20. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  21. package/dist/types/src/components/index.d.ts +1 -0
  22. package/dist/types/src/components/index.d.ts.map +1 -1
  23. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  24. package/dist/types/src/util/field.d.ts +0 -1
  25. package/dist/types/src/util/field.d.ts.map +1 -1
  26. package/dist/types/src/util/formatting.d.ts.map +1 -1
  27. package/package.json +24 -24
  28. package/src/components/ControlSection.tsx +113 -0
  29. package/src/components/FieldEditor/FieldEditor.stories.tsx +1 -1
  30. package/src/components/Form/Form.stories.tsx +2 -2
  31. package/src/components/Form/Input.tsx +1 -1
  32. package/src/components/Form/custom/SelectOptionsInput.tsx +5 -1
  33. package/src/components/Form/index.ts +1 -3
  34. package/src/components/ViewEditor/ViewEditor.stories.tsx +1 -1
  35. package/src/components/ViewEditor/ViewEditor.tsx +44 -28
  36. package/src/components/index.ts +1 -1
  37. package/src/hooks/useForm.ts +10 -1
  38. package/src/util/field.ts +4 -4
  39. package/src/util/formatting.ts +9 -10
@@ -5,7 +5,8 @@
5
5
  import React, { useCallback, useMemo, useState } from 'react';
6
6
 
7
7
  import { type SchemaRegistry } from '@dxos/echo-db';
8
- import { AST, Format, type EchoSchema, S, type JsonProp } from '@dxos/echo-schema';
8
+ import { AST, Format, type BaseSchema, S, type JsonProp } from '@dxos/echo-schema';
9
+ import { invariant } from '@dxos/invariant';
9
10
  import { IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';
10
11
  import { List } from '@dxos/react-ui-list';
11
12
  import { ghostHover, inputTextLabel, mx } from '@dxos/react-ui-theme';
@@ -29,13 +30,13 @@ const ViewMetaSchema = S.Struct({
29
30
  type ViewMetaType = S.Schema.Type<typeof ViewMetaSchema>;
30
31
 
31
32
  export type ViewEditorProps = ThemedClassName<{
32
- schema: EchoSchema;
33
+ schema: BaseSchema;
33
34
  view: ViewType;
34
35
  registry?: SchemaRegistry;
35
36
  readonly?: boolean;
36
37
  showHeading?: boolean;
37
- onTypenameChanged: (typename: string) => void;
38
- onDelete: (fieldId: string) => void;
38
+ onTypenameChanged?: (typename: string) => void;
39
+ onDelete?: (fieldId: string) => void;
39
40
  }>;
40
41
 
41
42
  /**
@@ -52,7 +53,7 @@ export const ViewEditor = ({
52
53
  onDelete,
53
54
  }: ViewEditorProps) => {
54
55
  const { t } = useTranslation(translationKey);
55
- const projection = useMemo(() => new ViewProjection(schema, view), [schema, view]);
56
+ const projection = useMemo(() => new ViewProjection(schema.jsonSchema, view), [schema, view]);
56
57
  const [field, setField] = useState<FieldType>();
57
58
 
58
59
  // TODO(burdon): Should be reactive.
@@ -65,48 +66,56 @@ export const ViewEditor = ({
65
66
  };
66
67
  }, [view]);
67
68
 
68
- const handleViewUpdate = useCallback(
69
+ const handleSelect = useCallback((field: FieldType) => {
70
+ setField((f) => (f === field ? undefined : field));
71
+ }, []);
72
+
73
+ // TODO(burdon): Check if mutable; variant of useCallback that return undefined if readonly?
74
+
75
+ const handleAdd = useCallback(() => {
76
+ invariant(!schema.readonly);
77
+ const field = projection.createFieldProjection();
78
+ setField(field);
79
+ }, [schema, view]);
80
+
81
+ const handleUpdate = useCallback(
69
82
  ({ name, typename }: ViewMetaType) => {
83
+ invariant(!schema.readonly);
70
84
  requestAnimationFrame(() => {
71
85
  if (view.name !== name) {
72
86
  view.name = name;
73
87
  }
88
+
74
89
  if (view.query.typename !== typename) {
75
- onTypenameChanged(typename);
90
+ onTypenameChanged?.(typename);
76
91
  }
77
92
  });
78
93
  },
79
- [view, schema, onTypenameChanged],
94
+ [schema, view, onTypenameChanged],
80
95
  );
81
96
 
82
- const handleSelect = useCallback((field: FieldType) => {
83
- setField((f) => (f === field ? undefined : field));
84
- }, []);
85
-
86
- const handleAdd = useCallback(() => {
87
- const field = projection.createFieldProjection();
88
- setField(field);
89
- }, [view]);
90
-
91
97
  const handleDelete = useCallback(
92
98
  (fieldId: string) => {
99
+ invariant(!schema.readonly);
93
100
  if (fieldId === field?.id) {
94
101
  setField(undefined);
95
102
  }
96
- onDelete(fieldId);
103
+
104
+ onDelete?.(fieldId);
97
105
  },
98
- [onDelete, field],
106
+ [schema, field, onDelete],
99
107
  );
100
108
 
101
109
  const handleMove = useCallback(
102
110
  (fromIndex: number, toIndex: number) => {
111
+ invariant(!schema.readonly);
103
112
  // NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
104
113
  const fields = [...view.fields];
105
114
  const [moved] = fields.splice(fromIndex, 1);
106
115
  fields.splice(toIndex, 0, moved);
107
116
  view.fields = fields;
108
117
  },
109
- [view.fields],
118
+ [schema, view.fields],
110
119
  );
111
120
 
112
121
  const handleClose = useCallback(() => setField(undefined), []);
@@ -131,7 +140,12 @@ export const ViewEditor = ({
131
140
 
132
141
  return (
133
142
  <div role='none' className={mx('flex flex-col w-full divide-y divide-separator', classNames)}>
134
- <Form<ViewMetaType> autoSave schema={ViewMetaSchema} values={viewValues} onSave={handleViewUpdate} />
143
+ <Form<ViewMetaType>
144
+ autoSave
145
+ schema={ViewMetaSchema}
146
+ values={viewValues}
147
+ onSave={schema.readonly ? undefined : handleUpdate}
148
+ />
135
149
 
136
150
  <div>
137
151
  {/* TODO(burdon): Clean up common form ux. */}
@@ -143,7 +157,7 @@ export const ViewEditor = ({
143
157
  items={view.fields}
144
158
  isItem={S.is(FieldSchema)}
145
159
  getId={(field) => field.id}
146
- onMove={handleMove}
160
+ onMove={schema.readonly ? undefined : handleMove}
147
161
  >
148
162
  {({ items: fields }) => (
149
163
  <>
@@ -165,11 +179,13 @@ export const ViewEditor = ({
165
179
  disabled={view.fields.length <= 1}
166
180
  onClick={() => handleHide(field.id)}
167
181
  />
168
- <List.ItemDeleteButton
169
- icon='ph--trash--regular'
170
- disabled={view.fields.length <= 1}
171
- onClick={() => handleDelete(field.id)}
172
- />
182
+ {!schema.readonly && (
183
+ <List.ItemDeleteButton
184
+ icon='ph--trash--regular'
185
+ disabled={view.fields.length <= 1}
186
+ onClick={() => handleDelete(field.id)}
187
+ />
188
+ )}
173
189
  </div>
174
190
  </List.Item>
175
191
  ))}
@@ -225,7 +241,7 @@ export const ViewEditor = ({
225
241
  <IconButton
226
242
  icon='ph--plus--regular'
227
243
  label={t('button add property')}
228
- onClick={handleAdd}
244
+ onClick={schema.readonly ? undefined : handleAdd}
229
245
  // TODO(burdon): Show field limit in ux (not tooltip).
230
246
  disabled={view.fields.length >= VIEW_FIELD_LIMIT}
231
247
  />
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  export * from './deprecated';
6
-
6
+ export * from './ControlSection';
7
7
  export * from './FieldEditor';
8
8
  export * from './Form';
9
9
  export * from './ViewEditor';
@@ -176,12 +176,21 @@ export const useForm = <T extends BaseObject>({
176
176
  errorPath === jsonPath || errorPath.startsWith(`${jsonPath}.`) || errorPath.startsWith(`${jsonPath}[`),
177
177
  );
178
178
 
179
+ // Only show errors for touched fields.
180
+ const isTouched = touched[jsonPath as JsonPath];
181
+ if (!isTouched) {
182
+ return {
183
+ status: undefined,
184
+ error: undefined,
185
+ };
186
+ }
187
+
179
188
  return {
180
189
  status: matchingError ? 'error' : undefined,
181
190
  error: matchingError ? matchingError[1] : undefined,
182
191
  };
183
192
  },
184
- [errors],
193
+ [errors, touched],
185
194
  );
186
195
 
187
196
  const getFormValue = useCallback<FormHandler<T>['getValue']>(
package/src/util/field.ts CHANGED
@@ -132,22 +132,22 @@ export const cellClassesForFieldType = ({ type, format }: CellClassesForFieldTyp
132
132
  switch (format) {
133
133
  case FormatEnum.Markdown:
134
134
  return undefined;
135
+ case FormatEnum.Time:
135
136
  case FormatEnum.Timestamp:
136
137
  case FormatEnum.DateTime:
137
138
  case FormatEnum.Date:
138
- case FormatEnum.Time:
139
- return ['font-mono'];
139
+ case FormatEnum.Duration:
140
+ return ['font-mono', 'text-right'];
140
141
  case FormatEnum.Currency:
141
142
  return ['text-right'];
142
143
  case FormatEnum.JSON:
144
+ case FormatEnum.DID:
143
145
  return ['font-mono'];
144
146
  default:
145
147
  return undefined;
146
148
  }
147
149
  };
148
150
 
149
- export const cellClassesForRowSelection = (selected: boolean) => (selected ? ['!bg-gridCellSelected'] : undefined);
150
-
151
151
  // TODO(ZaymonFC): How to do this with translations?
152
152
  export const pathNotUniqueError = (path: string): ValidationError => ({
153
153
  path: 'path',
@@ -2,6 +2,8 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { format as formatDate } from 'date-fns/format';
6
+
5
7
  import { FormatEnum, GeoLocation, TypeEnum } from '@dxos/echo-schema';
6
8
 
7
9
  type ValueFormatProps = {
@@ -61,17 +63,14 @@ export const formatForDisplay = ({ type, format, value, locale = undefined }: Va
61
63
  maximumFractionDigits: 2,
62
64
  });
63
65
  }
64
- case FormatEnum.DateTime:
65
- case FormatEnum.Date:
66
+ case FormatEnum.Date: {
67
+ return formatDate(new Date(value as number), 'yyyy-MM-dd');
68
+ }
66
69
  case FormatEnum.Time: {
67
- const date = new Date(value as number);
68
- if (format === FormatEnum.DateTime) {
69
- return date.toLocaleString(locale);
70
- } else if (format === FormatEnum.Date) {
71
- return date.toLocaleDateString(locale);
72
- } else {
73
- return date.toLocaleTimeString(locale);
74
- }
70
+ return formatDate(new Date(value as number), 'HH:mm:ss');
71
+ }
72
+ case FormatEnum.DateTime: {
73
+ return formatDate(new Date(value as number), 'yyyy-MM-dd HH:mm:ss');
75
74
  }
76
75
  case FormatEnum.GeoPoint: {
77
76
  if (value === null || value === undefined) {