@dxos/react-ui-form 0.8.4-main.72ec0f3 → 0.8.4-main.8360d9e660
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 +2287 -2298
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2287 -2298
- 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 -6
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- 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 +59 -83
- 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 +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/{ControlSection/ControlSection.stories.d.ts → Form/fields/SelectOptionField.stories.d.ts} +12 -13
- 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 +9 -5
- 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 -7
- 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 +12 -7
- 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 -6
- 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 -47
- 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 -110
- package/src/components/Form/FormField.tsx +294 -0
- package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
- package/src/components/Form/FormFieldComponent.tsx +171 -0
- package/src/components/Form/FormFieldSet.tsx +127 -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 +194 -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} +24 -18
- 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 -12
- package/src/components/ObjectPicker/ObjectPicker.tsx +76 -62
- 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 +45 -42
- package/src/components/ViewEditor/ViewEditor.test.tsx +5 -4
- package/src/components/ViewEditor/ViewEditor.tsx +297 -210
- 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 -9
- 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 +7 -13
- 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 -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/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
|
@@ -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,23 +118,23 @@ 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
|
>
|
|
123
130
|
{({ items }) => (
|
|
124
|
-
<div role='list' className='
|
|
131
|
+
<div role='list' className='w-full overflow-auto'>
|
|
125
132
|
{items?.map((item) => (
|
|
126
133
|
<div key={item.id}>
|
|
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';
|
|
@@ -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 w-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='w-full'>
|
|
80
81
|
Select Person
|
|
81
82
|
</Button>
|
|
82
83
|
</ObjectPicker.Trigger>
|
|
83
84
|
<ObjectPicker.Content
|
|
85
|
+
classNames='dx-card-popover-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>
|
|
@@ -99,7 +100,7 @@ const meta = {
|
|
|
99
100
|
title: 'ui/react-ui-form/ObjectPicker',
|
|
100
101
|
render: DefaultStory,
|
|
101
102
|
decorators: [
|
|
102
|
-
withTheme,
|
|
103
|
+
withTheme(),
|
|
103
104
|
withClientProvider({
|
|
104
105
|
types: [Person.Person],
|
|
105
106
|
createIdentity: true,
|
|
@@ -200,8 +201,10 @@ export const WithTest: Story = {
|
|
|
200
201
|
await userEvent.click(saveButton);
|
|
201
202
|
|
|
202
203
|
// Check that clicking the save button calls onCreate with expected values.
|
|
203
|
-
await expect(mockHandleCreate).toHaveBeenCalledWith(
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
await expect(mockHandleCreate).toHaveBeenCalledWith(
|
|
205
|
+
expect.objectContaining({
|
|
206
|
+
fullName: unrelatedTerm,
|
|
207
|
+
}),
|
|
208
|
+
);
|
|
206
209
|
},
|
|
207
210
|
};
|
|
@@ -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,
|