@dxos/react-ui-form 0.8.4-main.72ec0f3 → 0.8.4-main.7996785055

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 (186) hide show
  1. package/README.md +0 -1
  2. package/dist/lib/browser/index.mjs +2306 -2293
  3. package/dist/lib/browser/index.mjs.map +4 -4
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/node-esm/index.mjs +2306 -2293
  6. package/dist/lib/node-esm/index.mjs.map +4 -4
  7. package/dist/lib/node-esm/meta.json +1 -1
  8. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +8 -7
  9. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  10. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +14 -6
  11. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  12. package/dist/types/src/components/Form/Form.d.ts +84 -27
  13. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  14. package/dist/types/src/components/Form/Form.stories.d.ts +60 -83
  15. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/Form/FormField.d.ts +43 -0
  17. package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
  18. package/dist/types/src/components/Form/FormFieldComponent.d.ts +77 -0
  19. package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -0
  20. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +9 -0
  21. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -0
  22. package/dist/types/src/components/Form/FormFieldSet.d.ts +18 -0
  23. package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -0
  24. package/dist/types/src/components/Form/fields/ArrayField.d.ts +18 -0
  25. package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -0
  26. package/dist/types/src/components/Form/fields/BooleanField.d.ts +4 -0
  27. package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -0
  28. package/dist/types/src/components/Form/fields/GeoPointField.d.ts +5 -0
  29. package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -0
  30. package/dist/types/src/components/Form/fields/MarkdownField.d.ts +4 -0
  31. package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -0
  32. package/dist/types/src/components/Form/fields/NumberField.d.ts +4 -0
  33. package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -0
  34. package/dist/types/src/components/Form/fields/RefField.d.ts +16 -0
  35. package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -0
  36. package/dist/types/src/components/Form/fields/SelectField.d.ts +10 -0
  37. package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -0
  38. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +5 -0
  39. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -0
  40. package/dist/types/src/components/{ControlSection/ControlSection.stories.d.ts → Form/fields/SelectOptionField.stories.d.ts} +13 -13
  41. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -0
  42. package/dist/types/src/components/Form/fields/TextAreaField.d.ts +4 -0
  43. package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -0
  44. package/dist/types/src/components/Form/fields/TextField.d.ts +4 -0
  45. package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -0
  46. package/dist/types/src/components/Form/fields/TupleField.d.ts +6 -0
  47. package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -0
  48. package/dist/types/src/components/Form/fields/index.d.ts +12 -0
  49. package/dist/types/src/components/Form/fields/index.d.ts.map +1 -0
  50. package/dist/types/src/components/Form/index.d.ts +2 -4
  51. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  52. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +23 -10
  53. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
  54. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +10 -5
  55. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
  56. package/dist/types/src/components/Settings/Settings.d.ts +58 -0
  57. package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
  58. package/dist/types/src/components/{Form/custom/SelectOptionsInput.stories.d.ts → Settings/Settings.stories.d.ts} +11 -7
  59. package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
  60. package/dist/types/src/components/Settings/index.d.ts +2 -0
  61. package/dist/types/src/components/Settings/index.d.ts.map +1 -0
  62. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +11 -13
  63. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  64. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +15 -9
  65. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  66. package/dist/types/src/components/index.d.ts +1 -1
  67. package/dist/types/src/components/index.d.ts.map +1 -1
  68. package/dist/types/src/components/testing.d.ts +0 -2
  69. package/dist/types/src/components/testing.d.ts.map +1 -1
  70. package/dist/types/src/hooks/index.d.ts +2 -3
  71. package/dist/types/src/hooks/index.d.ts.map +1 -1
  72. package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
  73. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
  74. package/dist/types/src/hooks/useKeyHandler.d.ts +6 -0
  75. package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
  76. package/dist/types/src/translations.d.ts +11 -6
  77. package/dist/types/src/translations.d.ts.map +1 -1
  78. package/dist/types/src/util/field.d.ts +3 -3
  79. package/dist/types/src/util/field.d.ts.map +1 -1
  80. package/dist/types/src/util/formatting.d.ts +2 -2
  81. package/dist/types/src/util/formatting.d.ts.map +1 -1
  82. package/dist/types/src/util/index.d.ts +1 -1
  83. package/dist/types/src/util/index.d.ts.map +1 -1
  84. package/dist/types/src/util/properties.d.ts +7 -0
  85. package/dist/types/src/util/properties.d.ts.map +1 -0
  86. package/dist/types/src/util/refs.d.ts +2 -2
  87. package/dist/types/src/util/refs.d.ts.map +1 -1
  88. package/dist/types/tsconfig.tsbuildinfo +1 -1
  89. package/package.json +50 -47
  90. package/src/components/FieldEditor/FieldEditor.stories.tsx +43 -24
  91. package/src/components/FieldEditor/FieldEditor.tsx +74 -78
  92. package/src/components/Form/AUDIT.md +17 -0
  93. package/src/components/Form/Form.stories.tsx +149 -250
  94. package/src/components/Form/Form.tsx +314 -111
  95. package/src/components/Form/FormField.tsx +305 -0
  96. package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
  97. package/src/components/Form/FormFieldComponent.tsx +171 -0
  98. package/src/components/Form/FormFieldSet.tsx +129 -0
  99. package/src/components/Form/README.md +3 -0
  100. package/src/components/Form/fields/ArrayField.tsx +149 -0
  101. package/src/components/Form/fields/BooleanField.tsx +22 -0
  102. package/src/components/Form/fields/GeoPointField.tsx +120 -0
  103. package/src/components/Form/fields/MarkdownField.tsx +78 -0
  104. package/src/components/Form/fields/NumberField.tsx +39 -0
  105. package/src/components/Form/fields/RefField.tsx +199 -0
  106. package/src/components/Form/fields/SelectField.tsx +65 -0
  107. package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +19 -16
  108. package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +25 -19
  109. package/src/components/Form/fields/TextAreaField.tsx +38 -0
  110. package/src/components/Form/fields/TextField.tsx +38 -0
  111. package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +16 -9
  112. package/src/components/Form/fields/index.ts +15 -0
  113. package/src/components/Form/index.ts +3 -4
  114. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +15 -12
  115. package/src/components/ObjectPicker/ObjectPicker.tsx +103 -78
  116. package/src/components/Settings/Settings.stories.tsx +56 -0
  117. package/src/components/Settings/Settings.tsx +235 -0
  118. package/src/components/{ControlSection → Settings}/index.ts +1 -1
  119. package/src/components/ViewEditor/ViewEditor.stories.tsx +47 -44
  120. package/src/components/ViewEditor/ViewEditor.test.tsx +5 -4
  121. package/src/components/ViewEditor/ViewEditor.tsx +297 -210
  122. package/src/components/index.ts +1 -1
  123. package/src/components/testing.tsx +17 -8
  124. package/src/hooks/index.ts +2 -3
  125. package/src/hooks/useFormHandler.ts +380 -0
  126. package/src/hooks/useKeyHandler.tsx +37 -0
  127. package/src/translations.ts +13 -9
  128. package/src/util/field.ts +25 -25
  129. package/src/util/formatting.ts +22 -22
  130. package/src/util/index.ts +1 -1
  131. package/src/util/properties.ts +18 -0
  132. package/src/util/refs.ts +13 -15
  133. package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
  134. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
  135. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
  136. package/dist/types/src/components/ControlSection/index.d.ts +0 -2
  137. package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
  138. package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
  139. package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
  140. package/dist/types/src/components/Form/Defaults.d.ts +0 -14
  141. package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
  142. package/dist/types/src/components/Form/FormActions.d.ts +0 -9
  143. package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
  144. package/dist/types/src/components/Form/FormContent.d.ts +0 -63
  145. package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
  146. package/dist/types/src/components/Form/FormContext.d.ts +0 -23
  147. package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
  148. package/dist/types/src/components/Form/Input.d.ts +0 -29
  149. package/dist/types/src/components/Form/Input.d.ts.map +0 -1
  150. package/dist/types/src/components/Form/RefField.d.ts +0 -22
  151. package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
  152. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
  153. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
  154. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
  155. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
  156. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
  157. package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
  158. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
  159. package/dist/types/src/components/Form/custom/index.d.ts +0 -4
  160. package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
  161. package/dist/types/src/components/Form/factory.d.ts +0 -8
  162. package/dist/types/src/components/Form/factory.d.ts.map +0 -1
  163. package/dist/types/src/hooks/useForm.d.ts +0 -67
  164. package/dist/types/src/hooks/useForm.d.ts.map +0 -1
  165. package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -29
  166. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
  167. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
  168. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
  169. package/dist/types/src/util/schema.d.ts +0 -3
  170. package/dist/types/src/util/schema.d.ts.map +0 -1
  171. package/src/components/ControlSection/ControlSection.stories.tsx +0 -38
  172. package/src/components/ControlSection/ControlSection.tsx +0 -153
  173. package/src/components/Form/ArrayField.tsx +0 -125
  174. package/src/components/Form/Defaults.tsx +0 -205
  175. package/src/components/Form/FormActions.tsx +0 -54
  176. package/src/components/Form/FormContent.tsx +0 -362
  177. package/src/components/Form/FormContext.tsx +0 -110
  178. package/src/components/Form/Input.tsx +0 -54
  179. package/src/components/Form/RefField.tsx +0 -283
  180. package/src/components/Form/custom/GeoPointInput.tsx +0 -80
  181. package/src/components/Form/custom/index.ts +0 -7
  182. package/src/components/Form/factory.tsx +0 -31
  183. package/src/hooks/useForm.ts +0 -296
  184. package/src/hooks/useQueryRefOptions.ts +0 -62
  185. package/src/hooks/useRefQueryLookupHandler.ts +0 -39
  186. 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,235 @@
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 { composable, composableProps, 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: 'pb-trim-md text-base-surface-text text-lg',
34
+ description: 'text-base text-description',
35
+ grid: 'grid grid-cols-1 md:grid-cols-[1fr_1fr] gap-x-trim-lg',
36
+ };
37
+
38
+ //
39
+ // Root
40
+ //
41
+
42
+ const SettingsRoot = composable<HTMLDivElement>(({ children, ...props }, forwardedRef) => {
43
+ const { className, ...composedProps } = composableProps(props);
44
+ return (
45
+ <ScrollArea.Root
46
+ {...composedProps}
47
+ className={mx('dx-document', className)}
48
+ orientation='vertical'
49
+ centered
50
+ ref={forwardedRef}
51
+ >
52
+ <ScrollArea.Viewport classNames='p-trim-md'>{children}</ScrollArea.Viewport>
53
+ </ScrollArea.Root>
54
+ );
55
+ });
56
+
57
+ SettingsRoot.displayName = SETTINGS_ROOT_NAME;
58
+
59
+ //
60
+ // Section
61
+ //
62
+
63
+ type SettingsSectionProps = PropsWithChildren<{
64
+ title: Label;
65
+ description?: Label;
66
+ }>;
67
+
68
+ const SettingsSection = ({ title, description, children }: SettingsSectionProps) => {
69
+ return (
70
+ <>
71
+ <SettingsSectionHeading title={title} description={description} />
72
+ <div className='w-full pt-trim-md space-y-trim-md'>{children}</div>
73
+ </>
74
+ );
75
+ };
76
+
77
+ SettingsSection.displayName = SETTINGS_SECTION_NAME;
78
+
79
+ //
80
+ // Section Heading
81
+ //
82
+
83
+ const SettingsSectionHeading = ({ title, description }: Omit<SettingsSectionProps, 'children'>) => {
84
+ const { t } = useTranslation(translationKey);
85
+ return (
86
+ <>
87
+ <h2 className='px-trim-md mt-trim-md mb-trim-md text-xl'>{toLocalizedString(title, t)}</h2>
88
+ {description && <p className='px-trim-md my-trim-md text-description'>{toLocalizedString(description, t)}</p>}
89
+ </>
90
+ );
91
+ };
92
+
93
+ SettingsSectionHeading.displayName = SETTINGS_SECTION_HEADING_NAME;
94
+
95
+ //
96
+ // Group Button
97
+ //
98
+
99
+ const SettingsGroupButton = ({ classNames, ...props }: ButtonProps) => {
100
+ return <Button {...props} classNames={['md:col-span-2', classNames]} />;
101
+ };
102
+
103
+ SettingsGroupButton.displayName = SETTINGS_GROUP_BUTTON_NAME;
104
+
105
+ //
106
+ // Group
107
+ //
108
+
109
+ type SettingsGroupProps = ThemedClassName<PropsWithChildren>;
110
+
111
+ const SettingsGroup = ({ children, classNames }: SettingsGroupProps) => (
112
+ <div role='none' className={mx('group space-y-trim-md', classNames)}>
113
+ {children}
114
+ </div>
115
+ );
116
+
117
+ SettingsGroup.displayName = SETTINGS_GROUP_NAME;
118
+
119
+ //
120
+ // Frame
121
+ //
122
+
123
+ const SettingsFrame = ({ children }: SettingsGroupProps) => (
124
+ <div role='none' className='p-trim-md border border-separator rounded-md'>
125
+ {children}
126
+ </div>
127
+ );
128
+
129
+ SettingsFrame.displayName = SETTINGS_FRAME_NAME;
130
+
131
+ //
132
+ // Frame Item
133
+ //
134
+
135
+ const SettingsFrameItem = ({ title, description, children }: SettingsItemProps) => {
136
+ const { t } = useTranslation(translationKey);
137
+
138
+ return (
139
+ <div role='group' className='min-w-0'>
140
+ <h3 className='text-lg mb-2'>{toLocalizedString(title, t)}</h3>
141
+ {description && <p className='my-trim-sm md:mb-0 text-description'>{toLocalizedString(description, t)}</p>}
142
+ {children}
143
+ </div>
144
+ );
145
+ };
146
+
147
+ SettingsFrameItem.displayName = SETTINGS_FRAME_ITEM_NAME;
148
+
149
+ //
150
+ // Container
151
+ //
152
+
153
+ const SettingsContainer = ({ classNames, children }: ThemedClassName<PropsWithChildren>) => {
154
+ return (
155
+ <div
156
+ role='none'
157
+ className={mx([
158
+ 'dx-document',
159
+ '*:first:mt-0! *:last:mb-0! px-trim-md py-trim-md',
160
+ 'border border-separator rounded-md',
161
+ classNames,
162
+ ])}
163
+ >
164
+ {children}
165
+ </div>
166
+ );
167
+ };
168
+
169
+ SettingsContainer.displayName = SETTINGS_CONTAINER_NAME;
170
+
171
+ //
172
+ // Item
173
+ //
174
+
175
+ type SettingsItemProps = PropsWithChildren<{
176
+ title: Label;
177
+ description?: Label;
178
+ }>;
179
+
180
+ const SettingsItem = ({ title, description = '', children }: SettingsItemProps) => {
181
+ const { t } = useTranslation(translationKey);
182
+
183
+ return (
184
+ <SettingsContainer classNames={styles.grid}>
185
+ <h3 className={mx(styles.title, 'md:col-span-2')}>{toLocalizedString(title, t)}</h3>
186
+ <p className={styles.description}>{toLocalizedString(description, t)}</p>
187
+ <div role='none' className='overflow-hidden text-end py-1'>
188
+ {children}
189
+ </div>
190
+ </SettingsContainer>
191
+ );
192
+ };
193
+
194
+ SettingsItem.displayName = SETTINGS_ITEM_NAME;
195
+
196
+ //
197
+ // Item Input
198
+ //
199
+
200
+ const SettingsItemInput = ({ title, description = '', children }: SettingsItemProps) => {
201
+ const { t } = useTranslation(translationKey);
202
+
203
+ return (
204
+ <Input.Root>
205
+ <SettingsContainer classNames={styles.grid}>
206
+ <Input.Label classNames={mx(styles.title, 'md:col-span-2')}>{toLocalizedString(title, t)}</Input.Label>
207
+ <Input.DescriptionAndValidation>
208
+ <Input.Description classNames={styles.description}>{toLocalizedString(description, t)}</Input.Description>
209
+ </Input.DescriptionAndValidation>
210
+ <div role='none' className='text-end py-1'>
211
+ {children}
212
+ </div>
213
+ </SettingsContainer>
214
+ </Input.Root>
215
+ );
216
+ };
217
+
218
+ SettingsItemInput.displayName = SETTINGS_ITEM_INPUT_NAME;
219
+
220
+ //
221
+ // Settings
222
+ //
223
+
224
+ export const Settings = {
225
+ Root: SettingsRoot,
226
+ Section: SettingsSection,
227
+ SectionHeading: SettingsSectionHeading,
228
+ Group: SettingsGroup,
229
+ GroupButton: SettingsGroupButton,
230
+ Container: SettingsContainer,
231
+ Frame: SettingsFrame,
232
+ FrameItem: SettingsFrameItem,
233
+ Item: SettingsItem,
234
+ ItemInput: SettingsItemInput,
235
+ };
@@ -2,4 +2,4 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './ControlSection';
5
+ export * from './Settings';
@@ -6,17 +6,18 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import * as Schema from 'effect/Schema';
7
7
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
8
8
 
9
- import { DXN, Filter, Obj, Query, type QueryAST, Tag, Type } from '@dxos/echo';
10
- import { type EchoSchema, Format } from '@dxos/echo/internal';
9
+ import { DXN, Filter, JsonSchema, Obj, Query, type QueryAST, Tag, Type } from '@dxos/echo';
10
+ import { type View } from '@dxos/echo';
11
+ import { type EchoSchema, Format, type Mutable } from '@dxos/echo/internal';
11
12
  import { useQuery } from '@dxos/react-client/echo';
12
- import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
13
+ import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
13
14
  import { useAsyncEffect } from '@dxos/react-ui';
14
- import { withTheme } from '@dxos/react-ui/testing';
15
- import { type ProjectionModel, View, getTypenameFromQuery } from '@dxos/schema';
16
- import { Employer, Organization, Person, Project } from '@dxos/types';
15
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
16
+ import { type ProjectionModel, ViewModel, getTypenameFromQuery } from '@dxos/schema';
17
+ import { Employer, Organization, Person, Pipeline } from '@dxos/types';
17
18
 
18
19
  import { translations } from '../../translations';
19
- import { TestLayout, TestPanel, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
20
+ import { TestLayout, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
20
21
 
21
22
  import { ViewEditor, type ViewEditorProps } from './ViewEditor';
22
23
 
@@ -24,7 +25,7 @@ const types = [
24
25
  // TODO(burdon): Get label from annotation.
25
26
  { value: Organization.Organization.typename, label: 'Organization' },
26
27
  { value: Person.Person.typename, label: 'Person' },
27
- { value: Type.getTypename(Project.Project), label: 'Project' },
28
+ { value: Pipeline.Pipeline.typename, label: 'Project' },
28
29
  { value: Employer.Employer.typename, label: 'Employer' },
29
30
  ];
30
31
 
@@ -35,15 +36,15 @@ export type ViewEditorDebugObjects = {
35
36
  projection: ProjectionModel;
36
37
  };
37
38
 
38
- type StoryProps = Pick<ViewEditorProps, 'readonly' | 'mode'>;
39
+ type DefaultStoryProps = Pick<ViewEditorProps, 'readonly' | 'mode'>;
39
40
 
40
- const DefaultStory = (props: StoryProps) => {
41
- const { space } = useClientProvider();
41
+ const DefaultStory = (props: DefaultStoryProps) => {
42
+ const { space } = useClientStory();
42
43
  const [schema, setSchema] = useState<EchoSchema>();
43
44
  const [view, setView] = useState<View.View>();
44
45
  const projectionRef = useRef<ProjectionModel>(null);
45
46
 
46
- const tags = useQuery(space, Filter.type(Tag.Tag));
47
+ const tags = useQuery(space?.db, Filter.type(Tag.Tag));
47
48
 
48
49
  useAsyncEffect(async () => {
49
50
  if (space) {
@@ -52,8 +53,8 @@ const DefaultStory = (props: StoryProps) => {
52
53
  email: Format.Email,
53
54
  salary: Format.Currency(),
54
55
  }).pipe(
55
- Type.Obj({
56
- typename: 'example.com/type/Test',
56
+ Type.object({
57
+ typename: 'com.example.type.test',
57
58
  version: '0.1.0',
58
59
  }),
59
60
  );
@@ -63,18 +64,17 @@ const DefaultStory = (props: StoryProps) => {
63
64
  description: Schema.String,
64
65
  completed: Schema.Boolean,
65
66
  }).pipe(
66
- Type.Obj({
67
- typename: 'example.com/type/Alternate',
67
+ Type.object({
68
+ typename: 'com.example.type.alternate',
68
69
  version: '0.1.0',
69
70
  }),
70
71
  );
71
72
 
72
73
  const [testSchema] = await space.db.schemaRegistry.register([TestSchema, AlternateSchema]);
73
- const view = View.make({
74
+ const view = ViewModel.make({
74
75
  name: 'Test',
75
76
  query: Query.select(Filter.type(TestSchema)),
76
- jsonSchema: Type.toJsonSchema(TestSchema),
77
- presentation: Obj.make(Type.Expando, {}),
77
+ jsonSchema: JsonSchema.toJsonSchema(TestSchema),
78
78
  });
79
79
 
80
80
  setSchema(testSchema);
@@ -88,10 +88,12 @@ const DefaultStory = (props: StoryProps) => {
88
88
  return;
89
89
  }
90
90
 
91
- if (props.mode === 'query') {
91
+ if (props.mode === 'tag') {
92
92
  const queue = target && DXN.tryParse(target) ? target : undefined;
93
- const query = queue ? Query.fromAst(newQuery).options({ queues: [queue] }) : Query.fromAst(newQuery);
94
- view.query.ast = query.ast;
93
+ const query = queue ? Query.fromAst(newQuery).from({ queues: [queue] }) : Query.fromAst(newQuery);
94
+ Obj.change(view, (obj) => {
95
+ obj.query.ast = query.ast as Mutable<typeof query.ast>;
96
+ });
95
97
 
96
98
  const typename = getTypenameFromQuery(query.ast);
97
99
  const [newSchema] = await space.db.schemaRegistry.query({ typename }).run();
@@ -99,16 +101,18 @@ const DefaultStory = (props: StoryProps) => {
99
101
  return;
100
102
  }
101
103
 
102
- const newView = View.make({
104
+ const newView = ViewModel.make({
103
105
  query,
104
106
  jsonSchema: newSchema.jsonSchema,
105
- presentation: Obj.make(Type.Expando, {}),
106
107
  });
107
- view.projection = Obj.getSnapshot(newView).projection;
108
+ Obj.change(view, (obj) => {
109
+ obj.projection = Obj.getSnapshot(newView).projection as Mutable<typeof obj.projection>;
110
+ });
108
111
  setSchema(() => newSchema);
109
112
  } else {
110
- console.log('updateViewQuery', { newQuery });
111
- view.query.ast = newQuery;
113
+ Obj.change(view, (obj) => {
114
+ obj.query.ast = newQuery as Mutable<typeof newQuery>;
115
+ });
112
116
  schema.updateTypename(getTypenameFromQuery(newQuery));
113
117
  }
114
118
  },
@@ -143,20 +147,18 @@ const DefaultStory = (props: StoryProps) => {
143
147
 
144
148
  return (
145
149
  <TestLayout json={json}>
146
- <TestPanel>
147
- <ViewEditor
148
- ref={projectionRef}
149
- schema={schema}
150
- view={view}
151
- registry={space?.db.schemaRegistry}
152
- mode={props.mode}
153
- readonly={props.readonly}
154
- types={types}
155
- tags={tags}
156
- onQueryChanged={updateViewQuery}
157
- onDelete={handleDelete}
158
- />
159
- </TestPanel>
150
+ <ViewEditor
151
+ ref={projectionRef}
152
+ schema={schema}
153
+ view={view}
154
+ registry={space?.db.schemaRegistry}
155
+ mode={props.mode}
156
+ readonly={props.readonly}
157
+ types={types}
158
+ tags={tags}
159
+ onQueryChanged={updateViewQuery}
160
+ onDelete={handleDelete}
161
+ />
160
162
  </TestLayout>
161
163
  );
162
164
  };
@@ -174,7 +176,8 @@ const meta = {
174
176
  space.db.add(Tag.make({ label: 'New' }));
175
177
  },
176
178
  }),
177
- withTheme,
179
+ withTheme(),
180
+ withLayout({ layout: 'fullscreen' }),
178
181
  ],
179
182
  parameters: {
180
183
  layout: 'fullscreen',
@@ -194,8 +197,8 @@ export const Readonly: Story = {
194
197
  },
195
198
  };
196
199
 
197
- export const QueryMode: Story = {
200
+ export const TagQueryMode: Story = {
198
201
  args: {
199
- mode: 'query',
202
+ mode: 'tag',
200
203
  },
201
204
  };
@@ -6,8 +6,9 @@ import { composeStories } from '@storybook/react';
6
6
  import { cleanup, fireEvent, screen, waitFor } from '@testing-library/react';
7
7
  import { afterEach, describe, expect, test } from 'vitest';
8
8
 
9
+ import { View } from '@dxos/echo';
9
10
  import { EchoSchema, isInstanceOf } from '@dxos/echo/internal';
10
- import { ProjectionModel, View } from '@dxos/schema';
11
+ import { ProjectionModel } from '@dxos/schema';
11
12
 
12
13
  import { VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
13
14
 
@@ -54,7 +55,7 @@ describe('ViewEditor', () => {
54
55
  const nameField = screen.getByText('name');
55
56
  fireEvent.click(nameField);
56
57
 
57
- const fieldInput = screen.getByPlaceholderText('Field name.');
58
+ const fieldInput = screen.getByPlaceholderText('Property name');
58
59
  fireEvent.change(fieldInput, { target: { value: 'new_property' } });
59
60
 
60
61
  fireEvent.click(screen.getByTestId('save-button'));
@@ -89,12 +90,12 @@ describe('ViewEditor', () => {
89
90
  await Default.run();
90
91
  await waitForViewEditor();
91
92
 
92
- // Click the add property button.
93
+ // Click the add property button. The new field is auto-expanded by handleAdd.
93
94
  const addButton = screen.getByText('Add property');
94
95
  fireEvent.click(addButton);
95
96
 
96
97
  // Fill out the property field.
97
- const fieldInput = screen.getByPlaceholderText('Field name.');
98
+ const fieldInput = screen.getByPlaceholderText('Property name');
98
99
  fireEvent.change(fieldInput, { target: { value: 'added_property' } });
99
100
 
100
101
  // Click the format combo box and select the first option.