@dxos/react-ui-form 0.8.4-main.c4373fc → 0.8.4-main.c85a9c8dae

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 (185) hide show
  1. package/README.md +0 -1
  2. package/dist/lib/browser/index.mjs +2289 -2269
  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 +2289 -2269
  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 +13 -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 +81 -26
  13. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  14. package/dist/types/src/components/Form/Form.stories.d.ts +59 -84
  15. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/Form/FormField.d.ts +37 -0
  17. package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
  18. package/dist/types/src/components/Form/FormFieldComponent.d.ts +78 -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 +19 -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 +14 -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} +12 -14
  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 +15 -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 -6
  55. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
  56. package/dist/types/src/components/Settings/Settings.d.ts +56 -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} +10 -8
  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 +16 -16
  63. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  64. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +13 -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 +10 -7
  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 +1 -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 -46
  90. package/src/components/FieldEditor/FieldEditor.stories.tsx +43 -24
  91. package/src/components/FieldEditor/FieldEditor.tsx +74 -78
  92. package/src/components/Form/Form.stories.tsx +149 -248
  93. package/src/components/Form/Form.tsx +347 -107
  94. package/src/components/Form/FormField.tsx +294 -0
  95. package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
  96. package/src/components/Form/FormFieldComponent.tsx +172 -0
  97. package/src/components/Form/FormFieldSet.tsx +123 -0
  98. package/src/components/Form/README.md +3 -0
  99. package/src/components/Form/fields/ArrayField.tsx +149 -0
  100. package/src/components/Form/fields/BooleanField.tsx +22 -0
  101. package/src/components/Form/fields/GeoPointField.tsx +120 -0
  102. package/src/components/Form/fields/MarkdownField.tsx +78 -0
  103. package/src/components/Form/fields/NumberField.tsx +39 -0
  104. package/src/components/Form/fields/RefField.tsx +189 -0
  105. package/src/components/Form/fields/SelectField.tsx +65 -0
  106. package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +19 -16
  107. package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +23 -17
  108. package/src/components/Form/fields/TextAreaField.tsx +38 -0
  109. package/src/components/Form/fields/TextField.tsx +38 -0
  110. package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +15 -8
  111. package/src/components/Form/fields/index.ts +15 -0
  112. package/src/components/Form/index.ts +3 -4
  113. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +40 -37
  114. package/src/components/ObjectPicker/ObjectPicker.tsx +84 -70
  115. package/src/components/Settings/Settings.stories.tsx +56 -0
  116. package/src/components/Settings/Settings.tsx +230 -0
  117. package/src/components/{ControlSection → Settings}/index.ts +1 -1
  118. package/src/components/ViewEditor/ViewEditor.stories.tsx +69 -49
  119. package/src/components/ViewEditor/ViewEditor.test.tsx +8 -6
  120. package/src/components/ViewEditor/ViewEditor.tsx +302 -271
  121. package/src/components/index.ts +1 -1
  122. package/src/components/testing.tsx +12 -7
  123. package/src/hooks/index.ts +2 -3
  124. package/src/hooks/useFormHandler.ts +380 -0
  125. package/src/hooks/useKeyHandler.tsx +37 -0
  126. package/src/translations.ts +12 -10
  127. package/src/util/field.ts +25 -25
  128. package/src/util/formatting.ts +22 -22
  129. package/src/util/index.ts +1 -1
  130. package/src/util/properties.ts +18 -0
  131. package/src/util/refs.ts +11 -17
  132. package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
  133. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
  134. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
  135. package/dist/types/src/components/ControlSection/index.d.ts +0 -2
  136. package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
  137. package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
  138. package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
  139. package/dist/types/src/components/Form/Defaults.d.ts +0 -14
  140. package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
  141. package/dist/types/src/components/Form/FormActions.d.ts +0 -9
  142. package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
  143. package/dist/types/src/components/Form/FormContent.d.ts +0 -63
  144. package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
  145. package/dist/types/src/components/Form/FormContext.d.ts +0 -23
  146. package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
  147. package/dist/types/src/components/Form/Input.d.ts +0 -29
  148. package/dist/types/src/components/Form/Input.d.ts.map +0 -1
  149. package/dist/types/src/components/Form/RefField.d.ts +0 -21
  150. package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
  151. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
  152. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
  153. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
  154. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
  155. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
  156. package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
  157. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
  158. package/dist/types/src/components/Form/custom/index.d.ts +0 -4
  159. package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
  160. package/dist/types/src/components/Form/factory.d.ts +0 -8
  161. package/dist/types/src/components/Form/factory.d.ts.map +0 -1
  162. package/dist/types/src/hooks/useForm.d.ts +0 -67
  163. package/dist/types/src/hooks/useForm.d.ts.map +0 -1
  164. package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -28
  165. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
  166. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
  167. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
  168. package/dist/types/src/util/schema.d.ts +0 -3
  169. package/dist/types/src/util/schema.d.ts.map +0 -1
  170. package/src/components/ControlSection/ControlSection.stories.tsx +0 -38
  171. package/src/components/ControlSection/ControlSection.tsx +0 -153
  172. package/src/components/Form/ArrayField.tsx +0 -125
  173. package/src/components/Form/Defaults.tsx +0 -205
  174. package/src/components/Form/FormActions.tsx +0 -54
  175. package/src/components/Form/FormContent.tsx +0 -322
  176. package/src/components/Form/FormContext.tsx +0 -110
  177. package/src/components/Form/Input.tsx +0 -54
  178. package/src/components/Form/RefField.tsx +0 -269
  179. package/src/components/Form/custom/GeoPointInput.tsx +0 -80
  180. package/src/components/Form/custom/index.ts +0 -7
  181. package/src/components/Form/factory.tsx +0 -31
  182. package/src/hooks/useForm.ts +0 -295
  183. package/src/hooks/useQueryRefOptions.ts +0 -59
  184. package/src/hooks/useRefQueryLookupHandler.ts +0 -39
  185. package/src/util/schema.ts +0 -12
@@ -6,17 +6,17 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import * as Schema from 'effect/Schema';
7
7
  import React, { useState } from 'react';
8
8
 
9
- import { type SelectOption, SelectOptionSchema } from '@dxos/echo/internal';
10
- import { withTheme } from '@dxos/react-ui/testing';
9
+ import { SelectOption } from '@dxos/echo/internal';
10
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
11
 
12
12
  import { translations } from '../../../translations';
13
- import { TestLayout, TestPanel } from '../../testing';
13
+ import { TestLayout } from '../../testing';
14
14
  import { Form } from '../Form';
15
15
 
16
- import { SelectOptionInput } from './SelectOptionsInput';
16
+ import { SelectOptionField } from './SelectOptionField';
17
17
 
18
18
  const schema = Schema.Struct({
19
- options: Schema.Array(SelectOptionSchema).pipe(Schema.mutable),
19
+ options: Schema.Array(SelectOption).pipe(Schema.mutable),
20
20
  }).pipe(Schema.mutable);
21
21
 
22
22
  const DefaultStory = () => {
@@ -31,23 +31,26 @@ const DefaultStory = () => {
31
31
 
32
32
  return (
33
33
  <TestLayout>
34
- <TestPanel>
35
- <Form
36
- schema={schema}
37
- values={values}
38
- onSave={(vals) => setValues(vals)}
39
- Custom={{ options: SelectOptionInput }}
40
- />
41
- </TestPanel>
34
+ <Form.Root
35
+ schema={schema}
36
+ values={values}
37
+ fieldMap={{ options: SelectOptionField }}
38
+ onSave={(values) => setValues(values)}
39
+ >
40
+ <Form.Content>
41
+ <Form.FieldSet />
42
+ <Form.Actions />
43
+ </Form.Content>
44
+ </Form.Root>
42
45
  </TestLayout>
43
46
  );
44
47
  };
45
48
 
46
49
  const meta = {
47
- title: 'ui/react-ui-form/SelectOptionsInput',
48
- component: SelectOptionInput as any,
50
+ title: 'ui/react-ui-form/SelectOptionField',
51
+ component: SelectOptionField as any,
49
52
  render: DefaultStory,
50
- decorators: [withTheme],
53
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
51
54
  parameters: {
52
55
  layout: 'fullscreen',
53
56
  translations,
@@ -9,25 +9,32 @@ import { PublicKey } from '@dxos/keys';
9
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, subtleHover } 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
- export const SelectOptionInput = ({ type, label, readonly, 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();
18
27
  const [selected, setSelectedId] = useState<string | null>(null);
19
28
  const [isNewOption, setIsNewOption] = useState(false);
20
29
  const inputRef = useRef<HTMLInputElement>(null);
21
- const { t } = useTranslation(translationKey);
22
- const { status, error } = getStatus();
23
- const options = getValue<SelectOption[] | undefined>();
30
+ const options = getValue();
24
31
 
25
32
  // Initialization.
26
33
  useEffect(() => {
27
34
  if (options === undefined) {
28
35
  onValueChange(type, []);
29
36
  }
30
- }, [options, onValueChange, type]);
37
+ }, [options, type, onValueChange]);
31
38
 
32
39
  const randomHue = () => {
33
40
  const usedHues = new Set(options?.map((option) => option.color) ?? []);
@@ -88,8 +95,8 @@ export const SelectOptionInput = ({ type, label, readonly, getStatus, getValue,
88
95
  [options, type, onValueChange],
89
96
  );
90
97
 
91
- const handleKeyDown = useCallback((e: KeyboardEvent<HTMLInputElement>) => {
92
- if (e.key === 'Enter') {
98
+ const handleKeyDown = useCallback((ev: KeyboardEvent<HTMLInputElement>) => {
99
+ if (ev.key === 'Enter') {
93
100
  setSelectedId(null);
94
101
  }
95
102
  }, []);
@@ -111,12 +118,12 @@ export const SelectOptionInput = ({ type, label, readonly, getStatus, getValue,
111
118
 
112
119
  return (
113
120
  <Input.Root validationValence={status}>
114
- <InputHeader error={error} label={label} />
121
+ <FormFieldLabel error={error} readonly={readonly} label={label} />
115
122
  <div role='none'>
116
123
  {options && (
117
124
  <List.Root
118
125
  items={options}
119
- isItem={(item) => true}
126
+ isItem={(_item) => true}
120
127
  onMove={readonly ? undefined : handleMove}
121
128
  readonly={!!readonly}
122
129
  >
@@ -127,7 +134,7 @@ export const SelectOptionInput = ({ type, label, readonly, getStatus, getValue,
127
134
  <List.Item
128
135
  role='button'
129
136
  item={item}
130
- classNames={[subtleHover, 'flex flex-col cursor-pointer rounded-sm']}
137
+ classNames={[subtleHover, 'flex flex-col cursor-pointer rounded-xs']}
131
138
  aria-expanded={selected === item.id}
132
139
  >
133
140
  <div className='flex items-center'>
@@ -139,13 +146,13 @@ export const SelectOptionInput = ({ type, label, readonly, getStatus, getValue,
139
146
  <IconButton
140
147
  iconOnly
141
148
  variant='ghost'
142
- label={t(selected === item.id ? 'collapse label' : 'expand label', { ns: 'os' })}
149
+ label={t(selected === item.id ? 'collapse label' : 'expand label', { ns: osTranslations })}
143
150
  icon={selected === item.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
144
151
  onClick={() => handleClick(item.id)}
145
152
  />
146
153
  </div>
147
154
  {selected === item.id && (
148
- <div className='pli-cardSpacingInline plb-cardSpacingBlock flex flex-col gap-1 density-fine'>
155
+ <div className='flex flex-col p-form-padding gap-1 dx-density-fine'>
149
156
  {/* 16px to match drag handle width. */}
150
157
  <Input.Label classNames='text-xs'>{t('select option label')}</Input.Label>
151
158
  <div className='grid grid-cols-[1fr_min-content_min-content] gap-1'>
@@ -157,7 +164,6 @@ export const SelectOptionInput = ({ type, label, readonly, getStatus, getValue,
157
164
  onChange={handleTitleChange(item.id)}
158
165
  onKeyDown={handleKeyDown}
159
166
  classNames='flex-1'
160
- data-no-submit
161
167
  />
162
168
  <HuePicker disabled={!!readonly} value={item.color} onChange={handleColorChange(item.id)} />
163
169
  <IconButton
@@ -179,7 +185,7 @@ export const SelectOptionInput = ({ type, label, readonly, getStatus, getValue,
179
185
  label={t('select option add')}
180
186
  onClick={handleAdd}
181
187
  disabled={!!readonly}
182
- classNames='is-full'
188
+ classNames='w-full'
183
189
  />
184
190
  </div>
185
191
  )}
@@ -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
+ onValueChange,
16
+ onBlur,
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
+ onChange={handleChange}
33
+ onBlur={onBlur}
34
+ />
35
+ )}
36
+ </FormFieldWrapper>
37
+ );
38
+ };
@@ -5,31 +5,33 @@
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
- label,
19
18
  readonly,
19
+ label,
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} />
34
+ <FormFieldLabel error={error} readonly={readonly} label={label} />
33
35
  <div className={mx('grid gap-2', gridCols[binding.length - 1])}>
34
36
  {binding.map((prop) => (
35
37
  <Input.TextInput
@@ -37,7 +39,12 @@ export const TupleInput = ({
37
39
  type='number'
38
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';
@@ -9,12 +9,12 @@ import { expect, fn, userEvent, within } from 'storybook/test';
9
9
 
10
10
  import { Obj } from '@dxos/echo';
11
11
  import { faker } from '@dxos/random';
12
- import { useClient } from '@dxos/react-client';
13
12
  import { Filter, useQuery } from '@dxos/react-client/echo';
14
- import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
13
+ import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
15
14
  import { Button } from '@dxos/react-ui';
16
15
  import { withTheme } from '@dxos/react-ui/testing';
17
- import { DataType } from '@dxos/schema';
16
+ import { Person } from '@dxos/types';
17
+ import { osTranslations } from '@dxos/ui-theme';
18
18
 
19
19
  import { translations } from '../../translations';
20
20
 
@@ -23,20 +23,19 @@ import { ObjectPicker } from './ObjectPicker';
23
23
  faker.seed(1);
24
24
 
25
25
  const createPerson = () =>
26
- Obj.make(DataType.Person, {
26
+ Obj.make(Person.Person, {
27
27
  fullName: faker.person.fullName(),
28
28
  });
29
29
 
30
30
  const omitId = Schema.omit<any, any, ['id']>('id');
31
- const personSchema = omitId(DataType.Person);
31
+ const personSchema = omitId(Person.Person);
32
32
 
33
33
  // Mock functions for testing
34
34
  const mockHandleSelect = fn();
35
35
  const mockHandleCreate = fn();
36
36
 
37
37
  const DefaultStory = () => {
38
- const client = useClient();
39
- const { space } = useClientProvider();
38
+ const { space } = useClientStory();
40
39
  const [isOpen, setIsOpen] = useState(false);
41
40
 
42
41
  // Create sample Person objects
@@ -50,7 +49,7 @@ const DefaultStory = () => {
50
49
  }, [space]);
51
50
 
52
51
  // Get all objects in the space (similar to BoardContainer)
53
- const allObjects = useQuery(space, Filter.everything());
52
+ const allObjects = useQuery(space?.db, Filter.everything());
54
53
 
55
54
  // Map objects to options format expected by ObjectPicker
56
55
  const options = useMemo(
@@ -67,7 +66,7 @@ const DefaultStory = () => {
67
66
  (values: any) => {
68
67
  console.log('[on create]', values);
69
68
  if (!space) return;
70
- const newPerson = space.db.add(Obj.make(DataType.Person, values));
69
+ const newPerson = space.db.add(Obj.make(Person.Person, values));
71
70
  mockHandleCreate(values);
72
71
  return newPerson;
73
72
  },
@@ -75,22 +74,22 @@ const DefaultStory = () => {
75
74
  );
76
75
 
77
76
  return (
78
- <div className='p-4'>
77
+ <div role='none' className='flex w-96'>
79
78
  <ObjectPicker.Root open={isOpen} onOpenChange={setIsOpen}>
80
79
  <ObjectPicker.Trigger asChild>
81
- <Button variant='primary' data-testid='trigger'>
80
+ <Button variant='primary' data-testid='trigger' classNames='w-full'>
82
81
  Select Person
83
82
  </Button>
84
83
  </ObjectPicker.Trigger>
85
84
  <ObjectPicker.Content
85
+ classNames='dx-card-popover-width'
86
86
  options={options}
87
- onSelect={mockHandleSelect}
88
87
  createSchema={personSchema}
89
- createOptionLabel={['create new person label', { ns: 'os' }]}
88
+ createOptionLabel={['create new person label', { ns: osTranslations }]}
90
89
  createOptionIcon='ph--user-plus--regular'
91
90
  createInitialValuePath='fullName'
92
91
  onCreate={handleCreateCallback}
93
- classNames='popover-card-width'
92
+ onSelect={mockHandleSelect}
94
93
  />
95
94
  </ObjectPicker.Root>
96
95
  </div>
@@ -101,9 +100,9 @@ const meta = {
101
100
  title: 'ui/react-ui-form/ObjectPicker',
102
101
  render: DefaultStory,
103
102
  decorators: [
104
- withTheme,
103
+ withTheme(),
105
104
  withClientProvider({
106
- types: [DataType.Person],
105
+ types: [Person.Person],
107
106
  createIdentity: true,
108
107
  createSpace: true,
109
108
  }),
@@ -119,37 +118,39 @@ export default meta;
119
118
 
120
119
  type Story = StoryObj<typeof meta>;
121
120
 
122
- export const Default: Story = {
121
+ export const Default: Story = {};
122
+
123
+ export const WithTest: Story = {
123
124
  play: async ({ canvasElement }) => {
124
125
  const canvas = within(canvasElement);
125
126
  const body = within(document.body);
126
127
 
127
- // Find and click the trigger button
128
+ // Find and click the trigger button.
128
129
  const triggerButton = await canvas.findByTestId('trigger', undefined, { timeout: 5000 });
129
130
  await expect(triggerButton).toBeVisible();
130
131
  await userEvent.click(triggerButton);
131
132
 
132
- // Check that the listbox renders when the trigger is clicked
133
+ // Check that the listbox renders when the trigger is clicked.
133
134
  const listbox = await body.findByRole('listbox', undefined, { timeout: 5000 });
134
135
  await expect(listbox).toBeVisible();
135
136
 
136
- // Find the search input
137
+ // Find the search input.
137
138
  const searchInput = await body.findByPlaceholderText('Search…');
138
139
  await expect(searchInput).toBeVisible();
139
140
 
140
- // Test searching filters the results - get initial options count
141
+ // Test searching filters the results - get initial options count.
141
142
  const initialOptions = await body.findAllByRole('option');
142
143
  const initialCount = initialOptions.length;
143
144
  await expect(initialCount).toBeGreaterThan(0);
144
145
 
145
- // Type a search term that should filter results
146
+ // Type a search term that should filter results.
146
147
  await userEvent.type(searchInput, 'John');
147
148
 
148
- // Wait a moment for filtering
149
+ // Wait a moment for filtering.
149
150
  await new Promise((resolve) => setTimeout(resolve, 100));
150
151
 
151
152
  // Check that results are filtered (should be fewer options)
152
- const filteredOptions = await body.findAllByRole('option');
153
+ const _filteredOptions = await body.findAllByRole('option');
153
154
  // Note: We expect either fewer results or the same if no John exists
154
155
 
155
156
  // Clear search and test selecting a result
@@ -160,48 +161,50 @@ export const Default: Story = {
160
161
  const firstOption = updatedOptions[0];
161
162
  await expect(firstOption).toBeVisible();
162
163
 
163
- const firstOptionText = await firstOption.textContent;
164
+ const _firstOptionText = firstOption.textContent;
164
165
  await userEvent.click(firstOption);
165
166
 
166
167
  // Check that clicking a result fires onSelect with that id
167
168
  // The onSelect should have been called with the selected option's id
168
169
  await expect(mockHandleSelect).toHaveBeenCalled();
169
170
 
170
- // Re-open the picker to test create functionality
171
+ // Re-open the picker to test create functionality.
171
172
  await userEvent.click(triggerButton);
172
173
 
173
- // Wait for listbox to appear again
174
+ // Wait for listbox to appear again.
174
175
  const newListbox = await body.findByRole('listbox', undefined, { timeout: 5000 });
175
176
  await expect(newListbox).toBeVisible();
176
177
 
177
178
  const newSearchInput = await body.findByPlaceholderText('Search…');
178
179
 
179
- // Test searching an unrelated term provides the __create__ option
180
+ // Test searching an unrelated term provides the __create__ option.
180
181
  const unrelatedTerm = 'ZxyUnrelatedName123';
181
182
  await userEvent.type(newSearchInput, unrelatedTerm);
182
183
 
183
- // Wait for the create option to appear
184
+ // Wait for the create option to appear.
184
185
  await new Promise((resolve) => setTimeout(resolve, 200));
185
186
 
186
- // Look for the create option
187
+ // Look for the create option.
187
188
  const createOption = await body.findByRole('option', undefined, { timeout: 3000 });
188
189
  await expect(createOption).toBeVisible();
189
190
 
190
- // Click the create option - this should change state to the form field
191
+ // Click the create option - this should change state to the form field.
191
192
  await userEvent.click(createOption);
192
193
 
193
- // Check that clicking that option changes state to the form field
194
+ // Check that clicking that option changes state to the form field.
194
195
  const form = await body.findByTestId('create-referenced-object-form', undefined, { timeout: 5000 });
195
196
  await expect(form).toBeVisible();
196
197
 
197
- // Find and click the save button
198
+ // Find and click the save button.
198
199
  const saveButton = await within(form).findByTestId('save-button');
199
200
  await expect(saveButton).toBeVisible();
200
201
  await userEvent.click(saveButton);
201
202
 
202
- // Check that clicking the save button calls onCreate with expected values
203
- await expect(mockHandleCreate).toHaveBeenCalledWith({
204
- fullName: unrelatedTerm,
205
- });
203
+ // Check that clicking the save button calls onCreate with expected values.
204
+ await expect(mockHandleCreate).toHaveBeenCalledWith(
205
+ expect.objectContaining({
206
+ fullName: unrelatedTerm,
207
+ }),
208
+ );
206
209
  },
207
210
  };