@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.
- package/dist/lib/browser/index.mjs +190 -173
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +150 -133
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +190 -173
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.d.ts +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +3 -2
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +5 -0
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +6 -14
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- 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/FormActions.d.ts +3 -1
- package/dist/types/src/components/Form/FormActions.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormContent.d.ts +30 -3
- package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
- package/dist/types/src/components/Form/Input.d.ts +6 -4
- package/dist/types/src/components/Form/Input.d.ts.map +1 -1
- package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +3 -2
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +7 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +3 -1
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +3 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/package.json +28 -26
- package/src/components/ControlSection/ControlSection.stories.tsx +15 -3
- package/src/components/ControlSection/ControlSection.tsx +11 -9
- package/src/components/FieldEditor/FieldEditor.stories.tsx +25 -5
- package/src/components/FieldEditor/FieldEditor.test.tsx +21 -13
- package/src/components/FieldEditor/FieldEditor.tsx +11 -2
- package/src/components/Form/ArrayField.tsx +1 -1
- package/src/components/Form/Defaults.tsx +5 -25
- package/src/components/Form/Form.stories.tsx +5 -5
- package/src/components/Form/Form.tsx +44 -49
- package/src/components/Form/FormActions.tsx +17 -2
- package/src/components/Form/FormContent.tsx +46 -46
- package/src/components/Form/Input.tsx +13 -8
- package/src/components/Form/RefField.tsx +1 -5
- package/src/components/Form/custom/GeoPointInput.tsx +1 -3
- package/src/components/Form/custom/SelectOptionsInput.stories.tsx +7 -5
- package/src/components/Form/custom/SelectOptionsInput.tsx +1 -3
- package/src/components/Form/custom/TupleInput.tsx +1 -3
- package/src/components/ViewEditor/ViewEditor.stories.tsx +25 -9
- package/src/components/ViewEditor/ViewEditor.test.tsx +80 -42
- package/src/components/ViewEditor/ViewEditor.tsx +66 -66
- package/src/components/testing.tsx +12 -7
- 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 {
|
|
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-[
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
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 (
|
|
81
|
+
if (readonly) {
|
|
78
82
|
return;
|
|
79
83
|
}
|
|
80
84
|
setField((f) => (f === field ? undefined : field));
|
|
81
85
|
},
|
|
82
|
-
[
|
|
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(!
|
|
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(!
|
|
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 && !
|
|
105
|
+
if (view.query.typename !== typename && !readonly) {
|
|
102
106
|
onTypenameChanged?.(typename);
|
|
103
107
|
}
|
|
104
108
|
});
|
|
105
109
|
},
|
|
106
|
-
[schema, view, onTypenameChanged,
|
|
110
|
+
[schema, view, onTypenameChanged, readonly],
|
|
107
111
|
);
|
|
108
112
|
|
|
109
113
|
const handleDelete = useCallback(
|
|
110
114
|
(fieldId: string) => {
|
|
111
|
-
invariant(!
|
|
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(!
|
|
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(
|
|
155
|
-
{
|
|
156
|
-
<div role='none' className='
|
|
157
|
-
<
|
|
158
|
-
<
|
|
159
|
-
|
|
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
|
-
|
|
175
|
+
outerSpacing={outerSpacing}
|
|
171
176
|
/>
|
|
172
177
|
|
|
173
|
-
<div role='none' className='
|
|
174
|
-
{
|
|
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
|
-
|
|
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
|
|
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, !
|
|
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
|
-
<
|
|
204
|
-
|
|
205
|
-
|
|
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={() =>
|
|
208
|
-
data-testid='hide-field-button'
|
|
217
|
+
onClick={() => handleDelete(field.id)}
|
|
209
218
|
/>
|
|
210
|
-
|
|
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
|
-
|
|
227
|
-
<
|
|
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
|
|
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, !
|
|
242
|
+
classNames={mx(grid, ghostHover, !readonly && 'cursor-pointer')}
|
|
243
243
|
>
|
|
244
|
-
<
|
|
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
|
-
|
|
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='
|
|
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={
|
|
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('
|
|
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');
|
package/src/translations.ts
CHANGED
|
@@ -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
|
|
74
|
+
'system schema description': 'This is a system schema and cannot be modified.',
|
|
72
75
|
},
|
|
73
76
|
},
|
|
74
77
|
},
|