@dxos/react-ui-form 0.8.4-main.c4373fc → 0.8.4-main.c85a9c8dae
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 +2289 -2269
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2289 -2269
- 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 +13 -7
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +81 -26
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +59 -84
- 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/{ControlSection/ControlSection.stories.d.ts → Form/fields/SelectOptionField.stories.d.ts} +12 -14
- 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 +15 -10
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +10 -6
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.d.ts +56 -0
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
- package/dist/types/src/components/{Form/custom/SelectOptionsInput.stories.d.ts → Settings/Settings.stories.d.ts} +10 -8
- 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 +16 -16
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +13 -9
- 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 +6 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +10 -7
- 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 +1 -2
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +50 -46
- package/src/components/FieldEditor/FieldEditor.stories.tsx +43 -24
- package/src/components/FieldEditor/FieldEditor.tsx +74 -78
- package/src/components/Form/Form.stories.tsx +149 -248
- package/src/components/Form/Form.tsx +347 -107
- package/src/components/Form/FormField.tsx +294 -0
- package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
- package/src/components/Form/FormFieldComponent.tsx +172 -0
- package/src/components/Form/FormFieldSet.tsx +123 -0
- package/src/components/Form/README.md +3 -0
- package/src/components/Form/fields/ArrayField.tsx +149 -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 +189 -0
- package/src/components/Form/fields/SelectField.tsx +65 -0
- package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +19 -16
- package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +23 -17
- 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 +40 -37
- package/src/components/ObjectPicker/ObjectPicker.tsx +84 -70
- package/src/components/Settings/Settings.stories.tsx +56 -0
- package/src/components/Settings/Settings.tsx +230 -0
- package/src/components/{ControlSection → Settings}/index.ts +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +69 -49
- package/src/components/ViewEditor/ViewEditor.test.tsx +8 -6
- package/src/components/ViewEditor/ViewEditor.tsx +302 -271
- package/src/components/index.ts +1 -1
- package/src/components/testing.tsx +12 -7
- 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 +12 -10
- 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 +11 -17
- 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.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 -21
- 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 -28
- 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 -322
- package/src/components/Form/FormContext.tsx +0 -110
- package/src/components/Form/Input.tsx +0 -54
- package/src/components/Form/RefField.tsx +0 -269
- 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 -295
- package/src/hooks/useQueryRefOptions.ts +0 -59
- 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 {
|
|
10
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
9
|
+
import { SelectOption } from '@dxos/echo/internal';
|
|
10
|
+
import { withLayout, 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,23 +31,26 @@ 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
|
-
decorators: [withTheme],
|
|
53
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
51
54
|
parameters: {
|
|
52
55
|
layout: 'fullscreen',
|
|
53
56
|
translations,
|
|
@@ -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
|
>
|
|
@@ -127,7 +134,7 @@ export const SelectOptionInput = ({ type, label, readonly, getStatus, getValue,
|
|
|
127
134
|
<List.Item
|
|
128
135
|
role='button'
|
|
129
136
|
item={item}
|
|
130
|
-
classNames={[subtleHover, 'flex flex-col cursor-pointer rounded-
|
|
137
|
+
classNames={[subtleHover, 'flex flex-col cursor-pointer rounded-xs']}
|
|
131
138
|
aria-expanded={selected === item.id}
|
|
132
139
|
>
|
|
133
140
|
<div className='flex items-center'>
|
|
@@ -139,13 +146,13 @@ 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
|
/>
|
|
146
153
|
</div>
|
|
147
154
|
{selected === item.id && (
|
|
148
|
-
<div className='
|
|
155
|
+
<div className='flex flex-col p-form-padding gap-1 dx-density-fine'>
|
|
149
156
|
{/* 16px to match drag handle width. */}
|
|
150
157
|
<Input.Label classNames='text-xs'>{t('select option label')}</Input.Label>
|
|
151
158
|
<div className='grid grid-cols-[1fr_min-content_min-content] gap-1'>
|
|
@@ -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
|
|
@@ -179,7 +185,7 @@ export const SelectOptionInput = ({ type, label, readonly, getStatus, getValue,
|
|
|
179
185
|
label={t('select option add')}
|
|
180
186
|
onClick={handleAdd}
|
|
181
187
|
disabled={!!readonly}
|
|
182
|
-
classNames='
|
|
188
|
+
classNames='w-full'
|
|
183
189
|
/>
|
|
184
190
|
</div>
|
|
185
191
|
)}
|
|
@@ -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';
|
|
@@ -9,12 +9,12 @@ import { expect, fn, userEvent, within } from 'storybook/test';
|
|
|
9
9
|
|
|
10
10
|
import { Obj } from '@dxos/echo';
|
|
11
11
|
import { faker } from '@dxos/random';
|
|
12
|
-
import { useClient } from '@dxos/react-client';
|
|
13
12
|
import { Filter, useQuery } from '@dxos/react-client/echo';
|
|
14
|
-
import {
|
|
13
|
+
import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
|
|
15
14
|
import { Button } from '@dxos/react-ui';
|
|
16
15
|
import { withTheme } from '@dxos/react-ui/testing';
|
|
17
|
-
import {
|
|
16
|
+
import { Person } from '@dxos/types';
|
|
17
|
+
import { osTranslations } from '@dxos/ui-theme';
|
|
18
18
|
|
|
19
19
|
import { translations } from '../../translations';
|
|
20
20
|
|
|
@@ -23,20 +23,19 @@ import { ObjectPicker } from './ObjectPicker';
|
|
|
23
23
|
faker.seed(1);
|
|
24
24
|
|
|
25
25
|
const createPerson = () =>
|
|
26
|
-
Obj.make(
|
|
26
|
+
Obj.make(Person.Person, {
|
|
27
27
|
fullName: faker.person.fullName(),
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
const omitId = Schema.omit<any, any, ['id']>('id');
|
|
31
|
-
const personSchema = omitId(
|
|
31
|
+
const personSchema = omitId(Person.Person);
|
|
32
32
|
|
|
33
33
|
// Mock functions for testing
|
|
34
34
|
const mockHandleSelect = fn();
|
|
35
35
|
const mockHandleCreate = fn();
|
|
36
36
|
|
|
37
37
|
const DefaultStory = () => {
|
|
38
|
-
const
|
|
39
|
-
const { space } = useClientProvider();
|
|
38
|
+
const { space } = useClientStory();
|
|
40
39
|
const [isOpen, setIsOpen] = useState(false);
|
|
41
40
|
|
|
42
41
|
// Create sample Person objects
|
|
@@ -50,7 +49,7 @@ const DefaultStory = () => {
|
|
|
50
49
|
}, [space]);
|
|
51
50
|
|
|
52
51
|
// Get all objects in the space (similar to BoardContainer)
|
|
53
|
-
const allObjects = useQuery(space, Filter.everything());
|
|
52
|
+
const allObjects = useQuery(space?.db, Filter.everything());
|
|
54
53
|
|
|
55
54
|
// Map objects to options format expected by ObjectPicker
|
|
56
55
|
const options = useMemo(
|
|
@@ -67,7 +66,7 @@ const DefaultStory = () => {
|
|
|
67
66
|
(values: any) => {
|
|
68
67
|
console.log('[on create]', values);
|
|
69
68
|
if (!space) return;
|
|
70
|
-
const newPerson = space.db.add(Obj.make(
|
|
69
|
+
const newPerson = space.db.add(Obj.make(Person.Person, values));
|
|
71
70
|
mockHandleCreate(values);
|
|
72
71
|
return newPerson;
|
|
73
72
|
},
|
|
@@ -75,22 +74,22 @@ const DefaultStory = () => {
|
|
|
75
74
|
);
|
|
76
75
|
|
|
77
76
|
return (
|
|
78
|
-
<div className='
|
|
77
|
+
<div role='none' className='flex w-96'>
|
|
79
78
|
<ObjectPicker.Root open={isOpen} onOpenChange={setIsOpen}>
|
|
80
79
|
<ObjectPicker.Trigger asChild>
|
|
81
|
-
<Button variant='primary' data-testid='trigger'>
|
|
80
|
+
<Button variant='primary' data-testid='trigger' classNames='w-full'>
|
|
82
81
|
Select Person
|
|
83
82
|
</Button>
|
|
84
83
|
</ObjectPicker.Trigger>
|
|
85
84
|
<ObjectPicker.Content
|
|
85
|
+
classNames='dx-card-popover-width'
|
|
86
86
|
options={options}
|
|
87
|
-
onSelect={mockHandleSelect}
|
|
88
87
|
createSchema={personSchema}
|
|
89
|
-
createOptionLabel={['create new person label', { ns:
|
|
88
|
+
createOptionLabel={['create new person label', { ns: osTranslations }]}
|
|
90
89
|
createOptionIcon='ph--user-plus--regular'
|
|
91
90
|
createInitialValuePath='fullName'
|
|
92
91
|
onCreate={handleCreateCallback}
|
|
93
|
-
|
|
92
|
+
onSelect={mockHandleSelect}
|
|
94
93
|
/>
|
|
95
94
|
</ObjectPicker.Root>
|
|
96
95
|
</div>
|
|
@@ -101,9 +100,9 @@ const meta = {
|
|
|
101
100
|
title: 'ui/react-ui-form/ObjectPicker',
|
|
102
101
|
render: DefaultStory,
|
|
103
102
|
decorators: [
|
|
104
|
-
withTheme,
|
|
103
|
+
withTheme(),
|
|
105
104
|
withClientProvider({
|
|
106
|
-
types: [
|
|
105
|
+
types: [Person.Person],
|
|
107
106
|
createIdentity: true,
|
|
108
107
|
createSpace: true,
|
|
109
108
|
}),
|
|
@@ -119,37 +118,39 @@ export default meta;
|
|
|
119
118
|
|
|
120
119
|
type Story = StoryObj<typeof meta>;
|
|
121
120
|
|
|
122
|
-
export const Default: Story = {
|
|
121
|
+
export const Default: Story = {};
|
|
122
|
+
|
|
123
|
+
export const WithTest: Story = {
|
|
123
124
|
play: async ({ canvasElement }) => {
|
|
124
125
|
const canvas = within(canvasElement);
|
|
125
126
|
const body = within(document.body);
|
|
126
127
|
|
|
127
|
-
// Find and click the trigger button
|
|
128
|
+
// Find and click the trigger button.
|
|
128
129
|
const triggerButton = await canvas.findByTestId('trigger', undefined, { timeout: 5000 });
|
|
129
130
|
await expect(triggerButton).toBeVisible();
|
|
130
131
|
await userEvent.click(triggerButton);
|
|
131
132
|
|
|
132
|
-
// Check that the listbox renders when the trigger is clicked
|
|
133
|
+
// Check that the listbox renders when the trigger is clicked.
|
|
133
134
|
const listbox = await body.findByRole('listbox', undefined, { timeout: 5000 });
|
|
134
135
|
await expect(listbox).toBeVisible();
|
|
135
136
|
|
|
136
|
-
// Find the search input
|
|
137
|
+
// Find the search input.
|
|
137
138
|
const searchInput = await body.findByPlaceholderText('Search…');
|
|
138
139
|
await expect(searchInput).toBeVisible();
|
|
139
140
|
|
|
140
|
-
// Test searching filters the results - get initial options count
|
|
141
|
+
// Test searching filters the results - get initial options count.
|
|
141
142
|
const initialOptions = await body.findAllByRole('option');
|
|
142
143
|
const initialCount = initialOptions.length;
|
|
143
144
|
await expect(initialCount).toBeGreaterThan(0);
|
|
144
145
|
|
|
145
|
-
// Type a search term that should filter results
|
|
146
|
+
// Type a search term that should filter results.
|
|
146
147
|
await userEvent.type(searchInput, 'John');
|
|
147
148
|
|
|
148
|
-
// Wait a moment for filtering
|
|
149
|
+
// Wait a moment for filtering.
|
|
149
150
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
150
151
|
|
|
151
152
|
// Check that results are filtered (should be fewer options)
|
|
152
|
-
const
|
|
153
|
+
const _filteredOptions = await body.findAllByRole('option');
|
|
153
154
|
// Note: We expect either fewer results or the same if no John exists
|
|
154
155
|
|
|
155
156
|
// Clear search and test selecting a result
|
|
@@ -160,48 +161,50 @@ export const Default: Story = {
|
|
|
160
161
|
const firstOption = updatedOptions[0];
|
|
161
162
|
await expect(firstOption).toBeVisible();
|
|
162
163
|
|
|
163
|
-
const
|
|
164
|
+
const _firstOptionText = firstOption.textContent;
|
|
164
165
|
await userEvent.click(firstOption);
|
|
165
166
|
|
|
166
167
|
// Check that clicking a result fires onSelect with that id
|
|
167
168
|
// The onSelect should have been called with the selected option's id
|
|
168
169
|
await expect(mockHandleSelect).toHaveBeenCalled();
|
|
169
170
|
|
|
170
|
-
// Re-open the picker to test create functionality
|
|
171
|
+
// Re-open the picker to test create functionality.
|
|
171
172
|
await userEvent.click(triggerButton);
|
|
172
173
|
|
|
173
|
-
// Wait for listbox to appear again
|
|
174
|
+
// Wait for listbox to appear again.
|
|
174
175
|
const newListbox = await body.findByRole('listbox', undefined, { timeout: 5000 });
|
|
175
176
|
await expect(newListbox).toBeVisible();
|
|
176
177
|
|
|
177
178
|
const newSearchInput = await body.findByPlaceholderText('Search…');
|
|
178
179
|
|
|
179
|
-
// Test searching an unrelated term provides the __create__ option
|
|
180
|
+
// Test searching an unrelated term provides the __create__ option.
|
|
180
181
|
const unrelatedTerm = 'ZxyUnrelatedName123';
|
|
181
182
|
await userEvent.type(newSearchInput, unrelatedTerm);
|
|
182
183
|
|
|
183
|
-
// Wait for the create option to appear
|
|
184
|
+
// Wait for the create option to appear.
|
|
184
185
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
185
186
|
|
|
186
|
-
// Look for the create option
|
|
187
|
+
// Look for the create option.
|
|
187
188
|
const createOption = await body.findByRole('option', undefined, { timeout: 3000 });
|
|
188
189
|
await expect(createOption).toBeVisible();
|
|
189
190
|
|
|
190
|
-
// Click the create option - this should change state to the form field
|
|
191
|
+
// Click the create option - this should change state to the form field.
|
|
191
192
|
await userEvent.click(createOption);
|
|
192
193
|
|
|
193
|
-
// Check that clicking that option changes state to the form field
|
|
194
|
+
// Check that clicking that option changes state to the form field.
|
|
194
195
|
const form = await body.findByTestId('create-referenced-object-form', undefined, { timeout: 5000 });
|
|
195
196
|
await expect(form).toBeVisible();
|
|
196
197
|
|
|
197
|
-
// Find and click the save button
|
|
198
|
+
// Find and click the save button.
|
|
198
199
|
const saveButton = await within(form).findByTestId('save-button');
|
|
199
200
|
await expect(saveButton).toBeVisible();
|
|
200
201
|
await userEvent.click(saveButton);
|
|
201
202
|
|
|
202
|
-
// Check that clicking the save button calls onCreate with expected values
|
|
203
|
-
await expect(mockHandleCreate).toHaveBeenCalledWith(
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
// Check that clicking the save button calls onCreate with expected values.
|
|
204
|
+
await expect(mockHandleCreate).toHaveBeenCalledWith(
|
|
205
|
+
expect.objectContaining({
|
|
206
|
+
fullName: unrelatedTerm,
|
|
207
|
+
}),
|
|
208
|
+
);
|
|
206
209
|
},
|
|
207
210
|
};
|