@dxos/react-ui-form 0.8.4-main.f9ba587 → 0.8.4-main.fcfe5033a5

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 (229) hide show
  1. package/README.md +0 -1
  2. package/dist/lib/browser/index.mjs +2758 -2113
  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 +2758 -2113
  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 +9 -9
  9. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  10. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +85 -7
  11. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  12. package/dist/types/src/components/Form/Form.d.ts +86 -25
  13. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  14. package/dist/types/src/components/Form/Form.stories.d.ts +129 -56
  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/Form/fields/SelectOptionField.stories.d.ts +83 -0
  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/ObjectForm/ObjectForm.d.ts +9 -0
  53. package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -0
  54. package/dist/types/src/components/ObjectForm/index.d.ts +2 -0
  55. package/dist/types/src/components/ObjectForm/index.d.ts.map +1 -0
  56. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +54 -0
  57. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
  58. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +86 -0
  59. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
  60. package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
  61. package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
  62. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts +9 -0
  63. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -0
  64. package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +83 -0
  65. package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts.map +1 -0
  66. package/dist/types/src/components/ObjectProperties/index.d.ts +2 -0
  67. package/dist/types/src/components/ObjectProperties/index.d.ts.map +1 -0
  68. package/dist/types/src/components/Settings/Settings.d.ts +43 -0
  69. package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
  70. package/dist/types/src/components/Settings/Settings.stories.d.ts +83 -0
  71. package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
  72. package/dist/types/src/components/Settings/SettingsFieldSet.d.ts +38 -0
  73. package/dist/types/src/components/Settings/SettingsFieldSet.d.ts.map +1 -0
  74. package/dist/types/src/components/Settings/SettingsItem.d.ts +17 -0
  75. package/dist/types/src/components/Settings/SettingsItem.d.ts.map +1 -0
  76. package/dist/types/src/components/Settings/index.d.ts +3 -0
  77. package/dist/types/src/components/Settings/index.d.ts.map +1 -0
  78. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +22 -10
  79. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  80. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +86 -9
  81. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  82. package/dist/types/src/components/index.d.ts +4 -2
  83. package/dist/types/src/components/index.d.ts.map +1 -1
  84. package/dist/types/src/components/testing.d.ts +4 -4
  85. package/dist/types/src/components/testing.d.ts.map +1 -1
  86. package/dist/types/src/hooks/index.d.ts +2 -3
  87. package/dist/types/src/hooks/index.d.ts.map +1 -1
  88. package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
  89. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
  90. package/dist/types/src/hooks/useKeyHandler.d.ts +7 -0
  91. package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
  92. package/dist/types/src/translations.d.ts +64 -54
  93. package/dist/types/src/translations.d.ts.map +1 -1
  94. package/dist/types/src/util/field-type.d.ts +19 -0
  95. package/dist/types/src/util/field-type.d.ts.map +1 -0
  96. package/dist/types/src/util/field.d.ts +3 -3
  97. package/dist/types/src/util/field.d.ts.map +1 -1
  98. package/dist/types/src/util/formatting.d.ts +2 -2
  99. package/dist/types/src/util/formatting.d.ts.map +1 -1
  100. package/dist/types/src/util/index.d.ts +2 -1
  101. package/dist/types/src/util/index.d.ts.map +1 -1
  102. package/dist/types/src/util/properties.d.ts +7 -0
  103. package/dist/types/src/util/properties.d.ts.map +1 -0
  104. package/dist/types/src/util/refs.d.ts +3 -3
  105. package/dist/types/src/util/refs.d.ts.map +1 -1
  106. package/dist/types/tsconfig.tsbuildinfo +1 -1
  107. package/package.json +46 -43
  108. package/src/components/FieldEditor/FieldEditor.stories.tsx +51 -31
  109. package/src/components/FieldEditor/FieldEditor.test.tsx +13 -13
  110. package/src/components/FieldEditor/FieldEditor.tsx +88 -94
  111. package/src/components/Form/AUDIT.md +17 -0
  112. package/src/components/Form/Form.stories.tsx +144 -221
  113. package/src/components/Form/Form.tsx +359 -95
  114. package/src/components/Form/FormField.tsx +304 -0
  115. package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
  116. package/src/components/Form/FormFieldComponent.tsx +171 -0
  117. package/src/components/Form/FormFieldSet.tsx +144 -0
  118. package/src/components/Form/README.md +3 -0
  119. package/src/components/Form/fields/ArrayField.tsx +154 -0
  120. package/src/components/Form/fields/BooleanField.tsx +22 -0
  121. package/src/components/Form/fields/GeoPointField.tsx +120 -0
  122. package/src/components/Form/fields/MarkdownField.tsx +78 -0
  123. package/src/components/Form/fields/NumberField.tsx +39 -0
  124. package/src/components/Form/fields/RefField.tsx +198 -0
  125. package/src/components/Form/fields/SelectField.tsx +65 -0
  126. package/src/components/Form/fields/SelectOptionField.stories.tsx +65 -0
  127. package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +49 -45
  128. package/src/components/Form/fields/TextAreaField.tsx +38 -0
  129. package/src/components/Form/fields/TextField.tsx +38 -0
  130. package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +17 -10
  131. package/src/components/Form/fields/index.ts +15 -0
  132. package/src/components/Form/index.ts +3 -4
  133. package/src/components/ObjectForm/ObjectForm.tsx +113 -0
  134. package/src/components/{ControlSection → ObjectForm}/index.ts +1 -1
  135. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +209 -0
  136. package/src/components/ObjectPicker/ObjectPicker.tsx +186 -0
  137. package/src/components/ObjectPicker/index.ts +5 -0
  138. package/src/components/ObjectProperties/ObjectProperties.stories.tsx +63 -0
  139. package/src/components/ObjectProperties/ObjectProperties.tsx +129 -0
  140. package/src/components/ObjectProperties/index.ts +5 -0
  141. package/src/components/Settings/Settings.stories.tsx +103 -0
  142. package/src/components/Settings/Settings.tsx +81 -0
  143. package/src/components/Settings/SettingsFieldSet.tsx +211 -0
  144. package/src/components/Settings/SettingsItem.tsx +62 -0
  145. package/src/components/Settings/index.ts +6 -0
  146. package/src/components/ViewEditor/ViewEditor.stories.tsx +144 -61
  147. package/src/components/ViewEditor/ViewEditor.test.tsx +22 -22
  148. package/src/components/ViewEditor/ViewEditor.tsx +353 -218
  149. package/src/components/index.ts +4 -3
  150. package/src/components/testing.tsx +28 -12
  151. package/src/hooks/index.ts +2 -3
  152. package/src/hooks/useFormHandler.ts +380 -0
  153. package/src/hooks/useKeyHandler.tsx +37 -0
  154. package/src/translations.ts +67 -58
  155. package/src/util/field-type.ts +57 -0
  156. package/src/util/field.ts +33 -43
  157. package/src/util/formatting.ts +22 -22
  158. package/src/util/index.ts +2 -1
  159. package/src/util/properties.ts +18 -0
  160. package/src/util/refs.ts +17 -19
  161. package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
  162. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
  163. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +0 -8
  164. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
  165. package/dist/types/src/components/ControlSection/index.d.ts +0 -2
  166. package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
  167. package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
  168. package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
  169. package/dist/types/src/components/Form/Defaults.d.ts +0 -14
  170. package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
  171. package/dist/types/src/components/Form/FormActions.d.ts +0 -9
  172. package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
  173. package/dist/types/src/components/Form/FormContent.d.ts +0 -57
  174. package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
  175. package/dist/types/src/components/Form/FormContext.d.ts +0 -23
  176. package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
  177. package/dist/types/src/components/Form/Input.d.ts +0 -26
  178. package/dist/types/src/components/Form/Input.d.ts.map +0 -1
  179. package/dist/types/src/components/Form/RefField.d.ts +0 -12
  180. package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
  181. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
  182. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
  183. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
  184. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
  185. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +0 -7
  186. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
  187. package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
  188. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
  189. package/dist/types/src/components/Form/custom/index.d.ts +0 -4
  190. package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
  191. package/dist/types/src/components/Form/factory.d.ts +0 -8
  192. package/dist/types/src/components/Form/factory.d.ts.map +0 -1
  193. package/dist/types/src/components/deprecated/Form.d.ts +0 -15
  194. package/dist/types/src/components/deprecated/Form.d.ts.map +0 -1
  195. package/dist/types/src/components/deprecated/Form.stories.d.ts +0 -13
  196. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +0 -1
  197. package/dist/types/src/components/deprecated/FormInput.d.ts +0 -16
  198. package/dist/types/src/components/deprecated/FormInput.d.ts.map +0 -1
  199. package/dist/types/src/components/deprecated/index.d.ts +0 -2
  200. package/dist/types/src/components/deprecated/index.d.ts.map +0 -1
  201. package/dist/types/src/hooks/useForm.d.ts +0 -67
  202. package/dist/types/src/hooks/useForm.d.ts.map +0 -1
  203. package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -23
  204. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
  205. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
  206. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
  207. package/dist/types/src/util/schema.d.ts +0 -3
  208. package/dist/types/src/util/schema.d.ts.map +0 -1
  209. package/src/components/ControlSection/ControlSection.stories.tsx +0 -41
  210. package/src/components/ControlSection/ControlSection.tsx +0 -139
  211. package/src/components/Form/ArrayField.tsx +0 -124
  212. package/src/components/Form/Defaults.tsx +0 -203
  213. package/src/components/Form/FormActions.tsx +0 -53
  214. package/src/components/Form/FormContent.tsx +0 -240
  215. package/src/components/Form/FormContext.tsx +0 -110
  216. package/src/components/Form/Input.tsx +0 -51
  217. package/src/components/Form/RefField.tsx +0 -213
  218. package/src/components/Form/custom/GeoPointInput.tsx +0 -72
  219. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +0 -61
  220. package/src/components/Form/custom/index.ts +0 -7
  221. package/src/components/Form/factory.tsx +0 -31
  222. package/src/components/deprecated/Form.stories.tsx +0 -62
  223. package/src/components/deprecated/Form.tsx +0 -119
  224. package/src/components/deprecated/FormInput.tsx +0 -71
  225. package/src/components/deprecated/index.ts +0 -5
  226. package/src/hooks/useForm.ts +0 -291
  227. package/src/hooks/useQueryRefOptions.ts +0 -53
  228. package/src/hooks/useRefQueryLookupHandler.ts +0 -39
  229. package/src/util/schema.ts +0 -14
@@ -0,0 +1,65 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import * as Schema from 'effect/Schema';
7
+ import React, { useState } from 'react';
8
+
9
+ import { SelectOption } from '@dxos/echo/internal';
10
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
+
12
+ import { translations } from '../../../translations';
13
+ import { TestLayout } from '../../testing';
14
+ import { Form } from '../Form';
15
+ import { SelectOptionField } from './SelectOptionField';
16
+
17
+ const schema = Schema.Struct({
18
+ options: Schema.Array(SelectOption).pipe(Schema.mutable),
19
+ }).pipe(Schema.mutable);
20
+
21
+ const DefaultStory = () => {
22
+ const [values, setValues] = useState<{ options: SelectOption[] }>({
23
+ // TODO(Zaymon): Pick colors from our limited set.
24
+ options: [
25
+ { id: 'opt_1', title: 'First option', color: 'amber' },
26
+ { id: 'opt_2', title: 'Second option', color: 'emerald' },
27
+ { id: 'opt_3', title: 'Third option', color: 'rose' },
28
+ ],
29
+ });
30
+
31
+ return (
32
+ <TestLayout>
33
+ <Form.Root
34
+ schema={schema}
35
+ values={values}
36
+ fieldMap={{ options: SelectOptionField }}
37
+ onSave={(values) => setValues(values)}
38
+ >
39
+ <Form.Viewport>
40
+ <Form.Content>
41
+ <Form.FieldSet />
42
+ <Form.Actions />
43
+ </Form.Content>
44
+ </Form.Viewport>
45
+ </Form.Root>
46
+ </TestLayout>
47
+ );
48
+ };
49
+
50
+ const meta = {
51
+ title: 'ui/react-ui-form/SelectOptionField',
52
+ component: SelectOptionField as any,
53
+ render: DefaultStory,
54
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
55
+ parameters: {
56
+ layout: 'fullscreen',
57
+ translations,
58
+ },
59
+ } satisfies Meta<typeof DefaultStory>;
60
+
61
+ export default meta;
62
+
63
+ type Story = StoryObj<typeof meta>;
64
+
65
+ export const Default: Story = {};
@@ -2,33 +2,39 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React, { type ChangeEvent, type KeyboardEvent, useCallback, useEffect, useState, useRef } from 'react';
5
+ import React, { type ChangeEvent, type KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';
6
6
 
7
- import { type SelectOption } from '@dxos/echo-schema';
7
+ import { type SelectOption } from '@dxos/echo/internal';
8
8
  import { PublicKey } from '@dxos/keys';
9
- import { type ChromaticPalette, Icon, IconButton, Input, Tag, Toolbar, useTranslation } from '@dxos/react-ui';
9
+ import { type ChromaticPalette, IconButton, Input, Tag, useTranslation } from '@dxos/react-ui';
10
10
  import { List } from '@dxos/react-ui-list';
11
11
  import { HuePicker } from '@dxos/react-ui-pickers';
12
- import { hues, mx } from '@dxos/react-ui-theme';
12
+ import { hues, osTranslations, subtleHover } from '@dxos/ui-theme';
13
13
 
14
14
  import { translationKey } from '../../../translations';
15
- import { InputHeader, type InputProps } from '../Input';
16
-
17
- // TODO(ZaymonFC): Handle disabled.
18
- export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue, onValueChange }: InputProps) => {
15
+ import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
16
+
17
+ export const SelectOptionField = ({
18
+ type,
19
+ readonly,
20
+ label,
21
+ getStatus,
22
+ getValue,
23
+ onValueChange,
24
+ }: FormFieldComponentProps<SelectOption[] | undefined>) => {
25
+ const { t } = useTranslation(translationKey);
26
+ const { status, error } = getStatus();
19
27
  const [selected, setSelectedId] = useState<string | null>(null);
20
28
  const [isNewOption, setIsNewOption] = useState(false);
21
29
  const inputRef = useRef<HTMLInputElement>(null);
22
- const { t } = useTranslation(translationKey);
23
- const { status, error } = getStatus();
24
- const options = getValue<SelectOption[] | undefined>();
30
+ const options = getValue();
25
31
 
26
32
  // Initialization.
27
33
  useEffect(() => {
28
34
  if (options === undefined) {
29
35
  onValueChange(type, []);
30
36
  }
31
- }, [options, onValueChange, type]);
37
+ }, [options, type, onValueChange]);
32
38
 
33
39
  const randomHue = () => {
34
40
  const usedHues = new Set(options?.map((option) => option.color) ?? []);
@@ -89,8 +95,8 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
89
95
  [options, type, onValueChange],
90
96
  );
91
97
 
92
- const handleKeyDown = useCallback((e: KeyboardEvent<HTMLInputElement>) => {
93
- if (e.key === 'Enter') {
98
+ const handleKeyDown = useCallback((ev: KeyboardEvent<HTMLInputElement>) => {
99
+ if (ev.key === 'Enter') {
94
100
  setSelectedId(null);
95
101
  }
96
102
  }, []);
@@ -112,10 +118,15 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
112
118
 
113
119
  return (
114
120
  <Input.Root validationValence={status}>
115
- <InputHeader error={error} label={label} />
121
+ <FormFieldLabel error={error} readonly={readonly} label={label} />
116
122
  <div role='none'>
117
123
  {options && (
118
- <List.Root items={options} isItem={(item) => true} onMove={handleMove}>
124
+ <List.Root
125
+ items={options}
126
+ isItem={(_item) => true}
127
+ onMove={readonly ? undefined : handleMove}
128
+ readonly={!!readonly}
129
+ >
119
130
  {({ items }) => (
120
131
  <div role='list' className='w-full overflow-auto'>
121
132
  {items?.map((item) => (
@@ -123,45 +134,42 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
123
134
  <List.Item
124
135
  role='button'
125
136
  item={item}
126
- classNames={mx('p-1 plb-2', 'flex flex-col', 'cursor-pointer', 'hover:bg-hoverOverlay')}
137
+ classNames={[subtleHover, 'flex flex-col cursor-pointer rounded-xs']}
127
138
  aria-expanded={selected === item.id}
128
139
  >
129
140
  <div className='flex items-center'>
130
- <List.ItemDragHandle />
141
+ <List.ItemDragHandle disabled={!!readonly} />
131
142
  <List.ItemTitle onClick={() => handleClick(item.id)} classNames='flex-1'>
132
143
  {/* TODO(ZaymonFC): Move spacer into Tag component. */}
133
144
  <Tag palette={item.color as ChromaticPalette}>{item.title || '\u200b'}</Tag>
134
145
  </List.ItemTitle>
135
- <Icon
146
+ <IconButton
147
+ iconOnly
148
+ variant='ghost'
149
+ label={t(selected === item.id ? 'collapse label' : 'expand label', { ns: osTranslations })}
136
150
  icon={selected === item.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
137
151
  onClick={() => handleClick(item.id)}
138
152
  />
139
153
  </div>
140
154
  {selected === item.id && (
141
- <div className='mis-4 mbs-2 flex flex-col gap-1'>
142
- {/* 16px to match drag handle width. */}
143
- <Input.Label classNames='text-xs'>{t('select option label')}</Input.Label>
144
- <div className='flex flex-row items-center gap-1'>
155
+ <div className='flex flex-col p-form-padding gap-form-gap dx-density-fine'>
156
+ <Input.Label classNames='text-xs'>{t('select-option.label')}</Input.Label>
157
+ <div className='grid grid-cols-[1fr_min-content_min-content] gap-form-gap'>
145
158
  <Input.TextInput
146
- placeholder={t('select option label placeholder')}
159
+ disabled={!!readonly}
160
+ placeholder={t('select-option-label.placeholder')}
147
161
  ref={selected === item.id ? inputRef : undefined}
148
162
  value={item.title}
149
163
  onChange={handleTitleChange(item.id)}
150
164
  onKeyDown={handleKeyDown}
151
165
  classNames='flex-1'
152
- data-no-submit
153
166
  />
154
- <Toolbar.Root classNames='p-0 m-0 !is-auto'>
155
- <HuePicker
156
- value={item.color}
157
- onChange={handleColorChange(item.id)}
158
- rootVariant='toolbar-button'
159
- />
160
- </Toolbar.Root>
167
+ <HuePicker disabled={!!readonly} value={item.color} onChange={handleColorChange(item.id)} />
161
168
  <IconButton
169
+ disabled={!!readonly}
162
170
  icon='ph--trash--fill'
163
171
  iconOnly
164
- label={t('select option delete')}
172
+ label={t('select-option-delete.button')}
165
173
  onClick={() => handleDelete(item.id)}
166
174
  />
167
175
  </div>
@@ -170,18 +178,14 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
170
178
  </List.Item>
171
179
  </div>
172
180
  ))}
173
- <div role='listitem'>
174
- <div
175
- role='button'
176
- className={mx('p-1 plb-2', 'flex flex-col', 'cursor-pointer', 'hover:bg-hoverOverlay')}
177
- onClick={handleAdd}
178
- >
179
- <div className='flex items-center gap-1'>
180
- <Icon icon='ph--plus--regular' />
181
- <span className='text-sm'>{t('select option add')}</span>
182
- </div>
183
- </div>
184
- </div>
181
+ <IconButton
182
+ variant='ghost'
183
+ icon='ph--plus--regular'
184
+ label={t('select-option-add.button')}
185
+ onClick={handleAdd}
186
+ disabled={!!readonly}
187
+ classNames='w-full'
188
+ />
185
189
  </div>
186
190
  )}
187
191
  </List.Root>
@@ -0,0 +1,38 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { useCallback } from 'react';
6
+
7
+ import { Input, type TextAreaProps } from '@dxos/react-ui';
8
+
9
+ import { type FormFieldComponentProps, FormFieldWrapper } from '../FormFieldComponent';
10
+
11
+ export const TextAreaField = ({
12
+ type,
13
+ readonly,
14
+ placeholder,
15
+ onValueChange,
16
+ onBlur,
17
+ ...props
18
+ }: FormFieldComponentProps<string>) => {
19
+ const handleChange = useCallback<NonNullable<TextAreaProps['onChange']>>(
20
+ (event) => onValueChange(type, event.target.value),
21
+ [type, onValueChange],
22
+ );
23
+
24
+ return (
25
+ <FormFieldWrapper<string> readonly={readonly} {...props}>
26
+ {({ value = '' }) => (
27
+ <Input.TextArea
28
+ rows={3}
29
+ disabled={!!readonly}
30
+ placeholder={placeholder}
31
+ value={value}
32
+ onChange={handleChange}
33
+ onBlur={onBlur}
34
+ />
35
+ )}
36
+ </FormFieldWrapper>
37
+ );
38
+ };
@@ -0,0 +1,38 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { useCallback } from 'react';
6
+
7
+ import { Input, type TextInputProps } from '@dxos/react-ui';
8
+
9
+ import { type FormFieldComponentProps, FormFieldWrapper } from '../FormFieldComponent';
10
+
11
+ export const TextField = ({
12
+ type,
13
+ readonly,
14
+ placeholder,
15
+ onBlur,
16
+ onValueChange,
17
+ ...props
18
+ }: FormFieldComponentProps<string>) => {
19
+ const handleChange = useCallback<NonNullable<TextInputProps['onChange']>>(
20
+ (event) => onValueChange(type, event.target.value),
21
+ [type, onValueChange],
22
+ );
23
+
24
+ return (
25
+ <FormFieldWrapper<string> readonly={readonly} {...props}>
26
+ {({ value = '' }) => (
27
+ <Input.TextInput
28
+ noAutoFill
29
+ disabled={!!readonly}
30
+ placeholder={placeholder}
31
+ value={value}
32
+ onBlur={onBlur}
33
+ onChange={handleChange}
34
+ />
35
+ )}
36
+ </FormFieldWrapper>
37
+ );
38
+ };
@@ -5,39 +5,46 @@
5
5
  import React from 'react';
6
6
 
7
7
  import { Input } from '@dxos/react-ui';
8
- import { mx } from '@dxos/react-ui-theme';
8
+ import { mx } from '@dxos/ui-theme';
9
9
  import { safeParseFloat } from '@dxos/util';
10
10
 
11
- import { InputHeader, type InputProps } from '../Input';
11
+ import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
12
12
 
13
13
  const gridCols = ['grid-cols-1', 'grid-cols-2', 'grid-cols-3', 'grid-cols-4'];
14
14
 
15
- export const TupleInput = ({
15
+ export const TupleField = ({
16
16
  binding,
17
17
  type,
18
+ readonly,
18
19
  label,
19
- disabled,
20
20
  getStatus,
21
21
  getValue,
22
22
  onValueChange,
23
23
  onBlur,
24
- }: InputProps & { binding: string[] }) => {
24
+ }: FormFieldComponentProps<Record<string, number>> & {
25
+ binding: string[];
26
+ }) => {
25
27
  const { status, error } = getStatus();
26
28
 
27
29
  // TODO(burdon): Generalize number/float/string, etc.
28
- const values: Record<string, number> = getValue<Record<string, number>>() ?? {};
30
+ const values: Record<string, number> = getValue() ?? {};
29
31
 
30
32
  return (
31
33
  <Input.Root validationValence={status}>
32
- <InputHeader error={error} label={label} />
33
- <div className={mx('grid gap-2', gridCols[binding.length - 1])}>
34
+ <FormFieldLabel error={error} readonly={readonly} label={label} />
35
+ <div className={mx('grid gap-form-gap', gridCols[binding.length - 1])}>
34
36
  {binding.map((prop) => (
35
37
  <Input.TextInput
36
38
  key={prop}
37
39
  type='number'
38
- disabled={disabled}
40
+ disabled={!!readonly}
39
41
  value={values[prop]}
40
- onChange={(event) => onValueChange(type, { ...values, [prop]: safeParseFloat(event.target.value, 0) })}
42
+ onChange={(event) => {
43
+ onValueChange(type, {
44
+ ...values,
45
+ [prop]: safeParseFloat(event.target.value, 0),
46
+ });
47
+ }}
41
48
  onBlur={onBlur}
42
49
  />
43
50
  ))}
@@ -0,0 +1,15 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './ArrayField';
6
+ export * from './BooleanField';
7
+ export * from './GeoPointField';
8
+ export * from './MarkdownField';
9
+ export * from './NumberField';
10
+ export * from './RefField';
11
+ export * from './SelectField';
12
+ export * from './SelectOptionField';
13
+ export * from './TextField';
14
+ export * from './TextAreaField';
15
+ export * from './TupleField';
@@ -2,8 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export * from './custom';
6
- export * from './Defaults';
5
+ export * from './fields';
6
+
7
7
  export * from './Form';
8
- export * from './FormContext';
9
- export * from './Input';
8
+ export * from './FormFieldComponent';
@@ -0,0 +1,113 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Schema from 'effect/Schema';
6
+ import React, { useCallback, useMemo } from 'react';
7
+
8
+ import { DXN, Obj, Ref, Tag, Type } from '@dxos/echo';
9
+ import { type JsonPath, splitJsonPath } from '@dxos/echo/internal';
10
+ import { invariant } from '@dxos/invariant';
11
+ import { HuePicker } from '@dxos/react-ui-pickers';
12
+ import { isNonNullable } from '@dxos/util';
13
+
14
+ import { translationKey } from '../../translations';
15
+ import { Form, type FormFieldMap, omitId } from '../Form';
16
+
17
+ export type ObjectFormProps = {
18
+ schema: Schema.Schema.AnyNoContext;
19
+ object: Obj.Unknown;
20
+ };
21
+
22
+ const createFieldMap: FormFieldMap = {
23
+ hue: ({ type, label, layout, getValue, onValueChange }) => {
24
+ const handleChange = useCallback((nextHue: string) => onValueChange(type, nextHue), [onValueChange, type]);
25
+ const handleReset = useCallback(() => onValueChange(type, undefined), [onValueChange, type]);
26
+ return (
27
+ <>
28
+ {layout !== 'inline' && <Form.Label label={label} />}
29
+ <HuePicker value={getValue()} onChange={handleChange} onReset={handleReset} />
30
+ </>
31
+ );
32
+ },
33
+ };
34
+
35
+ export const ObjectForm = ({ object, schema }: ObjectFormProps) => {
36
+ const db = Obj.getDatabase(object);
37
+ const meta = Obj.getMeta(object);
38
+ const tags = (meta.tags ?? []).map((tag) => db?.makeRef(DXN.parse(tag))).filter(isNonNullable);
39
+ const values = useMemo(() => ({ tags, ...object }), [object, tags]);
40
+ const formSchema = useMemo(
41
+ () =>
42
+ omitId(
43
+ Schema.Struct({
44
+ tags: Schema.Array(Ref.Ref(Tag.Tag)).pipe(Schema.optional),
45
+ }).pipe(Schema.extend(schema)),
46
+ ),
47
+ [schema],
48
+ );
49
+
50
+ const handleCreate = useCallback((schema: Type.AnyEntity, values: any) => {
51
+ invariant(db);
52
+ invariant(Type.isObjectSchema(schema));
53
+ const newObject = db.add(Obj.make(schema, values));
54
+ if (Obj.instanceOf(Tag.Tag, newObject)) {
55
+ Obj.change(object, (object) => {
56
+ Obj.getMeta(object).tags = [...(Obj.getMeta(object).tags ?? []), Obj.getDXN(newObject).toString()];
57
+ });
58
+ }
59
+ }, []);
60
+
61
+ // TODO(wittjosiah): Use FormRootProps type.
62
+ const handleChange = useCallback(
63
+ ({ tags, ...values }: any, { isValid, changed }: { isValid: boolean; changed: Record<JsonPath, boolean> }) => {
64
+ if (!isValid) {
65
+ return;
66
+ }
67
+
68
+ const changedPaths = Object.keys(changed).filter((path) => changed[path as JsonPath]) as JsonPath[];
69
+
70
+ // Handle tags separately using Obj.change.
71
+ const hasTagsChange = changedPaths.some((path) => splitJsonPath(path)[0] === 'tags');
72
+ if (hasTagsChange) {
73
+ Obj.change(object, (object) => {
74
+ Obj.getMeta(object).tags = tags?.map((tag: Ref.Ref<Tag.Tag>) => tag.dxn.toString()) ?? [];
75
+ });
76
+ }
77
+
78
+ // Handle other property changes.
79
+ const nonTagPaths = changedPaths.filter((path) => splitJsonPath(path)[0] !== 'tags');
80
+ if (nonTagPaths.length > 0) {
81
+ Obj.change(object, () => {
82
+ for (const path of nonTagPaths) {
83
+ const parts = splitJsonPath(path);
84
+ const value = Obj.getValue(values, parts);
85
+ Obj.setValue(object, parts, value);
86
+ }
87
+ });
88
+ }
89
+ },
90
+ [object],
91
+ );
92
+
93
+ return (
94
+ <Form.Root
95
+ schema={formSchema}
96
+ defaultValues={values}
97
+ createTypename={Type.getTypename(Tag.Tag)}
98
+ createOptionIcon='ph--plus--regular'
99
+ createOptionLabel={['add-tag.label', { ns: translationKey }]}
100
+ createInitialValuePath='label'
101
+ createFieldMap={createFieldMap}
102
+ db={db}
103
+ onValuesChanged={handleChange}
104
+ onCreate={handleCreate}
105
+ >
106
+ <Form.Viewport>
107
+ <Form.Content>
108
+ <Form.FieldSet />
109
+ </Form.Content>
110
+ </Form.Viewport>
111
+ </Form.Root>
112
+ );
113
+ };
@@ -2,4 +2,4 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './ControlSection';
5
+ export * from './ObjectForm';