@dxos/react-ui-form 0.8.1-staging.391c573 → 0.8.1-staging.9eaf14f
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.
- package/dist/lib/browser/index.mjs +635 -511
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +710 -580
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +635 -511
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/ControlSection.d.ts +20 -0
- package/dist/types/src/components/ControlSection.d.ts.map +1 -0
- package/dist/types/src/components/Form/Form.stories.d.ts +2 -2
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Input.d.ts +1 -1
- package/dist/types/src/components/Form/Input.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/index.d.ts +1 -2
- package/dist/types/src/components/Form/index.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +4 -4
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useForm.d.ts.map +1 -1
- package/dist/types/src/util/field.d.ts +0 -1
- package/dist/types/src/util/field.d.ts.map +1 -1
- package/dist/types/src/util/formatting.d.ts.map +1 -1
- package/package.json +24 -24
- package/src/components/ControlSection.tsx +113 -0
- package/src/components/FieldEditor/FieldEditor.stories.tsx +1 -1
- package/src/components/Form/Form.stories.tsx +2 -2
- package/src/components/Form/Input.tsx +1 -1
- package/src/components/Form/custom/SelectOptionsInput.tsx +5 -1
- package/src/components/Form/index.ts +1 -3
- package/src/components/ViewEditor/ViewEditor.stories.tsx +1 -1
- package/src/components/ViewEditor/ViewEditor.tsx +44 -28
- package/src/components/index.ts +1 -1
- package/src/hooks/useForm.ts +10 -1
- package/src/util/field.ts +4 -4
- 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
|
|
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:
|
|
33
|
+
schema: BaseSchema;
|
|
33
34
|
view: ViewType;
|
|
34
35
|
registry?: SchemaRegistry;
|
|
35
36
|
readonly?: boolean;
|
|
36
37
|
showHeading?: boolean;
|
|
37
|
-
onTypenameChanged
|
|
38
|
-
onDelete
|
|
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
|
|
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
|
-
[
|
|
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
|
-
|
|
103
|
+
|
|
104
|
+
onDelete?.(fieldId);
|
|
97
105
|
},
|
|
98
|
-
[
|
|
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>
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
/>
|
package/src/components/index.ts
CHANGED
package/src/hooks/useForm.ts
CHANGED
|
@@ -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.
|
|
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',
|
package/src/util/formatting.ts
CHANGED
|
@@ -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.
|
|
65
|
-
|
|
66
|
+
case FormatEnum.Date: {
|
|
67
|
+
return formatDate(new Date(value as number), 'yyyy-MM-dd');
|
|
68
|
+
}
|
|
66
69
|
case FormatEnum.Time: {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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) {
|