@dxos/react-ui-form 0.8.4-main.f9ba587 → 0.8.4-main.fcc0d83b33
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 +2787 -2137
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/translations.mjs +82 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +2787 -2137
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/translations.mjs +84 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +9 -9
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +86 -7
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +109 -25
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +130 -56
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +43 -0
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +79 -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 +15 -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 +11 -0
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts +83 -0
- package/dist/types/src/components/Form/fields/MarkdownField.stories.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 +22 -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 +84 -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/ObjectForm/ObjectForm.d.ts +9 -0
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -0
- package/dist/types/src/components/ObjectForm/index.d.ts +2 -0
- package/dist/types/src/components/ObjectForm/index.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +68 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +87 -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/ObjectProperties/ObjectProperties.d.ts +9 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +127 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts.map +1 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts +2 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts.map +1 -0
- package/dist/types/src/components/ObjectProperties/index.d.ts +2 -0
- package/dist/types/src/components/ObjectProperties/index.d.ts.map +1 -0
- package/dist/types/src/components/Settings/Settings.d.ts +51 -0
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
- package/dist/types/src/components/Settings/Settings.stories.d.ts +84 -0
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
- package/dist/types/src/components/Settings/SettingsFieldSet.d.ts +43 -0
- package/dist/types/src/components/Settings/SettingsFieldSet.d.ts.map +1 -0
- package/dist/types/src/components/Settings/SettingsItem.d.ts +16 -0
- package/dist/types/src/components/Settings/SettingsItem.d.ts.map +1 -0
- package/dist/types/src/components/Settings/SettingsSelectField.d.ts +18 -0
- package/dist/types/src/components/Settings/SettingsSelectField.d.ts.map +1 -0
- package/dist/types/src/components/Settings/index.d.ts +4 -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 +87 -9
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +4 -2
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +5 -4
- 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 +7 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +0 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +65 -54
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/field-type.d.ts +19 -0
- package/dist/types/src/util/field-type.d.ts.map +1 -0
- 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 +2 -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 +3 -3
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +57 -46
- package/src/components/FieldEditor/FieldEditor.stories.tsx +52 -31
- package/src/components/FieldEditor/FieldEditor.test.tsx +13 -13
- package/src/components/FieldEditor/FieldEditor.tsx +90 -95
- package/src/components/Form/AUDIT.md +17 -0
- package/src/components/Form/Form.stories.tsx +147 -223
- package/src/components/Form/Form.tsx +376 -95
- package/src/components/Form/FormField.tsx +306 -0
- package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
- package/src/components/Form/FormFieldComponent.tsx +174 -0
- package/src/components/Form/FormFieldSet.tsx +144 -0
- package/src/components/Form/README.md +3 -0
- package/src/components/Form/fields/ArrayField.tsx +159 -0
- package/src/components/Form/fields/BooleanField.tsx +22 -0
- package/src/components/Form/fields/GeoPointField.tsx +121 -0
- package/src/components/Form/fields/MarkdownField.stories.tsx +152 -0
- package/src/components/Form/fields/MarkdownField.tsx +168 -0
- package/src/components/Form/fields/NumberField.tsx +39 -0
- package/src/components/Form/fields/RefField.tsx +229 -0
- package/src/components/Form/fields/SelectField.tsx +65 -0
- package/src/components/Form/fields/SelectOptionField.stories.tsx +66 -0
- package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +50 -45
- 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} +17 -10
- package/src/components/Form/fields/index.ts +15 -0
- package/src/components/Form/index.ts +3 -4
- package/src/components/ObjectForm/ObjectForm.tsx +114 -0
- package/src/components/{ControlSection → ObjectForm}/index.ts +1 -1
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +212 -0
- package/src/components/ObjectPicker/ObjectPicker.tsx +194 -0
- package/src/components/ObjectPicker/index.ts +5 -0
- package/src/components/ObjectProperties/ObjectProperties.stories.tsx +362 -0
- package/src/components/ObjectProperties/ObjectProperties.test.tsx +101 -0
- package/src/components/ObjectProperties/ObjectProperties.tsx +141 -0
- package/src/components/ObjectProperties/index.ts +5 -0
- package/src/components/Settings/AUDIT.md +52 -0
- package/src/components/Settings/Settings.stories.tsx +104 -0
- package/src/components/Settings/Settings.tsx +82 -0
- package/src/components/Settings/SettingsFieldSet.tsx +257 -0
- package/src/components/Settings/SettingsItem.tsx +62 -0
- package/src/components/Settings/SettingsSelectField.tsx +59 -0
- package/src/components/Settings/index.ts +7 -0
- package/src/components/ViewEditor/ViewEditor.stories.tsx +145 -61
- package/src/components/ViewEditor/ViewEditor.test.tsx +22 -22
- package/src/components/ViewEditor/ViewEditor.tsx +355 -219
- package/src/components/index.ts +4 -3
- package/src/components/testing.tsx +31 -12
- package/src/hooks/index.ts +2 -3
- package/src/hooks/useFormHandler.ts +380 -0
- package/src/hooks/useKeyHandler.tsx +37 -0
- package/src/index.ts +0 -4
- package/src/translations.ts +70 -58
- package/src/util/field-type.ts +60 -0
- package/src/util/field.ts +33 -43
- package/src/util/formatting.ts +22 -22
- package/src/util/index.ts +2 -1
- package/src/util/properties.ts +18 -0
- package/src/util/refs.ts +17 -19
- 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,159 @@
|
|
|
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, { 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
|
+
|
|
14
|
+
import { getFormProperties } from '../../../util';
|
|
15
|
+
import { useFormValues } from '../Form';
|
|
16
|
+
import { FormField, type FormFieldProps } from '../FormField';
|
|
17
|
+
import { FormFieldLabel, type FormFieldStateProps } from '../FormFieldComponent';
|
|
18
|
+
|
|
19
|
+
export type ArrayFieldProps = {
|
|
20
|
+
label: string;
|
|
21
|
+
fieldProps: FormFieldStateProps;
|
|
22
|
+
} & FormFieldProps;
|
|
23
|
+
|
|
24
|
+
export const ArrayField = ({
|
|
25
|
+
type,
|
|
26
|
+
path,
|
|
27
|
+
label,
|
|
28
|
+
readonly,
|
|
29
|
+
layout,
|
|
30
|
+
fieldProps: inputProps,
|
|
31
|
+
...props
|
|
32
|
+
}: ArrayFieldProps) => {
|
|
33
|
+
const { t } = useTranslation(translationKey);
|
|
34
|
+
const elementType = getArrayElementType(type);
|
|
35
|
+
const { onValueChange } = inputProps;
|
|
36
|
+
const values = useFormValues(ArrayField.displayName, path, () => []);
|
|
37
|
+
|
|
38
|
+
const getDefaultObjectValue = (typeNode: SchemaAST.AST): any => {
|
|
39
|
+
const baseNode = findNode(typeNode, isDiscriminatedUnion);
|
|
40
|
+
const typeLiteral = baseNode ? getDiscriminatedType(baseNode, {}) : findNode(typeNode, SchemaAST.isTypeLiteral);
|
|
41
|
+
if (!typeLiteral) {
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return Object.fromEntries(
|
|
46
|
+
getFormProperties(typeLiteral).map((prop) => {
|
|
47
|
+
const defaultValue = SchemaAST.getDefaultAnnotation(prop.type).pipe((annotation) =>
|
|
48
|
+
Option.getOrUndefined(annotation),
|
|
49
|
+
);
|
|
50
|
+
return [prop.name, defaultValue];
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const handleAdd = useCallback(() => {
|
|
56
|
+
const defaultValue =
|
|
57
|
+
isNestedType(type) && elementType ? getDefaultObjectValue(elementType) : getDefaultValue(elementType);
|
|
58
|
+
values && onValueChange(type, [...values, defaultValue]);
|
|
59
|
+
}, [onValueChange, type, elementType, values]);
|
|
60
|
+
|
|
61
|
+
const handleDelete = useCallback(
|
|
62
|
+
(idx: number) => {
|
|
63
|
+
onValueChange(
|
|
64
|
+
type,
|
|
65
|
+
values?.filter((_, i) => i !== idx),
|
|
66
|
+
);
|
|
67
|
+
},
|
|
68
|
+
[onValueChange, type, values],
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (!elementType || ((readonly || layout === 'static') && values && values.length < 1)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<>
|
|
77
|
+
{(layout !== 'static' || (values && values.length > 0)) && (
|
|
78
|
+
<FormFieldLabel readonly={readonly} label={label} asChild />
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
<div role='none' className='flex flex-col gap-form-gap'>
|
|
82
|
+
{values?.map((_, index) => {
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
role='none'
|
|
86
|
+
key={index}
|
|
87
|
+
className='grid grid-cols-[1fr_min-content] gap-form-gap last:mb-form-gap items-center'
|
|
88
|
+
>
|
|
89
|
+
<FormField
|
|
90
|
+
autoFocus={index === values.length - 1}
|
|
91
|
+
type={elementType}
|
|
92
|
+
path={[...(path ?? []), index]}
|
|
93
|
+
readonly={readonly || layout === 'static'}
|
|
94
|
+
layout='inline'
|
|
95
|
+
{...props}
|
|
96
|
+
/>
|
|
97
|
+
|
|
98
|
+
{!readonly && layout !== 'static' && (
|
|
99
|
+
<div role='none' className='flex flex-col h-full justify-end'>
|
|
100
|
+
{/* NOTE: Aligns with center of last field if multi-field object. */}
|
|
101
|
+
<div role='none' className='flex items-center h-[2rem]'>
|
|
102
|
+
<IconButton
|
|
103
|
+
icon='ph--x--regular'
|
|
104
|
+
iconOnly
|
|
105
|
+
label={t('remove.button')}
|
|
106
|
+
onClick={() => handleDelete(index)}
|
|
107
|
+
classNames='self-center'
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
})}
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
{/* TODO(burdon): Get label from schema. */}
|
|
118
|
+
{!readonly && layout !== 'static' && (
|
|
119
|
+
<IconButton
|
|
120
|
+
classNames='flex w-full'
|
|
121
|
+
icon='ph--plus--regular'
|
|
122
|
+
label={t('add-item.button')}
|
|
123
|
+
onClick={handleAdd}
|
|
124
|
+
/>
|
|
125
|
+
)}
|
|
126
|
+
</>
|
|
127
|
+
);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
ArrayField.displayName = 'Form.ArrayField';
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Returns the default empty value for a given AST.
|
|
134
|
+
* Used for initializing new array values etc.
|
|
135
|
+
*/
|
|
136
|
+
// TODO(wittjosiah): Factor out?
|
|
137
|
+
export const getDefaultValue = (ast?: SchemaAST.AST): any => {
|
|
138
|
+
switch (ast?._tag) {
|
|
139
|
+
case 'StringKeyword': {
|
|
140
|
+
return '';
|
|
141
|
+
}
|
|
142
|
+
case 'NumberKeyword': {
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
case 'BooleanKeyword': {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
case 'Suspend': {
|
|
149
|
+
return getDefaultValue(ast.f());
|
|
150
|
+
}
|
|
151
|
+
default: {
|
|
152
|
+
if (ast && isNestedType(ast)) {
|
|
153
|
+
return {};
|
|
154
|
+
} else {
|
|
155
|
+
throw new Error(`Unsupported type: ${ast?._tag}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
@@ -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,121 @@
|
|
|
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
|
+
|
|
13
|
+
import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
|
|
14
|
+
|
|
15
|
+
export const GeoPointField = ({
|
|
16
|
+
type,
|
|
17
|
+
label,
|
|
18
|
+
readonly,
|
|
19
|
+
layout,
|
|
20
|
+
getStatus,
|
|
21
|
+
getValue,
|
|
22
|
+
onValueChange,
|
|
23
|
+
onBlur,
|
|
24
|
+
}: FormFieldComponentProps<GeoPoint>) => {
|
|
25
|
+
const { t } = useTranslation(translationKey);
|
|
26
|
+
const { status, error } = getStatus();
|
|
27
|
+
const geoPoint = useMemo<GeoPoint>(() => getValue() ?? [0, 0], [getValue]);
|
|
28
|
+
const value = useMemo(() => GeoLocation.fromGeoPoint(geoPoint), [geoPoint]);
|
|
29
|
+
|
|
30
|
+
const [longitudeText, setLongitudeText] = useState(value.longitude?.toString());
|
|
31
|
+
const [latitudeText, setLatitudeText] = useState(value.latitude?.toString());
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const location = GeoLocation.fromGeoPoint(geoPoint);
|
|
34
|
+
setLongitudeText(location.longitude?.toString());
|
|
35
|
+
setLatitudeText(location.latitude?.toString());
|
|
36
|
+
}, [geoPoint]);
|
|
37
|
+
|
|
38
|
+
const handleChange = useCallback(
|
|
39
|
+
(coordinateType: keyof Pick<GeoLocation, 'longitude' | 'latitude'>, setText: (text: string) => void) =>
|
|
40
|
+
(event: ChangeEvent<HTMLInputElement>) => {
|
|
41
|
+
const inputText = event.target.value;
|
|
42
|
+
setText(inputText);
|
|
43
|
+
if (inputText !== '' && inputText !== '-') {
|
|
44
|
+
const coord = safeParseFloat(inputText);
|
|
45
|
+
if (coord !== undefined && !isNaN(coord)) {
|
|
46
|
+
const currentLocation = GeoLocation.fromGeoPoint(getValue() ?? [0, 0]);
|
|
47
|
+
const newLocation = { ...currentLocation, [coordinateType]: coord };
|
|
48
|
+
const newValue = GeoLocation.toGeoPoint(newLocation);
|
|
49
|
+
onValueChange(type, newValue);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
[type, getValue, onValueChange],
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if ((readonly || layout === 'static') && !value?.latitude && !value?.longitude) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Input.Root validationValence={status}>
|
|
62
|
+
{layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} asChild />}
|
|
63
|
+
{layout === 'static' ? (
|
|
64
|
+
<LatLng {...value} />
|
|
65
|
+
) : (
|
|
66
|
+
<div role='none' className='grid grid-cols-2 gap-form-gap'>
|
|
67
|
+
<div>
|
|
68
|
+
<Input.Root>
|
|
69
|
+
{layout !== 'inline' && <Input.Label>{t('latitude.label')}</Input.Label>}
|
|
70
|
+
<Input.TextInput
|
|
71
|
+
type='number'
|
|
72
|
+
step='0.00001'
|
|
73
|
+
min='-90'
|
|
74
|
+
max='90'
|
|
75
|
+
disabled={!!readonly}
|
|
76
|
+
placeholder={t('latitude.placeholder')}
|
|
77
|
+
value={latitudeText ?? ''}
|
|
78
|
+
onChange={handleChange('latitude', setLatitudeText)}
|
|
79
|
+
onBlur={onBlur}
|
|
80
|
+
/>
|
|
81
|
+
</Input.Root>
|
|
82
|
+
</div>
|
|
83
|
+
<div>
|
|
84
|
+
<Input.Root>
|
|
85
|
+
{layout !== 'inline' && <Input.Label>{t('longitude.label')}</Input.Label>}
|
|
86
|
+
<Input.TextInput
|
|
87
|
+
type='number'
|
|
88
|
+
step='0.00001'
|
|
89
|
+
min='-180'
|
|
90
|
+
max='180'
|
|
91
|
+
disabled={!!readonly}
|
|
92
|
+
placeholder={t('longitude.placeholder')}
|
|
93
|
+
value={longitudeText ?? ''}
|
|
94
|
+
onChange={handleChange('longitude', setLongitudeText)}
|
|
95
|
+
onBlur={onBlur}
|
|
96
|
+
/>
|
|
97
|
+
</Input.Root>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
</Input.Root>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const LatLng = ({ latitude = 0, longitude = 0 }: GeoLocation) => {
|
|
106
|
+
const latHem = latitude >= 0 ? 'N' : 'S';
|
|
107
|
+
const lngHem = longitude >= 0 ? 'E' : 'W';
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<span className='inline-flex items-center gap-form-gap'>
|
|
111
|
+
<span>
|
|
112
|
+
<span>{Math.abs(latitude).toFixed(5)}</span>
|
|
113
|
+
<span className='text-subdued'>°{latHem}</span>
|
|
114
|
+
</span>
|
|
115
|
+
<span>
|
|
116
|
+
<span>{Math.abs(longitude).toFixed(5)}</span>
|
|
117
|
+
<span className='text-subdued'>°{lngHem}</span>
|
|
118
|
+
</span>
|
|
119
|
+
</span>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
import React, { useMemo } from 'react';
|
|
8
|
+
|
|
9
|
+
import { Filter, Format, Obj, Ref } from '@dxos/echo';
|
|
10
|
+
import { useQuery } from '@dxos/react-client/echo';
|
|
11
|
+
import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
|
|
12
|
+
import { Panel } from '@dxos/react-ui';
|
|
13
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
14
|
+
import { Text } from '@dxos/schema';
|
|
15
|
+
|
|
16
|
+
import { translations } from '#translations';
|
|
17
|
+
|
|
18
|
+
import { TestLayout } from '../../testing';
|
|
19
|
+
import { Form } from '../Form';
|
|
20
|
+
|
|
21
|
+
//
|
|
22
|
+
// String-backed markdown.
|
|
23
|
+
//
|
|
24
|
+
|
|
25
|
+
const StringSchema = Schema.Struct({
|
|
26
|
+
notes: Schema.String.pipe(Format.FormatAnnotation.set(Format.TypeFormat.Markdown)).annotations({ title: 'Notes' }),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const StringStory = () => (
|
|
30
|
+
<TestLayout>
|
|
31
|
+
<Form.Root schema={StringSchema} defaultValues={{ notes: '# Hello\n\nMarkdown text *here*.' }}>
|
|
32
|
+
<Form.Viewport>
|
|
33
|
+
<Form.Content>
|
|
34
|
+
<Form.FieldSet />
|
|
35
|
+
</Form.Content>
|
|
36
|
+
</Form.Viewport>
|
|
37
|
+
</Form.Root>
|
|
38
|
+
</TestLayout>
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
//
|
|
42
|
+
// Ref<Text>-backed markdown.
|
|
43
|
+
//
|
|
44
|
+
|
|
45
|
+
const TextNoteHolder = Schema.Struct({
|
|
46
|
+
instructions: Ref.Ref(Text.Text)
|
|
47
|
+
.pipe(Format.FormatAnnotation.set(Format.TypeFormat.Markdown))
|
|
48
|
+
.annotations({ title: 'Instructions' }),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const RefStory = () => {
|
|
52
|
+
const { space } = useClientStory();
|
|
53
|
+
const [text] = useQuery(space?.db, Filter.type(Text.Text));
|
|
54
|
+
const values = useMemo(() => (text ? { instructions: Ref.make(text) } : undefined), [text]);
|
|
55
|
+
|
|
56
|
+
if (!space || !values) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<TestLayout>
|
|
62
|
+
<Panel.Root>
|
|
63
|
+
<Panel.Content asChild>
|
|
64
|
+
<Form.Root schema={TextNoteHolder} values={values as any} db={space.db}>
|
|
65
|
+
<Form.Viewport>
|
|
66
|
+
<Form.Content>
|
|
67
|
+
<Form.FieldSet />
|
|
68
|
+
</Form.Content>
|
|
69
|
+
</Form.Viewport>
|
|
70
|
+
</Form.Root>
|
|
71
|
+
</Panel.Content>
|
|
72
|
+
</Panel.Root>
|
|
73
|
+
</TestLayout>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
//
|
|
78
|
+
// Ref<Text> empty — shows the create button.
|
|
79
|
+
//
|
|
80
|
+
|
|
81
|
+
const EmptyRefSchema = Schema.Struct({
|
|
82
|
+
instructions: Schema.optional(
|
|
83
|
+
Ref.Ref(Text.Text)
|
|
84
|
+
.pipe(Format.FormatAnnotation.set(Format.TypeFormat.Markdown))
|
|
85
|
+
.annotations({ title: 'Instructions' }),
|
|
86
|
+
),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const EmptyRefStory = () => {
|
|
90
|
+
const { space } = useClientStory();
|
|
91
|
+
if (!space) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<TestLayout>
|
|
97
|
+
<Panel.Root>
|
|
98
|
+
<Panel.Content asChild>
|
|
99
|
+
<Form.Root schema={EmptyRefSchema} defaultValues={{}} db={space.db}>
|
|
100
|
+
<Form.Viewport>
|
|
101
|
+
<Form.Content>
|
|
102
|
+
<Form.FieldSet />
|
|
103
|
+
</Form.Content>
|
|
104
|
+
</Form.Viewport>
|
|
105
|
+
</Form.Root>
|
|
106
|
+
</Panel.Content>
|
|
107
|
+
</Panel.Root>
|
|
108
|
+
</TestLayout>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const meta = {
|
|
113
|
+
title: 'ui/react-ui-form/MarkdownField',
|
|
114
|
+
decorators: [withTheme(), withLayout({ layout: 'column' })],
|
|
115
|
+
parameters: {
|
|
116
|
+
layout: 'fullscreen',
|
|
117
|
+
translations,
|
|
118
|
+
},
|
|
119
|
+
} satisfies Meta;
|
|
120
|
+
|
|
121
|
+
export default meta;
|
|
122
|
+
|
|
123
|
+
type Story = StoryObj<typeof meta>;
|
|
124
|
+
|
|
125
|
+
export const StringBacked: Story = {
|
|
126
|
+
render: () => <StringStory />,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export const RefPopulated: Story = {
|
|
130
|
+
render: () => <RefStory />,
|
|
131
|
+
decorators: [
|
|
132
|
+
withClientProvider({
|
|
133
|
+
createIdentity: true,
|
|
134
|
+
createSpace: true,
|
|
135
|
+
types: [Text.Text],
|
|
136
|
+
onCreateSpace: async ({ space }) => {
|
|
137
|
+
space.db.add(Obj.make(Text.Text, { content: '# Magazine brief\n\nLong-form text describing what to gather.' }));
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const RefEmpty: Story = {
|
|
144
|
+
render: () => <EmptyRefStory />,
|
|
145
|
+
decorators: [
|
|
146
|
+
withClientProvider({
|
|
147
|
+
createIdentity: true,
|
|
148
|
+
createSpace: true,
|
|
149
|
+
types: [Text.Text],
|
|
150
|
+
}),
|
|
151
|
+
],
|
|
152
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useAtomValue } from '@effect-atom/atom-react';
|
|
6
|
+
import React, { useCallback, useMemo } from 'react';
|
|
7
|
+
|
|
8
|
+
import { type Database, Obj, Ref } from '@dxos/echo';
|
|
9
|
+
import { AtomRef } from '@dxos/echo-atom';
|
|
10
|
+
import { createDocAccessor } from '@dxos/echo-db';
|
|
11
|
+
import { Button, Icon, Input, useTranslation } from '@dxos/react-ui';
|
|
12
|
+
import { Editor, useBasicMarkdownExtensions } from '@dxos/react-ui-editor';
|
|
13
|
+
import { Text } from '@dxos/schema';
|
|
14
|
+
import { createDataExtensions } from '@dxos/ui-editor';
|
|
15
|
+
|
|
16
|
+
import { translationKey } from '#translations';
|
|
17
|
+
|
|
18
|
+
import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
|
|
19
|
+
|
|
20
|
+
const editorClassNames =
|
|
21
|
+
'transition-colors bg-input-surface focus-within:bg-focus-surface border border-separator rounded-xs p-1 px-2';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Form field that edits a markdown value in a CodeMirror editor.
|
|
25
|
+
* Supports two backings:
|
|
26
|
+
* - `Schema.String` annotated with `Format.TypeFormat.Markdown`: edited as plain text via `getValue`/`onValueChange`.
|
|
27
|
+
* - `Ref<Text>` annotated with `Format.TypeFormat.Markdown`: edited in-place via an Automerge doc accessor.
|
|
28
|
+
* When the ref is empty and a database is available, a create button is shown to allocate a new Text.
|
|
29
|
+
*/
|
|
30
|
+
export const MarkdownField = ({
|
|
31
|
+
type,
|
|
32
|
+
readonly,
|
|
33
|
+
label,
|
|
34
|
+
placeholder,
|
|
35
|
+
layout,
|
|
36
|
+
db,
|
|
37
|
+
getStatus,
|
|
38
|
+
getValue,
|
|
39
|
+
onValueChange,
|
|
40
|
+
}: FormFieldComponentProps) => {
|
|
41
|
+
const { status, error } = getStatus();
|
|
42
|
+
const isRef = Ref.isRefType(type);
|
|
43
|
+
const value = getValue();
|
|
44
|
+
|
|
45
|
+
if ((readonly || layout === 'static') && value == null) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const renderEditor = () => {
|
|
50
|
+
if (isRef) {
|
|
51
|
+
const reference = value as Ref.Ref<any> | undefined;
|
|
52
|
+
if (reference) {
|
|
53
|
+
return <RefMarkdownEditor reference={reference} placeholder={placeholder} />;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (readonly) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return <CreateTextButton db={db} disabled={!db} onCreate={(ref) => onValueChange(type, ref)} />;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<StringMarkdownEditor
|
|
65
|
+
value={(value as string | undefined) ?? ''}
|
|
66
|
+
placeholder={placeholder}
|
|
67
|
+
readonly={!!readonly}
|
|
68
|
+
onChange={(next) => onValueChange(type, next)}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const renderStatic = () => {
|
|
74
|
+
if (isRef) {
|
|
75
|
+
const reference = value as Ref.Ref<any> | undefined;
|
|
76
|
+
return reference ? <RefStaticText reference={reference} /> : null;
|
|
77
|
+
}
|
|
78
|
+
return <p className='whitespace-pre-wrap'>{(value as string | undefined) ?? ''}</p>;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Input.Root validationValence={status}>
|
|
83
|
+
{layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} />}
|
|
84
|
+
{layout === 'static' ? renderStatic() : renderEditor()}
|
|
85
|
+
{layout === 'full' && <Input.Validation>{error}</Input.Validation>}
|
|
86
|
+
</Input.Root>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** Read-only static rendering for a `Ref<Text>` value: resolve the ref and print its content. */
|
|
91
|
+
const RefStaticText = ({ reference }: { reference: Ref.Ref<any> }) => {
|
|
92
|
+
const text = useAtomValue(useMemo(() => AtomRef.make(reference), [reference]));
|
|
93
|
+
if (!text?.content) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return <p className='whitespace-pre-wrap'>{text.content}</p>;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
type RefMarkdownEditorProps = {
|
|
100
|
+
reference: Ref.Ref<any>;
|
|
101
|
+
placeholder?: string;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const RefMarkdownEditor = ({ reference, placeholder }: RefMarkdownEditorProps) => {
|
|
105
|
+
const text = useAtomValue(useMemo(() => AtomRef.make(reference), [reference]));
|
|
106
|
+
const dataExtensions = useMemo(
|
|
107
|
+
() =>
|
|
108
|
+
text ? [createDataExtensions({ id: reference.dxn.toString(), text: createDocAccessor(text, ['content']) })] : [],
|
|
109
|
+
[text, reference],
|
|
110
|
+
);
|
|
111
|
+
const extensions = useBasicMarkdownExtensions({ placeholder, extensions: dataExtensions });
|
|
112
|
+
if (!text) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<Editor.Root>
|
|
118
|
+
<Editor.View classNames={editorClassNames} extensions={extensions} />
|
|
119
|
+
</Editor.Root>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
type StringMarkdownEditorProps = {
|
|
124
|
+
value: string;
|
|
125
|
+
placeholder?: string;
|
|
126
|
+
readonly?: boolean;
|
|
127
|
+
onChange: (value: string) => void;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const StringMarkdownEditor = ({ value, placeholder, readonly, onChange }: StringMarkdownEditorProps) => {
|
|
131
|
+
const extensions = useBasicMarkdownExtensions({ placeholder });
|
|
132
|
+
const handleChange = useCallback((next: string) => onChange(next), [onChange]);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<Editor.Root>
|
|
136
|
+
<Editor.View
|
|
137
|
+
classNames={editorClassNames}
|
|
138
|
+
extensions={extensions}
|
|
139
|
+
value={value}
|
|
140
|
+
onChange={readonly ? undefined : handleChange}
|
|
141
|
+
/>
|
|
142
|
+
</Editor.Root>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
type CreateTextButtonProps = {
|
|
147
|
+
db?: Database.Database;
|
|
148
|
+
disabled?: boolean;
|
|
149
|
+
onCreate: (ref: Ref.Ref<Text.Text>) => void;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const CreateTextButton = ({ db, disabled, onCreate }: CreateTextButtonProps) => {
|
|
153
|
+
const { t } = useTranslation(translationKey);
|
|
154
|
+
const handleClick = useCallback(() => {
|
|
155
|
+
if (!db) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const text = db.add(Obj.make(Text.Text, { content: '' }));
|
|
159
|
+
onCreate(Ref.make(text));
|
|
160
|
+
}, [db, onCreate]);
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<Button onClick={handleClick} disabled={disabled}>
|
|
164
|
+
<Icon icon='ph--plus--regular' size={4} />
|
|
165
|
+
<span>{t('create-text.label')}</span>
|
|
166
|
+
</Button>
|
|
167
|
+
);
|
|
168
|
+
};
|
|
@@ -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
|
+
};
|