@dxos/react-ui-form 0.8.3 → 0.8.4-main.1068cf700f

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.
Files changed (218) hide show
  1. package/dist/lib/browser/index.mjs +2267 -2088
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +2267 -2088
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +8 -8
  8. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  9. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +84 -7
  10. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  11. package/dist/types/src/components/Form/Form-old.stories.d.ts +131 -0
  12. package/dist/types/src/components/Form/Form-old.stories.d.ts.map +1 -0
  13. package/dist/types/src/components/Form/Form.d.ts +81 -25
  14. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/Form.stories.d.ts +127 -55
  16. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/FormField.d.ts +37 -0
  18. package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
  19. package/dist/types/src/components/Form/FormFieldComponent.d.ts +78 -0
  20. package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -0
  21. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +9 -0
  22. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -0
  23. package/dist/types/src/components/Form/FormFieldSet.d.ts +19 -0
  24. package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -0
  25. package/dist/types/src/components/Form/fields/ArrayField.d.ts +18 -0
  26. package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -0
  27. package/dist/types/src/components/Form/fields/BooleanField.d.ts +4 -0
  28. package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -0
  29. package/dist/types/src/components/Form/fields/GeoPointField.d.ts +5 -0
  30. package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -0
  31. package/dist/types/src/components/Form/fields/MarkdownField.d.ts +4 -0
  32. package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -0
  33. package/dist/types/src/components/Form/fields/NumberField.d.ts +4 -0
  34. package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -0
  35. package/dist/types/src/components/Form/fields/RefField.d.ts +14 -0
  36. package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -0
  37. package/dist/types/src/components/Form/fields/SelectField.d.ts +10 -0
  38. package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -0
  39. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +5 -0
  40. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -0
  41. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +82 -0
  42. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -0
  43. package/dist/types/src/components/Form/fields/TextAreaField.d.ts +4 -0
  44. package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -0
  45. package/dist/types/src/components/Form/fields/TextField.d.ts +4 -0
  46. package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -0
  47. package/dist/types/src/components/Form/fields/TupleField.d.ts +6 -0
  48. package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -0
  49. package/dist/types/src/components/Form/fields/index.d.ts +12 -0
  50. package/dist/types/src/components/Form/fields/index.d.ts.map +1 -0
  51. package/dist/types/src/components/Form/index.d.ts +2 -4
  52. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  53. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +46 -0
  54. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
  55. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +85 -0
  56. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
  57. package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
  58. package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
  59. package/dist/types/src/components/Settings/Settings.d.ts +56 -0
  60. package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
  61. package/dist/types/src/components/Settings/Settings.stories.d.ts +81 -0
  62. package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
  63. package/dist/types/src/components/Settings/index.d.ts +2 -0
  64. package/dist/types/src/components/Settings/index.d.ts.map +1 -0
  65. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +21 -9
  66. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  67. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +84 -9
  68. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  69. package/dist/types/src/components/index.d.ts +2 -2
  70. package/dist/types/src/components/index.d.ts.map +1 -1
  71. package/dist/types/src/components/testing.d.ts +4 -3
  72. package/dist/types/src/components/testing.d.ts.map +1 -1
  73. package/dist/types/src/hooks/index.d.ts +2 -3
  74. package/dist/types/src/hooks/index.d.ts.map +1 -1
  75. package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
  76. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
  77. package/dist/types/src/hooks/useKeyHandler.d.ts +6 -0
  78. package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
  79. package/dist/types/src/index.d.ts +1 -1
  80. package/dist/types/src/index.d.ts.map +1 -1
  81. package/dist/types/src/translations.d.ts +66 -58
  82. package/dist/types/src/translations.d.ts.map +1 -1
  83. package/dist/types/src/util/echo.d.ts +6 -0
  84. package/dist/types/src/util/echo.d.ts.map +1 -0
  85. package/dist/types/src/util/field.d.ts +3 -3
  86. package/dist/types/src/util/field.d.ts.map +1 -1
  87. package/dist/types/src/util/formatting.d.ts +2 -2
  88. package/dist/types/src/util/formatting.d.ts.map +1 -1
  89. package/dist/types/src/util/index.d.ts +2 -1
  90. package/dist/types/src/util/index.d.ts.map +1 -1
  91. package/dist/types/src/util/properties.d.ts +7 -0
  92. package/dist/types/src/util/properties.d.ts.map +1 -0
  93. package/dist/types/src/util/refs.d.ts +2 -3
  94. package/dist/types/src/util/refs.d.ts.map +1 -1
  95. package/dist/types/tsconfig.tsbuildinfo +1 -1
  96. package/package.json +51 -42
  97. package/src/components/FieldEditor/FieldEditor.stories.tsx +54 -31
  98. package/src/components/FieldEditor/FieldEditor.test.tsx +14 -13
  99. package/src/components/FieldEditor/FieldEditor.tsx +87 -92
  100. package/src/components/Form/Form-old.stories.tsx +314 -0
  101. package/src/components/Form/Form.stories.tsx +162 -223
  102. package/src/components/Form/Form.tsx +329 -96
  103. package/src/components/Form/FormField.tsx +286 -0
  104. package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
  105. package/src/components/Form/FormFieldComponent.tsx +174 -0
  106. package/src/components/Form/FormFieldSet.tsx +127 -0
  107. package/src/components/Form/README.md +3 -0
  108. package/src/components/Form/fields/ArrayField.tsx +154 -0
  109. package/src/components/Form/fields/BooleanField.tsx +22 -0
  110. package/src/components/Form/fields/GeoPointField.tsx +120 -0
  111. package/src/components/Form/fields/MarkdownField.tsx +78 -0
  112. package/src/components/Form/fields/NumberField.tsx +39 -0
  113. package/src/components/Form/fields/RefField.tsx +189 -0
  114. package/src/components/Form/fields/SelectField.tsx +65 -0
  115. package/src/components/Form/fields/SelectOptionField.stories.tsx +64 -0
  116. package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +47 -42
  117. package/src/components/Form/fields/TextAreaField.tsx +38 -0
  118. package/src/components/Form/fields/TextField.tsx +38 -0
  119. package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +16 -9
  120. package/src/components/Form/fields/index.ts +15 -0
  121. package/src/components/Form/index.ts +3 -4
  122. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +214 -0
  123. package/src/components/ObjectPicker/ObjectPicker.tsx +175 -0
  124. package/src/components/{ControlSection → ObjectPicker}/index.ts +1 -1
  125. package/src/components/Settings/Settings.stories.tsx +56 -0
  126. package/src/components/Settings/Settings.tsx +234 -0
  127. package/src/components/Settings/index.ts +5 -0
  128. package/src/components/ViewEditor/ViewEditor.stories.tsx +143 -61
  129. package/src/components/ViewEditor/ViewEditor.test.tsx +43 -43
  130. package/src/components/ViewEditor/ViewEditor.tsx +314 -216
  131. package/src/components/index.ts +2 -3
  132. package/src/components/testing.tsx +19 -11
  133. package/src/hooks/index.ts +2 -3
  134. package/src/hooks/useFormHandler.ts +362 -0
  135. package/src/hooks/useKeyHandler.tsx +37 -0
  136. package/src/index.ts +1 -1
  137. package/src/translations.ts +25 -14
  138. package/src/util/echo.ts +25 -0
  139. package/src/util/field.ts +33 -43
  140. package/src/util/formatting.ts +22 -22
  141. package/src/util/index.ts +2 -1
  142. package/src/util/properties.ts +18 -0
  143. package/src/util/refs.ts +12 -18
  144. package/dist/lib/node/index.cjs +0 -2416
  145. package/dist/lib/node/index.cjs.map +0 -7
  146. package/dist/lib/node/meta.json +0 -1
  147. package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
  148. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
  149. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +0 -8
  150. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
  151. package/dist/types/src/components/ControlSection/index.d.ts +0 -2
  152. package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
  153. package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
  154. package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
  155. package/dist/types/src/components/Form/Defaults.d.ts +0 -14
  156. package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
  157. package/dist/types/src/components/Form/FormActions.d.ts +0 -9
  158. package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
  159. package/dist/types/src/components/Form/FormContent.d.ts +0 -57
  160. package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
  161. package/dist/types/src/components/Form/FormContext.d.ts +0 -23
  162. package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
  163. package/dist/types/src/components/Form/Input.d.ts +0 -26
  164. package/dist/types/src/components/Form/Input.d.ts.map +0 -1
  165. package/dist/types/src/components/Form/RefField.d.ts +0 -12
  166. package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
  167. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
  168. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
  169. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
  170. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
  171. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +0 -7
  172. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
  173. package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
  174. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
  175. package/dist/types/src/components/Form/custom/index.d.ts +0 -4
  176. package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
  177. package/dist/types/src/components/Form/factory.d.ts +0 -8
  178. package/dist/types/src/components/Form/factory.d.ts.map +0 -1
  179. package/dist/types/src/components/ViewEditor/Test.stories.d.ts +0 -10
  180. package/dist/types/src/components/ViewEditor/Test.stories.d.ts.map +0 -1
  181. package/dist/types/src/components/deprecated/Form.d.ts +0 -15
  182. package/dist/types/src/components/deprecated/Form.d.ts.map +0 -1
  183. package/dist/types/src/components/deprecated/Form.stories.d.ts +0 -13
  184. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +0 -1
  185. package/dist/types/src/components/deprecated/FormInput.d.ts +0 -16
  186. package/dist/types/src/components/deprecated/FormInput.d.ts.map +0 -1
  187. package/dist/types/src/components/deprecated/index.d.ts +0 -2
  188. package/dist/types/src/components/deprecated/index.d.ts.map +0 -1
  189. package/dist/types/src/hooks/useForm.d.ts +0 -67
  190. package/dist/types/src/hooks/useForm.d.ts.map +0 -1
  191. package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -23
  192. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
  193. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
  194. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
  195. package/dist/types/src/util/schema.d.ts +0 -3
  196. package/dist/types/src/util/schema.d.ts.map +0 -1
  197. package/src/components/ControlSection/ControlSection.stories.tsx +0 -41
  198. package/src/components/ControlSection/ControlSection.tsx +0 -139
  199. package/src/components/Form/ArrayField.tsx +0 -124
  200. package/src/components/Form/Defaults.tsx +0 -203
  201. package/src/components/Form/FormActions.tsx +0 -53
  202. package/src/components/Form/FormContent.tsx +0 -240
  203. package/src/components/Form/FormContext.tsx +0 -110
  204. package/src/components/Form/Input.tsx +0 -51
  205. package/src/components/Form/RefField.tsx +0 -213
  206. package/src/components/Form/custom/GeoPointInput.tsx +0 -72
  207. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +0 -61
  208. package/src/components/Form/custom/index.ts +0 -7
  209. package/src/components/Form/factory.tsx +0 -31
  210. package/src/components/ViewEditor/Test.stories.tsx +0 -45
  211. package/src/components/deprecated/Form.stories.tsx +0 -62
  212. package/src/components/deprecated/Form.tsx +0 -119
  213. package/src/components/deprecated/FormInput.tsx +0 -71
  214. package/src/components/deprecated/index.ts +0 -5
  215. package/src/hooks/useForm.ts +0 -291
  216. package/src/hooks/useQueryRefOptions.ts +0 -53
  217. package/src/hooks/useRefQueryLookupHandler.ts +0 -39
  218. package/src/util/schema.ts +0 -14
@@ -0,0 +1,175 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import type * as Schema from 'effect/Schema';
6
+ import React, { type KeyboardEvent, forwardRef, useCallback, useState } from 'react';
7
+
8
+ import { type Palette, Popover, type ThemedClassName, useTranslation } from '@dxos/react-ui';
9
+ import { Combobox, useSearchListInput, useSearchListResults } from '@dxos/react-ui-searchlist';
10
+
11
+ import { translationKey } from '../../translations';
12
+ import { Form } from '../Form';
13
+
14
+ export type RefOption = {
15
+ id: string;
16
+ label: string;
17
+ hue?: Palette;
18
+ };
19
+
20
+ export type ObjectPickerContentProps = ThemedClassName<{
21
+ options: RefOption[];
22
+ selectedIds?: string[];
23
+ createOptionLabel?: [string, { ns: string }];
24
+ createOptionIcon?: string;
25
+ createSchema?: Schema.Schema.AnyNoContext;
26
+ createInitialValuePath?: string;
27
+ onCreate?: (values: any) => void;
28
+ onSelect: (id: string) => void;
29
+ }>;
30
+
31
+ const CreateItem = ({
32
+ createOptionLabel,
33
+ createOptionIcon,
34
+ onCreateItemSelect,
35
+ }: {
36
+ createOptionLabel: [string, { ns: string }];
37
+ createOptionIcon: string;
38
+ onCreateItemSelect: (query: string) => void;
39
+ }) => {
40
+ const { t } = useTranslation(translationKey);
41
+ const { query } = useSearchListInput();
42
+
43
+ return (
44
+ <Combobox.Item
45
+ value='__create__'
46
+ label={t(createOptionLabel[0], { ns: createOptionLabel[1].ns, text: query })}
47
+ icon={createOptionIcon}
48
+ classNames='flex items-center gap-2'
49
+ closeOnSelect={false}
50
+ onSelect={() => {
51
+ onCreateItemSelect(query);
52
+ }}
53
+ />
54
+ );
55
+ };
56
+
57
+ const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>(
58
+ (
59
+ {
60
+ options,
61
+ selectedIds,
62
+ createSchema,
63
+ createOptionLabel,
64
+ createOptionIcon,
65
+ createInitialValuePath,
66
+ onSelect,
67
+ onCreate,
68
+ ...props
69
+ },
70
+ ref,
71
+ ) => {
72
+ const { t } = useTranslation(translationKey);
73
+ const [showForm, setShowForm] = useState(false);
74
+ const [formInitialValue, setFormInitialValue] = useState<string>('');
75
+
76
+ const { results, handleSearch } = useSearchListResults({
77
+ items: options,
78
+ });
79
+
80
+ const handleFormSave = useCallback(
81
+ (values: any) => {
82
+ onCreate?.(values);
83
+ setShowForm(false);
84
+ setFormInitialValue('');
85
+ },
86
+ [onCreate],
87
+ );
88
+
89
+ const handleFormCancel = useCallback(() => {
90
+ setShowForm(false);
91
+ setFormInitialValue('');
92
+ }, []);
93
+
94
+ const handleKeyDown = useCallback(
95
+ (event: KeyboardEvent) => {
96
+ if (
97
+ event.key === 'Enter' &&
98
+ createSchema &&
99
+ (event.currentTarget as HTMLElement).querySelector(
100
+ '[role="option"][aria-selected="true"][data-value="__create__"]',
101
+ )
102
+ ) {
103
+ event.stopPropagation();
104
+ event.preventDefault();
105
+ // Get the current query from the input element
106
+ const input = (event.currentTarget as HTMLElement).querySelector('input[type="text"]') as HTMLInputElement;
107
+ const currentQuery = input?.value || '';
108
+ setFormInitialValue(currentQuery);
109
+ setShowForm(true);
110
+ }
111
+ },
112
+ [createSchema],
113
+ );
114
+
115
+ return (
116
+ <Combobox.Content {...props} ref={ref} onSearch={handleSearch} onKeyDownCapture={handleKeyDown}>
117
+ {showForm && createSchema ? (
118
+ <Popover.Viewport>
119
+ <Form.Root
120
+ testId='create-referenced-object-form'
121
+ schema={createSchema}
122
+ values={createInitialValuePath ? { [createInitialValuePath]: formInitialValue } : {}}
123
+ onSave={handleFormSave}
124
+ onCancel={handleFormCancel}
125
+ >
126
+ <Form.Viewport>
127
+ <Form.Content>
128
+ <Form.FieldSet />
129
+ <Form.Actions />
130
+ </Form.Content>
131
+ </Form.Viewport>
132
+ </Form.Root>
133
+ </Popover.Viewport>
134
+ ) : (
135
+ <>
136
+ <Combobox.Input placeholder={t('ref field combobox input placeholder')} autoFocus />
137
+ <Combobox.List>
138
+ {results.map((option) => (
139
+ <Combobox.Item
140
+ key={option.id}
141
+ value={option.id}
142
+ label={option.label}
143
+ checked={selectedIds?.includes(option.id)}
144
+ onSelect={() => onSelect(option.id)}
145
+ classNames='flex items-center gap-2'
146
+ />
147
+ ))}
148
+ {createOptionLabel && createOptionIcon && createSchema && onCreate && (
149
+ <CreateItem
150
+ createOptionLabel={createOptionLabel}
151
+ createOptionIcon={createOptionIcon}
152
+ onCreateItemSelect={(query: string) => {
153
+ setFormInitialValue(query);
154
+ setShowForm(true);
155
+ }}
156
+ />
157
+ )}
158
+ </Combobox.List>
159
+ </>
160
+ )}
161
+ <Combobox.Arrow />
162
+ </Combobox.Content>
163
+ );
164
+ },
165
+ );
166
+
167
+ ObjectPickerContent.displayName = 'ObjectPickerContent';
168
+
169
+ export const ObjectPicker = {
170
+ Root: Combobox.Root,
171
+ Portal: Combobox.Portal,
172
+ Trigger: Combobox.Trigger,
173
+ VirtualTrigger: Combobox.VirtualTrigger,
174
+ Content: ObjectPickerContent,
175
+ };
@@ -2,4 +2,4 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './ControlSection';
5
+ export * from './ObjectPicker';
@@ -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 = {};
@@ -0,0 +1,234 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { type PropsWithChildren } from 'react';
6
+
7
+ import {
8
+ Button,
9
+ type ButtonProps,
10
+ Input,
11
+ type Label,
12
+ ScrollArea,
13
+ type ThemedClassName,
14
+ toLocalizedString,
15
+ useTranslation,
16
+ } from '@dxos/react-ui';
17
+ import { mx } from '@dxos/ui-theme';
18
+
19
+ import { translationKey } from '../../translations';
20
+
21
+ const SETTINGS_ROOT_NAME = 'Settings.Root';
22
+ const SETTINGS_SECTION_NAME = 'Settings.Section';
23
+ const SETTINGS_SECTION_HEADING_NAME = 'Settings.SectionHeading';
24
+ const SETTINGS_GROUP_NAME = 'Settings.Group';
25
+ const SETTINGS_GROUP_BUTTON_NAME = 'Settings.GroupButton';
26
+ const SETTINGS_CONTAINER_NAME = 'Settings.Container';
27
+ const SETTINGS_FRAME_NAME = 'Settings.Frame';
28
+ const SETTINGS_FRAME_ITEM_NAME = 'Settings.FrameItem';
29
+ const SETTINGS_ITEM_NAME = 'Settings.Item';
30
+ const SETTINGS_ITEM_INPUT_NAME = 'Settings.ItemInput';
31
+
32
+ const styles = {
33
+ title: 'pbe-trimMd text-baseText text-lg',
34
+ description: 'text-base text-description',
35
+ grid: 'grid grid-cols-1 md:grid-cols-[3fr_2fr] gap-x-trimLg',
36
+ };
37
+
38
+ //
39
+ // Root
40
+ //
41
+
42
+ type SettingsRootProps = PropsWithChildren;
43
+
44
+ const SettingsRoot = ({ children }: SettingsRootProps) => {
45
+ return (
46
+ <ScrollArea.Root orientation='vertical'>
47
+ <ScrollArea.Viewport classNames='p-trimMd'>{children}</ScrollArea.Viewport>
48
+ </ScrollArea.Root>
49
+ );
50
+ };
51
+
52
+ SettingsRoot.displayName = SETTINGS_ROOT_NAME;
53
+
54
+ //
55
+ // Section
56
+ //
57
+
58
+ type SettingsSectionProps = PropsWithChildren<{
59
+ title: Label;
60
+ description?: Label;
61
+ }>;
62
+
63
+ const SettingsSection = ({ title, description, children }: SettingsSectionProps) => {
64
+ return (
65
+ <>
66
+ <SettingsSectionHeading title={title} description={description} />
67
+ <div className='is-full pbs-trimMd space-y-trimMd'>{children}</div>
68
+ </>
69
+ );
70
+ };
71
+
72
+ SettingsSection.displayName = SETTINGS_SECTION_NAME;
73
+
74
+ //
75
+ // Section Heading
76
+ //
77
+
78
+ const SettingsSectionHeading = ({ title, description }: Omit<SettingsSectionProps, 'children'>) => {
79
+ const { t } = useTranslation(translationKey);
80
+ return (
81
+ <>
82
+ <h2 className='pli-trimMd container-max-width text-xl mbs-trimMd mbe-trimMd'>{toLocalizedString(title, t)}</h2>
83
+ {description && (
84
+ <p className='pli-trimMd mlb-trimMd container-max-width text-description'>
85
+ {toLocalizedString(description, t)}
86
+ </p>
87
+ )}
88
+ </>
89
+ );
90
+ };
91
+
92
+ SettingsSectionHeading.displayName = SETTINGS_SECTION_HEADING_NAME;
93
+
94
+ //
95
+ // Group Button
96
+ //
97
+
98
+ const SettingsGroupButton = ({ classNames, ...props }: ButtonProps) => {
99
+ return <Button {...props} classNames={['md:col-span-2', classNames]} />;
100
+ };
101
+
102
+ SettingsGroupButton.displayName = SETTINGS_GROUP_BUTTON_NAME;
103
+
104
+ //
105
+ // Group
106
+ //
107
+
108
+ type SettingsGroupProps = ThemedClassName<PropsWithChildren>;
109
+
110
+ const SettingsGroup = ({ children, classNames }: SettingsGroupProps) => (
111
+ <div role='none' className={mx('group container-max-width space-y-trimMd', classNames)}>
112
+ {children}
113
+ </div>
114
+ );
115
+
116
+ SettingsGroup.displayName = SETTINGS_GROUP_NAME;
117
+
118
+ //
119
+ // Frame
120
+ //
121
+
122
+ const SettingsFrame = ({ children }: SettingsGroupProps) => (
123
+ <div role='none' className={mx('container-max-width p-trimMd', 'border border-separator rounded-md')}>
124
+ {children}
125
+ </div>
126
+ );
127
+
128
+ SettingsFrame.displayName = SETTINGS_FRAME_NAME;
129
+
130
+ //
131
+ // Frame Item
132
+ //
133
+
134
+ const SettingsFrameItem = ({ title, description, children }: SettingsItemProps) => {
135
+ const { t } = useTranslation(translationKey);
136
+
137
+ return (
138
+ <div role='group' className='min-is-0'>
139
+ <h3 className='text-lg mbe-2'>{toLocalizedString(title, t)}</h3>
140
+ {description && <p className='mlb-trimSm md:mbe-0 text-description'>{toLocalizedString(description, t)}</p>}
141
+ {children}
142
+ </div>
143
+ );
144
+ };
145
+
146
+ SettingsFrameItem.displayName = SETTINGS_FRAME_ITEM_NAME;
147
+
148
+ //
149
+ // Container
150
+ //
151
+
152
+ const SettingsContainer = ({ classNames, children }: ThemedClassName<PropsWithChildren>) => {
153
+ return (
154
+ <div
155
+ role='none'
156
+ className={mx([
157
+ 'container-max-width',
158
+ '*:first:!mbs-0 *:last:!mbe-0 pli-trimMd plb-trimMd',
159
+ 'border border-separator rounded-md',
160
+ classNames,
161
+ ])}
162
+ >
163
+ {children}
164
+ </div>
165
+ );
166
+ };
167
+
168
+ SettingsContainer.displayName = SETTINGS_CONTAINER_NAME;
169
+
170
+ //
171
+ // Item
172
+ //
173
+
174
+ type SettingsItemProps = PropsWithChildren<{
175
+ title: Label;
176
+ description?: Label;
177
+ }>;
178
+
179
+ const SettingsItem = ({ title, description = '', children }: SettingsItemProps) => {
180
+ const { t } = useTranslation(translationKey);
181
+
182
+ return (
183
+ <SettingsContainer classNames={styles.grid}>
184
+ <h3 className={mx(styles.title, 'md:col-span-2')}>{toLocalizedString(title, t)}</h3>
185
+ <p className={styles.description}>{toLocalizedString(description, t)}</p>
186
+ <div role='none' className='text-end plb-1'>
187
+ {children}
188
+ </div>
189
+ </SettingsContainer>
190
+ );
191
+ };
192
+
193
+ SettingsItem.displayName = SETTINGS_ITEM_NAME;
194
+
195
+ //
196
+ // Item Input
197
+ //
198
+
199
+ const SettingsItemInput = ({ title, description = '', children }: SettingsItemProps) => {
200
+ const { t } = useTranslation(translationKey);
201
+
202
+ return (
203
+ <Input.Root>
204
+ <SettingsContainer classNames={styles.grid}>
205
+ <Input.Label classNames={mx(styles.title, 'md:col-span-2')}>{toLocalizedString(title, t)}</Input.Label>
206
+ <Input.DescriptionAndValidation>
207
+ <Input.Description classNames={styles.description}>{toLocalizedString(description, t)}</Input.Description>
208
+ </Input.DescriptionAndValidation>
209
+ <div role='none' className='text-end plb-1'>
210
+ {children}
211
+ </div>
212
+ </SettingsContainer>
213
+ </Input.Root>
214
+ );
215
+ };
216
+
217
+ SettingsItemInput.displayName = SETTINGS_ITEM_INPUT_NAME;
218
+
219
+ //
220
+ // Settings
221
+ //
222
+
223
+ export const Settings = {
224
+ Root: SettingsRoot,
225
+ Section: SettingsSection,
226
+ SectionHeading: SettingsSectionHeading,
227
+ Group: SettingsGroup,
228
+ GroupButton: SettingsGroupButton,
229
+ Container: SettingsContainer,
230
+ Frame: SettingsFrame,
231
+ FrameItem: SettingsFrameItem,
232
+ Item: SettingsItem,
233
+ ItemInput: SettingsItemInput,
234
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './Settings';