@dxos/react-ui-form 0.8.4-main.72ec0f3 → 0.8.4-main.74a063c4e0
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/README.md +0 -1
- package/dist/lib/browser/index.mjs +2305 -2352
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2305 -2352
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +8 -7
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +66 -59
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +87 -27
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +112 -136
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +43 -0
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +77 -0
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +9 -0
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldSet.d.ts +18 -0
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/ArrayField.d.ts +18 -0
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/BooleanField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts +5 -0
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/NumberField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/RefField.d.ts +16 -0
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectField.d.ts +10 -0
- package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +5 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +82 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TextField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TupleField.d.ts +6 -0
- package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/index.d.ts +12 -0
- package/dist/types/src/components/Form/fields/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/index.d.ts +2 -4
- package/dist/types/src/components/Form/index.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +23 -10
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +62 -58
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.d.ts +39 -0
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
- package/dist/types/src/components/Settings/Settings.stories.d.ts +81 -0
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
- package/dist/types/src/components/Settings/index.d.ts +2 -0
- package/dist/types/src/components/Settings/index.d.ts.map +1 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +11 -13
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +67 -62
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +0 -2
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -3
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts +7 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +63 -59
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/field.d.ts +3 -3
- package/dist/types/src/util/field.d.ts.map +1 -1
- package/dist/types/src/util/formatting.d.ts +2 -2
- package/dist/types/src/util/formatting.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/properties.d.ts +7 -0
- package/dist/types/src/util/properties.d.ts.map +1 -0
- package/dist/types/src/util/refs.d.ts +2 -2
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +51 -48
- package/src/components/FieldEditor/FieldEditor.stories.tsx +43 -25
- package/src/components/FieldEditor/FieldEditor.test.tsx +0 -1
- package/src/components/FieldEditor/FieldEditor.tsx +74 -78
- package/src/components/Form/AUDIT.md +17 -0
- package/src/components/Form/Form.stories.tsx +148 -250
- package/src/components/Form/Form.tsx +324 -110
- package/src/components/Form/FormField.tsx +304 -0
- package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
- package/src/components/Form/FormFieldComponent.tsx +171 -0
- package/src/components/Form/FormFieldSet.tsx +128 -0
- package/src/components/Form/README.md +3 -0
- package/src/components/Form/fields/ArrayField.tsx +154 -0
- package/src/components/Form/fields/BooleanField.tsx +22 -0
- package/src/components/Form/fields/GeoPointField.tsx +120 -0
- package/src/components/Form/fields/MarkdownField.tsx +78 -0
- package/src/components/Form/fields/NumberField.tsx +39 -0
- package/src/components/Form/fields/RefField.tsx +198 -0
- package/src/components/Form/fields/SelectField.tsx +65 -0
- package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +19 -17
- package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +29 -24
- package/src/components/Form/fields/TextAreaField.tsx +38 -0
- package/src/components/Form/fields/TextField.tsx +38 -0
- package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +16 -9
- package/src/components/Form/fields/index.ts +15 -0
- package/src/components/Form/index.ts +3 -4
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +18 -16
- package/src/components/ObjectPicker/ObjectPicker.tsx +103 -78
- package/src/components/Settings/Settings.stories.tsx +55 -0
- package/src/components/Settings/Settings.tsx +129 -0
- package/src/components/{ControlSection → Settings}/index.ts +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +47 -45
- package/src/components/ViewEditor/ViewEditor.test.tsx +5 -5
- package/src/components/ViewEditor/ViewEditor.tsx +297 -210
- package/src/components/index.ts +1 -1
- package/src/components/testing.tsx +23 -9
- package/src/hooks/index.ts +2 -3
- package/src/hooks/useFormHandler.ts +380 -0
- package/src/hooks/useKeyHandler.tsx +37 -0
- package/src/translations.ts +66 -64
- package/src/util/field.ts +25 -25
- package/src/util/formatting.ts +22 -22
- package/src/util/index.ts +1 -1
- package/src/util/properties.ts +18 -0
- package/src/util/refs.ts +13 -15
- package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +0 -83
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/index.d.ts +0 -2
- package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
- package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
- package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
- package/dist/types/src/components/Form/Defaults.d.ts +0 -14
- package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormActions.d.ts +0 -9
- package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormContent.d.ts +0 -63
- package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormContext.d.ts +0 -23
- package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
- package/dist/types/src/components/Form/Input.d.ts +0 -29
- package/dist/types/src/components/Form/Input.d.ts.map +0 -1
- package/dist/types/src/components/Form/RefField.d.ts +0 -22
- package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +0 -78
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
- package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/index.d.ts +0 -4
- package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
- package/dist/types/src/components/Form/factory.d.ts +0 -8
- package/dist/types/src/components/Form/factory.d.ts.map +0 -1
- package/dist/types/src/hooks/useForm.d.ts +0 -67
- package/dist/types/src/hooks/useForm.d.ts.map +0 -1
- package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -29
- package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
- package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
- package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
- package/dist/types/src/util/schema.d.ts +0 -3
- package/dist/types/src/util/schema.d.ts.map +0 -1
- package/src/components/ControlSection/ControlSection.stories.tsx +0 -38
- package/src/components/ControlSection/ControlSection.tsx +0 -153
- package/src/components/Form/ArrayField.tsx +0 -125
- package/src/components/Form/Defaults.tsx +0 -205
- package/src/components/Form/FormActions.tsx +0 -54
- package/src/components/Form/FormContent.tsx +0 -362
- package/src/components/Form/FormContext.tsx +0 -110
- package/src/components/Form/Input.tsx +0 -54
- package/src/components/Form/RefField.tsx +0 -283
- package/src/components/Form/custom/GeoPointInput.tsx +0 -80
- package/src/components/Form/custom/index.ts +0 -7
- package/src/components/Form/factory.tsx +0 -31
- package/src/hooks/useForm.ts +0 -296
- package/src/hooks/useQueryRefOptions.ts +0 -62
- package/src/hooks/useRefQueryLookupHandler.ts +0 -39
- package/src/util/schema.ts +0 -12
|
@@ -3,24 +3,31 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import type * as Schema from 'effect/Schema';
|
|
6
|
-
import React, { type KeyboardEvent,
|
|
6
|
+
import React, { type KeyboardEvent, forwardRef, useCallback, useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import { Combobox } from '@dxos/react-ui-
|
|
8
|
+
import { type Palette, Popover, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
import { Combobox, useSearchListInput, useSearchListResults } from '@dxos/react-ui-search';
|
|
10
10
|
|
|
11
|
-
import { type QueryTag } from '../../hooks';
|
|
12
11
|
import { translationKey } from '../../translations';
|
|
13
12
|
import { Form } from '../Form';
|
|
13
|
+
import { type FormFieldMap } from '../Form/FormFieldComponent';
|
|
14
|
+
|
|
15
|
+
export type RefOption = {
|
|
16
|
+
id: string;
|
|
17
|
+
label: string;
|
|
18
|
+
hue?: Palette;
|
|
19
|
+
};
|
|
14
20
|
|
|
15
21
|
export type ObjectPickerContentProps = ThemedClassName<{
|
|
16
|
-
options:
|
|
22
|
+
options: RefOption[];
|
|
17
23
|
selectedIds?: string[];
|
|
18
|
-
createSchema?: Schema.Schema.AnyNoContext;
|
|
19
24
|
createOptionLabel?: [string, { ns: string }];
|
|
20
25
|
createOptionIcon?: string;
|
|
26
|
+
createSchema?: Schema.Schema.AnyNoContext;
|
|
21
27
|
createInitialValuePath?: string;
|
|
22
|
-
|
|
28
|
+
createFieldMap?: FormFieldMap;
|
|
23
29
|
onCreate?: (values: any) => void;
|
|
30
|
+
onSelect: (id: string) => void;
|
|
24
31
|
}>;
|
|
25
32
|
|
|
26
33
|
const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>(
|
|
@@ -32,46 +39,35 @@ const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>
|
|
|
32
39
|
createOptionLabel,
|
|
33
40
|
createOptionIcon,
|
|
34
41
|
createInitialValuePath,
|
|
42
|
+
createFieldMap,
|
|
35
43
|
onSelect,
|
|
36
44
|
onCreate,
|
|
37
45
|
...props
|
|
38
46
|
},
|
|
39
|
-
|
|
47
|
+
forwardedRef,
|
|
40
48
|
) => {
|
|
41
49
|
const { t } = useTranslation(translationKey);
|
|
42
|
-
|
|
43
|
-
const [searchString, setSearchString] = useState('');
|
|
44
50
|
const [showForm, setShowForm] = useState(false);
|
|
51
|
+
const [formInitialValue, setFormInitialValue] = useState<string>('');
|
|
52
|
+
|
|
53
|
+
const { results, handleSearch } = useSearchListResults({
|
|
54
|
+
items: options,
|
|
55
|
+
});
|
|
45
56
|
|
|
46
57
|
const handleFormSave = useCallback(
|
|
47
58
|
(values: any) => {
|
|
48
59
|
onCreate?.(values);
|
|
49
60
|
setShowForm(false);
|
|
50
|
-
|
|
61
|
+
setFormInitialValue('');
|
|
51
62
|
},
|
|
52
63
|
[onCreate],
|
|
53
64
|
);
|
|
54
65
|
|
|
55
66
|
const handleFormCancel = useCallback(() => {
|
|
56
67
|
setShowForm(false);
|
|
57
|
-
|
|
68
|
+
setFormInitialValue('');
|
|
58
69
|
}, []);
|
|
59
70
|
|
|
60
|
-
// TODO(thure): The following click and keydown handlers are necessary because `onSelect` is called after the
|
|
61
|
-
// Popover is already closed. Augment/refactor CmdK, if possible, to facilitate stopping event default
|
|
62
|
-
// and propagation.
|
|
63
|
-
|
|
64
|
-
const handleClick = useCallback(
|
|
65
|
-
(event: MouseEvent) => {
|
|
66
|
-
if (createSchema && (event.target as HTMLElement).closest('[data-value="__create__"]')) {
|
|
67
|
-
event.stopPropagation();
|
|
68
|
-
event.preventDefault();
|
|
69
|
-
setShowForm(true);
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
[createSchema],
|
|
73
|
-
);
|
|
74
|
-
|
|
75
71
|
const handleKeyDown = useCallback(
|
|
76
72
|
(event: KeyboardEvent) => {
|
|
77
73
|
if (
|
|
@@ -83,78 +79,107 @@ const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>
|
|
|
83
79
|
) {
|
|
84
80
|
event.stopPropagation();
|
|
85
81
|
event.preventDefault();
|
|
82
|
+
// Get the current query from the input element
|
|
83
|
+
const input = (event.currentTarget as HTMLElement).querySelector('input[type="text"]') as HTMLInputElement;
|
|
84
|
+
const currentQuery = input?.value || '';
|
|
85
|
+
setFormInitialValue(currentQuery);
|
|
86
86
|
setShowForm(true);
|
|
87
87
|
}
|
|
88
88
|
},
|
|
89
89
|
[createSchema],
|
|
90
90
|
);
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
(
|
|
94
|
-
|
|
95
|
-
acc[option.id.toLowerCase()] = option.label.toLowerCase();
|
|
96
|
-
return acc;
|
|
97
|
-
}, {}),
|
|
98
|
-
[options],
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<Combobox.Content
|
|
103
|
-
{...props}
|
|
104
|
-
ref={ref}
|
|
105
|
-
filter={(value, search) => (value === '__create__' || labelById[value]?.includes(search.toLowerCase()) ? 1 : 0)}
|
|
106
|
-
onClickCapture={handleClick}
|
|
107
|
-
onKeyDownCapture={handleKeyDown}
|
|
108
|
-
>
|
|
109
|
-
{showForm && createSchema ? (
|
|
92
|
+
if (showForm && createSchema) {
|
|
93
|
+
return (
|
|
94
|
+
<Combobox.Content {...props} onSearch={handleSearch} onKeyDownCapture={handleKeyDown} ref={forwardedRef}>
|
|
110
95
|
<Popover.Viewport>
|
|
111
|
-
<Form
|
|
96
|
+
<Form.Root
|
|
97
|
+
testId='create-referenced-object-form'
|
|
112
98
|
schema={createSchema}
|
|
113
|
-
|
|
99
|
+
defaultValues={createInitialValuePath ? { [createInitialValuePath]: formInitialValue } : {}}
|
|
100
|
+
fieldMap={createFieldMap}
|
|
114
101
|
onSave={handleFormSave}
|
|
115
102
|
onCancel={handleFormCancel}
|
|
116
|
-
|
|
117
|
-
|
|
103
|
+
>
|
|
104
|
+
<Form.Viewport>
|
|
105
|
+
<Form.Content>
|
|
106
|
+
<Form.FieldSet />
|
|
107
|
+
<Form.Actions />
|
|
108
|
+
</Form.Content>
|
|
109
|
+
</Form.Viewport>
|
|
110
|
+
</Form.Root>
|
|
118
111
|
</Popover.Viewport>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
112
|
+
<Combobox.Arrow />
|
|
113
|
+
</Combobox.Content>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<Combobox.Content {...props} onSearch={handleSearch} onKeyDownCapture={handleKeyDown} ref={forwardedRef}>
|
|
119
|
+
<Combobox.Input placeholder={t('ref-field-combobox-input.placeholder')} autoFocus />
|
|
120
|
+
<Combobox.List>
|
|
121
|
+
{results.map((option) => (
|
|
122
|
+
<Combobox.Item
|
|
123
|
+
key={option.id}
|
|
124
|
+
value={option.id}
|
|
125
|
+
label={option.label}
|
|
126
|
+
checked={selectedIds?.includes(option.id)}
|
|
127
|
+
onSelect={() => onSelect(option.id)}
|
|
128
|
+
classNames='flex items-center gap-2'
|
|
129
|
+
/>
|
|
130
|
+
))}
|
|
131
|
+
{createSchema && onCreate && (
|
|
132
|
+
<CreateItem
|
|
133
|
+
createOptionLabel={createOptionLabel}
|
|
134
|
+
createOptionIcon={createOptionIcon ?? 'ph--plus--regular'}
|
|
135
|
+
onCreateItemSelect={(query: string) => {
|
|
136
|
+
setFormInitialValue(query);
|
|
137
|
+
setShowForm(true);
|
|
138
|
+
}}
|
|
126
139
|
/>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
<Combobox.Item
|
|
130
|
-
key={option.id}
|
|
131
|
-
value={option.id}
|
|
132
|
-
onSelect={() => onSelect(option.id)}
|
|
133
|
-
classNames='flex items-center gap-2'
|
|
134
|
-
>
|
|
135
|
-
<span className='grow'>{option.label}</span>
|
|
136
|
-
{selectedIds?.includes(option.id) && <Icon icon='ph--check--regular' />}
|
|
137
|
-
</Combobox.Item>
|
|
138
|
-
))}
|
|
139
|
-
{searchString.length > 0 && createOptionLabel && createOptionIcon && createSchema && onCreate && (
|
|
140
|
-
<Combobox.Item value='__create__' classNames='flex items-center gap-2'>
|
|
141
|
-
<Icon icon={createOptionIcon} />
|
|
142
|
-
{t(createOptionLabel[0], { ns: createOptionLabel[1].ns, text: searchString })}
|
|
143
|
-
</Combobox.Item>
|
|
144
|
-
)}
|
|
145
|
-
</Combobox.List>
|
|
146
|
-
</>
|
|
147
|
-
)}
|
|
140
|
+
)}
|
|
141
|
+
</Combobox.List>
|
|
148
142
|
<Combobox.Arrow />
|
|
149
143
|
</Combobox.Content>
|
|
150
144
|
);
|
|
151
145
|
},
|
|
152
146
|
);
|
|
153
147
|
|
|
154
|
-
|
|
148
|
+
const CreateItem = ({
|
|
149
|
+
createOptionLabel,
|
|
150
|
+
createOptionIcon,
|
|
151
|
+
onCreateItemSelect,
|
|
152
|
+
}: {
|
|
153
|
+
createOptionLabel?: [string, { ns: string }];
|
|
154
|
+
createOptionIcon: string;
|
|
155
|
+
onCreateItemSelect: (query: string) => void;
|
|
156
|
+
}) => {
|
|
157
|
+
const { t } = useTranslation(translationKey);
|
|
158
|
+
const { query } = useSearchListInput();
|
|
159
|
+
|
|
160
|
+
const label = createOptionLabel
|
|
161
|
+
? t(createOptionLabel[0], { ns: createOptionLabel[1].ns, text: query })
|
|
162
|
+
: t('create-option.label');
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<Combobox.Item
|
|
166
|
+
value='__create__'
|
|
167
|
+
label={label}
|
|
168
|
+
icon={createOptionIcon}
|
|
169
|
+
classNames='flex items-center gap-2'
|
|
170
|
+
closeOnSelect={false}
|
|
171
|
+
onSelect={() => {
|
|
172
|
+
onCreateItemSelect(query);
|
|
173
|
+
}}
|
|
174
|
+
/>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
ObjectPickerContent.displayName = 'ObjectPicker.Content';
|
|
155
179
|
|
|
156
180
|
export const ObjectPicker = {
|
|
157
181
|
Root: Combobox.Root,
|
|
182
|
+
Portal: Combobox.Portal,
|
|
158
183
|
Trigger: Combobox.Trigger,
|
|
159
184
|
VirtualTrigger: Combobox.VirtualTrigger,
|
|
160
185
|
Content: ObjectPickerContent,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
import { random } from '@dxos/random';
|
|
9
|
+
import { Button, Input } from '@dxos/react-ui';
|
|
10
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
11
|
+
|
|
12
|
+
import { translations } from '../../translations';
|
|
13
|
+
import { Settings } from './Settings';
|
|
14
|
+
|
|
15
|
+
random.seed(132);
|
|
16
|
+
|
|
17
|
+
const DefaultStory = () => {
|
|
18
|
+
return (
|
|
19
|
+
<Settings.Viewport>
|
|
20
|
+
<Settings.Section title='Settings' description={random.lorem.paragraphs(1)}>
|
|
21
|
+
<Settings.Item title={random.lorem.sentence(2)} description={random.lorem.paragraphs(1)}>
|
|
22
|
+
<Input.TextInput placeholder='Input' />
|
|
23
|
+
</Settings.Item>
|
|
24
|
+
<Settings.Item title={random.lorem.sentence(2)} description={random.lorem.paragraphs(2)}>
|
|
25
|
+
<Input.Switch />
|
|
26
|
+
</Settings.Item>
|
|
27
|
+
<Settings.Item title={random.lorem.sentence(3)} description={random.lorem.paragraphs(2)}>
|
|
28
|
+
<Button>Test</Button>
|
|
29
|
+
</Settings.Item>
|
|
30
|
+
</Settings.Section>
|
|
31
|
+
<Settings.Section title='Panel Example'>
|
|
32
|
+
<Settings.Panel>
|
|
33
|
+
<h3 className='text-lg mb-2'>Members</h3>
|
|
34
|
+
<p className='text-description'>Content inside a panel.</p>
|
|
35
|
+
</Settings.Panel>
|
|
36
|
+
</Settings.Section>
|
|
37
|
+
</Settings.Viewport>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const meta = {
|
|
42
|
+
title: 'ui/react-ui-form/Settings',
|
|
43
|
+
render: DefaultStory,
|
|
44
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
45
|
+
parameters: {
|
|
46
|
+
layout: 'fullscreen',
|
|
47
|
+
translations,
|
|
48
|
+
},
|
|
49
|
+
} satisfies Meta;
|
|
50
|
+
|
|
51
|
+
export default meta;
|
|
52
|
+
|
|
53
|
+
type Story = StoryObj<typeof meta>;
|
|
54
|
+
|
|
55
|
+
export const Default: Story = {};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { type PropsWithChildren } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Input, type Label, ScrollArea, type ThemedClassName, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
8
|
+
import { composable, composableProps, mx } from '@dxos/ui-theme';
|
|
9
|
+
|
|
10
|
+
import { translationKey } from '../../translations';
|
|
11
|
+
|
|
12
|
+
const SETTINGS_ROOT_NAME = 'Settings.Root';
|
|
13
|
+
const SETTINGS_VIEWPORT_NAME = 'Settings.Viewport';
|
|
14
|
+
const SETTINGS_SECTION_NAME = 'Settings.Section';
|
|
15
|
+
const SETTINGS_PANEL_NAME = 'Settings.Panel';
|
|
16
|
+
const SETTINGS_ITEM_NAME = 'Settings.Item';
|
|
17
|
+
|
|
18
|
+
//
|
|
19
|
+
// Root (headless — no styling, no scroll).
|
|
20
|
+
//
|
|
21
|
+
|
|
22
|
+
type SettingsRootProps = ThemedClassName<PropsWithChildren>;
|
|
23
|
+
|
|
24
|
+
const SettingsRoot = ({ children, classNames }: SettingsRootProps) => {
|
|
25
|
+
return <div className={mx('flex flex-col gap-2', classNames)}>{children}</div>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
SettingsRoot.displayName = SETTINGS_ROOT_NAME;
|
|
29
|
+
|
|
30
|
+
//
|
|
31
|
+
// Viewport (scroll area + padding + document layout).
|
|
32
|
+
//
|
|
33
|
+
|
|
34
|
+
const SettingsViewport = composable<HTMLDivElement>(({ children, ...props }, forwardedRef) => {
|
|
35
|
+
return (
|
|
36
|
+
<ScrollArea.Root {...composableProps(props)} orientation='vertical' centered thin ref={forwardedRef}>
|
|
37
|
+
<ScrollArea.Viewport classNames='px-4 pointer-coarse:px-2'>
|
|
38
|
+
<div role='none' className='dx-document flex flex-col gap-2 py-4'>
|
|
39
|
+
{children}
|
|
40
|
+
</div>
|
|
41
|
+
</ScrollArea.Viewport>
|
|
42
|
+
</ScrollArea.Root>
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
SettingsViewport.displayName = SETTINGS_VIEWPORT_NAME;
|
|
47
|
+
|
|
48
|
+
//
|
|
49
|
+
// Section (title + optional description + children with spacing).
|
|
50
|
+
//
|
|
51
|
+
|
|
52
|
+
type SettingsSectionProps = PropsWithChildren<{
|
|
53
|
+
title: Label;
|
|
54
|
+
description?: Label;
|
|
55
|
+
}>;
|
|
56
|
+
|
|
57
|
+
const SettingsSection = ({ title, description, children }: SettingsSectionProps) => {
|
|
58
|
+
const { t } = useTranslation(translationKey);
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
<h2 className='px-trim-md mt-trim-md text-xl'>{toLocalizedString(title, t)}</h2>
|
|
62
|
+
{description && <p className='px-trim-md text-description'>{toLocalizedString(description, t)}</p>}
|
|
63
|
+
<div className='w-full pt-trim-md space-y-trim-md'>{children}</div>
|
|
64
|
+
</>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
SettingsSection.displayName = SETTINGS_SECTION_NAME;
|
|
69
|
+
|
|
70
|
+
//
|
|
71
|
+
// Panel (bordered card container).
|
|
72
|
+
//
|
|
73
|
+
|
|
74
|
+
type SettingsPanelProps = ThemedClassName<PropsWithChildren>;
|
|
75
|
+
|
|
76
|
+
const SettingsPanel = ({ classNames, children }: SettingsPanelProps) => {
|
|
77
|
+
return (
|
|
78
|
+
<div role='none' className={mx('flex flex-col gap-3 p-trim-md border border-separator rounded-sm', classNames)}>
|
|
79
|
+
{children}
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
SettingsPanel.displayName = SETTINGS_PANEL_NAME;
|
|
85
|
+
|
|
86
|
+
//
|
|
87
|
+
// Item (label + optional description + control, proper label semantics).
|
|
88
|
+
//
|
|
89
|
+
|
|
90
|
+
type SettingsItemProps = PropsWithChildren<{
|
|
91
|
+
title: Label;
|
|
92
|
+
description?: Label;
|
|
93
|
+
}>;
|
|
94
|
+
|
|
95
|
+
const SettingsItem = ({ title, description = '', children }: SettingsItemProps) => {
|
|
96
|
+
const { t } = useTranslation(translationKey);
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<Input.Root>
|
|
100
|
+
<SettingsPanel classNames='grid grid-cols-1 md:grid-cols-[1fr_1fr] gap-x-trim-lg gap-y-0'>
|
|
101
|
+
<Input.Label classNames='pb-trim-md text-base-surface-text text-lg md:col-span-2'>
|
|
102
|
+
{toLocalizedString(title, t)}
|
|
103
|
+
</Input.Label>
|
|
104
|
+
<Input.DescriptionAndValidation>
|
|
105
|
+
<Input.Description classNames='text-base text-description'>
|
|
106
|
+
{toLocalizedString(description, t)}
|
|
107
|
+
</Input.Description>
|
|
108
|
+
</Input.DescriptionAndValidation>
|
|
109
|
+
<div role='none' className='text-end py-1'>
|
|
110
|
+
{children}
|
|
111
|
+
</div>
|
|
112
|
+
</SettingsPanel>
|
|
113
|
+
</Input.Root>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
SettingsItem.displayName = SETTINGS_ITEM_NAME;
|
|
118
|
+
|
|
119
|
+
//
|
|
120
|
+
// Settings
|
|
121
|
+
//
|
|
122
|
+
|
|
123
|
+
export const Settings = {
|
|
124
|
+
Root: SettingsRoot,
|
|
125
|
+
Viewport: SettingsViewport,
|
|
126
|
+
Section: SettingsSection,
|
|
127
|
+
Panel: SettingsPanel,
|
|
128
|
+
Item: SettingsItem,
|
|
129
|
+
};
|
|
@@ -6,25 +6,25 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
|
6
6
|
import * as Schema from 'effect/Schema';
|
|
7
7
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
8
8
|
|
|
9
|
-
import { DXN, Filter, Obj, Query, type QueryAST, Tag, Type } from '@dxos/echo';
|
|
10
|
-
import { type
|
|
9
|
+
import { DXN, Filter, JsonSchema, Obj, Query, type QueryAST, Tag, Type } from '@dxos/echo';
|
|
10
|
+
import { type View } from '@dxos/echo';
|
|
11
|
+
import { type EchoSchema, Format, type Mutable } from '@dxos/echo/internal';
|
|
11
12
|
import { useQuery } from '@dxos/react-client/echo';
|
|
12
|
-
import {
|
|
13
|
+
import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
|
|
13
14
|
import { useAsyncEffect } from '@dxos/react-ui';
|
|
14
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
15
|
-
import { type ProjectionModel,
|
|
16
|
-
import { Employer, Organization, Person,
|
|
15
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
16
|
+
import { type ProjectionModel, ViewModel, getTypenameFromQuery } from '@dxos/schema';
|
|
17
|
+
import { Employer, Organization, Person, Pipeline } from '@dxos/types';
|
|
17
18
|
|
|
18
19
|
import { translations } from '../../translations';
|
|
19
|
-
import { TestLayout,
|
|
20
|
-
|
|
20
|
+
import { TestLayout, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
21
21
|
import { ViewEditor, type ViewEditorProps } from './ViewEditor';
|
|
22
22
|
|
|
23
23
|
const types = [
|
|
24
24
|
// TODO(burdon): Get label from annotation.
|
|
25
25
|
{ value: Organization.Organization.typename, label: 'Organization' },
|
|
26
26
|
{ value: Person.Person.typename, label: 'Person' },
|
|
27
|
-
{ value:
|
|
27
|
+
{ value: Pipeline.Pipeline.typename, label: 'Project' },
|
|
28
28
|
{ value: Employer.Employer.typename, label: 'Employer' },
|
|
29
29
|
];
|
|
30
30
|
|
|
@@ -35,15 +35,15 @@ export type ViewEditorDebugObjects = {
|
|
|
35
35
|
projection: ProjectionModel;
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
type
|
|
38
|
+
type DefaultStoryProps = Pick<ViewEditorProps, 'readonly' | 'mode'>;
|
|
39
39
|
|
|
40
|
-
const DefaultStory = (props:
|
|
41
|
-
const { space } =
|
|
40
|
+
const DefaultStory = (props: DefaultStoryProps) => {
|
|
41
|
+
const { space } = useClientStory();
|
|
42
42
|
const [schema, setSchema] = useState<EchoSchema>();
|
|
43
43
|
const [view, setView] = useState<View.View>();
|
|
44
44
|
const projectionRef = useRef<ProjectionModel>(null);
|
|
45
45
|
|
|
46
|
-
const tags = useQuery(space, Filter.type(Tag.Tag));
|
|
46
|
+
const tags = useQuery(space?.db, Filter.type(Tag.Tag));
|
|
47
47
|
|
|
48
48
|
useAsyncEffect(async () => {
|
|
49
49
|
if (space) {
|
|
@@ -52,8 +52,8 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
52
52
|
email: Format.Email,
|
|
53
53
|
salary: Format.Currency(),
|
|
54
54
|
}).pipe(
|
|
55
|
-
Type.
|
|
56
|
-
typename: 'example.
|
|
55
|
+
Type.object({
|
|
56
|
+
typename: 'com.example.type.test',
|
|
57
57
|
version: '0.1.0',
|
|
58
58
|
}),
|
|
59
59
|
);
|
|
@@ -63,18 +63,17 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
63
63
|
description: Schema.String,
|
|
64
64
|
completed: Schema.Boolean,
|
|
65
65
|
}).pipe(
|
|
66
|
-
Type.
|
|
67
|
-
typename: 'example.
|
|
66
|
+
Type.object({
|
|
67
|
+
typename: 'com.example.type.alternate',
|
|
68
68
|
version: '0.1.0',
|
|
69
69
|
}),
|
|
70
70
|
);
|
|
71
71
|
|
|
72
72
|
const [testSchema] = await space.db.schemaRegistry.register([TestSchema, AlternateSchema]);
|
|
73
|
-
const view =
|
|
73
|
+
const view = ViewModel.make({
|
|
74
74
|
name: 'Test',
|
|
75
75
|
query: Query.select(Filter.type(TestSchema)),
|
|
76
|
-
jsonSchema:
|
|
77
|
-
presentation: Obj.make(Type.Expando, {}),
|
|
76
|
+
jsonSchema: JsonSchema.toJsonSchema(TestSchema),
|
|
78
77
|
});
|
|
79
78
|
|
|
80
79
|
setSchema(testSchema);
|
|
@@ -88,10 +87,12 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
88
87
|
return;
|
|
89
88
|
}
|
|
90
89
|
|
|
91
|
-
if (props.mode === '
|
|
90
|
+
if (props.mode === 'tag') {
|
|
92
91
|
const queue = target && DXN.tryParse(target) ? target : undefined;
|
|
93
|
-
const query = queue ? Query.fromAst(newQuery).
|
|
94
|
-
view
|
|
92
|
+
const query = queue ? Query.fromAst(newQuery).from({ queues: [queue] }) : Query.fromAst(newQuery);
|
|
93
|
+
Obj.change(view, (obj) => {
|
|
94
|
+
obj.query.ast = query.ast as Mutable<typeof query.ast>;
|
|
95
|
+
});
|
|
95
96
|
|
|
96
97
|
const typename = getTypenameFromQuery(query.ast);
|
|
97
98
|
const [newSchema] = await space.db.schemaRegistry.query({ typename }).run();
|
|
@@ -99,16 +100,18 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
99
100
|
return;
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
const newView =
|
|
103
|
+
const newView = ViewModel.make({
|
|
103
104
|
query,
|
|
104
105
|
jsonSchema: newSchema.jsonSchema,
|
|
105
|
-
presentation: Obj.make(Type.Expando, {}),
|
|
106
106
|
});
|
|
107
|
-
|
|
107
|
+
Obj.change(view, (obj) => {
|
|
108
|
+
obj.projection = Obj.getSnapshot(newView).projection as Mutable<typeof obj.projection>;
|
|
109
|
+
});
|
|
108
110
|
setSchema(() => newSchema);
|
|
109
111
|
} else {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
Obj.change(view, (obj) => {
|
|
113
|
+
obj.query.ast = newQuery as Mutable<typeof newQuery>;
|
|
114
|
+
});
|
|
112
115
|
schema.updateTypename(getTypenameFromQuery(newQuery));
|
|
113
116
|
}
|
|
114
117
|
},
|
|
@@ -143,20 +146,18 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
143
146
|
|
|
144
147
|
return (
|
|
145
148
|
<TestLayout json={json}>
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
/>
|
|
159
|
-
</TestPanel>
|
|
149
|
+
<ViewEditor
|
|
150
|
+
ref={projectionRef}
|
|
151
|
+
schema={schema}
|
|
152
|
+
view={view}
|
|
153
|
+
registry={space?.db.schemaRegistry}
|
|
154
|
+
mode={props.mode}
|
|
155
|
+
readonly={props.readonly}
|
|
156
|
+
types={types}
|
|
157
|
+
tags={tags}
|
|
158
|
+
onQueryChanged={updateViewQuery}
|
|
159
|
+
onDelete={handleDelete}
|
|
160
|
+
/>
|
|
160
161
|
</TestLayout>
|
|
161
162
|
);
|
|
162
163
|
};
|
|
@@ -174,7 +175,8 @@ const meta = {
|
|
|
174
175
|
space.db.add(Tag.make({ label: 'New' }));
|
|
175
176
|
},
|
|
176
177
|
}),
|
|
177
|
-
withTheme,
|
|
178
|
+
withTheme(),
|
|
179
|
+
withLayout({ layout: 'fullscreen' }),
|
|
178
180
|
],
|
|
179
181
|
parameters: {
|
|
180
182
|
layout: 'fullscreen',
|
|
@@ -194,8 +196,8 @@ export const Readonly: Story = {
|
|
|
194
196
|
},
|
|
195
197
|
};
|
|
196
198
|
|
|
197
|
-
export const
|
|
199
|
+
export const TagQueryMode: Story = {
|
|
198
200
|
args: {
|
|
199
|
-
mode: '
|
|
201
|
+
mode: 'tag',
|
|
200
202
|
},
|
|
201
203
|
};
|
|
@@ -6,11 +6,11 @@ import { composeStories } from '@storybook/react';
|
|
|
6
6
|
import { cleanup, fireEvent, screen, waitFor } from '@testing-library/react';
|
|
7
7
|
import { afterEach, describe, expect, test } from 'vitest';
|
|
8
8
|
|
|
9
|
+
import { View } from '@dxos/echo';
|
|
9
10
|
import { EchoSchema, isInstanceOf } from '@dxos/echo/internal';
|
|
10
|
-
import { ProjectionModel
|
|
11
|
+
import { ProjectionModel } from '@dxos/schema';
|
|
11
12
|
|
|
12
13
|
import { VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
13
|
-
|
|
14
14
|
import * as stories from './ViewEditor.stories';
|
|
15
15
|
import { type ViewEditorDebugObjects } from './ViewEditor.stories';
|
|
16
16
|
|
|
@@ -54,7 +54,7 @@ describe('ViewEditor', () => {
|
|
|
54
54
|
const nameField = screen.getByText('name');
|
|
55
55
|
fireEvent.click(nameField);
|
|
56
56
|
|
|
57
|
-
const fieldInput = screen.getByPlaceholderText('
|
|
57
|
+
const fieldInput = screen.getByPlaceholderText('Property name');
|
|
58
58
|
fireEvent.change(fieldInput, { target: { value: 'new_property' } });
|
|
59
59
|
|
|
60
60
|
fireEvent.click(screen.getByTestId('save-button'));
|
|
@@ -89,12 +89,12 @@ describe('ViewEditor', () => {
|
|
|
89
89
|
await Default.run();
|
|
90
90
|
await waitForViewEditor();
|
|
91
91
|
|
|
92
|
-
// Click the add property button.
|
|
92
|
+
// Click the add property button. The new field is auto-expanded by handleAdd.
|
|
93
93
|
const addButton = screen.getByText('Add property');
|
|
94
94
|
fireEvent.click(addButton);
|
|
95
95
|
|
|
96
96
|
// Fill out the property field.
|
|
97
|
-
const fieldInput = screen.getByPlaceholderText('
|
|
97
|
+
const fieldInput = screen.getByPlaceholderText('Property name');
|
|
98
98
|
fireEvent.change(fieldInput, { target: { value: 'added_property' } });
|
|
99
99
|
|
|
100
100
|
// Click the format combo box and select the first option.
|