@dxos/react-ui-form 0.9.1-staging.ee54ba693a → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/index.mjs +4064 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/translations.mjs +77 -0
- package/dist/lib/translations.mjs.map +1 -0
- package/dist/lib/types.mjs +0 -0
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +1 -0
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Card.stories.d.ts +88 -0
- package/dist/types/src/components/Form/Card.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/Form.d.ts +7 -7
- package/dist/types/src/components/Form/Form.stories.d.ts +21 -0
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.theme.d.ts +6 -0
- package/dist/types/src/components/Form/Form.theme.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormControls.d.ts +7 -7
- package/dist/types/src/components/Form/FormControls.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/FormField.d.ts +1 -1
- package/dist/types/src/components/Form/FormField/FormField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/FormRow.d.ts +17 -3
- package/dist/types/src/components/Form/FormField/FormRow.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/ArrayField/ArrayField.d.ts +0 -6
- package/dist/types/src/components/Form/FormField/fields/ArrayField/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/ArrayField/ArrayField.stories.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/ArrayField/ArrayField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/ArrayField/default-value.d.ts +7 -0
- package/dist/types/src/components/Form/FormField/fields/ArrayField/default-value.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/ArrayField/index.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/ArrayField/index.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/BooleanField/BooleanField.stories.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/BooleanField/BooleanField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/DateField/DateField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/DateField/DateField.stories.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/DateField/DateField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/GeoPointField/GeoPointField.stories.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/GeoPointField/GeoPointField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/MarkdownField/MarkdownField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/MarkdownField/MarkdownField.stories.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/MarkdownField/MarkdownField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.d.ts +0 -11
- package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.stories.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/NumberField/index.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/NumberField/index.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/NumberField/numeric-constraints.d.ts +12 -0
- package/dist/types/src/components/Form/FormField/fields/NumberField/numeric-constraints.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/PasswordField/PasswordField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/PasswordField/PasswordField.stories.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/PasswordField/PasswordField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/RefField/RefField.d.ts +1 -11
- package/dist/types/src/components/Form/FormField/fields/RefField/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/RefField/RefField.stories.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/RefField/RefField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/RefField/find-ref-option.d.ts +13 -0
- package/dist/types/src/components/Form/FormField/fields/RefField/find-ref-option.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/RefField/index.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/RefField/index.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/SelectField/SelectField.d.ts +1 -13
- package/dist/types/src/components/Form/FormField/fields/SelectField/SelectField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/SelectField/SelectField.stories.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/SelectField/SelectField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/SelectField/create-select-field.d.ts +15 -0
- package/dist/types/src/components/Form/FormField/fields/SelectField/create-select-field.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/SelectField/index.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/SelectField/index.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/SelectOptionField/SelectOptionField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/SelectOptionField/SelectOptionField.stories.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/SelectOptionField/SelectOptionField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/TextAreaField/TextAreaField.stories.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/TextAreaField/TextAreaField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/TextField/TextField.stories.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/TextField/TextField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField/fields/TupleField/TupleField.stories.d.ts +1 -0
- package/dist/types/src/components/Form/FormField/fields/TupleField/TupleField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldSet/FormFieldSetContainer.d.ts +1 -1
- package/dist/types/src/components/Form/FormFieldSet/FormFieldSetContainer.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormLayout/FormLayout.d.ts +0 -27
- package/dist/types/src/components/Form/FormLayout/FormLayout.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormLayout/FormLayout.stories.d.ts +1 -0
- package/dist/types/src/components/Form/FormLayout/FormLayout.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormLayout/index.d.ts +1 -0
- package/dist/types/src/components/Form/FormLayout/index.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormLayout/resolve-layout-field.d.ts +29 -0
- package/dist/types/src/components/Form/FormLayout/resolve-layout-field.d.ts.map +1 -0
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts +10 -1
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -1
- package/dist/types/src/components/ObjectForm/ObjectForm.stories.d.ts +2 -0
- package/dist/types/src/components/ObjectForm/ObjectForm.stories.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +8 -23
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +1 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts +1 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +1 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts.map +1 -1
- package/dist/types/src/components/ObjectTree/ObjectTree.stories.d.ts +1 -0
- package/dist/types/src/components/ObjectTree/ObjectTree.stories.d.ts.map +1 -1
- package/dist/types/src/components/RefEditor/RefEditor.d.ts.map +1 -1
- package/dist/types/src/components/RefEditor/RefEditor.stories.d.ts +2 -0
- package/dist/types/src/components/RefEditor/RefEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/RefEditor/ref-editor-extension.d.ts +9 -8
- package/dist/types/src/components/RefEditor/ref-editor-extension.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +1 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/hooks/useFormHandler.d.ts +6 -2
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useFormHandler.test.d.ts +2 -0
- package/dist/types/src/hooks/useFormHandler.test.d.ts.map +1 -0
- package/dist/types/src/testing/TestLayout.d.ts +1 -1
- package/dist/types/src/testing/TestLayout.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +5 -0
- package/dist/types/src/types.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 +39 -42
- package/src/components/Form/Card.stories.tsx +93 -0
- package/src/components/Form/Form.stories.tsx +95 -3
- package/src/components/Form/Form.theme.ts +14 -8
- package/src/components/Form/Form.tsx +2 -2
- package/src/components/Form/FormControls.tsx +32 -13
- package/src/components/Form/FormField/FormField.tsx +11 -3
- package/src/components/Form/FormField/FormRow.tsx +20 -1
- package/src/components/Form/FormField/fields/ArrayField/ArrayField.tsx +3 -46
- package/src/components/Form/FormField/fields/ArrayField/default-value.ts +49 -0
- package/src/components/Form/FormField/fields/ArrayField/index.ts +2 -0
- package/src/components/Form/FormField/fields/ComboboxField/ComboboxField.tsx +12 -2
- package/src/components/Form/FormField/fields/DateField/DateField.tsx +10 -3
- package/src/components/Form/FormField/fields/MarkdownField/MarkdownField.stories.tsx +1 -1
- package/src/components/Form/FormField/fields/MarkdownField/MarkdownField.tsx +2 -12
- package/src/components/Form/FormField/fields/NumberField/NumberField.test.ts +1 -1
- package/src/components/Form/FormField/fields/NumberField/NumberField.tsx +1 -36
- package/src/components/Form/FormField/fields/NumberField/index.ts +2 -0
- package/src/components/Form/FormField/fields/NumberField/numeric-constraints.ts +43 -0
- package/src/components/Form/FormField/fields/PasswordField/PasswordField.tsx +1 -0
- package/src/components/Form/FormField/fields/RefField/InlineRefField.tsx +2 -2
- package/src/components/Form/FormField/fields/RefField/RefField.test.ts +1 -1
- package/src/components/Form/FormField/fields/RefField/RefField.tsx +3 -45
- package/src/components/Form/FormField/fields/RefField/find-ref-option.ts +56 -0
- package/src/components/Form/FormField/fields/RefField/index.ts +2 -0
- package/src/components/Form/FormField/fields/SelectField/SelectField.tsx +2 -67
- package/src/components/Form/FormField/fields/SelectField/create-select-field.tsx +80 -0
- package/src/components/Form/FormField/fields/SelectField/index.ts +2 -0
- package/src/components/Form/FormField/fields/SelectOptionField/SelectOptionField.tsx +2 -1
- package/src/components/Form/FormFieldSet/FormFieldSetContainer.tsx +11 -9
- package/src/components/Form/FormLayout/FormLayout.test.ts +1 -1
- package/src/components/Form/FormLayout/FormLayout.tsx +1 -86
- package/src/components/Form/FormLayout/index.ts +2 -0
- package/src/components/Form/FormLayout/resolve-layout-field.ts +98 -0
- package/src/components/ObjectForm/ObjectForm.stories.tsx +93 -3
- package/src/components/ObjectForm/ObjectForm.tsx +22 -4
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +1 -1
- package/src/components/ObjectPicker/ObjectPicker.tsx +3 -3
- package/src/components/ObjectProperties/ObjectProperties.stories.tsx +1 -1
- package/src/components/ObjectProperties/ObjectProperties.tsx +7 -1
- package/src/components/ObjectTree/ObjectTree.stories.tsx +1 -1
- package/src/components/RefEditor/RefEditor.stories.tsx +9 -1
- package/src/components/RefEditor/RefEditor.tsx +42 -6
- package/src/components/RefEditor/ref-editor-extension.ts +168 -91
- package/src/components/ViewEditor/ViewEditor.stories.tsx +1 -1
- package/src/components/ViewEditor/ViewEditor.tsx +9 -9
- package/src/hooks/useFormHandler.test.ts +85 -0
- package/src/hooks/useFormHandler.ts +118 -31
- package/src/translations.ts +1 -0
- package/src/types.ts +5 -0
- package/src/util/index.ts +0 -1
- package/dist/lib/browser/index.mjs +0 -4675
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/translations.mjs +0 -87
- package/dist/lib/browser/translations.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -4677
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/translations.mjs +0 -89
- package/dist/lib/node-esm/translations.mjs.map +0 -7
- package/dist/types/src/util/formatting.d.ts +0 -15
- package/dist/types/src/util/formatting.d.ts.map +0 -1
- package/src/util/formatting.ts +0 -171
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-form",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Data components.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -19,14 +19,12 @@
|
|
|
19
19
|
".": {
|
|
20
20
|
"source": "./src/index.ts",
|
|
21
21
|
"types": "./dist/types/src/index.d.ts",
|
|
22
|
-
"
|
|
23
|
-
"node": "./dist/lib/node-esm/index.mjs"
|
|
22
|
+
"import": "./dist/lib/index.mjs"
|
|
24
23
|
},
|
|
25
24
|
"./translations": {
|
|
26
25
|
"source": "./src/translations.ts",
|
|
27
26
|
"types": "./dist/types/src/translations.d.ts",
|
|
28
|
-
"
|
|
29
|
-
"node": "./dist/lib/node-esm/translations.mjs"
|
|
27
|
+
"import": "./dist/lib/translations.mjs"
|
|
30
28
|
}
|
|
31
29
|
},
|
|
32
30
|
"types": "dist/types/src/index.d.ts",
|
|
@@ -36,36 +34,35 @@
|
|
|
36
34
|
],
|
|
37
35
|
"dependencies": {
|
|
38
36
|
"@codemirror/lang-xml": "^6.1.0",
|
|
39
|
-
"@codemirror/state": "^6.
|
|
40
|
-
"@codemirror/view": "^6.43.
|
|
37
|
+
"@codemirror/state": "^6.7.1",
|
|
38
|
+
"@codemirror/view": "^6.43.6",
|
|
41
39
|
"@effect-atom/atom-react": "^0.5.0",
|
|
42
40
|
"@fluentui/react-tabster": "9.26.11",
|
|
43
41
|
"@radix-ui/react-context": "1.1.1",
|
|
44
|
-
"@radix-ui/react-use-controllable-state": "1.1.0",
|
|
45
42
|
"date-fns": "^3.6.0",
|
|
46
43
|
"tabster": "^8.5.5",
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/echo": "0.
|
|
50
|
-
"@dxos/echo-protocol": "0.
|
|
51
|
-
"@dxos/
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/invariant": "0.
|
|
54
|
-
"@dxos/keys": "0.
|
|
55
|
-
"@dxos/lit-ui": "0.
|
|
56
|
-
"@dxos/log": "0.
|
|
57
|
-
"@dxos/react-client": "0.
|
|
58
|
-
"@dxos/react-ui-
|
|
59
|
-
"@dxos/react-ui-
|
|
60
|
-
"@dxos/react-ui-
|
|
61
|
-
"@dxos/react-ui-
|
|
62
|
-
"@dxos/react-ui-
|
|
63
|
-
"@dxos/
|
|
64
|
-
"@dxos/
|
|
65
|
-
"@dxos/
|
|
66
|
-
"@dxos/ui": "0.
|
|
67
|
-
"@dxos/
|
|
68
|
-
"@dxos/
|
|
44
|
+
"@dxos/echo-doc": "0.11.0",
|
|
45
|
+
"@dxos/async": "0.11.0",
|
|
46
|
+
"@dxos/echo": "0.11.0",
|
|
47
|
+
"@dxos/echo-protocol": "0.11.0",
|
|
48
|
+
"@dxos/effect": "0.11.0",
|
|
49
|
+
"@dxos/echo-react": "0.11.0",
|
|
50
|
+
"@dxos/invariant": "0.11.0",
|
|
51
|
+
"@dxos/keys": "0.11.0",
|
|
52
|
+
"@dxos/lit-ui": "0.11.0",
|
|
53
|
+
"@dxos/log": "0.11.0",
|
|
54
|
+
"@dxos/react-client": "0.11.0",
|
|
55
|
+
"@dxos/react-ui-list": "0.11.0",
|
|
56
|
+
"@dxos/react-ui-markdown": "0.11.0",
|
|
57
|
+
"@dxos/react-ui-components": "0.11.0",
|
|
58
|
+
"@dxos/react-ui-pickers": "0.11.0",
|
|
59
|
+
"@dxos/react-ui-search": "0.11.0",
|
|
60
|
+
"@dxos/react-ui-editor": "0.11.0",
|
|
61
|
+
"@dxos/schema": "0.11.0",
|
|
62
|
+
"@dxos/types": "0.11.0",
|
|
63
|
+
"@dxos/ui": "0.11.0",
|
|
64
|
+
"@dxos/ui-editor": "0.11.0",
|
|
65
|
+
"@dxos/util": "0.11.0"
|
|
69
66
|
},
|
|
70
67
|
"devDependencies": {
|
|
71
68
|
"@types/react": "~19.2.17",
|
|
@@ -74,23 +71,23 @@
|
|
|
74
71
|
"fast-check": "^3.19.0",
|
|
75
72
|
"react": "~19.2.7",
|
|
76
73
|
"react-dom": "~19.2.7",
|
|
77
|
-
"vite": "^8.
|
|
78
|
-
"@dxos/
|
|
79
|
-
"@dxos/
|
|
80
|
-
"@dxos/
|
|
81
|
-
"@dxos/
|
|
82
|
-
"@dxos/react-ui": "0.
|
|
83
|
-
"@dxos/react-ui-syntax-highlighter": "0.
|
|
84
|
-
"@dxos/ui-theme": "0.
|
|
85
|
-
"@dxos/ui-types": "0.
|
|
86
|
-
"@dxos/storybook-utils": "0.
|
|
74
|
+
"vite": "^8.1.4",
|
|
75
|
+
"@dxos/effect-proto": "0.11.0",
|
|
76
|
+
"@dxos/protocols": "0.11.0",
|
|
77
|
+
"@dxos/random": "0.11.0",
|
|
78
|
+
"@dxos/react-client": "0.11.0",
|
|
79
|
+
"@dxos/react-ui": "0.11.0",
|
|
80
|
+
"@dxos/react-ui-syntax-highlighter": "0.11.0",
|
|
81
|
+
"@dxos/ui-theme": "0.11.0",
|
|
82
|
+
"@dxos/ui-types": "0.11.0",
|
|
83
|
+
"@dxos/storybook-utils": "0.11.0"
|
|
87
84
|
},
|
|
88
85
|
"peerDependencies": {
|
|
89
86
|
"effect": "3.21.4",
|
|
90
87
|
"react": "~19.2.7",
|
|
91
88
|
"react-dom": "~19.2.7",
|
|
92
|
-
"@dxos/ui
|
|
93
|
-
"@dxos/
|
|
89
|
+
"@dxos/react-ui": "^0.11.0",
|
|
90
|
+
"@dxos/ui-theme": "^0.11.0"
|
|
94
91
|
},
|
|
95
92
|
"publishConfig": {
|
|
96
93
|
"access": "public"
|
|
@@ -0,0 +1,93 @@
|
|
|
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 from 'react';
|
|
8
|
+
|
|
9
|
+
import { Card, Icon } from '@dxos/react-ui';
|
|
10
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
11
|
+
|
|
12
|
+
import { translations } from '#translations';
|
|
13
|
+
|
|
14
|
+
import { Form } from './Form';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Alignment harness for a form hosted inside a Card.
|
|
18
|
+
*
|
|
19
|
+
* `Form.Viewport` used to declare its own gutter grid, so a hosted form's fields inset by the
|
|
20
|
+
* form's gutter (8px) while the card's own rows inset by the card's (32px) — the two never lined
|
|
21
|
+
* up, and call sites grew three different workarounds. It now detects the host Column and places
|
|
22
|
+
* the body in the host's content track instead.
|
|
23
|
+
*
|
|
24
|
+
* Read the story by the vertical edges: the reference card's rows and the form card's fields
|
|
25
|
+
* should share one left edge, and the trailing edge should likewise agree.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const Contact = Schema.Struct({
|
|
29
|
+
name: Schema.String.annotations({ title: 'Full name' }),
|
|
30
|
+
email: Schema.String.annotations({ title: 'Email' }),
|
|
31
|
+
role: Schema.optional(Schema.String.annotations({ title: 'Role' })),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const values = { name: 'Ada Lovelace', email: 'ada@example.com', role: 'Engineer' };
|
|
35
|
+
|
|
36
|
+
const ReferenceCard = () => (
|
|
37
|
+
<Card.Root fullWidth>
|
|
38
|
+
<Card.Header>
|
|
39
|
+
<Card.Block>
|
|
40
|
+
<Icon icon='ph--user--regular' />
|
|
41
|
+
</Card.Block>
|
|
42
|
+
<Card.Title>Reference card</Card.Title>
|
|
43
|
+
</Card.Header>
|
|
44
|
+
<Card.Body>
|
|
45
|
+
<Card.Row>
|
|
46
|
+
<Card.Text>A card row — the inset to match.</Card.Text>
|
|
47
|
+
</Card.Row>
|
|
48
|
+
<Card.Row>
|
|
49
|
+
<Card.Text variant='description'>Second row, same track.</Card.Text>
|
|
50
|
+
</Card.Row>
|
|
51
|
+
</Card.Body>
|
|
52
|
+
</Card.Root>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const FormCard = () => (
|
|
56
|
+
<Card.Root fullWidth>
|
|
57
|
+
<Card.Header>
|
|
58
|
+
<Card.Block>
|
|
59
|
+
<Icon icon='ph--pencil--regular' />
|
|
60
|
+
</Card.Block>
|
|
61
|
+
<Card.Title>Form card</Card.Title>
|
|
62
|
+
</Card.Header>
|
|
63
|
+
<Card.Body>
|
|
64
|
+
<Form.Root schema={Contact} values={values}>
|
|
65
|
+
<Form.Viewport>
|
|
66
|
+
<Form.Content>
|
|
67
|
+
<Form.FieldSet />
|
|
68
|
+
</Form.Content>
|
|
69
|
+
</Form.Viewport>
|
|
70
|
+
</Form.Root>
|
|
71
|
+
</Card.Body>
|
|
72
|
+
</Card.Root>
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const DefaultStory = () => (
|
|
76
|
+
<div className='flex flex-col gap-4 w-96'>
|
|
77
|
+
<ReferenceCard />
|
|
78
|
+
<FormCard />
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const meta = {
|
|
83
|
+
title: 'ui/react-ui-form/Card',
|
|
84
|
+
render: DefaultStory,
|
|
85
|
+
decorators: [withTheme(), withLayout({ layout: 'centered' })],
|
|
86
|
+
parameters: { translations },
|
|
87
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
88
|
+
|
|
89
|
+
export default meta;
|
|
90
|
+
|
|
91
|
+
type Story = StoryObj<typeof meta>;
|
|
92
|
+
|
|
93
|
+
export const Default: Story = {};
|
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
6
|
import * as Effect from 'effect/Effect';
|
|
7
7
|
import * as Schema from 'effect/Schema';
|
|
8
|
-
import React, { useCallback, useMemo, useState } from 'react';
|
|
8
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
9
9
|
|
|
10
10
|
import { Annotation, Filter, Format, Obj, Ref, Tag, Type } from '@dxos/echo';
|
|
11
|
+
import { useQuery } from '@dxos/echo-react';
|
|
11
12
|
import { type AnyProperties } from '@dxos/echo/internal';
|
|
12
13
|
import { log } from '@dxos/log';
|
|
13
|
-
import {
|
|
14
|
+
import { useSpaces } from '@dxos/react-client/echo';
|
|
14
15
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
15
|
-
import { Tooltip } from '@dxos/react-ui';
|
|
16
|
+
import { Toolbar, Tooltip } from '@dxos/react-ui';
|
|
16
17
|
import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
17
18
|
import { Text } from '@dxos/schema';
|
|
18
19
|
|
|
@@ -394,3 +395,94 @@ export const DiscriminatedUnion: Story<Schema.Schema.Type<typeof CreateFeedSchem
|
|
|
394
395
|
schema: CreateFeedSchema,
|
|
395
396
|
},
|
|
396
397
|
};
|
|
398
|
+
|
|
399
|
+
// Reactive-source + local-buffer pattern: `values` is a live source, and a parent that gates persistence on
|
|
400
|
+
// validity (like the selected-objects `ObjectForm`). `counter` ticks once a second from outside the form to
|
|
401
|
+
// stand in for an external mutation.
|
|
402
|
+
const ReactiveSchema = Schema.mutable(
|
|
403
|
+
Schema.Struct({
|
|
404
|
+
name: Schema.NonEmptyString.annotations({
|
|
405
|
+
title: 'Name',
|
|
406
|
+
description: 'Required — clear it and the form holds the invalid draft instead of snapping back.',
|
|
407
|
+
}),
|
|
408
|
+
counter: Schema.Number.annotations({
|
|
409
|
+
title: 'Counter',
|
|
410
|
+
description: 'Ticks every second from an external source; updates live even while you edit Name.',
|
|
411
|
+
}),
|
|
412
|
+
}),
|
|
413
|
+
);
|
|
414
|
+
type ReactiveValues = Schema.Schema.Type<typeof ReactiveSchema>;
|
|
415
|
+
|
|
416
|
+
const ReactiveBufferedStory = () => {
|
|
417
|
+
const [source, setSource] = useState<Partial<ReactiveValues>>({ name: 'Alice', counter: 0 });
|
|
418
|
+
const [running, setRunning] = useState(true);
|
|
419
|
+
|
|
420
|
+
const tick = useCallback(() => setSource((prev) => ({ ...prev, counter: (prev.counter ?? 0) + 1 })), []);
|
|
421
|
+
|
|
422
|
+
// External source of truth: tick the counter outside the form while running. Stop it to edit the counter by hand.
|
|
423
|
+
useEffect(() => {
|
|
424
|
+
if (!running) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const interval = setInterval(tick, 1000);
|
|
428
|
+
return () => clearInterval(interval);
|
|
429
|
+
}, [running, tick]);
|
|
430
|
+
|
|
431
|
+
// Persist only valid changes (mirrors ObjectForm): invalid intermediate edits stay buffered in the form.
|
|
432
|
+
const handleValuesChanged = useCallback<NonNullable<FormRootProps<ReactiveValues>['onValuesChanged']>>(
|
|
433
|
+
(values, { isValid }) => {
|
|
434
|
+
if (!isValid) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
setSource((prev) => ({ ...prev, ...values }));
|
|
438
|
+
},
|
|
439
|
+
[],
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
return (
|
|
443
|
+
<Tooltip.Provider>
|
|
444
|
+
<TestLayout json={{ source }}>
|
|
445
|
+
<div className='flex flex-col h-full'>
|
|
446
|
+
<Toolbar.Root>
|
|
447
|
+
<Toolbar.IconButton
|
|
448
|
+
icon={running ? 'ph--pause--regular' : 'ph--play--regular'}
|
|
449
|
+
label={running ? 'Stop external ticks' : 'Start external ticks'}
|
|
450
|
+
onClick={() => setRunning((value) => !value)}
|
|
451
|
+
/>
|
|
452
|
+
<Toolbar.IconButton
|
|
453
|
+
icon='ph--plus--regular'
|
|
454
|
+
label='Tick counter once (external)'
|
|
455
|
+
disabled={running}
|
|
456
|
+
onClick={tick}
|
|
457
|
+
/>
|
|
458
|
+
</Toolbar.Root>
|
|
459
|
+
<Form.Root schema={ReactiveSchema} values={source} onValuesChanged={handleValuesChanged}>
|
|
460
|
+
<Form.Viewport>
|
|
461
|
+
<Form.Content>
|
|
462
|
+
<Form.FieldSet />
|
|
463
|
+
</Form.Content>
|
|
464
|
+
</Form.Viewport>
|
|
465
|
+
</Form.Root>
|
|
466
|
+
</div>
|
|
467
|
+
</TestLayout>
|
|
468
|
+
</Tooltip.Provider>
|
|
469
|
+
);
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Demonstrates the reactive-source + local-buffer pattern used by the selected-objects companion. Pass a live
|
|
474
|
+
* object as `values` and gate persistence on validity in `onValuesChanged`. Watch the `Counter` field update
|
|
475
|
+
* live from the external tick while you clear `Name`: the form holds the invalid draft (shows the error) without
|
|
476
|
+
* snapping back, and the external counter keeps flowing in. Fixing `Name` commits it and reconciles.
|
|
477
|
+
*
|
|
478
|
+
* Use the toolbar to stop the external ticks (then edit `Counter` by hand, or step it once with the `+` button)
|
|
479
|
+
* to see manual edits and external updates reconcile through the same path.
|
|
480
|
+
*
|
|
481
|
+
* @idiom org.dxos.react-ui-form.reactiveBufferedForm
|
|
482
|
+
* applies: Editing a live/remote object where fields can change externally mid-edit (object/article editors, companions)
|
|
483
|
+
* instead-of: Uncontrolled `defaultValues` (ignores external changes) or naive controlled `values` (snaps invalid input back)
|
|
484
|
+
* uses: {@link Form.Root}
|
|
485
|
+
*/
|
|
486
|
+
export const ReactiveBuffered: Story<ReactiveValues> = {
|
|
487
|
+
render: () => <ReactiveBufferedStory />,
|
|
488
|
+
};
|
|
@@ -13,15 +13,18 @@ export type FormBehavior = { showDescription: boolean };
|
|
|
13
13
|
const formStyles = tv({
|
|
14
14
|
slots: {
|
|
15
15
|
viewport: '',
|
|
16
|
-
|
|
16
|
+
// Bottom padding on the body, so the last field never sits flush against its host's edge
|
|
17
|
+
// (a form in a card, a dialog body, a scrolled panel all need it).
|
|
18
|
+
content: 'pb-form-padding',
|
|
17
19
|
section: 'flex flex-col py-form-section-gap first:pt-0',
|
|
18
|
-
group: 'flex flex-col gap-
|
|
20
|
+
group: 'flex flex-col gap-trim-md p-trim-md border border-separator rounded-sm',
|
|
19
21
|
sectionHeader: '',
|
|
20
22
|
sectionTitle: 'text-lg',
|
|
21
23
|
sectionDescription: 'text-description',
|
|
22
24
|
fieldSet: '',
|
|
23
25
|
field: '',
|
|
24
|
-
|
|
26
|
+
// Columns: label (fills) → optional `labelEnd` readout → error icon (or its spacer) → optional trailing `button`.
|
|
27
|
+
fieldLabel: 'min-h-(--dx-control) grid grid-cols-[1fr_auto_auto_auto] items-center select-none',
|
|
25
28
|
fieldLabelText: '',
|
|
26
29
|
fieldDescription: 'text-description',
|
|
27
30
|
fieldControl: '',
|
|
@@ -31,10 +34,10 @@ const formStyles = tv({
|
|
|
31
34
|
// Standalone submit row (full-width primary button).
|
|
32
35
|
submit: 'flex w-full pt-form-padding',
|
|
33
36
|
// Collapsible field-set body: indented column of sub-fields.
|
|
34
|
-
fieldSetBody: 'flex flex-col
|
|
37
|
+
fieldSetBody: 'flex flex-col px-trim-sm pb-trim-sm',
|
|
35
38
|
// Bordered container wrapping a collapsible nested group, plus its top spacing.
|
|
36
39
|
fieldSetBox: 'border border-subdued-separator rounded-sm',
|
|
37
|
-
fieldSetBoxOuter: '
|
|
40
|
+
fieldSetBoxOuter: '',
|
|
38
41
|
},
|
|
39
42
|
variants: {
|
|
40
43
|
variant: {
|
|
@@ -49,16 +52,19 @@ const formStyles = tv({
|
|
|
49
52
|
sectionDescription: 'px-trim-md',
|
|
50
53
|
// No top padding: the section gap already separates the field set from the title above it.
|
|
51
54
|
fieldSet: 'flex flex-col gap-trim-md',
|
|
55
|
+
// Nested groups render their sub-fields through `fieldSetBody`, so it needs the same gap as
|
|
56
|
+
// `fieldSet` — otherwise fields inside a group sit flush while their top-level siblings don't.
|
|
57
|
+
fieldSetBody: 'flex flex-col gap-trim-md px-trim-sm pb-trim-sm',
|
|
52
58
|
field: mx(
|
|
53
59
|
'grid',
|
|
54
60
|
'grid-cols-1 [grid-template-areas:"header""description""control""validation"]',
|
|
55
61
|
'md:grid-cols-2 md:[grid-template-areas:"header_header""description_control""validation_validation"]',
|
|
56
|
-
'gap-x-trim-
|
|
62
|
+
'gap-x-trim-xl gap-y-0 p-trim-md border border-input-separator rounded-md',
|
|
57
63
|
),
|
|
58
64
|
fieldLabel: '[grid-area:header]',
|
|
59
65
|
fieldLabelText: 'text-base-fg text-lg',
|
|
60
|
-
fieldDescription: '[grid-area:description] pt-
|
|
61
|
-
fieldControl: '[grid-area:control] flex justify-end items-start
|
|
66
|
+
fieldDescription: '[grid-area:description] pt-trim-xs text-description',
|
|
67
|
+
fieldControl: '[grid-area:control] flex justify-end items-start pt-trim-md md:pt-0',
|
|
62
68
|
fieldValidation: '[grid-area:validation]',
|
|
63
69
|
},
|
|
64
70
|
},
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
FormContent,
|
|
9
9
|
type FormContentProps,
|
|
10
10
|
FormError,
|
|
11
|
-
|
|
11
|
+
FormFieldSetContainer,
|
|
12
12
|
FormGroup,
|
|
13
13
|
type FormGroupProps,
|
|
14
14
|
FormLayoutController,
|
|
@@ -30,7 +30,7 @@ export const Form = {
|
|
|
30
30
|
Content: FormContent,
|
|
31
31
|
Section: FormSection,
|
|
32
32
|
Group: FormGroup,
|
|
33
|
-
FieldSet:
|
|
33
|
+
FieldSet: FormFieldSetContainer,
|
|
34
34
|
Layout: FormLayoutController,
|
|
35
35
|
Label: FormFieldLabel,
|
|
36
36
|
Row: FormRow,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type ThemedClassName,
|
|
16
16
|
composable,
|
|
17
17
|
composableProps,
|
|
18
|
+
useInColumn,
|
|
18
19
|
useMergeRefs,
|
|
19
20
|
useTranslation,
|
|
20
21
|
withColumn,
|
|
@@ -59,7 +60,6 @@ export type FormRootProps<T extends AnyProperties = AnyProperties> = Merge<
|
|
|
59
60
|
Omit<NaturalFormFieldSetProps<T>, 'schema' | 'path'>
|
|
60
61
|
>;
|
|
61
62
|
|
|
62
|
-
// `variant` (from FormContextValue) flows through `...props` into the context for all parts to read.
|
|
63
63
|
export const FormRoot = <T extends AnyProperties = AnyProperties>({
|
|
64
64
|
children,
|
|
65
65
|
schema,
|
|
@@ -96,20 +96,39 @@ export const FormViewport = composable<HTMLDivElement, FormViewportProps>(
|
|
|
96
96
|
({ children, scroll, gutter = 'sm', ...props }, forwardedRef) => {
|
|
97
97
|
const { variant = 'default' } = useFormContext(FORM_VIEWPORT_NAME);
|
|
98
98
|
const styles = formTheme.styles({ variant });
|
|
99
|
+
const inColumn = useInColumn();
|
|
99
100
|
// Span the full width when nested inside another Column grid (e.g. Card.Root)
|
|
100
101
|
// instead of landing in a single narrow track.
|
|
101
102
|
const span = '[.dx-column-root_&]:col-span-full';
|
|
103
|
+
|
|
104
|
+
// Inside a host Column (a Card, a Dialog body), the host already owns the gutter: place the
|
|
105
|
+
// body in its content track rather than nesting a second grid, which is what made form fields
|
|
106
|
+
// inset differently from the card title above them. Non-scrolling only — a scrolling viewport
|
|
107
|
+
// needs its own gutter to host the scrollbar.
|
|
108
|
+
if (inColumn && !scroll) {
|
|
109
|
+
return (
|
|
110
|
+
<Column.Center
|
|
111
|
+
{...composableProps(props, { classNames: ['w-full min-w-0', styles.viewport()] })}
|
|
112
|
+
ref={forwardedRef}
|
|
113
|
+
>
|
|
114
|
+
{children}
|
|
115
|
+
</Column.Center>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Forwarded props land on the OUTERMOST element, never on the inner ScrollArea. A host places
|
|
120
|
+
// this component by passing placement down (`Panel.Content asChild` merges `[grid-area:content]`
|
|
121
|
+
// into it); on an inner element that name belongs to no line of the enclosing Column grid, and
|
|
122
|
+
// CSS answers an unmatched name by fabricating an implicit row rather than ignoring it — which
|
|
123
|
+
// silently pushed the form into a phantom second row (AUDIT.md §3).
|
|
102
124
|
if (scroll) {
|
|
103
125
|
return (
|
|
104
|
-
<Column.Root
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
thin
|
|
111
|
-
ref={forwardedRef}
|
|
112
|
-
>
|
|
126
|
+
<Column.Root
|
|
127
|
+
{...composableProps(props, { classNames: ['dx-expander', span, styles.viewport()] })}
|
|
128
|
+
gutter={gutter}
|
|
129
|
+
ref={forwardedRef}
|
|
130
|
+
>
|
|
131
|
+
<ScrollArea.Root orientation='vertical' centered padding thin>
|
|
113
132
|
<ScrollArea.Viewport>{children}</ScrollArea.Viewport>
|
|
114
133
|
</ScrollArea.Root>
|
|
115
134
|
</Column.Root>
|
|
@@ -168,10 +187,10 @@ FormContent.displayName = FORM_CONTENT_NAME;
|
|
|
168
187
|
|
|
169
188
|
const FORM_FIELDSET_NAME = 'Form.FieldSet';
|
|
170
189
|
|
|
171
|
-
export type
|
|
190
|
+
export type FormFieldSetContainerProps = ThemedClassName<NaturalFormFieldSetProps<any>>;
|
|
172
191
|
|
|
173
192
|
/** Context-reading binding for `Form.FieldSet`: pulls the schema + field context off the form and delegates to {@link FormFieldSet}. */
|
|
174
|
-
export const
|
|
193
|
+
export const FormFieldSetContainer = ({ classNames, ...props }: FormFieldSetContainerProps) => {
|
|
175
194
|
const { form, variant = 'default', ...contextProps } = useFormContext(FORM_FIELDSET_NAME);
|
|
176
195
|
const styles = formTheme.styles({ variant });
|
|
177
196
|
return (
|
|
@@ -184,7 +203,7 @@ export const FormFieldSetController = ({ classNames, ...props }: FormFieldSetCon
|
|
|
184
203
|
);
|
|
185
204
|
};
|
|
186
205
|
|
|
187
|
-
|
|
206
|
+
FormFieldSetContainer.displayName = FORM_FIELDSET_NAME;
|
|
188
207
|
|
|
189
208
|
//
|
|
190
209
|
// Layout
|
|
@@ -12,6 +12,7 @@ import React, { useMemo } from 'react';
|
|
|
12
12
|
import { Annotation, Format } from '@dxos/echo';
|
|
13
13
|
import { SchemaEx } from '@dxos/effect';
|
|
14
14
|
import { IconButton, IconButtonProps, useTranslation } from '@dxos/react-ui';
|
|
15
|
+
import { mx } from '@dxos/ui-theme';
|
|
15
16
|
|
|
16
17
|
import { translationKey } from '#translations';
|
|
17
18
|
import { type FieldContext, type FormFieldRenderer, type FormFieldRendererProps } from '#types';
|
|
@@ -87,6 +88,7 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
87
88
|
fieldMap,
|
|
88
89
|
fieldProvider,
|
|
89
90
|
readonly,
|
|
91
|
+
hideEmpty = true,
|
|
90
92
|
layout,
|
|
91
93
|
|
|
92
94
|
// RefFieldProps
|
|
@@ -144,7 +146,7 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
144
146
|
// (`ArrayField`, nested-struct -> `FormFieldSet`) keep their own
|
|
145
147
|
// empty-value checks, but those branches only apply when the value is
|
|
146
148
|
// actually a non-null array/object, so this check doesn't interfere.
|
|
147
|
-
if (readonly && fieldState.getValue() == null) {
|
|
149
|
+
if (readonly && hideEmpty && fieldState.getValue() == null) {
|
|
148
150
|
return null;
|
|
149
151
|
}
|
|
150
152
|
|
|
@@ -310,8 +312,14 @@ FormField.displayName = 'Form.FormField';
|
|
|
310
312
|
// Layout components
|
|
311
313
|
//
|
|
312
314
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
+
// End-of-row form buttons occupy a consistent 32px block (matching the standard `h-8` label row) with
|
|
316
|
+
// the button inset so its hover fill never touches the row's top/bottom/right edges.
|
|
317
|
+
export const CompactIconButton = ({ classNames, ...props }: IconButtonProps) => {
|
|
318
|
+
return (
|
|
319
|
+
<span className='grid size-8 shrink-0 place-items-center'>
|
|
320
|
+
<IconButton variant='ghost' iconOnly {...props} classNames={mx('min-h-0 h-7 w-7 p-0', classNames)} />
|
|
321
|
+
</span>
|
|
322
|
+
);
|
|
315
323
|
};
|
|
316
324
|
|
|
317
325
|
/**
|
|
@@ -34,10 +34,17 @@ export type FormFieldLabelProps = ThemedClassName<
|
|
|
34
34
|
*/
|
|
35
35
|
path?: string;
|
|
36
36
|
/**
|
|
37
|
-
* Trailing button rendered at the end of the label row (
|
|
37
|
+
* Trailing button rendered at the end of the label row (last grid column).
|
|
38
38
|
* Used by nested-object field sets to surface a collapse toggle.
|
|
39
39
|
*/
|
|
40
40
|
button?: ReactNode;
|
|
41
|
+
/**
|
|
42
|
+
* Read-only content rendered right after the label text (e.g. a live numeric readout for a
|
|
43
|
+
* slider field) — a sibling of `Input.Label`, never a child, so the label's `textContent`
|
|
44
|
+
* stays exactly `label` (see the comment on `labelClassNames` below) and doesn't masquerade
|
|
45
|
+
* as an interactive control the way `button` does.
|
|
46
|
+
*/
|
|
47
|
+
labelEnd?: ReactNode;
|
|
41
48
|
onClick?: () => void;
|
|
42
49
|
} & Pick<FormFieldRendererProps, 'label' | 'readonly' | 'required'>
|
|
43
50
|
>;
|
|
@@ -52,6 +59,7 @@ export const FormFieldLabel = ({
|
|
|
52
59
|
required,
|
|
53
60
|
standalone,
|
|
54
61
|
button,
|
|
62
|
+
labelEnd,
|
|
55
63
|
onClick,
|
|
56
64
|
}: FormFieldLabelProps) => {
|
|
57
65
|
const styles = formTheme.styles({ variant });
|
|
@@ -77,6 +85,7 @@ export const FormFieldLabel = ({
|
|
|
77
85
|
return (
|
|
78
86
|
<div className={styles.fieldLabel({ class: mx(onClick && 'cursor-pointer', classNames) })} onClick={onClick}>
|
|
79
87
|
{labelNode}
|
|
88
|
+
{labelEnd}
|
|
80
89
|
{error ? (
|
|
81
90
|
<Tooltip.Trigger asChild content={error} side='bottom'>
|
|
82
91
|
<Icon icon='ph--warning--regular' size={4} classNames='text-error-text' />
|
|
@@ -135,6 +144,13 @@ export type FormRowProps<T = any> = ThemedClassName<
|
|
|
135
144
|
* Render the label as a standalone `<span>` (group/multi-input fields with no single associated control).
|
|
136
145
|
*/
|
|
137
146
|
standalone?: boolean;
|
|
147
|
+
/**
|
|
148
|
+
* Read-only content rendered right after the label text, right-aligned by the label row's
|
|
149
|
+
* grid (e.g. a live numeric readout for a slider field). Forwarded to `FormFieldLabel`'s
|
|
150
|
+
* `labelEnd` slot — a sibling of the label, never a child, so the label's `textContent` and
|
|
151
|
+
* accessible name stay exactly `label`.
|
|
152
|
+
*/
|
|
153
|
+
labelEnd?: ReactNode;
|
|
138
154
|
/**
|
|
139
155
|
* Override the read-only/`static` rendering of the value. Fields whose value is not plain text
|
|
140
156
|
* (refs, selects, markdown) supply this; the default formats scalars/dates via `formatStaticValue`.
|
|
@@ -175,6 +191,7 @@ export const FormRow = <T,>({
|
|
|
175
191
|
format,
|
|
176
192
|
required,
|
|
177
193
|
standalone,
|
|
194
|
+
labelEnd,
|
|
178
195
|
renderStatic,
|
|
179
196
|
validation,
|
|
180
197
|
getStatus,
|
|
@@ -210,6 +227,7 @@ export const FormRow = <T,>({
|
|
|
210
227
|
readonly={readonly}
|
|
211
228
|
required={required}
|
|
212
229
|
standalone={standalone}
|
|
230
|
+
labelEnd={labelEnd}
|
|
213
231
|
label={label}
|
|
214
232
|
path={jsonPath}
|
|
215
233
|
/>
|
|
@@ -241,6 +259,7 @@ export const FormRow = <T,>({
|
|
|
241
259
|
readonly={readonly}
|
|
242
260
|
required={required}
|
|
243
261
|
standalone
|
|
262
|
+
labelEnd={labelEnd}
|
|
244
263
|
label={label}
|
|
245
264
|
path={jsonPath}
|
|
246
265
|
/>
|