@dxos/react-ui-form 0.8.4-main.7ace549 → 0.8.4-main.937b3ca
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 +2191 -2283
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2191 -2283
- 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/ControlSection.d.ts +2 -2
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +7 -4
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +6 -5
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +9 -3
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form-old.stories.d.ts +131 -0
- package/dist/types/src/components/Form/Form-old.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/Form.d.ts +81 -27
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +53 -92
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +37 -0
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +78 -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 +19 -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 +14 -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/{custom/SelectOptionsInput.stories.d.ts → fields/SelectOptionField.stories.d.ts} +6 -3
- 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 +14 -9
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +5 -2
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +6 -8
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +5 -2
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.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 +6 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +6 -3
- package/dist/types/src/translations.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 +1 -2
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +49 -44
- package/src/components/ControlSection/ControlSection.tsx +8 -8
- package/src/components/FieldEditor/FieldEditor.stories.tsx +42 -21
- package/src/components/FieldEditor/FieldEditor.tsx +72 -75
- package/src/components/Form/Form-old.stories.tsx +314 -0
- package/src/components/Form/Form.stories.tsx +146 -246
- package/src/components/Form/Form.tsx +319 -110
- package/src/components/Form/FormField.tsx +286 -0
- package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
- package/src/components/Form/FormFieldComponent.tsx +174 -0
- package/src/components/Form/FormFieldSet.tsx +125 -0
- package/src/components/Form/README.md +3 -0
- package/src/components/Form/fields/ArrayField.tsx +151 -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 +181 -0
- package/src/components/Form/fields/SelectField.tsx +65 -0
- package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +17 -14
- package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +20 -14
- 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} +15 -8
- package/src/components/Form/fields/index.ts +15 -0
- package/src/components/Form/index.ts +3 -4
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +15 -8
- package/src/components/ObjectPicker/ObjectPicker.tsx +76 -62
- package/src/components/ViewEditor/ViewEditor.stories.tsx +29 -25
- package/src/components/ViewEditor/ViewEditor.test.tsx +6 -2
- package/src/components/ViewEditor/ViewEditor.tsx +220 -154
- package/src/components/testing.tsx +9 -7
- package/src/hooks/index.ts +2 -3
- package/src/hooks/useFormHandler.ts +356 -0
- package/src/hooks/useKeyHandler.tsx +37 -0
- package/src/translations.ts +8 -6
- package/src/util/index.ts +1 -1
- package/src/util/properties.ts +18 -0
- package/src/util/refs.ts +7 -13
- 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.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/Form/ArrayField.tsx +0 -127
- package/src/components/Form/Defaults.tsx +0 -205
- package/src/components/Form/FormActions.tsx +0 -59
- 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 -34
- 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
|
@@ -6,17 +6,17 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
|
6
6
|
import * as Schema from 'effect/Schema';
|
|
7
7
|
import React, { useState } from 'react';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { SelectOption } from '@dxos/echo/internal';
|
|
10
10
|
import { withTheme } from '@dxos/react-ui/testing';
|
|
11
11
|
|
|
12
12
|
import { translations } from '../../../translations';
|
|
13
|
-
import { TestLayout
|
|
13
|
+
import { TestLayout } from '../../testing';
|
|
14
14
|
import { Form } from '../Form';
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { SelectOptionField } from './SelectOptionField';
|
|
17
17
|
|
|
18
18
|
const schema = Schema.Struct({
|
|
19
|
-
options: Schema.Array(
|
|
19
|
+
options: Schema.Array(SelectOption).pipe(Schema.mutable),
|
|
20
20
|
}).pipe(Schema.mutable);
|
|
21
21
|
|
|
22
22
|
const DefaultStory = () => {
|
|
@@ -31,21 +31,24 @@ const DefaultStory = () => {
|
|
|
31
31
|
|
|
32
32
|
return (
|
|
33
33
|
<TestLayout>
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
<Form.Root
|
|
35
|
+
schema={schema}
|
|
36
|
+
values={values}
|
|
37
|
+
fieldMap={{ options: SelectOptionField }}
|
|
38
|
+
onSave={(values) => setValues(values)}
|
|
39
|
+
>
|
|
40
|
+
<Form.Content>
|
|
41
|
+
<Form.FieldSet />
|
|
42
|
+
<Form.Actions />
|
|
43
|
+
</Form.Content>
|
|
44
|
+
</Form.Root>
|
|
42
45
|
</TestLayout>
|
|
43
46
|
);
|
|
44
47
|
};
|
|
45
48
|
|
|
46
49
|
const meta = {
|
|
47
|
-
title: 'ui/react-ui-form/
|
|
48
|
-
component:
|
|
50
|
+
title: 'ui/react-ui-form/SelectOptionField',
|
|
51
|
+
component: SelectOptionField as any,
|
|
49
52
|
render: DefaultStory,
|
|
50
53
|
decorators: [withTheme],
|
|
51
54
|
parameters: {
|
|
@@ -9,25 +9,32 @@ import { PublicKey } from '@dxos/keys';
|
|
|
9
9
|
import { type ChromaticPalette, IconButton, Input, Tag, useTranslation } from '@dxos/react-ui';
|
|
10
10
|
import { List } from '@dxos/react-ui-list';
|
|
11
11
|
import { HuePicker } from '@dxos/react-ui-pickers';
|
|
12
|
-
import { hues, subtleHover } from '@dxos/
|
|
12
|
+
import { hues, osTranslations, subtleHover } from '@dxos/ui-theme';
|
|
13
13
|
|
|
14
14
|
import { translationKey } from '../../../translations';
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
export const
|
|
15
|
+
import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
|
|
16
|
+
|
|
17
|
+
export const SelectOptionField = ({
|
|
18
|
+
type,
|
|
19
|
+
readonly,
|
|
20
|
+
label,
|
|
21
|
+
getStatus,
|
|
22
|
+
getValue,
|
|
23
|
+
onValueChange,
|
|
24
|
+
}: FormFieldComponentProps<SelectOption[] | undefined>) => {
|
|
25
|
+
const { t } = useTranslation(translationKey);
|
|
26
|
+
const { status, error } = getStatus();
|
|
18
27
|
const [selected, setSelectedId] = useState<string | null>(null);
|
|
19
28
|
const [isNewOption, setIsNewOption] = useState(false);
|
|
20
29
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
21
|
-
const
|
|
22
|
-
const { status, error } = getStatus();
|
|
23
|
-
const options = getValue<SelectOption[] | undefined>();
|
|
30
|
+
const options = getValue();
|
|
24
31
|
|
|
25
32
|
// Initialization.
|
|
26
33
|
useEffect(() => {
|
|
27
34
|
if (options === undefined) {
|
|
28
35
|
onValueChange(type, []);
|
|
29
36
|
}
|
|
30
|
-
}, [options,
|
|
37
|
+
}, [options, type, onValueChange]);
|
|
31
38
|
|
|
32
39
|
const randomHue = () => {
|
|
33
40
|
const usedHues = new Set(options?.map((option) => option.color) ?? []);
|
|
@@ -88,8 +95,8 @@ export const SelectOptionInput = ({ type, label, readonly, getStatus, getValue,
|
|
|
88
95
|
[options, type, onValueChange],
|
|
89
96
|
);
|
|
90
97
|
|
|
91
|
-
const handleKeyDown = useCallback((
|
|
92
|
-
if (
|
|
98
|
+
const handleKeyDown = useCallback((ev: KeyboardEvent<HTMLInputElement>) => {
|
|
99
|
+
if (ev.key === 'Enter') {
|
|
93
100
|
setSelectedId(null);
|
|
94
101
|
}
|
|
95
102
|
}, []);
|
|
@@ -111,12 +118,12 @@ export const SelectOptionInput = ({ type, label, readonly, getStatus, getValue,
|
|
|
111
118
|
|
|
112
119
|
return (
|
|
113
120
|
<Input.Root validationValence={status}>
|
|
114
|
-
<
|
|
121
|
+
<FormFieldLabel error={error} readonly={readonly} label={label} />
|
|
115
122
|
<div role='none'>
|
|
116
123
|
{options && (
|
|
117
124
|
<List.Root
|
|
118
125
|
items={options}
|
|
119
|
-
isItem={(
|
|
126
|
+
isItem={(_item) => true}
|
|
120
127
|
onMove={readonly ? undefined : handleMove}
|
|
121
128
|
readonly={!!readonly}
|
|
122
129
|
>
|
|
@@ -139,7 +146,7 @@ export const SelectOptionInput = ({ type, label, readonly, getStatus, getValue,
|
|
|
139
146
|
<IconButton
|
|
140
147
|
iconOnly
|
|
141
148
|
variant='ghost'
|
|
142
|
-
label={t(selected === item.id ? 'collapse label' : 'expand label', { ns:
|
|
149
|
+
label={t(selected === item.id ? 'collapse label' : 'expand label', { ns: osTranslations })}
|
|
143
150
|
icon={selected === item.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
|
|
144
151
|
onClick={() => handleClick(item.id)}
|
|
145
152
|
/>
|
|
@@ -157,7 +164,6 @@ export const SelectOptionInput = ({ type, label, readonly, getStatus, getValue,
|
|
|
157
164
|
onChange={handleTitleChange(item.id)}
|
|
158
165
|
onKeyDown={handleKeyDown}
|
|
159
166
|
classNames='flex-1'
|
|
160
|
-
data-no-submit
|
|
161
167
|
/>
|
|
162
168
|
<HuePicker disabled={!!readonly} value={item.color} onChange={handleColorChange(item.id)} />
|
|
163
169
|
<IconButton
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Input, type TextAreaProps } from '@dxos/react-ui';
|
|
8
|
+
|
|
9
|
+
import { type FormFieldComponentProps, FormFieldWrapper } from '../FormFieldComponent';
|
|
10
|
+
|
|
11
|
+
export const TextAreaField = ({
|
|
12
|
+
type,
|
|
13
|
+
readonly,
|
|
14
|
+
placeholder,
|
|
15
|
+
onValueChange,
|
|
16
|
+
onBlur,
|
|
17
|
+
...props
|
|
18
|
+
}: FormFieldComponentProps<string>) => {
|
|
19
|
+
const handleChange = useCallback<NonNullable<TextAreaProps['onChange']>>(
|
|
20
|
+
(event) => onValueChange(type, event.target.value),
|
|
21
|
+
[type, onValueChange],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<FormFieldWrapper<string> readonly={readonly} {...props}>
|
|
26
|
+
{({ value = '' }) => (
|
|
27
|
+
<Input.TextArea
|
|
28
|
+
rows={3}
|
|
29
|
+
disabled={!!readonly}
|
|
30
|
+
placeholder={placeholder}
|
|
31
|
+
value={value}
|
|
32
|
+
onChange={handleChange}
|
|
33
|
+
onBlur={onBlur}
|
|
34
|
+
/>
|
|
35
|
+
)}
|
|
36
|
+
</FormFieldWrapper>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Input, type TextInputProps } from '@dxos/react-ui';
|
|
8
|
+
|
|
9
|
+
import { type FormFieldComponentProps, FormFieldWrapper } from '../FormFieldComponent';
|
|
10
|
+
|
|
11
|
+
export const TextField = ({
|
|
12
|
+
type,
|
|
13
|
+
readonly,
|
|
14
|
+
placeholder,
|
|
15
|
+
onValueChange,
|
|
16
|
+
onBlur,
|
|
17
|
+
...props
|
|
18
|
+
}: FormFieldComponentProps<string>) => {
|
|
19
|
+
const handleChange = useCallback<NonNullable<TextInputProps['onChange']>>(
|
|
20
|
+
(event) => onValueChange(type, event.target.value),
|
|
21
|
+
[type, onValueChange],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<FormFieldWrapper<string> readonly={readonly} {...props}>
|
|
26
|
+
{({ value = '' }) => (
|
|
27
|
+
<Input.TextInput
|
|
28
|
+
noAutoFill
|
|
29
|
+
disabled={!!readonly}
|
|
30
|
+
placeholder={placeholder}
|
|
31
|
+
value={value}
|
|
32
|
+
onChange={handleChange}
|
|
33
|
+
onBlur={onBlur}
|
|
34
|
+
/>
|
|
35
|
+
)}
|
|
36
|
+
</FormFieldWrapper>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
@@ -5,31 +5,33 @@
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
7
|
import { Input } from '@dxos/react-ui';
|
|
8
|
-
import { mx } from '@dxos/
|
|
8
|
+
import { mx } from '@dxos/ui-theme';
|
|
9
9
|
import { safeParseFloat } from '@dxos/util';
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
|
|
12
12
|
|
|
13
13
|
const gridCols = ['grid-cols-1', 'grid-cols-2', 'grid-cols-3', 'grid-cols-4'];
|
|
14
14
|
|
|
15
|
-
export const
|
|
15
|
+
export const TupleField = ({
|
|
16
16
|
binding,
|
|
17
17
|
type,
|
|
18
|
-
label,
|
|
19
18
|
readonly,
|
|
19
|
+
label,
|
|
20
20
|
getStatus,
|
|
21
21
|
getValue,
|
|
22
22
|
onValueChange,
|
|
23
23
|
onBlur,
|
|
24
|
-
}:
|
|
24
|
+
}: FormFieldComponentProps<Record<string, number>> & {
|
|
25
|
+
binding: string[];
|
|
26
|
+
}) => {
|
|
25
27
|
const { status, error } = getStatus();
|
|
26
28
|
|
|
27
29
|
// TODO(burdon): Generalize number/float/string, etc.
|
|
28
|
-
const values: Record<string, number> = getValue
|
|
30
|
+
const values: Record<string, number> = getValue() ?? {};
|
|
29
31
|
|
|
30
32
|
return (
|
|
31
33
|
<Input.Root validationValence={status}>
|
|
32
|
-
<
|
|
34
|
+
<FormFieldLabel error={error} readonly={readonly} label={label} />
|
|
33
35
|
<div className={mx('grid gap-2', gridCols[binding.length - 1])}>
|
|
34
36
|
{binding.map((prop) => (
|
|
35
37
|
<Input.TextInput
|
|
@@ -37,7 +39,12 @@ export const TupleInput = ({
|
|
|
37
39
|
type='number'
|
|
38
40
|
disabled={!!readonly}
|
|
39
41
|
value={values[prop]}
|
|
40
|
-
onChange={(event) =>
|
|
42
|
+
onChange={(event) => {
|
|
43
|
+
onValueChange(type, {
|
|
44
|
+
...values,
|
|
45
|
+
[prop]: safeParseFloat(event.target.value, 0),
|
|
46
|
+
});
|
|
47
|
+
}}
|
|
41
48
|
onBlur={onBlur}
|
|
42
49
|
/>
|
|
43
50
|
))}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
export * from './ArrayField';
|
|
6
|
+
export * from './BooleanField';
|
|
7
|
+
export * from './GeoPointField';
|
|
8
|
+
export * from './MarkdownField';
|
|
9
|
+
export * from './NumberField';
|
|
10
|
+
export * from './RefField';
|
|
11
|
+
export * from './SelectField';
|
|
12
|
+
export * from './SelectOptionField';
|
|
13
|
+
export * from './TextField';
|
|
14
|
+
export * from './TextAreaField';
|
|
15
|
+
export * from './TupleField';
|
|
@@ -10,10 +10,11 @@ import { expect, fn, userEvent, within } from 'storybook/test';
|
|
|
10
10
|
import { Obj } from '@dxos/echo';
|
|
11
11
|
import { faker } from '@dxos/random';
|
|
12
12
|
import { Filter, useQuery } from '@dxos/react-client/echo';
|
|
13
|
-
import {
|
|
13
|
+
import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
|
|
14
14
|
import { Button } from '@dxos/react-ui';
|
|
15
15
|
import { withTheme } from '@dxos/react-ui/testing';
|
|
16
16
|
import { Person } from '@dxos/types';
|
|
17
|
+
import { osTranslations } from '@dxos/ui-theme';
|
|
17
18
|
|
|
18
19
|
import { translations } from '../../translations';
|
|
19
20
|
|
|
@@ -34,7 +35,7 @@ const mockHandleSelect = fn();
|
|
|
34
35
|
const mockHandleCreate = fn();
|
|
35
36
|
|
|
36
37
|
const DefaultStory = () => {
|
|
37
|
-
const { space } =
|
|
38
|
+
const { space } = useClientStory();
|
|
38
39
|
const [isOpen, setIsOpen] = useState(false);
|
|
39
40
|
|
|
40
41
|
// Create sample Person objects
|
|
@@ -48,7 +49,7 @@ const DefaultStory = () => {
|
|
|
48
49
|
}, [space]);
|
|
49
50
|
|
|
50
51
|
// Get all objects in the space (similar to BoardContainer)
|
|
51
|
-
const allObjects = useQuery(space, Filter.everything());
|
|
52
|
+
const allObjects = useQuery(space?.db, Filter.everything());
|
|
52
53
|
|
|
53
54
|
// Map objects to options format expected by ObjectPicker
|
|
54
55
|
const options = useMemo(
|
|
@@ -73,22 +74,22 @@ const DefaultStory = () => {
|
|
|
73
74
|
);
|
|
74
75
|
|
|
75
76
|
return (
|
|
76
|
-
<div className='
|
|
77
|
+
<div role='none' className='flex is-96'>
|
|
77
78
|
<ObjectPicker.Root open={isOpen} onOpenChange={setIsOpen}>
|
|
78
79
|
<ObjectPicker.Trigger asChild>
|
|
79
|
-
<Button variant='primary' data-testid='trigger'>
|
|
80
|
+
<Button variant='primary' data-testid='trigger' classNames='is-full'>
|
|
80
81
|
Select Person
|
|
81
82
|
</Button>
|
|
82
83
|
</ObjectPicker.Trigger>
|
|
83
84
|
<ObjectPicker.Content
|
|
85
|
+
classNames='popover-card-width'
|
|
84
86
|
options={options}
|
|
85
|
-
onSelect={mockHandleSelect}
|
|
86
87
|
createSchema={personSchema}
|
|
87
|
-
createOptionLabel={['create new person label', { ns:
|
|
88
|
+
createOptionLabel={['create new person label', { ns: osTranslations }]}
|
|
88
89
|
createOptionIcon='ph--user-plus--regular'
|
|
89
90
|
createInitialValuePath='fullName'
|
|
90
91
|
onCreate={handleCreateCallback}
|
|
91
|
-
|
|
92
|
+
onSelect={mockHandleSelect}
|
|
92
93
|
/>
|
|
93
94
|
</ObjectPicker.Root>
|
|
94
95
|
</div>
|
|
@@ -202,6 +203,12 @@ export const WithTest: Story = {
|
|
|
202
203
|
// Check that clicking the save button calls onCreate with expected values.
|
|
203
204
|
await expect(mockHandleCreate).toHaveBeenCalledWith({
|
|
204
205
|
fullName: unrelatedTerm,
|
|
206
|
+
addresses: [],
|
|
207
|
+
phoneNumbers: [],
|
|
208
|
+
emails: [],
|
|
209
|
+
identities: [],
|
|
210
|
+
urls: [],
|
|
211
|
+
fields: [],
|
|
205
212
|
});
|
|
206
213
|
},
|
|
207
214
|
};
|
|
@@ -3,26 +3,57 @@
|
|
|
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-searchlist';
|
|
8
|
+
import { type Palette, Popover, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
import { Combobox, useSearchListInput, useSearchListResults } from '@dxos/react-ui-searchlist';
|
|
10
10
|
|
|
11
|
-
import { type QueryTag } from '../../hooks';
|
|
12
11
|
import { translationKey } from '../../translations';
|
|
13
12
|
import { Form } from '../Form';
|
|
14
13
|
|
|
14
|
+
export type RefOption = {
|
|
15
|
+
id: string;
|
|
16
|
+
label: string;
|
|
17
|
+
hue?: Palette;
|
|
18
|
+
};
|
|
19
|
+
|
|
15
20
|
export type ObjectPickerContentProps = ThemedClassName<{
|
|
16
|
-
options:
|
|
21
|
+
options: RefOption[];
|
|
17
22
|
selectedIds?: string[];
|
|
18
|
-
createSchema?: Schema.Schema.AnyNoContext;
|
|
19
23
|
createOptionLabel?: [string, { ns: string }];
|
|
20
24
|
createOptionIcon?: string;
|
|
25
|
+
createSchema?: Schema.Schema.AnyNoContext;
|
|
21
26
|
createInitialValuePath?: string;
|
|
22
|
-
onSelect: (id: string) => void;
|
|
23
27
|
onCreate?: (values: any) => void;
|
|
28
|
+
onSelect: (id: string) => void;
|
|
24
29
|
}>;
|
|
25
30
|
|
|
31
|
+
const CreateItem = ({
|
|
32
|
+
createOptionLabel,
|
|
33
|
+
createOptionIcon,
|
|
34
|
+
onCreateItemSelect,
|
|
35
|
+
}: {
|
|
36
|
+
createOptionLabel: [string, { ns: string }];
|
|
37
|
+
createOptionIcon: string;
|
|
38
|
+
onCreateItemSelect: (query: string) => void;
|
|
39
|
+
}) => {
|
|
40
|
+
const { t } = useTranslation(translationKey);
|
|
41
|
+
const { query } = useSearchListInput();
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Combobox.Item
|
|
45
|
+
value='__create__'
|
|
46
|
+
label={t(createOptionLabel[0], { ns: createOptionLabel[1].ns, text: query })}
|
|
47
|
+
icon={createOptionIcon}
|
|
48
|
+
classNames='flex items-center gap-2'
|
|
49
|
+
closeOnSelect={false}
|
|
50
|
+
onSelect={() => {
|
|
51
|
+
onCreateItemSelect(query);
|
|
52
|
+
}}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
26
57
|
const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>(
|
|
27
58
|
(
|
|
28
59
|
{
|
|
@@ -39,39 +70,27 @@ const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>
|
|
|
39
70
|
ref,
|
|
40
71
|
) => {
|
|
41
72
|
const { t } = useTranslation(translationKey);
|
|
42
|
-
|
|
43
|
-
const [searchString, setSearchString] = useState('');
|
|
44
73
|
const [showForm, setShowForm] = useState(false);
|
|
74
|
+
const [formInitialValue, setFormInitialValue] = useState<string>('');
|
|
75
|
+
|
|
76
|
+
const { results, handleSearch } = useSearchListResults({
|
|
77
|
+
items: options,
|
|
78
|
+
});
|
|
45
79
|
|
|
46
80
|
const handleFormSave = useCallback(
|
|
47
81
|
(values: any) => {
|
|
48
82
|
onCreate?.(values);
|
|
49
83
|
setShowForm(false);
|
|
50
|
-
|
|
84
|
+
setFormInitialValue('');
|
|
51
85
|
},
|
|
52
86
|
[onCreate],
|
|
53
87
|
);
|
|
54
88
|
|
|
55
89
|
const handleFormCancel = useCallback(() => {
|
|
56
90
|
setShowForm(false);
|
|
57
|
-
|
|
91
|
+
setFormInitialValue('');
|
|
58
92
|
}, []);
|
|
59
93
|
|
|
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
94
|
const handleKeyDown = useCallback(
|
|
76
95
|
(event: KeyboardEvent) => {
|
|
77
96
|
if (
|
|
@@ -83,64 +102,58 @@ const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>
|
|
|
83
102
|
) {
|
|
84
103
|
event.stopPropagation();
|
|
85
104
|
event.preventDefault();
|
|
105
|
+
// Get the current query from the input element
|
|
106
|
+
const input = (event.currentTarget as HTMLElement).querySelector('input[type="text"]') as HTMLInputElement;
|
|
107
|
+
const currentQuery = input?.value || '';
|
|
108
|
+
setFormInitialValue(currentQuery);
|
|
86
109
|
setShowForm(true);
|
|
87
110
|
}
|
|
88
111
|
},
|
|
89
112
|
[createSchema],
|
|
90
113
|
);
|
|
91
114
|
|
|
92
|
-
const labelById = useMemo(
|
|
93
|
-
() =>
|
|
94
|
-
options.reduce((acc: Record<string, string>, option) => {
|
|
95
|
-
acc[option.id.toLowerCase()] = option.label.toLowerCase();
|
|
96
|
-
return acc;
|
|
97
|
-
}, {}),
|
|
98
|
-
[options],
|
|
99
|
-
);
|
|
100
|
-
|
|
101
115
|
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
|
-
>
|
|
116
|
+
<Combobox.Content {...props} ref={ref} onSearch={handleSearch} onKeyDownCapture={handleKeyDown}>
|
|
109
117
|
{showForm && createSchema ? (
|
|
110
118
|
<Popover.Viewport>
|
|
111
|
-
<Form
|
|
119
|
+
<Form.Root
|
|
120
|
+
testId='create-referenced-object-form'
|
|
112
121
|
schema={createSchema}
|
|
113
|
-
values={createInitialValuePath ? { [createInitialValuePath]:
|
|
122
|
+
values={createInitialValuePath ? { [createInitialValuePath]: formInitialValue } : {}}
|
|
114
123
|
onSave={handleFormSave}
|
|
115
124
|
onCancel={handleFormCancel}
|
|
116
|
-
|
|
117
|
-
|
|
125
|
+
>
|
|
126
|
+
<Form.Viewport>
|
|
127
|
+
<Form.Content>
|
|
128
|
+
<Form.FieldSet />
|
|
129
|
+
<Form.Actions />
|
|
130
|
+
</Form.Content>
|
|
131
|
+
</Form.Viewport>
|
|
132
|
+
</Form.Root>
|
|
118
133
|
</Popover.Viewport>
|
|
119
134
|
) : (
|
|
120
135
|
<>
|
|
121
|
-
<Combobox.Input
|
|
122
|
-
placeholder={t('ref field combobox input placeholder')}
|
|
123
|
-
value={searchString}
|
|
124
|
-
onValueChange={setSearchString}
|
|
125
|
-
autoFocus
|
|
126
|
-
/>
|
|
136
|
+
<Combobox.Input placeholder={t('ref field combobox input placeholder')} autoFocus />
|
|
127
137
|
<Combobox.List>
|
|
128
|
-
{
|
|
138
|
+
{results.map((option) => (
|
|
129
139
|
<Combobox.Item
|
|
130
140
|
key={option.id}
|
|
131
141
|
value={option.id}
|
|
142
|
+
label={option.label}
|
|
143
|
+
checked={selectedIds?.includes(option.id)}
|
|
132
144
|
onSelect={() => onSelect(option.id)}
|
|
133
145
|
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>
|
|
146
|
+
/>
|
|
138
147
|
))}
|
|
139
|
-
{
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
{
|
|
143
|
-
|
|
148
|
+
{createOptionLabel && createOptionIcon && createSchema && onCreate && (
|
|
149
|
+
<CreateItem
|
|
150
|
+
createOptionLabel={createOptionLabel}
|
|
151
|
+
createOptionIcon={createOptionIcon}
|
|
152
|
+
onCreateItemSelect={(query: string) => {
|
|
153
|
+
setFormInitialValue(query);
|
|
154
|
+
setShowForm(true);
|
|
155
|
+
}}
|
|
156
|
+
/>
|
|
144
157
|
)}
|
|
145
158
|
</Combobox.List>
|
|
146
159
|
</>
|
|
@@ -155,6 +168,7 @@ ObjectPickerContent.displayName = 'ObjectPickerContent';
|
|
|
155
168
|
|
|
156
169
|
export const ObjectPicker = {
|
|
157
170
|
Root: Combobox.Root,
|
|
171
|
+
Portal: Combobox.Portal,
|
|
158
172
|
Trigger: Combobox.Trigger,
|
|
159
173
|
VirtualTrigger: Combobox.VirtualTrigger,
|
|
160
174
|
Content: ObjectPickerContent,
|