@dxos/react-ui-form 0.8.4-main.3a94e84 → 0.8.4-main.3c1ae3b
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 +2138 -1738
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2138 -1738
- 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 +84 -5
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +5 -5
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +82 -5
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form-old.stories.d.ts +172 -0
- package/dist/types/src/components/Form/Form-old.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/Form.d.ts +78 -25
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +123 -51
- 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 +18 -0
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectField.d.ts +10 -0
- package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +5 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +82 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TextField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TupleField.d.ts +6 -0
- package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/index.d.ts +12 -0
- package/dist/types/src/components/Form/fields/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/index.d.ts +2 -4
- package/dist/types/src/components/Form/index.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +41 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +85 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
- package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +21 -9
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +83 -8
- 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 +4 -3
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +3 -2
- 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/hooks/useQueryRefOptions.d.ts +13 -6
- package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +1 -1
- package/dist/types/src/hooks/useRefQueryOptions.d.ts +8 -0
- package/dist/types/src/hooks/useRefQueryOptions.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +17 -8
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/field.d.ts +3 -3
- package/dist/types/src/util/field.d.ts.map +1 -1
- package/dist/types/src/util/formatting.d.ts +2 -2
- package/dist/types/src/util/formatting.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/properties.d.ts +7 -0
- package/dist/types/src/util/properties.d.ts.map +1 -0
- package/dist/types/src/util/refs.d.ts +2 -3
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +43 -36
- package/src/components/ControlSection/ControlSection.stories.tsx +11 -14
- package/src/components/ControlSection/ControlSection.tsx +26 -12
- package/src/components/FieldEditor/FieldEditor.stories.tsx +20 -20
- package/src/components/FieldEditor/FieldEditor.test.tsx +3 -2
- package/src/components/FieldEditor/FieldEditor.tsx +75 -77
- package/src/components/Form/Form-old.stories.tsx +314 -0
- package/src/components/Form/Form.stories.tsx +163 -219
- package/src/components/Form/Form.tsx +306 -98
- package/src/components/Form/FormField.tsx +280 -0
- package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
- package/src/components/Form/FormFieldComponent.tsx +173 -0
- package/src/components/Form/FormFieldSet.tsx +124 -0
- package/src/components/Form/README.md +3 -0
- package/src/components/Form/fields/ArrayField.tsx +154 -0
- package/src/components/Form/fields/BooleanField.tsx +22 -0
- package/src/components/Form/fields/GeoPointField.tsx +120 -0
- package/src/components/Form/fields/MarkdownField.tsx +78 -0
- package/src/components/Form/fields/NumberField.tsx +39 -0
- package/src/components/Form/fields/RefField.tsx +206 -0
- package/src/components/Form/fields/SelectField.tsx +65 -0
- package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +25 -22
- package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +47 -42
- 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 +207 -0
- package/src/components/ObjectPicker/ObjectPicker.tsx +166 -0
- package/src/components/ObjectPicker/index.ts +5 -0
- package/src/components/ViewEditor/ViewEditor.stories.tsx +131 -61
- package/src/components/ViewEditor/ViewEditor.test.tsx +19 -17
- package/src/components/ViewEditor/ViewEditor.tsx +277 -197
- package/src/components/index.ts +1 -2
- package/src/components/testing.tsx +20 -10
- package/src/hooks/index.ts +3 -2
- package/src/hooks/useFormHandler.ts +348 -0
- package/src/hooks/useKeyHandler.tsx +37 -0
- package/src/hooks/useQueryRefOptions.ts +49 -32
- package/src/hooks/useRefQueryOptions.ts +37 -0
- package/src/translations.ts +20 -11
- package/src/util/field.ts +33 -43
- 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 +12 -18
- 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 -57
- 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 -26
- package/dist/types/src/components/Form/Input.d.ts.map +0 -1
- package/dist/types/src/components/Form/RefField.d.ts +0 -12
- package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +0 -7
- 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/components/deprecated/Form.d.ts +0 -15
- package/dist/types/src/components/deprecated/Form.d.ts.map +0 -1
- package/dist/types/src/components/deprecated/Form.stories.d.ts +0 -13
- package/dist/types/src/components/deprecated/Form.stories.d.ts.map +0 -1
- package/dist/types/src/components/deprecated/FormInput.d.ts +0 -16
- package/dist/types/src/components/deprecated/FormInput.d.ts.map +0 -1
- package/dist/types/src/components/deprecated/index.d.ts +0 -2
- package/dist/types/src/components/deprecated/index.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/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 -124
- package/src/components/Form/Defaults.tsx +0 -203
- package/src/components/Form/FormActions.tsx +0 -53
- package/src/components/Form/FormContent.tsx +0 -240
- package/src/components/Form/FormContext.tsx +0 -110
- package/src/components/Form/Input.tsx +0 -51
- package/src/components/Form/RefField.tsx +0 -213
- package/src/components/Form/custom/GeoPointInput.tsx +0 -72
- package/src/components/Form/custom/index.ts +0 -7
- package/src/components/Form/factory.tsx +0 -31
- package/src/components/deprecated/Form.stories.tsx +0 -62
- package/src/components/deprecated/Form.tsx +0 -119
- package/src/components/deprecated/FormInput.tsx +0 -71
- package/src/components/deprecated/index.ts +0 -5
- package/src/hooks/useForm.ts +0 -291
- package/src/hooks/useRefQueryLookupHandler.ts +0 -39
- package/src/util/schema.ts +0 -14
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useEffect, useRef } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Input, type TextAreaProps } from '@dxos/react-ui';
|
|
8
|
+
|
|
9
|
+
import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
|
|
10
|
+
|
|
11
|
+
export const MarkdownField = ({
|
|
12
|
+
type,
|
|
13
|
+
readonly,
|
|
14
|
+
label,
|
|
15
|
+
placeholder,
|
|
16
|
+
layout,
|
|
17
|
+
getStatus,
|
|
18
|
+
getValue,
|
|
19
|
+
onValueChange,
|
|
20
|
+
onBlur,
|
|
21
|
+
}: FormFieldComponentProps) => {
|
|
22
|
+
const { status, error } = getStatus();
|
|
23
|
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
24
|
+
|
|
25
|
+
// TODO(ZaymonFC): If we start using `Enter` for form submission, we should prevent default behavior here
|
|
26
|
+
// since we're using a textarea element and the user needs to be able to enter new lines.
|
|
27
|
+
|
|
28
|
+
const adjustHeight = useCallback(() => {
|
|
29
|
+
const textarea = textareaRef.current;
|
|
30
|
+
if (textarea) {
|
|
31
|
+
textarea.style.blockSize = 'auto'; // For measurement.
|
|
32
|
+
textarea.style.blockSize = `${textarea.scrollHeight + 2}px`;
|
|
33
|
+
}
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
// Adjust height on initial render.
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
adjustHeight();
|
|
39
|
+
}, [adjustHeight]);
|
|
40
|
+
|
|
41
|
+
// Adjust height when the content changes.
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
adjustHeight();
|
|
44
|
+
}, [getValue(), adjustHeight]);
|
|
45
|
+
|
|
46
|
+
const handleChange = useCallback<NonNullable<TextAreaProps['onChange']>>(
|
|
47
|
+
(event) => onValueChange(type, event.target.value),
|
|
48
|
+
[type, onValueChange],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const value = getValue();
|
|
52
|
+
if ((readonly || layout === 'static') && !value) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// TODO(burdon): Use Markdown Editor.
|
|
57
|
+
return (
|
|
58
|
+
<Input.Root validationValence={status}>
|
|
59
|
+
{layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} />}
|
|
60
|
+
{layout === 'static' ? (
|
|
61
|
+
<p>{value}</p>
|
|
62
|
+
) : (
|
|
63
|
+
<Input.TextArea
|
|
64
|
+
ref={textareaRef}
|
|
65
|
+
disabled={!!readonly}
|
|
66
|
+
placeholder={placeholder}
|
|
67
|
+
value={value ?? ''}
|
|
68
|
+
classNames={'min-bs-auto max-bs-40 overflow-auto'}
|
|
69
|
+
onChange={handleChange}
|
|
70
|
+
onBlur={onBlur}
|
|
71
|
+
style={{ resize: 'none' }}
|
|
72
|
+
spellCheck={false}
|
|
73
|
+
/>
|
|
74
|
+
)}
|
|
75
|
+
{layout === 'full' && <Input.Validation>{error}</Input.Validation>}
|
|
76
|
+
</Input.Root>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
import { safeParseFloat } from '@dxos/util';
|
|
9
|
+
|
|
10
|
+
import { type FormFieldComponentProps, FormFieldWrapper } from '../FormFieldComponent';
|
|
11
|
+
|
|
12
|
+
export const NumberField = ({
|
|
13
|
+
type,
|
|
14
|
+
readonly,
|
|
15
|
+
placeholder,
|
|
16
|
+
onValueChange,
|
|
17
|
+
onBlur,
|
|
18
|
+
...props
|
|
19
|
+
}: FormFieldComponentProps<number>) => {
|
|
20
|
+
const handleChange = useCallback<NonNullable<TextInputProps['onChange']>>(
|
|
21
|
+
(event) => onValueChange(type, safeParseFloat(event.target.value) || 0),
|
|
22
|
+
[type, onValueChange],
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<FormFieldWrapper<number> readonly={readonly} {...props}>
|
|
27
|
+
{({ value = '' }) => (
|
|
28
|
+
<Input.TextInput
|
|
29
|
+
type='number'
|
|
30
|
+
disabled={!!readonly}
|
|
31
|
+
placeholder={placeholder}
|
|
32
|
+
value={value}
|
|
33
|
+
onChange={handleChange}
|
|
34
|
+
onBlur={onBlur}
|
|
35
|
+
/>
|
|
36
|
+
)}
|
|
37
|
+
</FormFieldWrapper>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxos/lit-ui/dx-tag-picker.pcss';
|
|
6
|
+
|
|
7
|
+
import type * as Schema from 'effect/Schema';
|
|
8
|
+
import React, { useCallback, useMemo } from 'react';
|
|
9
|
+
|
|
10
|
+
import { Ref } from '@dxos/echo';
|
|
11
|
+
import { ReferenceAnnotationId, type ReferenceAnnotationValue } from '@dxos/echo/internal';
|
|
12
|
+
import { findAnnotation } from '@dxos/effect';
|
|
13
|
+
import { DXN } from '@dxos/keys';
|
|
14
|
+
import { DxAnchor } from '@dxos/lit-ui/react';
|
|
15
|
+
import { Button, Icon, Input, useTranslation } from '@dxos/react-ui';
|
|
16
|
+
import { descriptionText, mx } from '@dxos/react-ui-theme';
|
|
17
|
+
import { isNonNullable } from '@dxos/util';
|
|
18
|
+
|
|
19
|
+
import { type QueryRefOptions, useQueryRefOptions } from '../../../hooks';
|
|
20
|
+
import { translationKey } from '../../../translations';
|
|
21
|
+
import { ObjectPicker } from '../../ObjectPicker';
|
|
22
|
+
import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
|
|
23
|
+
|
|
24
|
+
// TODO(burdon): Factor out.
|
|
25
|
+
const isRefSnapShot = (val: any): val is { '/': string } => {
|
|
26
|
+
return typeof val === 'object' && typeof (val as any)?.['/'] === 'string';
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type RefFieldProps = FormFieldComponentProps & {
|
|
30
|
+
// TODO(wittjosiah): Remove this. Array is handled upstream.
|
|
31
|
+
array?: boolean;
|
|
32
|
+
createOptionLabel?: [string, { ns: string }];
|
|
33
|
+
createOptionIcon?: string;
|
|
34
|
+
createSchema?: Schema.Schema.AnyNoContext;
|
|
35
|
+
createInitialValuePath?: string;
|
|
36
|
+
onCreate?: (values: any) => void;
|
|
37
|
+
onQueryRefOptions?: QueryRefOptions;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const RefField = (props: RefFieldProps) => {
|
|
41
|
+
const {
|
|
42
|
+
type,
|
|
43
|
+
readonly,
|
|
44
|
+
label,
|
|
45
|
+
placeholder,
|
|
46
|
+
layout,
|
|
47
|
+
getStatus,
|
|
48
|
+
getValue,
|
|
49
|
+
array,
|
|
50
|
+
createOptionLabel,
|
|
51
|
+
createOptionIcon,
|
|
52
|
+
createSchema,
|
|
53
|
+
createInitialValuePath,
|
|
54
|
+
onCreate,
|
|
55
|
+
onQueryRefOptions,
|
|
56
|
+
onValueChange,
|
|
57
|
+
} = props;
|
|
58
|
+
const { t } = useTranslation(translationKey);
|
|
59
|
+
const { status, error } = getStatus();
|
|
60
|
+
|
|
61
|
+
const typename = useMemo(
|
|
62
|
+
() => (type ? findAnnotation<ReferenceAnnotationValue>(type, ReferenceAnnotationId)?.typename : undefined),
|
|
63
|
+
[type],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// TODO(burdon): Query items on demand.
|
|
67
|
+
const { options, update: updateOptions } = useQueryRefOptions({ typename, onQueryRefOptions });
|
|
68
|
+
|
|
69
|
+
const handleGetValue = useCallback(() => {
|
|
70
|
+
const formValue = getValue();
|
|
71
|
+
|
|
72
|
+
const unknownToRefOption = (value: unknown) => {
|
|
73
|
+
const isRef = Ref.isRef(value);
|
|
74
|
+
if (isRef || isRefSnapShot(value)) {
|
|
75
|
+
const dxnString = isRef ? value.dxn.toString() : value['/'];
|
|
76
|
+
const matchingOption = options.find((option) => option.id === dxnString);
|
|
77
|
+
if (matchingOption) {
|
|
78
|
+
return matchingOption;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (array && Array.isArray(formValue)) {
|
|
85
|
+
return formValue.map(unknownToRefOption).filter(isNonNullable) ?? [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const option = unknownToRefOption(formValue);
|
|
89
|
+
if (option) {
|
|
90
|
+
return [option];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return [];
|
|
94
|
+
}, [options, array, getValue]);
|
|
95
|
+
|
|
96
|
+
const handleUpdate = useCallback(
|
|
97
|
+
(ids: string[]) => {
|
|
98
|
+
if (ids.length === 0) {
|
|
99
|
+
onValueChange(type, undefined);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const refs = ids
|
|
104
|
+
.map((id) => {
|
|
105
|
+
const item = options.find((option) => option.id === id);
|
|
106
|
+
if (item) {
|
|
107
|
+
const dxn = DXN.parse(item.id);
|
|
108
|
+
return Ref.fromDXN(dxn);
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
})
|
|
112
|
+
.filter(isNonNullable);
|
|
113
|
+
|
|
114
|
+
if (array) {
|
|
115
|
+
onValueChange(type, refs);
|
|
116
|
+
} else {
|
|
117
|
+
onValueChange(type, refs[0]);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
[options, type, array, onValueChange],
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const handleCreate = useCallback(
|
|
124
|
+
(values: any) => {
|
|
125
|
+
onCreate?.(values);
|
|
126
|
+
updateOptions();
|
|
127
|
+
},
|
|
128
|
+
[onCreate, updateOptions],
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const items = handleGetValue();
|
|
132
|
+
const selectedIds = useMemo(() => items.map((i: any) => i.id), [items]);
|
|
133
|
+
const toggleSelect = useCallback(
|
|
134
|
+
(id: string) => {
|
|
135
|
+
if (array) {
|
|
136
|
+
const nextIds = selectedIds.includes(id) ? selectedIds.filter((x) => x !== id) : [...selectedIds, id];
|
|
137
|
+
handleUpdate(nextIds);
|
|
138
|
+
} else {
|
|
139
|
+
if (selectedIds[0] === id) {
|
|
140
|
+
handleUpdate([]);
|
|
141
|
+
} else {
|
|
142
|
+
handleUpdate([id]);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
[array, selectedIds, handleUpdate],
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (!typename || ((readonly || layout === 'static') && items.length < 1)) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<Input.Root validationValence={status}>
|
|
155
|
+
{layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} />}
|
|
156
|
+
<div>
|
|
157
|
+
{readonly ? (
|
|
158
|
+
items.length < 1 ? (
|
|
159
|
+
<p className={mx(descriptionText, 'mbe-2')}>{t('empty readonly ref field label')}</p>
|
|
160
|
+
) : (
|
|
161
|
+
items.map((item) => (
|
|
162
|
+
<DxAnchor key={item.id} refid={item.id} rootclassname='mie-1'>
|
|
163
|
+
{item.label}
|
|
164
|
+
</DxAnchor>
|
|
165
|
+
))
|
|
166
|
+
)
|
|
167
|
+
) : (
|
|
168
|
+
<ObjectPicker.Root>
|
|
169
|
+
<ObjectPicker.Trigger asChild classNames='p-0'>
|
|
170
|
+
{items?.length === 1 ? (
|
|
171
|
+
<div className='flex gap-2 is-full'>
|
|
172
|
+
{items?.map((item) => (
|
|
173
|
+
<Input.Root key={item.id}>
|
|
174
|
+
<Input.TextInput value={item.label} readOnly classNames='is-full' />
|
|
175
|
+
</Input.Root>
|
|
176
|
+
))}
|
|
177
|
+
</div>
|
|
178
|
+
) : (
|
|
179
|
+
<Button classNames='is-full text-start gap-2'>
|
|
180
|
+
<div role='none' className='grow overflow-hidden'>
|
|
181
|
+
<span className='flex truncate text-description'>
|
|
182
|
+
{placeholder ?? t('ref field placeholder', { count: array ? 99 : 1 })}
|
|
183
|
+
</span>
|
|
184
|
+
</div>
|
|
185
|
+
<Icon size={3} icon='ph--caret-down--bold' />
|
|
186
|
+
</Button>
|
|
187
|
+
)}
|
|
188
|
+
</ObjectPicker.Trigger>
|
|
189
|
+
<ObjectPicker.Content
|
|
190
|
+
classNames='popover-card-width'
|
|
191
|
+
options={options}
|
|
192
|
+
selectedIds={selectedIds}
|
|
193
|
+
createSchema={createSchema}
|
|
194
|
+
createOptionLabel={createOptionLabel}
|
|
195
|
+
createOptionIcon={createOptionIcon}
|
|
196
|
+
createInitialValuePath={createInitialValuePath}
|
|
197
|
+
onCreate={handleCreate}
|
|
198
|
+
onSelect={toggleSelect}
|
|
199
|
+
/>
|
|
200
|
+
</ObjectPicker.Root>
|
|
201
|
+
)}
|
|
202
|
+
</div>
|
|
203
|
+
{layout === 'full' && <Input.DescriptionAndValidation>{error}</Input.DescriptionAndValidation>}
|
|
204
|
+
</Input.Root>
|
|
205
|
+
);
|
|
206
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Input, Select, type SelectRootProps } from '@dxos/react-ui';
|
|
8
|
+
|
|
9
|
+
import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
|
|
10
|
+
|
|
11
|
+
export type SelectFieldOptions = FormFieldComponentProps & {
|
|
12
|
+
options?: Array<{ value: string | number; label?: string }>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const SelectField = ({
|
|
16
|
+
type,
|
|
17
|
+
readonly,
|
|
18
|
+
label,
|
|
19
|
+
layout,
|
|
20
|
+
placeholder,
|
|
21
|
+
options,
|
|
22
|
+
getStatus,
|
|
23
|
+
getValue,
|
|
24
|
+
onValueChange,
|
|
25
|
+
}: SelectFieldOptions) => {
|
|
26
|
+
const { status, error } = getStatus();
|
|
27
|
+
const value = getValue() as string | undefined;
|
|
28
|
+
|
|
29
|
+
const handleValueChange = useCallback<NonNullable<SelectRootProps['onValueChange']>>(
|
|
30
|
+
(value) => onValueChange(type, value),
|
|
31
|
+
[type, onValueChange],
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if ((readonly || layout === 'static') && value == null) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Input.Root validationValence={status}>
|
|
40
|
+
{layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} />}
|
|
41
|
+
{layout === 'static' ? (
|
|
42
|
+
<p>{options?.find(({ value: optionValue }) => optionValue === value)?.label ?? String(value)}</p>
|
|
43
|
+
) : (
|
|
44
|
+
<Select.Root value={value} onValueChange={handleValueChange}>
|
|
45
|
+
{/* TODO(burdon): Placeholder not working? */}
|
|
46
|
+
<Select.TriggerButton classNames='is-full' disabled={!!readonly} placeholder={placeholder} />
|
|
47
|
+
<Select.Portal>
|
|
48
|
+
<Select.Content>
|
|
49
|
+
<Select.Viewport>
|
|
50
|
+
{options?.map(({ value, label }) => (
|
|
51
|
+
// NOTE: Numeric values are converted to and from strings.
|
|
52
|
+
<Select.Option key={String(value)} value={String(value)}>
|
|
53
|
+
{label ?? String(value)}
|
|
54
|
+
</Select.Option>
|
|
55
|
+
))}
|
|
56
|
+
</Select.Viewport>
|
|
57
|
+
<Select.Arrow />
|
|
58
|
+
</Select.Content>
|
|
59
|
+
</Select.Portal>
|
|
60
|
+
</Select.Root>
|
|
61
|
+
)}
|
|
62
|
+
{layout === 'full' && <Input.DescriptionAndValidation>{error}</Input.DescriptionAndValidation>}
|
|
63
|
+
</Input.Root>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
@@ -2,22 +2,21 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
|
-
import
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
9
7
|
import React, { useState } from 'react';
|
|
10
8
|
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
9
|
+
import { SelectOption } from '@dxos/echo/internal';
|
|
10
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
13
11
|
|
|
14
|
-
import { SelectOptionInput } from './SelectOptionsInput';
|
|
15
12
|
import { translations } from '../../../translations';
|
|
16
|
-
import { TestLayout
|
|
13
|
+
import { TestLayout } from '../../testing';
|
|
17
14
|
import { Form } from '../Form';
|
|
18
15
|
|
|
16
|
+
import { SelectOptionField } from './SelectOptionField';
|
|
17
|
+
|
|
19
18
|
const schema = Schema.Struct({
|
|
20
|
-
options: Schema.Array(
|
|
19
|
+
options: Schema.Array(SelectOption).pipe(Schema.mutable),
|
|
21
20
|
}).pipe(Schema.mutable);
|
|
22
21
|
|
|
23
22
|
const DefaultStory = () => {
|
|
@@ -32,30 +31,34 @@ const DefaultStory = () => {
|
|
|
32
31
|
|
|
33
32
|
return (
|
|
34
33
|
<TestLayout>
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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>
|
|
43
45
|
</TestLayout>
|
|
44
46
|
);
|
|
45
47
|
};
|
|
46
48
|
|
|
47
|
-
const meta
|
|
48
|
-
title: 'ui/react-ui-form/
|
|
49
|
-
component:
|
|
49
|
+
const meta = {
|
|
50
|
+
title: 'ui/react-ui-form/SelectOptionField',
|
|
51
|
+
component: SelectOptionField as any,
|
|
50
52
|
render: DefaultStory,
|
|
51
|
-
decorators: [withTheme
|
|
53
|
+
decorators: [withTheme],
|
|
52
54
|
parameters: {
|
|
55
|
+
layout: 'fullscreen',
|
|
53
56
|
translations,
|
|
54
57
|
},
|
|
55
|
-
}
|
|
58
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
56
59
|
|
|
57
60
|
export default meta;
|
|
58
61
|
|
|
59
|
-
type Story = StoryObj
|
|
62
|
+
type Story = StoryObj<typeof meta>;
|
|
60
63
|
|
|
61
64
|
export const Default: Story = {};
|
|
@@ -2,33 +2,39 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { type ChangeEvent, type KeyboardEvent, useCallback, useEffect,
|
|
5
|
+
import React, { type ChangeEvent, type KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type SelectOption } from '@dxos/echo
|
|
7
|
+
import { type SelectOption } from '@dxos/echo/internal';
|
|
8
8
|
import { PublicKey } from '@dxos/keys';
|
|
9
|
-
import { type ChromaticPalette,
|
|
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,
|
|
12
|
+
import { hues, subtleHover } from '@dxos/react-ui-theme';
|
|
13
13
|
|
|
14
14
|
import { translationKey } from '../../../translations';
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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();
|
|
19
27
|
const [selected, setSelectedId] = useState<string | null>(null);
|
|
20
28
|
const [isNewOption, setIsNewOption] = useState(false);
|
|
21
29
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
22
|
-
const
|
|
23
|
-
const { status, error } = getStatus();
|
|
24
|
-
const options = getValue<SelectOption[] | undefined>();
|
|
30
|
+
const options = getValue();
|
|
25
31
|
|
|
26
32
|
// Initialization.
|
|
27
33
|
useEffect(() => {
|
|
28
34
|
if (options === undefined) {
|
|
29
35
|
onValueChange(type, []);
|
|
30
36
|
}
|
|
31
|
-
}, [options,
|
|
37
|
+
}, [options, type, onValueChange]);
|
|
32
38
|
|
|
33
39
|
const randomHue = () => {
|
|
34
40
|
const usedHues = new Set(options?.map((option) => option.color) ?? []);
|
|
@@ -89,8 +95,8 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
|
|
|
89
95
|
[options, type, onValueChange],
|
|
90
96
|
);
|
|
91
97
|
|
|
92
|
-
const handleKeyDown = useCallback((
|
|
93
|
-
if (
|
|
98
|
+
const handleKeyDown = useCallback((ev: KeyboardEvent<HTMLInputElement>) => {
|
|
99
|
+
if (ev.key === 'Enter') {
|
|
94
100
|
setSelectedId(null);
|
|
95
101
|
}
|
|
96
102
|
}, []);
|
|
@@ -112,53 +118,56 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
|
|
|
112
118
|
|
|
113
119
|
return (
|
|
114
120
|
<Input.Root validationValence={status}>
|
|
115
|
-
<
|
|
121
|
+
<FormFieldLabel error={error} readonly={readonly} label={label} />
|
|
116
122
|
<div role='none'>
|
|
117
123
|
{options && (
|
|
118
|
-
<List.Root
|
|
124
|
+
<List.Root
|
|
125
|
+
items={options}
|
|
126
|
+
isItem={(_item) => true}
|
|
127
|
+
onMove={readonly ? undefined : handleMove}
|
|
128
|
+
readonly={!!readonly}
|
|
129
|
+
>
|
|
119
130
|
{({ items }) => (
|
|
120
|
-
<div role='list' className='
|
|
131
|
+
<div role='list' className='is-full overflow-auto'>
|
|
121
132
|
{items?.map((item) => (
|
|
122
133
|
<div key={item.id}>
|
|
123
134
|
<List.Item
|
|
124
135
|
role='button'
|
|
125
136
|
item={item}
|
|
126
|
-
classNames={[
|
|
137
|
+
classNames={[subtleHover, 'flex flex-col cursor-pointer rounded-sm']}
|
|
127
138
|
aria-expanded={selected === item.id}
|
|
128
139
|
>
|
|
129
140
|
<div className='flex items-center'>
|
|
130
|
-
<List.ItemDragHandle />
|
|
141
|
+
<List.ItemDragHandle disabled={!!readonly} />
|
|
131
142
|
<List.ItemTitle onClick={() => handleClick(item.id)} classNames='flex-1'>
|
|
132
143
|
{/* TODO(ZaymonFC): Move spacer into Tag component. */}
|
|
133
144
|
<Tag palette={item.color as ChromaticPalette}>{item.title || '\u200b'}</Tag>
|
|
134
145
|
</List.ItemTitle>
|
|
135
|
-
<
|
|
146
|
+
<IconButton
|
|
147
|
+
iconOnly
|
|
148
|
+
variant='ghost'
|
|
149
|
+
label={t(selected === item.id ? 'collapse label' : 'expand label', { ns: 'os' })}
|
|
136
150
|
icon={selected === item.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
|
|
137
151
|
onClick={() => handleClick(item.id)}
|
|
138
152
|
/>
|
|
139
153
|
</div>
|
|
140
154
|
{selected === item.id && (
|
|
141
|
-
<div className='
|
|
155
|
+
<div className='pli-cardSpacingInline plb-cardSpacingBlock flex flex-col gap-1 density-fine'>
|
|
142
156
|
{/* 16px to match drag handle width. */}
|
|
143
157
|
<Input.Label classNames='text-xs'>{t('select option label')}</Input.Label>
|
|
144
|
-
<div className='
|
|
158
|
+
<div className='grid grid-cols-[1fr_min-content_min-content] gap-1'>
|
|
145
159
|
<Input.TextInput
|
|
160
|
+
disabled={!!readonly}
|
|
146
161
|
placeholder={t('select option label placeholder')}
|
|
147
162
|
ref={selected === item.id ? inputRef : undefined}
|
|
148
163
|
value={item.title}
|
|
149
164
|
onChange={handleTitleChange(item.id)}
|
|
150
165
|
onKeyDown={handleKeyDown}
|
|
151
166
|
classNames='flex-1'
|
|
152
|
-
data-no-submit
|
|
153
167
|
/>
|
|
154
|
-
<
|
|
155
|
-
<HuePicker
|
|
156
|
-
value={item.color}
|
|
157
|
-
onChange={handleColorChange(item.id)}
|
|
158
|
-
rootVariant='toolbar-button'
|
|
159
|
-
/>
|
|
160
|
-
</Toolbar.Root>
|
|
168
|
+
<HuePicker disabled={!!readonly} value={item.color} onChange={handleColorChange(item.id)} />
|
|
161
169
|
<IconButton
|
|
170
|
+
disabled={!!readonly}
|
|
162
171
|
icon='ph--trash--fill'
|
|
163
172
|
iconOnly
|
|
164
173
|
label={t('select option delete')}
|
|
@@ -170,18 +179,14 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
|
|
|
170
179
|
</List.Item>
|
|
171
180
|
</div>
|
|
172
181
|
))}
|
|
173
|
-
<
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
<span className='text-sm'>{t('select option add')}</span>
|
|
182
|
-
</div>
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
182
|
+
<IconButton
|
|
183
|
+
variant='ghost'
|
|
184
|
+
icon='ph--plus--regular'
|
|
185
|
+
label={t('select option add')}
|
|
186
|
+
onClick={handleAdd}
|
|
187
|
+
disabled={!!readonly}
|
|
188
|
+
classNames='is-full'
|
|
189
|
+
/>
|
|
185
190
|
</div>
|
|
186
191
|
)}
|
|
187
192
|
</List.Root>
|
|
@@ -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
|
+
};
|