@dxos/react-ui-form 0.8.4-main.406dc2a → 0.8.4-main.52d7546f51

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 (190) hide show
  1. package/dist/lib/browser/index.mjs +2228 -2254
  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 +2228 -2254
  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 +6 -5
  8. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  9. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +13 -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 -26
  14. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/Form.stories.d.ts +59 -84
  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/{ControlSection/ControlSection.stories.d.ts → Form/fields/SelectOptionField.stories.d.ts} +12 -14
  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 +14 -9
  54. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
  55. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +9 -6
  56. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
  57. package/dist/types/src/components/Settings/Settings.d.ts +56 -0
  58. package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
  59. package/dist/types/src/components/{Form/custom/SelectOptionsInput.stories.d.ts → Settings/Settings.stories.d.ts} +10 -8
  60. package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
  61. package/dist/types/src/components/Settings/index.d.ts +2 -0
  62. package/dist/types/src/components/Settings/index.d.ts.map +1 -0
  63. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +11 -13
  64. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  65. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +12 -9
  66. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  67. package/dist/types/src/components/index.d.ts +1 -1
  68. package/dist/types/src/components/index.d.ts.map +1 -1
  69. package/dist/types/src/components/testing.d.ts +0 -2
  70. package/dist/types/src/components/testing.d.ts.map +1 -1
  71. package/dist/types/src/hooks/index.d.ts +2 -3
  72. package/dist/types/src/hooks/index.d.ts.map +1 -1
  73. package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
  74. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
  75. package/dist/types/src/hooks/useKeyHandler.d.ts +6 -0
  76. package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
  77. package/dist/types/src/translations.d.ts +10 -7
  78. package/dist/types/src/translations.d.ts.map +1 -1
  79. package/dist/types/src/util/echo.d.ts +6 -0
  80. package/dist/types/src/util/echo.d.ts.map +1 -0
  81. package/dist/types/src/util/field.d.ts +3 -3
  82. package/dist/types/src/util/field.d.ts.map +1 -1
  83. package/dist/types/src/util/formatting.d.ts +2 -2
  84. package/dist/types/src/util/formatting.d.ts.map +1 -1
  85. package/dist/types/src/util/index.d.ts +2 -1
  86. package/dist/types/src/util/index.d.ts.map +1 -1
  87. package/dist/types/src/util/properties.d.ts +7 -0
  88. package/dist/types/src/util/properties.d.ts.map +1 -0
  89. package/dist/types/src/util/refs.d.ts +1 -2
  90. package/dist/types/src/util/refs.d.ts.map +1 -1
  91. package/dist/types/tsconfig.tsbuildinfo +1 -1
  92. package/package.json +49 -43
  93. package/src/components/FieldEditor/FieldEditor.stories.tsx +43 -22
  94. package/src/components/FieldEditor/FieldEditor.tsx +73 -76
  95. package/src/components/Form/Form-old.stories.tsx +314 -0
  96. package/src/components/Form/Form.stories.tsx +151 -248
  97. package/src/components/Form/Form.tsx +329 -108
  98. package/src/components/Form/FormField.tsx +286 -0
  99. package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
  100. package/src/components/Form/FormFieldComponent.tsx +174 -0
  101. package/src/components/Form/FormFieldSet.tsx +127 -0
  102. package/src/components/Form/README.md +3 -0
  103. package/src/components/Form/fields/ArrayField.tsx +154 -0
  104. package/src/components/Form/fields/BooleanField.tsx +22 -0
  105. package/src/components/Form/fields/GeoPointField.tsx +120 -0
  106. package/src/components/Form/fields/MarkdownField.tsx +78 -0
  107. package/src/components/Form/fields/NumberField.tsx +39 -0
  108. package/src/components/Form/fields/RefField.tsx +189 -0
  109. package/src/components/Form/fields/SelectField.tsx +65 -0
  110. package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +18 -15
  111. package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +21 -15
  112. package/src/components/Form/fields/TextAreaField.tsx +38 -0
  113. package/src/components/Form/fields/TextField.tsx +38 -0
  114. package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +15 -8
  115. package/src/components/Form/fields/index.ts +15 -0
  116. package/src/components/Form/index.ts +3 -4
  117. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +21 -14
  118. package/src/components/ObjectPicker/ObjectPicker.tsx +76 -62
  119. package/src/components/Settings/Settings.stories.tsx +56 -0
  120. package/src/components/Settings/Settings.tsx +234 -0
  121. package/src/components/{ControlSection → Settings}/index.ts +1 -1
  122. package/src/components/ViewEditor/ViewEditor.stories.tsx +43 -41
  123. package/src/components/ViewEditor/ViewEditor.test.tsx +5 -5
  124. package/src/components/ViewEditor/ViewEditor.tsx +245 -192
  125. package/src/components/index.ts +1 -1
  126. package/src/components/testing.tsx +9 -7
  127. package/src/hooks/index.ts +2 -3
  128. package/src/hooks/useFormHandler.ts +362 -0
  129. package/src/hooks/useKeyHandler.tsx +37 -0
  130. package/src/translations.ts +12 -10
  131. package/src/util/echo.ts +25 -0
  132. package/src/util/field.ts +25 -25
  133. package/src/util/formatting.ts +22 -22
  134. package/src/util/index.ts +2 -1
  135. package/src/util/properties.ts +18 -0
  136. package/src/util/refs.ts +11 -17
  137. package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
  138. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
  139. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
  140. package/dist/types/src/components/ControlSection/index.d.ts +0 -2
  141. package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
  142. package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
  143. package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
  144. package/dist/types/src/components/Form/Defaults.d.ts +0 -14
  145. package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
  146. package/dist/types/src/components/Form/FormActions.d.ts +0 -9
  147. package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
  148. package/dist/types/src/components/Form/FormContent.d.ts +0 -63
  149. package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
  150. package/dist/types/src/components/Form/FormContext.d.ts +0 -23
  151. package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
  152. package/dist/types/src/components/Form/Input.d.ts +0 -29
  153. package/dist/types/src/components/Form/Input.d.ts.map +0 -1
  154. package/dist/types/src/components/Form/RefField.d.ts +0 -22
  155. package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
  156. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
  157. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
  158. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
  159. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
  160. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
  161. package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
  162. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
  163. package/dist/types/src/components/Form/custom/index.d.ts +0 -4
  164. package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
  165. package/dist/types/src/components/Form/factory.d.ts +0 -8
  166. package/dist/types/src/components/Form/factory.d.ts.map +0 -1
  167. package/dist/types/src/hooks/useForm.d.ts +0 -67
  168. package/dist/types/src/hooks/useForm.d.ts.map +0 -1
  169. package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -29
  170. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
  171. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
  172. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
  173. package/dist/types/src/util/schema.d.ts +0 -3
  174. package/dist/types/src/util/schema.d.ts.map +0 -1
  175. package/src/components/ControlSection/ControlSection.stories.tsx +0 -38
  176. package/src/components/ControlSection/ControlSection.tsx +0 -153
  177. package/src/components/Form/ArrayField.tsx +0 -125
  178. package/src/components/Form/Defaults.tsx +0 -205
  179. package/src/components/Form/FormActions.tsx +0 -54
  180. package/src/components/Form/FormContent.tsx +0 -322
  181. package/src/components/Form/FormContext.tsx +0 -110
  182. package/src/components/Form/Input.tsx +0 -54
  183. package/src/components/Form/RefField.tsx +0 -283
  184. package/src/components/Form/custom/GeoPointInput.tsx +0 -80
  185. package/src/components/Form/custom/index.ts +0 -7
  186. package/src/components/Form/factory.tsx +0 -31
  187. package/src/hooks/useForm.ts +0 -296
  188. package/src/hooks/useQueryRefOptions.ts +0 -62
  189. package/src/hooks/useRefQueryLookupHandler.ts +0 -39
  190. package/src/util/schema.ts +0 -12
@@ -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
+ };
@@ -2,4 +2,4 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './ControlSection';
5
+ export * from './Settings';
@@ -7,42 +7,43 @@ import * as Schema from 'effect/Schema';
7
7
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
8
8
 
9
9
  import { DXN, Filter, Obj, Query, type QueryAST, Tag, Type } from '@dxos/echo';
10
- import { type EchoSchema, Format } from '@dxos/echo/internal';
10
+ import { type EchoSchema, Format, type Mutable } from '@dxos/echo/internal';
11
11
  import { useQuery } from '@dxos/react-client/echo';
12
- import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
12
+ import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
13
13
  import { useAsyncEffect } from '@dxos/react-ui';
14
14
  import { withTheme } from '@dxos/react-ui/testing';
15
- import { DataType, type ProjectionModel, createView, getTypenameFromQuery } from '@dxos/schema';
15
+ import { type ProjectionModel, View, getTypenameFromQuery } from '@dxos/schema';
16
+ import { Employer, Organization, Person, Pipeline } from '@dxos/types';
16
17
 
17
18
  import { translations } from '../../translations';
18
- import { TestLayout, TestPanel, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
19
+ import { TestLayout, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
19
20
 
20
21
  import { ViewEditor, type ViewEditorProps } from './ViewEditor';
21
22
 
22
23
  const types = [
23
24
  // TODO(burdon): Get label from annotation.
24
- { value: Type.getTypename(DataType.Organization), label: 'Organization' },
25
- { value: Type.getTypename(DataType.Person), label: 'Person' },
26
- { value: Type.getTypename(DataType.Project), label: 'Project' },
27
- { value: Type.getTypename(DataType.Employer), label: 'Employer' },
25
+ { value: Organization.Organization.typename, label: 'Organization' },
26
+ { value: Person.Person.typename, label: 'Person' },
27
+ { value: Pipeline.Pipeline.typename, label: 'Project' },
28
+ { value: Employer.Employer.typename, label: 'Employer' },
28
29
  ];
29
30
 
30
31
  // Type definition for debug objects exposed to tests.
31
32
  export type ViewEditorDebugObjects = {
32
33
  schema: EchoSchema;
33
- view: DataType.View;
34
+ view: View.View;
34
35
  projection: ProjectionModel;
35
36
  };
36
37
 
37
38
  type StoryProps = Pick<ViewEditorProps, 'readonly' | 'mode'>;
38
39
 
39
40
  const DefaultStory = (props: StoryProps) => {
40
- const { space } = useClientProvider();
41
+ const { space } = useClientStory();
41
42
  const [schema, setSchema] = useState<EchoSchema>();
42
- const [view, setView] = useState<DataType.View>();
43
+ const [view, setView] = useState<View.View>();
43
44
  const projectionRef = useRef<ProjectionModel>(null);
44
45
 
45
- const tags = useQuery(space, Filter.type(Tag.Tag));
46
+ const tags = useQuery(space?.db, Filter.type(Tag.Tag));
46
47
 
47
48
  useAsyncEffect(async () => {
48
49
  if (space) {
@@ -51,7 +52,7 @@ const DefaultStory = (props: StoryProps) => {
51
52
  email: Format.Email,
52
53
  salary: Format.Currency(),
53
54
  }).pipe(
54
- Type.Obj({
55
+ Type.object({
55
56
  typename: 'example.com/type/Test',
56
57
  version: '0.1.0',
57
58
  }),
@@ -62,18 +63,17 @@ const DefaultStory = (props: StoryProps) => {
62
63
  description: Schema.String,
63
64
  completed: Schema.Boolean,
64
65
  }).pipe(
65
- Type.Obj({
66
+ Type.object({
66
67
  typename: 'example.com/type/Alternate',
67
68
  version: '0.1.0',
68
69
  }),
69
70
  );
70
71
 
71
72
  const [testSchema] = await space.db.schemaRegistry.register([TestSchema, AlternateSchema]);
72
- const view = createView({
73
+ const view = View.make({
73
74
  name: 'Test',
74
- query: Query.select(Filter.typename(TestSchema.typename)),
75
+ query: Query.select(Filter.type(TestSchema)),
75
76
  jsonSchema: Type.toJsonSchema(TestSchema),
76
- presentation: Obj.make(Type.Expando, {}),
77
77
  });
78
78
 
79
79
  setSchema(testSchema);
@@ -87,10 +87,12 @@ const DefaultStory = (props: StoryProps) => {
87
87
  return;
88
88
  }
89
89
 
90
- if (props.mode === 'query') {
90
+ if (props.mode === 'tag') {
91
91
  const queue = target && DXN.tryParse(target) ? target : undefined;
92
92
  const query = queue ? Query.fromAst(newQuery).options({ queues: [queue] }) : Query.fromAst(newQuery);
93
- view.query.ast = query.ast;
93
+ Obj.change(view, (v) => {
94
+ v.query.ast = query.ast as Mutable<typeof query.ast>;
95
+ });
94
96
 
95
97
  const typename = getTypenameFromQuery(query.ast);
96
98
  const [newSchema] = await space.db.schemaRegistry.query({ typename }).run();
@@ -98,16 +100,18 @@ const DefaultStory = (props: StoryProps) => {
98
100
  return;
99
101
  }
100
102
 
101
- const newView = createView({
103
+ const newView = View.make({
102
104
  query,
103
105
  jsonSchema: newSchema.jsonSchema,
104
- presentation: Obj.make(Type.Expando, {}),
105
106
  });
106
- view.projection = Obj.getSnapshot(newView).projection;
107
+ Obj.change(view, (v) => {
108
+ v.projection = Obj.getSnapshot(newView).projection as Mutable<typeof v.projection>;
109
+ });
107
110
  setSchema(() => newSchema);
108
111
  } else {
109
- console.log('updateViewQuery', { newQuery });
110
- view.query.ast = newQuery;
112
+ Obj.change(view, (v) => {
113
+ v.query.ast = newQuery as Mutable<typeof newQuery>;
114
+ });
111
115
  schema.updateTypename(getTypenameFromQuery(newQuery));
112
116
  }
113
117
  },
@@ -142,20 +146,18 @@ const DefaultStory = (props: StoryProps) => {
142
146
 
143
147
  return (
144
148
  <TestLayout json={json}>
145
- <TestPanel>
146
- <ViewEditor
147
- ref={projectionRef}
148
- schema={schema}
149
- view={view}
150
- registry={space?.db.schemaRegistry}
151
- mode={props.mode}
152
- readonly={props.readonly}
153
- types={types}
154
- tags={tags}
155
- onQueryChanged={updateViewQuery}
156
- onDelete={handleDelete}
157
- />
158
- </TestPanel>
149
+ <ViewEditor
150
+ ref={projectionRef}
151
+ schema={schema}
152
+ view={view}
153
+ registry={space?.db.schemaRegistry}
154
+ mode={props.mode}
155
+ readonly={props.readonly}
156
+ types={types}
157
+ tags={tags}
158
+ onQueryChanged={updateViewQuery}
159
+ onDelete={handleDelete}
160
+ />
159
161
  </TestLayout>
160
162
  );
161
163
  };
@@ -173,7 +175,7 @@ const meta = {
173
175
  space.db.add(Tag.make({ label: 'New' }));
174
176
  },
175
177
  }),
176
- withTheme,
178
+ withTheme(),
177
179
  ],
178
180
  parameters: {
179
181
  layout: 'fullscreen',
@@ -193,8 +195,8 @@ export const Readonly: Story = {
193
195
  },
194
196
  };
195
197
 
196
- export const QueryMode: Story = {
198
+ export const TagQueryMode: Story = {
197
199
  args: {
198
- mode: 'query',
200
+ mode: 'tag',
199
201
  },
200
202
  };
@@ -7,7 +7,7 @@ import { cleanup, fireEvent, screen, waitFor } from '@testing-library/react';
7
7
  import { afterEach, describe, expect, test } from 'vitest';
8
8
 
9
9
  import { EchoSchema, isInstanceOf } from '@dxos/echo/internal';
10
- import { DataType, ProjectionModel } from '@dxos/schema';
10
+ import { ProjectionModel, View } from '@dxos/schema';
11
11
 
12
12
  import { VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
13
13
 
@@ -20,7 +20,7 @@ const getViewEditorDebugObjects = (): ViewEditorDebugObjects => {
20
20
  const debugObjects = (window as any)[VIEW_EDITOR_DEBUG_SYMBOL] as ViewEditorDebugObjects;
21
21
  expect(debugObjects).toBeDefined();
22
22
  expect(debugObjects.schema).toBeInstanceOf(EchoSchema);
23
- expect(isInstanceOf(DataType.View, debugObjects.view)).toBeTruthy();
23
+ expect(isInstanceOf(View.View, debugObjects.view)).toBeTruthy();
24
24
  expect(debugObjects.projection).toBeInstanceOf(ProjectionModel);
25
25
  return debugObjects;
26
26
  };
@@ -54,7 +54,7 @@ describe('ViewEditor', () => {
54
54
  const nameField = screen.getByText('name');
55
55
  fireEvent.click(nameField);
56
56
 
57
- const fieldInput = screen.getByPlaceholderText('Field name.');
57
+ const fieldInput = screen.getByPlaceholderText('Property name');
58
58
  fireEvent.change(fieldInput, { target: { value: 'new_property' } });
59
59
 
60
60
  fireEvent.click(screen.getByTestId('save-button'));
@@ -89,12 +89,12 @@ describe('ViewEditor', () => {
89
89
  await Default.run();
90
90
  await waitForViewEditor();
91
91
 
92
- // Click the add property button.
92
+ // Click the add property button. The new field is auto-expanded by handleAdd.
93
93
  const addButton = screen.getByText('Add property');
94
94
  fireEvent.click(addButton);
95
95
 
96
96
  // Fill out the property field.
97
- const fieldInput = screen.getByPlaceholderText('Field name.');
97
+ const fieldInput = screen.getByPlaceholderText('Property name');
98
98
  fireEvent.change(fieldInput, { target: { value: 'added_property' } });
99
99
 
100
100
  // Click the format combo box and select the first option.