@dxos/react-ui-form 0.8.4-main.bc674ce → 0.8.4-main.c351d160a8
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 +538 -458
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +538 -458
- 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 +3 -3
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +13 -13
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +3 -3
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +0 -1
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +2 -2
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldSet.d.ts +9 -10
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/RefField.d.ts +6 -4
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.d.ts +56 -0
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
- package/dist/types/src/components/{ControlSection/ControlSection.stories.d.ts → Settings/Settings.stories.d.ts} +2 -8
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
- package/dist/types/src/components/Settings/index.d.ts +2 -0
- package/dist/types/src/components/Settings/index.d.ts.map +1 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +5 -5
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +2 -1
- 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.map +1 -1
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +0 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +35 -37
- package/src/components/FieldEditor/FieldEditor.stories.tsx +4 -6
- package/src/components/FieldEditor/FieldEditor.tsx +2 -3
- package/src/components/Form/Form.stories.tsx +8 -7
- package/src/components/Form/Form.tsx +82 -55
- package/src/components/Form/FormField.tsx +12 -4
- package/src/components/Form/FormFieldComponent.stories.tsx +5 -5
- package/src/components/Form/FormFieldComponent.tsx +17 -20
- package/src/components/Form/FormFieldSet.tsx +106 -104
- package/src/components/Form/fields/ArrayField.tsx +6 -8
- package/src/components/Form/fields/MarkdownField.tsx +1 -1
- package/src/components/Form/fields/RefField.tsx +28 -15
- package/src/components/Form/fields/SelectField.tsx +1 -1
- package/src/components/Form/fields/SelectOptionField.stories.tsx +2 -2
- package/src/components/Form/fields/SelectOptionField.tsx +4 -4
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +9 -13
- package/src/components/Settings/Settings.stories.tsx +56 -0
- package/src/components/Settings/Settings.tsx +230 -0
- package/src/components/{ControlSection → Settings}/index.ts +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +14 -12
- package/src/components/ViewEditor/ViewEditor.test.tsx +3 -6
- package/src/components/ViewEditor/ViewEditor.tsx +92 -63
- package/src/components/index.ts +1 -1
- package/src/components/testing.tsx +9 -6
- package/src/hooks/useFormHandler.ts +42 -24
- package/src/util/index.ts +0 -1
- package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/index.d.ts +0 -2
- package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
- package/dist/types/src/components/Form/Form-old.stories.d.ts +0 -131
- package/dist/types/src/components/Form/Form-old.stories.d.ts.map +0 -1
- package/dist/types/src/util/echo.d.ts +0 -6
- package/dist/types/src/util/echo.d.ts.map +0 -1
- package/src/components/ControlSection/ControlSection.stories.tsx +0 -38
- package/src/components/ControlSection/ControlSection.tsx +0 -153
- package/src/components/Form/Form-old.stories.tsx +0 -314
- package/src/util/echo.ts +0 -25
|
@@ -2,13 +2,10 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, {
|
|
5
|
+
import React, { useMemo } from 'react';
|
|
6
6
|
|
|
7
7
|
import { type AnyProperties } from '@dxos/echo/internal';
|
|
8
8
|
import { type SchemaProperty } from '@dxos/effect';
|
|
9
|
-
import { type ThemedClassName } from '@dxos/react-ui';
|
|
10
|
-
import { mx } from '@dxos/ui-theme';
|
|
11
|
-
import { isTruthy } from '@dxos/util';
|
|
12
9
|
|
|
13
10
|
import { type FormHandlerProps } from '../../hooks';
|
|
14
11
|
import { getFormProperties } from '../../util';
|
|
@@ -17,109 +14,114 @@ import { useFormValues } from './Form';
|
|
|
17
14
|
import { FormField, type FormFieldProps } from './FormField';
|
|
18
15
|
import { FormFieldErrorBoundary, FormFieldLabel } from './FormFieldComponent';
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
>;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
17
|
+
const FORM_FIELDSET_NAME = 'Form.FieldSet';
|
|
18
|
+
|
|
19
|
+
export type FormFieldSetProps<T extends AnyProperties> = {
|
|
20
|
+
label?: string;
|
|
21
|
+
sort?: string[];
|
|
22
|
+
exclude?: (props: SchemaProperty[]) => SchemaProperty[];
|
|
23
|
+
} & Pick<FormHandlerProps<T>, 'schema'> &
|
|
24
|
+
Pick<
|
|
25
|
+
FormFieldProps,
|
|
26
|
+
| 'path'
|
|
27
|
+
| 'autoFocus'
|
|
28
|
+
| 'readonly'
|
|
29
|
+
| 'layout'
|
|
30
|
+
| 'projection'
|
|
31
|
+
| 'fieldMap'
|
|
32
|
+
| 'fieldProvider'
|
|
33
|
+
| 'createOptionLabel'
|
|
34
|
+
| 'createOptionIcon'
|
|
35
|
+
| 'createInitialValuePath'
|
|
36
|
+
| 'db'
|
|
37
|
+
| 'schemaHook'
|
|
38
|
+
| 'getOptions'
|
|
39
|
+
| 'onCreate'
|
|
40
|
+
>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Renders a set of form fields derived from a schema object.
|
|
44
|
+
*/
|
|
45
|
+
export const FormFieldSet = ({
|
|
46
|
+
label,
|
|
47
|
+
schema,
|
|
48
|
+
readonly,
|
|
49
|
+
path,
|
|
50
|
+
sort,
|
|
51
|
+
exclude,
|
|
52
|
+
projection,
|
|
53
|
+
layout,
|
|
54
|
+
...props
|
|
55
|
+
}: FormFieldSetProps<any>) => {
|
|
56
|
+
const values = useFormValues(FORM_FIELDSET_NAME, path);
|
|
57
|
+
|
|
58
|
+
// TODO(burdon): Updates on every value change.
|
|
59
|
+
// Remove values dep if can remove from getSchemaProperties.
|
|
60
|
+
const properties = useMemo(() => {
|
|
61
|
+
if (!schema) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
65
|
+
// TODO(wittjosiah): Reconcile FormInputAnnotation with projection hidden properties & exclude function.
|
|
66
|
+
const schemaProps = getFormProperties(schema.ast);
|
|
67
|
+
const filteredProps = exclude ? exclude(schemaProps) : schemaProps;
|
|
68
|
+
|
|
69
|
+
// Use projection-based field management when view and projection are available.
|
|
70
|
+
if (projection) {
|
|
71
|
+
const fieldProjections = projection.getFieldProjections();
|
|
72
|
+
const hiddenProperties = new Set(projection.getHiddenProperties());
|
|
73
|
+
|
|
74
|
+
// Filter properties to only include visible ones and order by projection.
|
|
75
|
+
const visibleProps = filteredProps.filter((prop) => !hiddenProperties.has(prop.name.toString()));
|
|
76
|
+
const orderedProps: SchemaProperty[] = [];
|
|
77
|
+
|
|
78
|
+
// Add properties in projection field order.
|
|
79
|
+
for (const fieldProjection of fieldProjections) {
|
|
80
|
+
const fieldPath = String(fieldProjection.field.path);
|
|
81
|
+
const prop = visibleProps.find((p) => p.name === fieldPath);
|
|
82
|
+
if (prop) {
|
|
83
|
+
orderedProps.push(prop);
|
|
75
84
|
}
|
|
76
|
-
|
|
77
|
-
// Add any remaining properties not in projection.
|
|
78
|
-
const projectionPaths = new Set(fieldProjections.map((projection) => String(projection.field.path)));
|
|
79
|
-
const remainingProps = visibleProps.filter((prop) => !projectionPaths.has(prop.name.toString()));
|
|
80
|
-
orderedProps.push(...remainingProps);
|
|
81
|
-
return orderedProps;
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
//
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}, [schema, values, exclude, sort, projection?.fields]);
|
|
90
|
-
|
|
91
|
-
if ((readonly || layout === 'static') && values == null) {
|
|
92
|
-
return null;
|
|
87
|
+
// Add any remaining properties not in projection.
|
|
88
|
+
const projectionPaths = new Set(fieldProjections.map((projection) => String(projection.field.path)));
|
|
89
|
+
const remainingProps = visibleProps.filter((prop) => !projectionPaths.has(prop.name.toString()));
|
|
90
|
+
orderedProps.push(...remainingProps);
|
|
91
|
+
return orderedProps;
|
|
93
92
|
}
|
|
94
93
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
|
|
94
|
+
// Fallback to legacy filter/sort behavior.
|
|
95
|
+
return sort
|
|
96
|
+
? [...filteredProps].sort(({ name: a }, { name: b }) => sort.indexOf(a.toString()) - sort.indexOf(b.toString()))
|
|
97
|
+
: filteredProps;
|
|
98
|
+
}, [schema, values, exclude, sort, projection]);
|
|
99
|
+
|
|
100
|
+
if ((readonly || layout === 'static') && values == null) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<>
|
|
106
|
+
{layout !== 'inline' && label && <FormFieldLabel label={label} asChild />}
|
|
107
|
+
{properties.map((property) => {
|
|
108
|
+
const name = property.name.toString();
|
|
109
|
+
return (
|
|
110
|
+
<FormFieldErrorBoundary key={name} path={[...(path ?? []), name]}>
|
|
111
|
+
<FormField
|
|
112
|
+
type={property.type}
|
|
113
|
+
name={name}
|
|
114
|
+
path={[...(path ?? []), name]}
|
|
115
|
+
readonly={readonly}
|
|
116
|
+
layout={layout}
|
|
117
|
+
projection={projection}
|
|
118
|
+
{...props}
|
|
119
|
+
/>
|
|
120
|
+
</FormFieldErrorBoundary>
|
|
121
|
+
);
|
|
122
|
+
})}
|
|
123
|
+
</>
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
FormFieldSet.displayName = FORM_FIELDSET_NAME;
|
|
@@ -91,9 +91,9 @@ export const ArrayField = ({
|
|
|
91
91
|
/>
|
|
92
92
|
|
|
93
93
|
{!readonly && layout !== 'static' && (
|
|
94
|
-
<div role='none' className='flex flex-col
|
|
94
|
+
<div role='none' className='flex flex-col h-full justify-end'>
|
|
95
95
|
{/* NOTE: Aligns with center of last field if multi-field object. */}
|
|
96
|
-
<div role='none' className='flex items-center
|
|
96
|
+
<div role='none' className='flex items-center h-[2rem]'>
|
|
97
97
|
<IconButton
|
|
98
98
|
icon='ph--x--regular'
|
|
99
99
|
iconOnly
|
|
@@ -111,12 +111,7 @@ export const ArrayField = ({
|
|
|
111
111
|
|
|
112
112
|
{/* TODO(burdon): Get label from schema. */}
|
|
113
113
|
{!readonly && layout !== 'static' && (
|
|
114
|
-
<IconButton
|
|
115
|
-
classNames='flex is-full _mlb-cardSpacingBlock'
|
|
116
|
-
icon='ph--plus--regular'
|
|
117
|
-
label={t('add field')}
|
|
118
|
-
onClick={handleAdd}
|
|
119
|
-
/>
|
|
114
|
+
<IconButton classNames='flex w-full' icon='ph--plus--regular' label={t('add field')} onClick={handleAdd} />
|
|
120
115
|
)}
|
|
121
116
|
</>
|
|
122
117
|
);
|
|
@@ -140,6 +135,9 @@ export const getDefaultValue = (ast?: SchemaAST.AST): any => {
|
|
|
140
135
|
case 'BooleanKeyword': {
|
|
141
136
|
return false;
|
|
142
137
|
}
|
|
138
|
+
case 'Suspend': {
|
|
139
|
+
return getDefaultValue(ast.f());
|
|
140
|
+
}
|
|
143
141
|
default: {
|
|
144
142
|
if (ast && isNestedType(ast)) {
|
|
145
143
|
return {};
|
|
@@ -65,7 +65,7 @@ export const MarkdownField = ({
|
|
|
65
65
|
disabled={!!readonly}
|
|
66
66
|
placeholder={placeholder}
|
|
67
67
|
value={value ?? ''}
|
|
68
|
-
classNames={'min-
|
|
68
|
+
classNames={'min-h-auto max-h-40 overflow-auto'}
|
|
69
69
|
onChange={handleChange}
|
|
70
70
|
onBlur={onBlur}
|
|
71
71
|
style={{ resize: 'none' }}
|
|
@@ -6,14 +6,15 @@ import '@dxos/lit-ui/dx-tag-picker.pcss';
|
|
|
6
6
|
|
|
7
7
|
import React, { useCallback, useMemo } from 'react';
|
|
8
8
|
|
|
9
|
-
import { type Database, Entity, Filter, Ref,
|
|
9
|
+
import { type Database, Entity, Filter, Obj, Ref, Type } from '@dxos/echo';
|
|
10
10
|
import { ReferenceAnnotationId, type ReferenceAnnotationValue } from '@dxos/echo/internal';
|
|
11
11
|
import { useQuery, useSchema as useSchema$ } from '@dxos/echo-react';
|
|
12
12
|
import { findAnnotation } from '@dxos/effect';
|
|
13
13
|
import { DXN } from '@dxos/keys';
|
|
14
14
|
import { DxAnchor } from '@dxos/lit-ui/react';
|
|
15
15
|
import { Button, Icon, Input, useTranslation } from '@dxos/react-ui';
|
|
16
|
-
import {
|
|
16
|
+
import { ParentLabelAnnotationId } from '@dxos/schema';
|
|
17
|
+
import { mx } from '@dxos/ui-theme';
|
|
17
18
|
|
|
18
19
|
import { translationKey } from '../../../translations';
|
|
19
20
|
import { ObjectPicker, type ObjectPickerContentProps, type RefOption } from '../../ObjectPicker';
|
|
@@ -25,23 +26,32 @@ const isRefSnapshot = (val: any): val is { '/': string } => {
|
|
|
25
26
|
return typeof val === 'object' && typeof (val as any)?.['/'] === 'string';
|
|
26
27
|
};
|
|
27
28
|
|
|
28
|
-
const defaultGetOptions: NonNullable<RefFieldProps['getOptions']> = (results) =>
|
|
29
|
+
const defaultGetOptions: NonNullable<RefFieldProps['getOptions']> = (results, { parentLabel } = {}) =>
|
|
29
30
|
results.map((result) => {
|
|
30
31
|
const id = Entity.getDXN(result).toString();
|
|
31
|
-
const
|
|
32
|
+
const parent = parentLabel ? Obj.getParent(result as Obj.Unknown) : undefined;
|
|
33
|
+
const label = parent ? Entity.getLabel(parent) : Entity.getLabel(result);
|
|
32
34
|
return { id, label: label ?? id };
|
|
33
35
|
});
|
|
34
36
|
|
|
35
37
|
const defaultResultsHook: NonNullable<RefFieldProps['resultsHook']> = (db, typename) =>
|
|
36
|
-
useQuery(
|
|
38
|
+
useQuery(
|
|
39
|
+
db,
|
|
40
|
+
typename
|
|
41
|
+
? // For Ref.Ref(Obj.Unknown) we want to show all objects.
|
|
42
|
+
typename === Type.getTypename(Obj.Unknown)
|
|
43
|
+
? Filter.everything()
|
|
44
|
+
: Filter.typename(typename)
|
|
45
|
+
: Filter.nothing(),
|
|
46
|
+
);
|
|
37
47
|
|
|
38
48
|
export type RefFieldProps = FormFieldComponentProps &
|
|
39
49
|
Pick<ObjectPickerContentProps, 'createOptionLabel' | 'createOptionIcon' | 'createInitialValuePath'> & {
|
|
40
50
|
db?: Database.Database;
|
|
41
51
|
resultsHook?: (db?: Database.Database, typename?: string) => Entity.Any[];
|
|
42
|
-
schemaHook?: (db?: Database.Database, typename?: string) => Type.
|
|
43
|
-
getOptions?: (objects: Entity.Any[]) => RefOption[];
|
|
44
|
-
onCreate?: (schema: Type.
|
|
52
|
+
schemaHook?: (db?: Database.Database, typename?: string) => Type.AnyEntity;
|
|
53
|
+
getOptions?: (objects: Entity.Any[], options?: { parentLabel?: boolean }) => RefOption[];
|
|
54
|
+
onCreate?: (schema: Type.AnyEntity, values: any) => void;
|
|
45
55
|
};
|
|
46
56
|
|
|
47
57
|
export const RefField = (props: RefFieldProps) => {
|
|
@@ -72,7 +82,10 @@ export const RefField = (props: RefFieldProps) => {
|
|
|
72
82
|
);
|
|
73
83
|
|
|
74
84
|
const results = useResults(db, typename);
|
|
75
|
-
const options = useMemo(() =>
|
|
85
|
+
const options = useMemo(() => {
|
|
86
|
+
const parentLabel = type ? findAnnotation<boolean>(type, ParentLabelAnnotationId) === true : false;
|
|
87
|
+
return getOptions(results, { parentLabel });
|
|
88
|
+
}, [results, getOptions, type]);
|
|
76
89
|
|
|
77
90
|
const handleGetValue = useCallback(() => {
|
|
78
91
|
const formValue = getValue();
|
|
@@ -135,9 +148,9 @@ export const RefField = (props: RefFieldProps) => {
|
|
|
135
148
|
<div>
|
|
136
149
|
{readonly ? (
|
|
137
150
|
!item ? (
|
|
138
|
-
<p className={mx(
|
|
151
|
+
<p className={mx('text-description', 'mb-2')}>{t('empty readonly ref field label')}</p>
|
|
139
152
|
) : (
|
|
140
|
-
<DxAnchor key={item.id}
|
|
153
|
+
<DxAnchor key={item.id} dxn={item.id} rootclassname='me-1'>
|
|
141
154
|
{item.label}
|
|
142
155
|
</DxAnchor>
|
|
143
156
|
)
|
|
@@ -145,13 +158,13 @@ export const RefField = (props: RefFieldProps) => {
|
|
|
145
158
|
<ObjectPicker.Root>
|
|
146
159
|
<ObjectPicker.Trigger asChild classNames='p-0'>
|
|
147
160
|
{item ? (
|
|
148
|
-
<div className='flex gap-2
|
|
161
|
+
<div className='flex gap-2 w-full'>
|
|
149
162
|
<Input.Root key={item.id}>
|
|
150
|
-
<Input.TextInput value={item.label} readOnly classNames='
|
|
163
|
+
<Input.TextInput value={item.label} readOnly classNames='w-full' />
|
|
151
164
|
</Input.Root>
|
|
152
165
|
</div>
|
|
153
166
|
) : (
|
|
154
|
-
<Button classNames='
|
|
167
|
+
<Button classNames='w-full text-start gap-2'>
|
|
155
168
|
<div role='none' className='grow overflow-hidden'>
|
|
156
169
|
<span className='flex truncate text-description'>{placeholder ?? t('ref field placeholder')}</span>
|
|
157
170
|
</div>
|
|
@@ -161,7 +174,7 @@ export const RefField = (props: RefFieldProps) => {
|
|
|
161
174
|
</ObjectPicker.Trigger>
|
|
162
175
|
<ObjectPicker.Portal>
|
|
163
176
|
<ObjectPicker.Content
|
|
164
|
-
classNames='
|
|
177
|
+
classNames='dx-card-popover-width'
|
|
165
178
|
options={options}
|
|
166
179
|
selectedIds={selectedIds}
|
|
167
180
|
createSchema={createSchema && omitId(createSchema)}
|
|
@@ -43,7 +43,7 @@ export const SelectField = ({
|
|
|
43
43
|
) : (
|
|
44
44
|
<Select.Root value={value} onValueChange={handleValueChange}>
|
|
45
45
|
{/* TODO(burdon): Placeholder not working? */}
|
|
46
|
-
<Select.TriggerButton classNames='
|
|
46
|
+
<Select.TriggerButton classNames='w-full' disabled={!!readonly} placeholder={placeholder} />
|
|
47
47
|
<Select.Portal>
|
|
48
48
|
<Select.Content>
|
|
49
49
|
<Select.Viewport>
|
|
@@ -7,7 +7,7 @@ import * as Schema from 'effect/Schema';
|
|
|
7
7
|
import React, { useState } from 'react';
|
|
8
8
|
|
|
9
9
|
import { SelectOption } from '@dxos/echo/internal';
|
|
10
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
10
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
11
11
|
|
|
12
12
|
import { translations } from '../../../translations';
|
|
13
13
|
import { TestLayout } from '../../testing';
|
|
@@ -50,7 +50,7 @@ const meta = {
|
|
|
50
50
|
title: 'ui/react-ui-form/SelectOptionField',
|
|
51
51
|
component: SelectOptionField as any,
|
|
52
52
|
render: DefaultStory,
|
|
53
|
-
decorators: [withTheme],
|
|
53
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
54
54
|
parameters: {
|
|
55
55
|
layout: 'fullscreen',
|
|
56
56
|
translations,
|
|
@@ -128,13 +128,13 @@ export const SelectOptionField = ({
|
|
|
128
128
|
readonly={!!readonly}
|
|
129
129
|
>
|
|
130
130
|
{({ items }) => (
|
|
131
|
-
<div role='list' className='
|
|
131
|
+
<div role='list' className='w-full overflow-auto'>
|
|
132
132
|
{items?.map((item) => (
|
|
133
133
|
<div key={item.id}>
|
|
134
134
|
<List.Item
|
|
135
135
|
role='button'
|
|
136
136
|
item={item}
|
|
137
|
-
classNames={[subtleHover, 'flex flex-col cursor-pointer rounded-
|
|
137
|
+
classNames={[subtleHover, 'flex flex-col cursor-pointer rounded-xs']}
|
|
138
138
|
aria-expanded={selected === item.id}
|
|
139
139
|
>
|
|
140
140
|
<div className='flex items-center'>
|
|
@@ -152,7 +152,7 @@ export const SelectOptionField = ({
|
|
|
152
152
|
/>
|
|
153
153
|
</div>
|
|
154
154
|
{selected === item.id && (
|
|
155
|
-
<div className='
|
|
155
|
+
<div className='flex flex-col p-form-padding gap-1 dx-density-fine'>
|
|
156
156
|
{/* 16px to match drag handle width. */}
|
|
157
157
|
<Input.Label classNames='text-xs'>{t('select option label')}</Input.Label>
|
|
158
158
|
<div className='grid grid-cols-[1fr_min-content_min-content] gap-1'>
|
|
@@ -185,7 +185,7 @@ export const SelectOptionField = ({
|
|
|
185
185
|
label={t('select option add')}
|
|
186
186
|
onClick={handleAdd}
|
|
187
187
|
disabled={!!readonly}
|
|
188
|
-
classNames='
|
|
188
|
+
classNames='w-full'
|
|
189
189
|
/>
|
|
190
190
|
</div>
|
|
191
191
|
)}
|
|
@@ -74,15 +74,15 @@ const DefaultStory = () => {
|
|
|
74
74
|
);
|
|
75
75
|
|
|
76
76
|
return (
|
|
77
|
-
<div role='none' className='flex
|
|
77
|
+
<div role='none' className='flex w-96'>
|
|
78
78
|
<ObjectPicker.Root open={isOpen} onOpenChange={setIsOpen}>
|
|
79
79
|
<ObjectPicker.Trigger asChild>
|
|
80
|
-
<Button variant='primary' data-testid='trigger' classNames='
|
|
80
|
+
<Button variant='primary' data-testid='trigger' classNames='w-full'>
|
|
81
81
|
Select Person
|
|
82
82
|
</Button>
|
|
83
83
|
</ObjectPicker.Trigger>
|
|
84
84
|
<ObjectPicker.Content
|
|
85
|
-
classNames='
|
|
85
|
+
classNames='dx-card-popover-width'
|
|
86
86
|
options={options}
|
|
87
87
|
createSchema={personSchema}
|
|
88
88
|
createOptionLabel={['create new person label', { ns: osTranslations }]}
|
|
@@ -100,7 +100,7 @@ const meta = {
|
|
|
100
100
|
title: 'ui/react-ui-form/ObjectPicker',
|
|
101
101
|
render: DefaultStory,
|
|
102
102
|
decorators: [
|
|
103
|
-
withTheme,
|
|
103
|
+
withTheme(),
|
|
104
104
|
withClientProvider({
|
|
105
105
|
types: [Person.Person],
|
|
106
106
|
createIdentity: true,
|
|
@@ -201,14 +201,10 @@ export const WithTest: Story = {
|
|
|
201
201
|
await userEvent.click(saveButton);
|
|
202
202
|
|
|
203
203
|
// Check that clicking the save button calls onCreate with expected values.
|
|
204
|
-
await expect(mockHandleCreate).toHaveBeenCalledWith(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
identities: [],
|
|
210
|
-
urls: [],
|
|
211
|
-
fields: [],
|
|
212
|
-
});
|
|
204
|
+
await expect(mockHandleCreate).toHaveBeenCalledWith(
|
|
205
|
+
expect.objectContaining({
|
|
206
|
+
fullName: unrelatedTerm,
|
|
207
|
+
}),
|
|
208
|
+
);
|
|
213
209
|
},
|
|
214
210
|
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
import { faker } from '@dxos/random';
|
|
9
|
+
import { Button, Input } from '@dxos/react-ui';
|
|
10
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
11
|
+
|
|
12
|
+
import { translations } from '../../translations';
|
|
13
|
+
|
|
14
|
+
import { Settings } from './Settings';
|
|
15
|
+
|
|
16
|
+
faker.seed(132);
|
|
17
|
+
|
|
18
|
+
const DefaultStory = () => {
|
|
19
|
+
return (
|
|
20
|
+
<Settings.Root>
|
|
21
|
+
<Settings.Section title='Settings' description={faker.lorem.paragraphs(1)}>
|
|
22
|
+
<Settings.Group>
|
|
23
|
+
<Settings.ItemInput title={faker.lorem.sentence(2)} description={faker.lorem.paragraphs(1)}>
|
|
24
|
+
<Input.Root>
|
|
25
|
+
<Input.TextInput placeholder='Input' />
|
|
26
|
+
</Input.Root>
|
|
27
|
+
</Settings.ItemInput>
|
|
28
|
+
<Settings.ItemInput title={faker.lorem.sentence(2)} description={faker.lorem.paragraphs(2)}>
|
|
29
|
+
<Input.Root>
|
|
30
|
+
<Input.Switch />
|
|
31
|
+
</Input.Root>
|
|
32
|
+
</Settings.ItemInput>
|
|
33
|
+
<Settings.ItemInput title={faker.lorem.sentence(3)} description={faker.lorem.paragraphs(2)}>
|
|
34
|
+
<Button>Test</Button>
|
|
35
|
+
</Settings.ItemInput>
|
|
36
|
+
</Settings.Group>
|
|
37
|
+
</Settings.Section>
|
|
38
|
+
</Settings.Root>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const meta = {
|
|
43
|
+
title: 'ui/react-ui-form/Settings',
|
|
44
|
+
render: DefaultStory,
|
|
45
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
46
|
+
parameters: {
|
|
47
|
+
layout: 'fullscreen',
|
|
48
|
+
translations,
|
|
49
|
+
},
|
|
50
|
+
} satisfies Meta;
|
|
51
|
+
|
|
52
|
+
export default meta;
|
|
53
|
+
|
|
54
|
+
type Story = StoryObj<typeof meta>;
|
|
55
|
+
|
|
56
|
+
export const Default: Story = {};
|