@dxos/react-ui-form 0.8.4-main.ae835ea → 0.8.4-main.bc674ce

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 (177) hide show
  1. package/dist/lib/browser/index.mjs +2182 -2231
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +2182 -2231
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/ControlSection/ControlSection.d.ts +2 -2
  8. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +1 -1
  9. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +11 -8
  10. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
  11. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +6 -5
  12. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  13. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +13 -7
  14. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/Form-old.stories.d.ts +131 -0
  16. package/dist/types/src/components/Form/Form-old.stories.d.ts.map +1 -0
  17. package/dist/types/src/components/Form/Form.d.ts +81 -26
  18. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/Form.stories.d.ts +59 -84
  20. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  21. package/dist/types/src/components/Form/FormField.d.ts +37 -0
  22. package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
  23. package/dist/types/src/components/Form/FormFieldComponent.d.ts +78 -0
  24. package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -0
  25. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +9 -0
  26. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -0
  27. package/dist/types/src/components/Form/FormFieldSet.d.ts +19 -0
  28. package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -0
  29. package/dist/types/src/components/Form/fields/ArrayField.d.ts +18 -0
  30. package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -0
  31. package/dist/types/src/components/Form/fields/BooleanField.d.ts +4 -0
  32. package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -0
  33. package/dist/types/src/components/Form/fields/GeoPointField.d.ts +5 -0
  34. package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -0
  35. package/dist/types/src/components/Form/fields/MarkdownField.d.ts +4 -0
  36. package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -0
  37. package/dist/types/src/components/Form/fields/NumberField.d.ts +4 -0
  38. package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -0
  39. package/dist/types/src/components/Form/fields/RefField.d.ts +14 -0
  40. package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -0
  41. package/dist/types/src/components/Form/fields/SelectField.d.ts +10 -0
  42. package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -0
  43. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +5 -0
  44. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -0
  45. package/dist/types/src/components/Form/{custom/SelectOptionsInput.stories.d.ts → fields/SelectOptionField.stories.d.ts} +10 -7
  46. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -0
  47. package/dist/types/src/components/Form/fields/TextAreaField.d.ts +4 -0
  48. package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -0
  49. package/dist/types/src/components/Form/fields/TextField.d.ts +4 -0
  50. package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -0
  51. package/dist/types/src/components/Form/fields/TupleField.d.ts +6 -0
  52. package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -0
  53. package/dist/types/src/components/Form/fields/index.d.ts +12 -0
  54. package/dist/types/src/components/Form/fields/index.d.ts.map +1 -0
  55. package/dist/types/src/components/Form/index.d.ts +2 -4
  56. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  57. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +14 -9
  58. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
  59. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +9 -6
  60. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
  61. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +11 -13
  62. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  63. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +12 -9
  64. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  65. package/dist/types/src/components/testing.d.ts +0 -2
  66. package/dist/types/src/components/testing.d.ts.map +1 -1
  67. package/dist/types/src/hooks/index.d.ts +2 -3
  68. package/dist/types/src/hooks/index.d.ts.map +1 -1
  69. package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
  70. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
  71. package/dist/types/src/hooks/useKeyHandler.d.ts +6 -0
  72. package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
  73. package/dist/types/src/translations.d.ts +10 -7
  74. package/dist/types/src/translations.d.ts.map +1 -1
  75. package/dist/types/src/util/echo.d.ts +6 -0
  76. package/dist/types/src/util/echo.d.ts.map +1 -0
  77. package/dist/types/src/util/field.d.ts +3 -3
  78. package/dist/types/src/util/field.d.ts.map +1 -1
  79. package/dist/types/src/util/formatting.d.ts +2 -2
  80. package/dist/types/src/util/formatting.d.ts.map +1 -1
  81. package/dist/types/src/util/index.d.ts +2 -1
  82. package/dist/types/src/util/index.d.ts.map +1 -1
  83. package/dist/types/src/util/properties.d.ts +7 -0
  84. package/dist/types/src/util/properties.d.ts.map +1 -0
  85. package/dist/types/src/util/refs.d.ts +1 -2
  86. package/dist/types/src/util/refs.d.ts.map +1 -1
  87. package/dist/types/tsconfig.tsbuildinfo +1 -1
  88. package/package.json +49 -43
  89. package/src/components/ControlSection/ControlSection.stories.tsx +1 -1
  90. package/src/components/ControlSection/ControlSection.tsx +8 -8
  91. package/src/components/FieldEditor/FieldEditor.stories.tsx +42 -21
  92. package/src/components/FieldEditor/FieldEditor.tsx +73 -76
  93. package/src/components/Form/Form-old.stories.tsx +314 -0
  94. package/src/components/Form/Form.stories.tsx +148 -248
  95. package/src/components/Form/Form.tsx +320 -108
  96. package/src/components/Form/FormField.tsx +286 -0
  97. package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
  98. package/src/components/Form/FormFieldComponent.tsx +174 -0
  99. package/src/components/Form/FormFieldSet.tsx +125 -0
  100. package/src/components/Form/README.md +3 -0
  101. package/src/components/Form/fields/ArrayField.tsx +151 -0
  102. package/src/components/Form/fields/BooleanField.tsx +22 -0
  103. package/src/components/Form/fields/GeoPointField.tsx +120 -0
  104. package/src/components/Form/fields/MarkdownField.tsx +78 -0
  105. package/src/components/Form/fields/NumberField.tsx +39 -0
  106. package/src/components/Form/fields/RefField.tsx +181 -0
  107. package/src/components/Form/fields/SelectField.tsx +65 -0
  108. package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +17 -14
  109. package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +21 -15
  110. package/src/components/Form/fields/TextAreaField.tsx +38 -0
  111. package/src/components/Form/fields/TextField.tsx +38 -0
  112. package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +15 -8
  113. package/src/components/Form/fields/index.ts +15 -0
  114. package/src/components/Form/index.ts +3 -4
  115. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +20 -13
  116. package/src/components/ObjectPicker/ObjectPicker.tsx +76 -62
  117. package/src/components/ViewEditor/ViewEditor.stories.tsx +42 -40
  118. package/src/components/ViewEditor/ViewEditor.test.tsx +8 -4
  119. package/src/components/ViewEditor/ViewEditor.tsx +233 -172
  120. package/src/components/testing.tsx +9 -7
  121. package/src/hooks/index.ts +2 -3
  122. package/src/hooks/useFormHandler.ts +362 -0
  123. package/src/hooks/useKeyHandler.tsx +37 -0
  124. package/src/translations.ts +12 -10
  125. package/src/util/echo.ts +25 -0
  126. package/src/util/field.ts +25 -25
  127. package/src/util/formatting.ts +22 -22
  128. package/src/util/index.ts +2 -1
  129. package/src/util/properties.ts +18 -0
  130. package/src/util/refs.ts +11 -17
  131. package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
  132. package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
  133. package/dist/types/src/components/Form/Defaults.d.ts +0 -14
  134. package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
  135. package/dist/types/src/components/Form/FormActions.d.ts +0 -9
  136. package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
  137. package/dist/types/src/components/Form/FormContent.d.ts +0 -63
  138. package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
  139. package/dist/types/src/components/Form/FormContext.d.ts +0 -23
  140. package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
  141. package/dist/types/src/components/Form/Input.d.ts +0 -29
  142. package/dist/types/src/components/Form/Input.d.ts.map +0 -1
  143. package/dist/types/src/components/Form/RefField.d.ts +0 -22
  144. package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
  145. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
  146. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
  147. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
  148. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
  149. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
  150. package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
  151. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
  152. package/dist/types/src/components/Form/custom/index.d.ts +0 -4
  153. package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
  154. package/dist/types/src/components/Form/factory.d.ts +0 -8
  155. package/dist/types/src/components/Form/factory.d.ts.map +0 -1
  156. package/dist/types/src/hooks/useForm.d.ts +0 -67
  157. package/dist/types/src/hooks/useForm.d.ts.map +0 -1
  158. package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -29
  159. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
  160. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
  161. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
  162. package/dist/types/src/util/schema.d.ts +0 -3
  163. package/dist/types/src/util/schema.d.ts.map +0 -1
  164. package/src/components/Form/ArrayField.tsx +0 -125
  165. package/src/components/Form/Defaults.tsx +0 -205
  166. package/src/components/Form/FormActions.tsx +0 -54
  167. package/src/components/Form/FormContent.tsx +0 -322
  168. package/src/components/Form/FormContext.tsx +0 -110
  169. package/src/components/Form/Input.tsx +0 -54
  170. package/src/components/Form/RefField.tsx +0 -283
  171. package/src/components/Form/custom/GeoPointInput.tsx +0 -80
  172. package/src/components/Form/custom/index.ts +0 -7
  173. package/src/components/Form/factory.tsx +0 -31
  174. package/src/hooks/useForm.ts +0 -296
  175. package/src/hooks/useQueryRefOptions.ts +0 -62
  176. package/src/hooks/useRefQueryLookupHandler.ts +0 -39
  177. 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';
9
+ import { SelectOption } from '@dxos/echo/internal';
10
10
  import { 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,21 +31,24 @@ 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
53
  decorators: [withTheme],
51
54
  parameters: {
@@ -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,17 +118,17 @@ 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
  >
123
130
  {({ items }) => (
124
- <div role='list' className='w-full overflow-auto'>
131
+ <div role='list' className='is-full overflow-auto'>
125
132
  {items?.map((item) => (
126
133
  <div key={item.id}>
127
134
  <List.Item
@@ -139,7 +146,7 @@ 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
  />
@@ -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
@@ -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';
@@ -10,10 +10,11 @@ import { expect, fn, userEvent, within } from 'storybook/test';
10
10
  import { Obj } from '@dxos/echo';
11
11
  import { faker } from '@dxos/random';
12
12
  import { Filter, useQuery } from '@dxos/react-client/echo';
13
- import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
13
+ import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
14
14
  import { Button } from '@dxos/react-ui';
15
15
  import { withTheme } from '@dxos/react-ui/testing';
16
- import { DataType } from '@dxos/schema';
16
+ import { Person } from '@dxos/types';
17
+ import { osTranslations } from '@dxos/ui-theme';
17
18
 
18
19
  import { translations } from '../../translations';
19
20
 
@@ -22,19 +23,19 @@ import { ObjectPicker } from './ObjectPicker';
22
23
  faker.seed(1);
23
24
 
24
25
  const createPerson = () =>
25
- Obj.make(DataType.Person, {
26
+ Obj.make(Person.Person, {
26
27
  fullName: faker.person.fullName(),
27
28
  });
28
29
 
29
30
  const omitId = Schema.omit<any, any, ['id']>('id');
30
- const personSchema = omitId(DataType.Person);
31
+ const personSchema = omitId(Person.Person);
31
32
 
32
33
  // Mock functions for testing
33
34
  const mockHandleSelect = fn();
34
35
  const mockHandleCreate = fn();
35
36
 
36
37
  const DefaultStory = () => {
37
- const { space } = useClientProvider();
38
+ const { space } = useClientStory();
38
39
  const [isOpen, setIsOpen] = useState(false);
39
40
 
40
41
  // Create sample Person objects
@@ -48,7 +49,7 @@ const DefaultStory = () => {
48
49
  }, [space]);
49
50
 
50
51
  // Get all objects in the space (similar to BoardContainer)
51
- const allObjects = useQuery(space, Filter.everything());
52
+ const allObjects = useQuery(space?.db, Filter.everything());
52
53
 
53
54
  // Map objects to options format expected by ObjectPicker
54
55
  const options = useMemo(
@@ -65,7 +66,7 @@ const DefaultStory = () => {
65
66
  (values: any) => {
66
67
  console.log('[on create]', values);
67
68
  if (!space) return;
68
- const newPerson = space.db.add(Obj.make(DataType.Person, values));
69
+ const newPerson = space.db.add(Obj.make(Person.Person, values));
69
70
  mockHandleCreate(values);
70
71
  return newPerson;
71
72
  },
@@ -73,22 +74,22 @@ const DefaultStory = () => {
73
74
  );
74
75
 
75
76
  return (
76
- <div className='p-4'>
77
+ <div role='none' className='flex is-96'>
77
78
  <ObjectPicker.Root open={isOpen} onOpenChange={setIsOpen}>
78
79
  <ObjectPicker.Trigger asChild>
79
- <Button variant='primary' data-testid='trigger'>
80
+ <Button variant='primary' data-testid='trigger' classNames='is-full'>
80
81
  Select Person
81
82
  </Button>
82
83
  </ObjectPicker.Trigger>
83
84
  <ObjectPicker.Content
85
+ classNames='popover-card-width'
84
86
  options={options}
85
- onSelect={mockHandleSelect}
86
87
  createSchema={personSchema}
87
- createOptionLabel={['create new person label', { ns: 'os' }]}
88
+ createOptionLabel={['create new person label', { ns: osTranslations }]}
88
89
  createOptionIcon='ph--user-plus--regular'
89
90
  createInitialValuePath='fullName'
90
91
  onCreate={handleCreateCallback}
91
- classNames='popover-card-width'
92
+ onSelect={mockHandleSelect}
92
93
  />
93
94
  </ObjectPicker.Root>
94
95
  </div>
@@ -101,7 +102,7 @@ const meta = {
101
102
  decorators: [
102
103
  withTheme,
103
104
  withClientProvider({
104
- types: [DataType.Person],
105
+ types: [Person.Person],
105
106
  createIdentity: true,
106
107
  createSpace: true,
107
108
  }),
@@ -202,6 +203,12 @@ export const WithTest: Story = {
202
203
  // Check that clicking the save button calls onCreate with expected values.
203
204
  await expect(mockHandleCreate).toHaveBeenCalledWith({
204
205
  fullName: unrelatedTerm,
206
+ addresses: [],
207
+ phoneNumbers: [],
208
+ emails: [],
209
+ identities: [],
210
+ urls: [],
211
+ fields: [],
205
212
  });
206
213
  },
207
214
  };
@@ -3,26 +3,57 @@
3
3
  //
4
4
 
5
5
  import type * as Schema from 'effect/Schema';
6
- import React, { type KeyboardEvent, type MouseEvent, forwardRef, useCallback, useMemo, useState } from 'react';
6
+ import React, { type KeyboardEvent, forwardRef, useCallback, useState } from 'react';
7
7
 
8
- import { Icon, Popover, type ThemedClassName, useTranslation } from '@dxos/react-ui';
9
- import { Combobox } from '@dxos/react-ui-searchlist';
8
+ import { type Palette, Popover, type ThemedClassName, useTranslation } from '@dxos/react-ui';
9
+ import { Combobox, useSearchListInput, useSearchListResults } from '@dxos/react-ui-searchlist';
10
10
 
11
- import { type QueryTag } from '../../hooks';
12
11
  import { translationKey } from '../../translations';
13
12
  import { Form } from '../Form';
14
13
 
14
+ export type RefOption = {
15
+ id: string;
16
+ label: string;
17
+ hue?: Palette;
18
+ };
19
+
15
20
  export type ObjectPickerContentProps = ThemedClassName<{
16
- options: QueryTag[];
21
+ options: RefOption[];
17
22
  selectedIds?: string[];
18
- createSchema?: Schema.Schema.AnyNoContext;
19
23
  createOptionLabel?: [string, { ns: string }];
20
24
  createOptionIcon?: string;
25
+ createSchema?: Schema.Schema.AnyNoContext;
21
26
  createInitialValuePath?: string;
22
- onSelect: (id: string) => void;
23
27
  onCreate?: (values: any) => void;
28
+ onSelect: (id: string) => void;
24
29
  }>;
25
30
 
31
+ const CreateItem = ({
32
+ createOptionLabel,
33
+ createOptionIcon,
34
+ onCreateItemSelect,
35
+ }: {
36
+ createOptionLabel: [string, { ns: string }];
37
+ createOptionIcon: string;
38
+ onCreateItemSelect: (query: string) => void;
39
+ }) => {
40
+ const { t } = useTranslation(translationKey);
41
+ const { query } = useSearchListInput();
42
+
43
+ return (
44
+ <Combobox.Item
45
+ value='__create__'
46
+ label={t(createOptionLabel[0], { ns: createOptionLabel[1].ns, text: query })}
47
+ icon={createOptionIcon}
48
+ classNames='flex items-center gap-2'
49
+ closeOnSelect={false}
50
+ onSelect={() => {
51
+ onCreateItemSelect(query);
52
+ }}
53
+ />
54
+ );
55
+ };
56
+
26
57
  const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>(
27
58
  (
28
59
  {
@@ -39,39 +70,27 @@ const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>
39
70
  ref,
40
71
  ) => {
41
72
  const { t } = useTranslation(translationKey);
42
-
43
- const [searchString, setSearchString] = useState('');
44
73
  const [showForm, setShowForm] = useState(false);
74
+ const [formInitialValue, setFormInitialValue] = useState<string>('');
75
+
76
+ const { results, handleSearch } = useSearchListResults({
77
+ items: options,
78
+ });
45
79
 
46
80
  const handleFormSave = useCallback(
47
81
  (values: any) => {
48
82
  onCreate?.(values);
49
83
  setShowForm(false);
50
- setSearchString('');
84
+ setFormInitialValue('');
51
85
  },
52
86
  [onCreate],
53
87
  );
54
88
 
55
89
  const handleFormCancel = useCallback(() => {
56
90
  setShowForm(false);
57
- setSearchString('');
91
+ setFormInitialValue('');
58
92
  }, []);
59
93
 
60
- // TODO(thure): The following click and keydown handlers are necessary because `onSelect` is called after the
61
- // Popover is already closed. Augment/refactor CmdK, if possible, to facilitate stopping event default
62
- // and propagation.
63
-
64
- const handleClick = useCallback(
65
- (event: MouseEvent) => {
66
- if (createSchema && (event.target as HTMLElement).closest('[data-value="__create__"]')) {
67
- event.stopPropagation();
68
- event.preventDefault();
69
- setShowForm(true);
70
- }
71
- },
72
- [createSchema],
73
- );
74
-
75
94
  const handleKeyDown = useCallback(
76
95
  (event: KeyboardEvent) => {
77
96
  if (
@@ -83,64 +102,58 @@ const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>
83
102
  ) {
84
103
  event.stopPropagation();
85
104
  event.preventDefault();
105
+ // Get the current query from the input element
106
+ const input = (event.currentTarget as HTMLElement).querySelector('input[type="text"]') as HTMLInputElement;
107
+ const currentQuery = input?.value || '';
108
+ setFormInitialValue(currentQuery);
86
109
  setShowForm(true);
87
110
  }
88
111
  },
89
112
  [createSchema],
90
113
  );
91
114
 
92
- const labelById = useMemo(
93
- () =>
94
- options.reduce((acc: Record<string, string>, option) => {
95
- acc[option.id.toLowerCase()] = option.label.toLowerCase();
96
- return acc;
97
- }, {}),
98
- [options],
99
- );
100
-
101
115
  return (
102
- <Combobox.Content
103
- {...props}
104
- ref={ref}
105
- filter={(value, search) => (value === '__create__' || labelById[value]?.includes(search.toLowerCase()) ? 1 : 0)}
106
- onClickCapture={handleClick}
107
- onKeyDownCapture={handleKeyDown}
108
- >
116
+ <Combobox.Content {...props} ref={ref} onSearch={handleSearch} onKeyDownCapture={handleKeyDown}>
109
117
  {showForm && createSchema ? (
110
118
  <Popover.Viewport>
111
- <Form
119
+ <Form.Root
120
+ testId='create-referenced-object-form'
112
121
  schema={createSchema}
113
- values={createInitialValuePath ? { [createInitialValuePath]: searchString } : {}}
122
+ values={createInitialValuePath ? { [createInitialValuePath]: formInitialValue } : {}}
114
123
  onSave={handleFormSave}
115
124
  onCancel={handleFormCancel}
116
- testId='create-referenced-object-form'
117
- />
125
+ >
126
+ <Form.Viewport>
127
+ <Form.Content>
128
+ <Form.FieldSet />
129
+ <Form.Actions />
130
+ </Form.Content>
131
+ </Form.Viewport>
132
+ </Form.Root>
118
133
  </Popover.Viewport>
119
134
  ) : (
120
135
  <>
121
- <Combobox.Input
122
- placeholder={t('ref field combobox input placeholder')}
123
- value={searchString}
124
- onValueChange={setSearchString}
125
- autoFocus
126
- />
136
+ <Combobox.Input placeholder={t('ref field combobox input placeholder')} autoFocus />
127
137
  <Combobox.List>
128
- {options.map((option) => (
138
+ {results.map((option) => (
129
139
  <Combobox.Item
130
140
  key={option.id}
131
141
  value={option.id}
142
+ label={option.label}
143
+ checked={selectedIds?.includes(option.id)}
132
144
  onSelect={() => onSelect(option.id)}
133
145
  classNames='flex items-center gap-2'
134
- >
135
- <span className='grow'>{option.label}</span>
136
- {selectedIds?.includes(option.id) && <Icon icon='ph--check--regular' />}
137
- </Combobox.Item>
146
+ />
138
147
  ))}
139
- {searchString.length > 0 && createOptionLabel && createOptionIcon && createSchema && onCreate && (
140
- <Combobox.Item value='__create__' classNames='flex items-center gap-2'>
141
- <Icon icon={createOptionIcon} />
142
- {t(createOptionLabel[0], { ns: createOptionLabel[1].ns, text: searchString })}
143
- </Combobox.Item>
148
+ {createOptionLabel && createOptionIcon && createSchema && onCreate && (
149
+ <CreateItem
150
+ createOptionLabel={createOptionLabel}
151
+ createOptionIcon={createOptionIcon}
152
+ onCreateItemSelect={(query: string) => {
153
+ setFormInitialValue(query);
154
+ setShowForm(true);
155
+ }}
156
+ />
144
157
  )}
145
158
  </Combobox.List>
146
159
  </>
@@ -155,6 +168,7 @@ ObjectPickerContent.displayName = 'ObjectPickerContent';
155
168
 
156
169
  export const ObjectPicker = {
157
170
  Root: Combobox.Root,
171
+ Portal: Combobox.Portal,
158
172
  Trigger: Combobox.Trigger,
159
173
  VirtualTrigger: Combobox.VirtualTrigger,
160
174
  Content: ObjectPickerContent,