@dxos/react-ui-form 0.8.4-main.c1de068 → 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 +2314 -2078
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2314 -2078
- 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 +82 -5
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +81 -25
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +127 -55
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +37 -0
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +78 -0
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +9 -0
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldSet.d.ts +19 -0
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/ArrayField.d.ts +18 -0
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/BooleanField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts +5 -0
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/NumberField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/RefField.d.ts +14 -0
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectField.d.ts +10 -0
- package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +5 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -0
- package/dist/types/src/components/Form/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 +46 -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/Settings/Settings.d.ts +56 -0
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
- package/dist/types/src/components/Settings/Settings.stories.d.ts +81 -0
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
- package/dist/types/src/components/Settings/index.d.ts +2 -0
- package/dist/types/src/components/Settings/index.d.ts.map +1 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +22 -10
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +84 -8
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +2 -2
- 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 +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 +19 -10
- 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 +51 -42
- package/src/components/FieldEditor/FieldEditor.stories.tsx +51 -32
- package/src/components/FieldEditor/FieldEditor.test.tsx +3 -2
- package/src/components/FieldEditor/FieldEditor.tsx +87 -83
- package/src/components/Form/Form.stories.tsx +156 -219
- package/src/components/Form/Form.tsx +347 -96
- 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/fields/SelectOptionField.stories.tsx +64 -0
- package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +46 -41
- package/src/components/Form/fields/TextAreaField.tsx +38 -0
- package/src/components/Form/fields/TextField.tsx +38 -0
- package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +16 -9
- package/src/components/Form/fields/index.ts +15 -0
- package/src/components/Form/index.ts +3 -4
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +210 -0
- package/src/components/ObjectPicker/ObjectPicker.tsx +175 -0
- package/src/components/{ControlSection → ObjectPicker}/index.ts +1 -1
- package/src/components/Settings/Settings.stories.tsx +56 -0
- package/src/components/Settings/Settings.tsx +230 -0
- package/src/components/Settings/index.ts +5 -0
- package/src/components/ViewEditor/ViewEditor.stories.tsx +140 -62
- package/src/components/ViewEditor/ViewEditor.test.tsx +17 -18
- package/src/components/ViewEditor/ViewEditor.tsx +341 -202
- package/src/components/index.ts +2 -3
- package/src/components/testing.tsx +22 -11
- 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 +21 -12
- 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/ControlSection/ControlSection.d.ts +0 -23
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +0 -8
- 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 -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/useQueryRefOptions.d.ts +0 -23
- 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 -41
- package/src/components/ControlSection/ControlSection.tsx +0 -139
- 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/SelectOptionsInput.stories.tsx +0 -61
- 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/useQueryRefOptions.ts +0 -53
- package/src/hooks/useRefQueryLookupHandler.ts +0 -39
- package/src/util/schema.ts +0 -14
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Option from 'effect/Option';
|
|
6
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
7
|
+
import React, { Fragment, useCallback } from 'react';
|
|
8
|
+
|
|
9
|
+
import { findNode, getArrayElementType, getDiscriminatedType, isDiscriminatedUnion, isNestedType } from '@dxos/effect';
|
|
10
|
+
import { IconButton, useTranslation } from '@dxos/react-ui';
|
|
11
|
+
|
|
12
|
+
import { translationKey } from '../../../translations';
|
|
13
|
+
import { getFormProperties } from '../../../util';
|
|
14
|
+
import { useFormValues } from '../Form';
|
|
15
|
+
import { FormField, type FormFieldProps } from '../FormField';
|
|
16
|
+
import { FormFieldLabel, type FormFieldStateProps } from '../FormFieldComponent';
|
|
17
|
+
|
|
18
|
+
export type ArrayFieldProps = {
|
|
19
|
+
label: string;
|
|
20
|
+
fieldProps: FormFieldStateProps;
|
|
21
|
+
} & FormFieldProps;
|
|
22
|
+
|
|
23
|
+
export const ArrayField = ({
|
|
24
|
+
type,
|
|
25
|
+
path,
|
|
26
|
+
label,
|
|
27
|
+
readonly,
|
|
28
|
+
layout,
|
|
29
|
+
fieldProps: inputProps,
|
|
30
|
+
...props
|
|
31
|
+
}: ArrayFieldProps) => {
|
|
32
|
+
const { t } = useTranslation(translationKey);
|
|
33
|
+
const elementType = getArrayElementType(type);
|
|
34
|
+
const { onValueChange } = inputProps;
|
|
35
|
+
const values = useFormValues(ArrayField.displayName, path, () => []);
|
|
36
|
+
|
|
37
|
+
const getDefaultObjectValue = (typeNode: SchemaAST.AST): any => {
|
|
38
|
+
const baseNode = findNode(typeNode, isDiscriminatedUnion);
|
|
39
|
+
const typeLiteral = baseNode ? getDiscriminatedType(baseNode, {}) : findNode(typeNode, SchemaAST.isTypeLiteral);
|
|
40
|
+
if (!typeLiteral) {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return Object.fromEntries(
|
|
45
|
+
getFormProperties(typeLiteral).map((prop) => {
|
|
46
|
+
const defaultValue = SchemaAST.getDefaultAnnotation(prop.type).pipe((annotation) =>
|
|
47
|
+
Option.getOrUndefined(annotation),
|
|
48
|
+
);
|
|
49
|
+
return [prop.name, defaultValue];
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleAdd = useCallback(() => {
|
|
55
|
+
const defaultValue =
|
|
56
|
+
isNestedType(type) && elementType ? getDefaultObjectValue(elementType) : getDefaultValue(elementType);
|
|
57
|
+
values && onValueChange(type, [...values, defaultValue]);
|
|
58
|
+
}, [onValueChange, type, elementType, values]);
|
|
59
|
+
|
|
60
|
+
const handleDelete = useCallback(
|
|
61
|
+
(idx: number) => {
|
|
62
|
+
onValueChange(
|
|
63
|
+
type,
|
|
64
|
+
values?.filter((_, i) => i !== idx),
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
[onValueChange, type, values],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (!elementType || ((readonly || layout === 'static') && values && values.length < 1)) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<>
|
|
76
|
+
{(layout !== 'static' || (values && values.length > 0)) && (
|
|
77
|
+
<FormFieldLabel readonly={readonly} label={label} asChild />
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
<div role='none' className='flex flex-col gap-2'>
|
|
81
|
+
{values?.map((_, index) => {
|
|
82
|
+
return (
|
|
83
|
+
<div role='none' key={index} className='grid grid-cols-[1fr_min-content] gap-2 last:mb-3'>
|
|
84
|
+
<FormField
|
|
85
|
+
autoFocus={index === values.length - 1}
|
|
86
|
+
type={elementType}
|
|
87
|
+
path={[...(path ?? []), index]}
|
|
88
|
+
readonly={readonly || layout === 'static'}
|
|
89
|
+
layout='inline'
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
|
|
93
|
+
{!readonly && layout !== 'static' && (
|
|
94
|
+
<div role='none' className='flex flex-col h-full justify-end'>
|
|
95
|
+
{/* NOTE: Aligns with center of last field if multi-field object. */}
|
|
96
|
+
<div role='none' className='flex items-center h-[2rem]'>
|
|
97
|
+
<IconButton
|
|
98
|
+
icon='ph--x--regular'
|
|
99
|
+
iconOnly
|
|
100
|
+
label={t('button remove')}
|
|
101
|
+
onClick={() => handleDelete(index)}
|
|
102
|
+
classNames='self-center'
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
})}
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
{/* TODO(burdon): Get label from schema. */}
|
|
113
|
+
{!readonly && layout !== 'static' && (
|
|
114
|
+
<IconButton classNames='flex w-full' icon='ph--plus--regular' label={t('add field')} onClick={handleAdd} />
|
|
115
|
+
)}
|
|
116
|
+
</>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
ArrayField.displayName = 'Form.ArrayField';
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Returns the default empty value for a given AST.
|
|
124
|
+
* Used for initializing new array values etc.
|
|
125
|
+
*/
|
|
126
|
+
// TODO(wittjosiah): Factor out?
|
|
127
|
+
export const getDefaultValue = (ast?: SchemaAST.AST): any => {
|
|
128
|
+
switch (ast?._tag) {
|
|
129
|
+
case 'StringKeyword': {
|
|
130
|
+
return '';
|
|
131
|
+
}
|
|
132
|
+
case 'NumberKeyword': {
|
|
133
|
+
return 0;
|
|
134
|
+
}
|
|
135
|
+
case 'BooleanKeyword': {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
case 'Suspend': {
|
|
139
|
+
return getDefaultValue(ast.f());
|
|
140
|
+
}
|
|
141
|
+
default: {
|
|
142
|
+
if (ast && isNestedType(ast)) {
|
|
143
|
+
return {};
|
|
144
|
+
} else {
|
|
145
|
+
throw new Error(`Unsupported type: ${ast?._tag}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Input, type SwitchProps } from '@dxos/react-ui';
|
|
8
|
+
|
|
9
|
+
import { type FormFieldComponentProps, FormFieldWrapper } from '../FormFieldComponent';
|
|
10
|
+
|
|
11
|
+
export const BooleanField = ({ type, readonly, onValueChange, ...props }: FormFieldComponentProps<boolean>) => {
|
|
12
|
+
const handleChange = useCallback<NonNullable<SwitchProps['onCheckedChange']>>(
|
|
13
|
+
(value) => onValueChange?.(type, value),
|
|
14
|
+
[type, onValueChange],
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<FormFieldWrapper<boolean> readonly={readonly} {...props}>
|
|
19
|
+
{({ value }) => <Input.Switch disabled={!!readonly} checked={value} onCheckedChange={handleChange} />}
|
|
20
|
+
</FormFieldWrapper>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { type ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { GeoLocation, type GeoPoint } from '@dxos/echo/internal';
|
|
8
|
+
import { Input, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
import { safeParseFloat } from '@dxos/util';
|
|
10
|
+
|
|
11
|
+
import { translationKey } from '../../../translations';
|
|
12
|
+
import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
|
|
13
|
+
|
|
14
|
+
export const GeoPointField = ({
|
|
15
|
+
type,
|
|
16
|
+
label,
|
|
17
|
+
readonly,
|
|
18
|
+
layout,
|
|
19
|
+
getStatus,
|
|
20
|
+
getValue,
|
|
21
|
+
onValueChange,
|
|
22
|
+
onBlur,
|
|
23
|
+
}: FormFieldComponentProps<GeoPoint>) => {
|
|
24
|
+
const { t } = useTranslation(translationKey);
|
|
25
|
+
const { status, error } = getStatus();
|
|
26
|
+
const geoPoint = useMemo<GeoPoint>(() => getValue() ?? [0, 0], [getValue]);
|
|
27
|
+
const value = useMemo(() => GeoLocation.fromGeoPoint(geoPoint), [geoPoint]);
|
|
28
|
+
|
|
29
|
+
const [longitudeText, setLongitudeText] = useState(value.longitude?.toString());
|
|
30
|
+
const [latitudeText, setLatitudeText] = useState(value.latitude?.toString());
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const location = GeoLocation.fromGeoPoint(geoPoint);
|
|
33
|
+
setLongitudeText(location.longitude?.toString());
|
|
34
|
+
setLatitudeText(location.latitude?.toString());
|
|
35
|
+
}, [geoPoint]);
|
|
36
|
+
|
|
37
|
+
const handleChange = useCallback(
|
|
38
|
+
(coordinateType: keyof Pick<GeoLocation, 'longitude' | 'latitude'>, setText: (text: string) => void) =>
|
|
39
|
+
(event: ChangeEvent<HTMLInputElement>) => {
|
|
40
|
+
const inputText = event.target.value;
|
|
41
|
+
setText(inputText);
|
|
42
|
+
if (inputText !== '' && inputText !== '-') {
|
|
43
|
+
const coord = safeParseFloat(inputText);
|
|
44
|
+
if (coord !== undefined && !isNaN(coord)) {
|
|
45
|
+
const currentLocation = GeoLocation.fromGeoPoint(getValue() ?? [0, 0]);
|
|
46
|
+
const newLocation = { ...currentLocation, [coordinateType]: coord };
|
|
47
|
+
const newValue = GeoLocation.toGeoPoint(newLocation);
|
|
48
|
+
onValueChange(type, newValue);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
[type, getValue, onValueChange],
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
if ((readonly || layout === 'static') && !value?.latitude && !value?.longitude) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Input.Root validationValence={status}>
|
|
61
|
+
{layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} asChild />}
|
|
62
|
+
{layout === 'static' ? (
|
|
63
|
+
<LatLng {...value} />
|
|
64
|
+
) : (
|
|
65
|
+
<div role='none' className='grid grid-cols-2 gap-2'>
|
|
66
|
+
<div>
|
|
67
|
+
<Input.Root>
|
|
68
|
+
{layout !== 'inline' && <Input.Label>{t('latitude label')}</Input.Label>}
|
|
69
|
+
<Input.TextInput
|
|
70
|
+
type='number'
|
|
71
|
+
step='0.00001'
|
|
72
|
+
min='-90'
|
|
73
|
+
max='90'
|
|
74
|
+
disabled={!!readonly}
|
|
75
|
+
placeholder={t('latitude placeholder')}
|
|
76
|
+
value={latitudeText ?? ''}
|
|
77
|
+
onChange={handleChange('latitude', setLatitudeText)}
|
|
78
|
+
onBlur={onBlur}
|
|
79
|
+
/>
|
|
80
|
+
</Input.Root>
|
|
81
|
+
</div>
|
|
82
|
+
<div>
|
|
83
|
+
<Input.Root>
|
|
84
|
+
{layout !== 'inline' && <Input.Label>{t('longitude label')}</Input.Label>}
|
|
85
|
+
<Input.TextInput
|
|
86
|
+
type='number'
|
|
87
|
+
step='0.00001'
|
|
88
|
+
min='-180'
|
|
89
|
+
max='180'
|
|
90
|
+
disabled={!!readonly}
|
|
91
|
+
placeholder={t('longitude placeholder')}
|
|
92
|
+
value={longitudeText ?? ''}
|
|
93
|
+
onChange={handleChange('longitude', setLongitudeText)}
|
|
94
|
+
onBlur={onBlur}
|
|
95
|
+
/>
|
|
96
|
+
</Input.Root>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
</Input.Root>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const LatLng = ({ latitude = 0, longitude = 0 }: GeoLocation) => {
|
|
105
|
+
const latHem = latitude >= 0 ? 'N' : 'S';
|
|
106
|
+
const lngHem = longitude >= 0 ? 'E' : 'W';
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<span className='inline-flex items-center gap-1'>
|
|
110
|
+
<span>
|
|
111
|
+
<span>{Math.abs(latitude).toFixed(5)}</span>
|
|
112
|
+
<span className='text-subdued'>°{latHem}</span>
|
|
113
|
+
</span>
|
|
114
|
+
<span>
|
|
115
|
+
<span>{Math.abs(longitude).toFixed(5)}</span>
|
|
116
|
+
<span className='text-subdued'>°{lngHem}</span>
|
|
117
|
+
</span>
|
|
118
|
+
</span>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
@@ -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-h-auto max-h-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,189 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxos/lit-ui/dx-tag-picker.pcss';
|
|
6
|
+
|
|
7
|
+
import React, { useCallback, useMemo } from 'react';
|
|
8
|
+
|
|
9
|
+
import { type Database, Entity, Filter, Obj, Ref, Type } from '@dxos/echo';
|
|
10
|
+
import { ReferenceAnnotationId, type ReferenceAnnotationValue } from '@dxos/echo/internal';
|
|
11
|
+
import { useQuery, useSchema as useSchema$ } from '@dxos/echo-react';
|
|
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 { mx } from '@dxos/ui-theme';
|
|
17
|
+
|
|
18
|
+
import { translationKey } from '../../../translations';
|
|
19
|
+
import { ObjectPicker, type ObjectPickerContentProps, type RefOption } from '../../ObjectPicker';
|
|
20
|
+
import { omitId } from '../Form';
|
|
21
|
+
import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
|
|
22
|
+
|
|
23
|
+
// TODO(burdon): Factor out.
|
|
24
|
+
const isRefSnapshot = (val: any): val is { '/': string } => {
|
|
25
|
+
return typeof val === 'object' && typeof (val as any)?.['/'] === 'string';
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const defaultGetOptions: NonNullable<RefFieldProps['getOptions']> = (results) =>
|
|
29
|
+
results.map((result) => {
|
|
30
|
+
const id = Entity.getDXN(result).toString();
|
|
31
|
+
const label = Entity.getLabel(result);
|
|
32
|
+
return { id, label: label ?? id };
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const defaultResultsHook: NonNullable<RefFieldProps['resultsHook']> = (db, typename) =>
|
|
36
|
+
useQuery(
|
|
37
|
+
db,
|
|
38
|
+
typename
|
|
39
|
+
? // For Ref.Ref(Obj.Unknown) we want to show all objects.
|
|
40
|
+
typename === Type.getTypename(Obj.Unknown)
|
|
41
|
+
? Filter.everything()
|
|
42
|
+
: Filter.typename(typename)
|
|
43
|
+
: Filter.nothing(),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export type RefFieldProps = FormFieldComponentProps &
|
|
47
|
+
Pick<ObjectPickerContentProps, 'createOptionLabel' | 'createOptionIcon' | 'createInitialValuePath'> & {
|
|
48
|
+
db?: Database.Database;
|
|
49
|
+
resultsHook?: (db?: Database.Database, typename?: string) => Entity.Any[];
|
|
50
|
+
schemaHook?: (db?: Database.Database, typename?: string) => Type.AnyEntity;
|
|
51
|
+
getOptions?: (objects: Entity.Any[]) => RefOption[];
|
|
52
|
+
onCreate?: (schema: Type.AnyEntity, values: any) => void;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const RefField = (props: RefFieldProps) => {
|
|
56
|
+
const {
|
|
57
|
+
type,
|
|
58
|
+
readonly,
|
|
59
|
+
label,
|
|
60
|
+
placeholder,
|
|
61
|
+
layout,
|
|
62
|
+
getStatus,
|
|
63
|
+
getValue,
|
|
64
|
+
createOptionLabel,
|
|
65
|
+
createOptionIcon,
|
|
66
|
+
createInitialValuePath,
|
|
67
|
+
db,
|
|
68
|
+
resultsHook: useResults = defaultResultsHook,
|
|
69
|
+
schemaHook: useSchema = useSchema$,
|
|
70
|
+
getOptions = defaultGetOptions,
|
|
71
|
+
onCreate,
|
|
72
|
+
onValueChange,
|
|
73
|
+
} = props;
|
|
74
|
+
const { t } = useTranslation(translationKey);
|
|
75
|
+
const { status, error } = getStatus();
|
|
76
|
+
|
|
77
|
+
const typename = useMemo(
|
|
78
|
+
() => (type ? findAnnotation<ReferenceAnnotationValue>(type, ReferenceAnnotationId)?.typename : undefined),
|
|
79
|
+
[type],
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const results = useResults(db, typename);
|
|
83
|
+
const options = useMemo(() => getOptions(results), [results, getOptions]);
|
|
84
|
+
|
|
85
|
+
const handleGetValue = useCallback(() => {
|
|
86
|
+
const formValue = getValue();
|
|
87
|
+
|
|
88
|
+
const unknownToRefOption = (value: unknown) => {
|
|
89
|
+
const isRef = Ref.isRef(value);
|
|
90
|
+
if (isRef || isRefSnapshot(value)) {
|
|
91
|
+
const dxnString = isRef ? value.dxn.toString() : value['/'];
|
|
92
|
+
const matchingOption = options.find((option) => option.id === dxnString);
|
|
93
|
+
if (matchingOption) {
|
|
94
|
+
return matchingOption;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return unknownToRefOption(formValue);
|
|
101
|
+
}, [options, getValue]);
|
|
102
|
+
|
|
103
|
+
const handleUpdate = useCallback(
|
|
104
|
+
(id: string | undefined) => {
|
|
105
|
+
const item = options.find((option) => option.id === id);
|
|
106
|
+
const ref = item ? Ref.fromDXN(DXN.parse(item.id)) : undefined;
|
|
107
|
+
onValueChange(type, ref);
|
|
108
|
+
},
|
|
109
|
+
[options, type, onValueChange],
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const item = handleGetValue();
|
|
113
|
+
const selectedIds = useMemo(() => (item ? [item.id] : []), [item]);
|
|
114
|
+
const createSchema = useSchema(db, typename);
|
|
115
|
+
|
|
116
|
+
const handleCreate = useCallback(
|
|
117
|
+
(values: any) => {
|
|
118
|
+
if (createSchema && onCreate) {
|
|
119
|
+
onCreate(createSchema, values);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
[createSchema, onCreate],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const handleSelect = useCallback(
|
|
126
|
+
(id: string) => {
|
|
127
|
+
if (item?.id === id) {
|
|
128
|
+
handleUpdate(undefined);
|
|
129
|
+
} else {
|
|
130
|
+
handleUpdate(id);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
[item, handleUpdate],
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (!typename || ((readonly || layout === 'static') && !item)) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<Input.Root validationValence={status}>
|
|
142
|
+
{layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} />}
|
|
143
|
+
<div>
|
|
144
|
+
{readonly ? (
|
|
145
|
+
!item ? (
|
|
146
|
+
<p className={mx('text-description', 'mb-2')}>{t('empty readonly ref field label')}</p>
|
|
147
|
+
) : (
|
|
148
|
+
<DxAnchor key={item.id} dxn={item.id} rootclassname='me-1'>
|
|
149
|
+
{item.label}
|
|
150
|
+
</DxAnchor>
|
|
151
|
+
)
|
|
152
|
+
) : (
|
|
153
|
+
<ObjectPicker.Root>
|
|
154
|
+
<ObjectPicker.Trigger asChild classNames='p-0'>
|
|
155
|
+
{item ? (
|
|
156
|
+
<div className='flex gap-2 w-full'>
|
|
157
|
+
<Input.Root key={item.id}>
|
|
158
|
+
<Input.TextInput value={item.label} readOnly classNames='w-full' />
|
|
159
|
+
</Input.Root>
|
|
160
|
+
</div>
|
|
161
|
+
) : (
|
|
162
|
+
<Button classNames='w-full text-start gap-2'>
|
|
163
|
+
<div role='none' className='grow overflow-hidden'>
|
|
164
|
+
<span className='flex truncate text-description'>{placeholder ?? t('ref field placeholder')}</span>
|
|
165
|
+
</div>
|
|
166
|
+
<Icon size={3} icon='ph--caret-down--bold' />
|
|
167
|
+
</Button>
|
|
168
|
+
)}
|
|
169
|
+
</ObjectPicker.Trigger>
|
|
170
|
+
<ObjectPicker.Portal>
|
|
171
|
+
<ObjectPicker.Content
|
|
172
|
+
classNames='dx-card-popover-width'
|
|
173
|
+
options={options}
|
|
174
|
+
selectedIds={selectedIds}
|
|
175
|
+
createSchema={createSchema && omitId(createSchema)}
|
|
176
|
+
createOptionLabel={createOptionLabel}
|
|
177
|
+
createOptionIcon={createOptionIcon}
|
|
178
|
+
createInitialValuePath={createInitialValuePath}
|
|
179
|
+
onCreate={handleCreate}
|
|
180
|
+
onSelect={handleSelect}
|
|
181
|
+
/>
|
|
182
|
+
</ObjectPicker.Portal>
|
|
183
|
+
</ObjectPicker.Root>
|
|
184
|
+
)}
|
|
185
|
+
</div>
|
|
186
|
+
{layout === 'full' && <Input.DescriptionAndValidation>{error}</Input.DescriptionAndValidation>}
|
|
187
|
+
</Input.Root>
|
|
188
|
+
);
|
|
189
|
+
};
|
|
@@ -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='w-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
|
+
};
|