@dxos/react-ui-form 0.8.4-main.b97322e → 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 (197) hide show
  1. package/dist/lib/browser/index.mjs +2220 -2058
  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 +2220 -2058
  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 +4 -4
  8. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +1 -1
  9. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +84 -5
  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 +82 -5
  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 -25
  18. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/Form.stories.d.ts +127 -55
  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/fields/SelectOptionField.stories.d.ts +82 -0
  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 +46 -0
  58. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
  59. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +85 -0
  60. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
  61. package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
  62. package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
  63. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +21 -9
  64. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  65. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +83 -8
  66. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  67. package/dist/types/src/components/index.d.ts +1 -1
  68. package/dist/types/src/components/index.d.ts.map +1 -1
  69. package/dist/types/src/components/testing.d.ts +4 -3
  70. package/dist/types/src/components/testing.d.ts.map +1 -1
  71. package/dist/types/src/hooks/index.d.ts +2 -3
  72. package/dist/types/src/hooks/index.d.ts.map +1 -1
  73. package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
  74. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
  75. package/dist/types/src/hooks/useKeyHandler.d.ts +6 -0
  76. package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
  77. package/dist/types/src/translations.d.ts +19 -10
  78. package/dist/types/src/translations.d.ts.map +1 -1
  79. package/dist/types/src/util/echo.d.ts +6 -0
  80. package/dist/types/src/util/echo.d.ts.map +1 -0
  81. package/dist/types/src/util/field.d.ts +3 -3
  82. package/dist/types/src/util/field.d.ts.map +1 -1
  83. package/dist/types/src/util/formatting.d.ts +2 -2
  84. package/dist/types/src/util/formatting.d.ts.map +1 -1
  85. package/dist/types/src/util/index.d.ts +2 -1
  86. package/dist/types/src/util/index.d.ts.map +1 -1
  87. package/dist/types/src/util/properties.d.ts +7 -0
  88. package/dist/types/src/util/properties.d.ts.map +1 -0
  89. package/dist/types/src/util/refs.d.ts +2 -3
  90. package/dist/types/src/util/refs.d.ts.map +1 -1
  91. package/dist/types/tsconfig.tsbuildinfo +1 -1
  92. package/package.json +51 -40
  93. package/src/components/ControlSection/ControlSection.stories.tsx +11 -14
  94. package/src/components/ControlSection/ControlSection.tsx +30 -16
  95. package/src/components/FieldEditor/FieldEditor.stories.tsx +52 -31
  96. package/src/components/FieldEditor/FieldEditor.test.tsx +3 -2
  97. package/src/components/FieldEditor/FieldEditor.tsx +86 -81
  98. package/src/components/Form/Form-old.stories.tsx +314 -0
  99. package/src/components/Form/Form.stories.tsx +155 -219
  100. package/src/components/Form/Form.tsx +320 -96
  101. package/src/components/Form/FormField.tsx +286 -0
  102. package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
  103. package/src/components/Form/FormFieldComponent.tsx +174 -0
  104. package/src/components/Form/FormFieldSet.tsx +125 -0
  105. package/src/components/Form/README.md +3 -0
  106. package/src/components/Form/fields/ArrayField.tsx +151 -0
  107. package/src/components/Form/fields/BooleanField.tsx +22 -0
  108. package/src/components/Form/fields/GeoPointField.tsx +120 -0
  109. package/src/components/Form/fields/MarkdownField.tsx +78 -0
  110. package/src/components/Form/fields/NumberField.tsx +39 -0
  111. package/src/components/Form/fields/RefField.tsx +181 -0
  112. package/src/components/Form/fields/SelectField.tsx +65 -0
  113. package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +25 -22
  114. package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +47 -42
  115. package/src/components/Form/fields/TextAreaField.tsx +38 -0
  116. package/src/components/Form/fields/TextField.tsx +38 -0
  117. package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +16 -9
  118. package/src/components/Form/fields/index.ts +15 -0
  119. package/src/components/Form/index.ts +3 -4
  120. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +214 -0
  121. package/src/components/ObjectPicker/ObjectPicker.tsx +175 -0
  122. package/src/components/ObjectPicker/index.ts +5 -0
  123. package/src/components/ViewEditor/ViewEditor.stories.tsx +137 -61
  124. package/src/components/ViewEditor/ViewEditor.test.tsx +19 -17
  125. package/src/components/ViewEditor/ViewEditor.tsx +324 -203
  126. package/src/components/index.ts +1 -2
  127. package/src/components/testing.tsx +19 -11
  128. package/src/hooks/index.ts +2 -3
  129. package/src/hooks/useFormHandler.ts +362 -0
  130. package/src/hooks/useKeyHandler.tsx +37 -0
  131. package/src/translations.ts +21 -12
  132. package/src/util/echo.ts +25 -0
  133. package/src/util/field.ts +33 -43
  134. package/src/util/formatting.ts +22 -22
  135. package/src/util/index.ts +2 -1
  136. package/src/util/properties.ts +18 -0
  137. package/src/util/refs.ts +12 -18
  138. package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
  139. package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
  140. package/dist/types/src/components/Form/Defaults.d.ts +0 -14
  141. package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
  142. package/dist/types/src/components/Form/FormActions.d.ts +0 -9
  143. package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
  144. package/dist/types/src/components/Form/FormContent.d.ts +0 -57
  145. package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
  146. package/dist/types/src/components/Form/FormContext.d.ts +0 -23
  147. package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
  148. package/dist/types/src/components/Form/Input.d.ts +0 -26
  149. package/dist/types/src/components/Form/Input.d.ts.map +0 -1
  150. package/dist/types/src/components/Form/RefField.d.ts +0 -12
  151. package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
  152. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
  153. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
  154. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
  155. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
  156. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +0 -7
  157. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
  158. package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
  159. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
  160. package/dist/types/src/components/Form/custom/index.d.ts +0 -4
  161. package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
  162. package/dist/types/src/components/Form/factory.d.ts +0 -8
  163. package/dist/types/src/components/Form/factory.d.ts.map +0 -1
  164. package/dist/types/src/components/deprecated/Form.d.ts +0 -15
  165. package/dist/types/src/components/deprecated/Form.d.ts.map +0 -1
  166. package/dist/types/src/components/deprecated/Form.stories.d.ts +0 -13
  167. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +0 -1
  168. package/dist/types/src/components/deprecated/FormInput.d.ts +0 -16
  169. package/dist/types/src/components/deprecated/FormInput.d.ts.map +0 -1
  170. package/dist/types/src/components/deprecated/index.d.ts +0 -2
  171. package/dist/types/src/components/deprecated/index.d.ts.map +0 -1
  172. package/dist/types/src/hooks/useForm.d.ts +0 -67
  173. package/dist/types/src/hooks/useForm.d.ts.map +0 -1
  174. package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -23
  175. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
  176. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
  177. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
  178. package/dist/types/src/util/schema.d.ts +0 -3
  179. package/dist/types/src/util/schema.d.ts.map +0 -1
  180. package/src/components/Form/ArrayField.tsx +0 -124
  181. package/src/components/Form/Defaults.tsx +0 -203
  182. package/src/components/Form/FormActions.tsx +0 -53
  183. package/src/components/Form/FormContent.tsx +0 -240
  184. package/src/components/Form/FormContext.tsx +0 -110
  185. package/src/components/Form/Input.tsx +0 -51
  186. package/src/components/Form/RefField.tsx +0 -213
  187. package/src/components/Form/custom/GeoPointInput.tsx +0 -72
  188. package/src/components/Form/custom/index.ts +0 -7
  189. package/src/components/Form/factory.tsx +0 -31
  190. package/src/components/deprecated/Form.stories.tsx +0 -62
  191. package/src/components/deprecated/Form.tsx +0 -119
  192. package/src/components/deprecated/FormInput.tsx +0 -71
  193. package/src/components/deprecated/index.ts +0 -5
  194. package/src/hooks/useForm.ts +0 -291
  195. package/src/hooks/useQueryRefOptions.ts +0 -53
  196. package/src/hooks/useRefQueryLookupHandler.ts +0 -39
  197. package/src/util/schema.ts +0 -14
@@ -1 +0,0 @@
1
- {"version":3,"file":"useQueryRefOptions.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useQueryRefOptions.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,MAAM,SAAS,GAAG;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACrD,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,cAAc,KAAK,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC;AAElF,KAAK,uBAAuB,GAAG;IAAE,WAAW,EAAE,cAAc,GAAG,SAAS,CAAC;IAAC,iBAAiB,CAAC,EAAE,eAAe,CAAA;CAAE,CAAC;AAEhH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,oCAAoC,uBAAuB;;;CA+B7F,CAAC"}
@@ -1,8 +0,0 @@
1
- import { type Space } from '@dxos/react-client/echo';
2
- import { type QueryRefOptions } from './useQueryRefOptions';
3
- type UseRefQueryLookupProps = {
4
- space?: Space;
5
- };
6
- export declare const useRefQueryLookupHandler: ({ space }: UseRefQueryLookupProps) => QueryRefOptions;
7
- export {};
8
- //# sourceMappingURL=useRefQueryLookupHandler.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useRefQueryLookupHandler.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useRefQueryLookupHandler.ts"],"names":[],"mappings":"AAOA,OAAO,EAAU,KAAK,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAG7D,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,KAAK,sBAAsB,GAAG;IAAE,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAEhD,eAAO,MAAM,wBAAwB,GAAI,WAAW,sBAAsB,KAAG,eAwB5E,CAAC"}
@@ -1,3 +0,0 @@
1
- import { SchemaAST } from 'effect';
2
- export declare const findArrayElementType: (ast: SchemaAST.AST) => SchemaAST.AST | undefined;
3
- //# sourceMappingURL=schema.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/util/schema.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAInC,eAAO,MAAM,oBAAoB,GAAI,KAAK,SAAS,CAAC,GAAG,8BAKtD,CAAC"}
@@ -1,124 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import { SchemaAST, pipe } from 'effect';
6
- import { capitalize } from 'effect/String';
7
- import React, { Fragment, useCallback } from 'react';
8
-
9
- import { findNode, getDiscriminatedType, isDiscriminatedUnion, SimpleType } from '@dxos/effect';
10
- import { invariant } from '@dxos/invariant';
11
- import { IconButton, useTranslation } from '@dxos/react-ui';
12
- import { getSchemaProperties, type SchemaProperty } from '@dxos/schema';
13
-
14
- import { type ComponentLookup } from './Form';
15
- import { FormField } from './FormContent';
16
- import { useFormValues, type FormInputStateProps } from './FormContext';
17
- import { InputHeader, type InputComponent } from './Input';
18
- import { translationKey } from '../../translations';
19
- import { findArrayElementType } from '../../util';
20
-
21
- type ArrayFieldProps = {
22
- property: SchemaProperty<any>;
23
- readonly?: boolean;
24
- inputProps: FormInputStateProps;
25
- path?: (string | number)[];
26
- Custom?: Partial<Record<string, InputComponent>>;
27
- lookupComponent?: ComponentLookup;
28
- };
29
-
30
- export const ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponent }: ArrayFieldProps) => {
31
- const { t } = useTranslation(translationKey);
32
- const { ast, name, type, title } = property;
33
- // TODO(wittjosiah): The fallback to an empty array stops the form from crashing but isn't immediately live.
34
- // It doesn't become live until another field is touched, but that's better than the whole form crashing.
35
- const values = (useFormValues(path ?? []) ?? []) as any[];
36
- invariant(Array.isArray(values), `Values at path ${path?.join('.')} must be an array.`);
37
- const label = title ?? pipe(name, capitalize);
38
-
39
- const elementType = findArrayElementType(ast);
40
-
41
- const getDefaultObjectValue = (typeNode: SchemaAST.AST): any => {
42
- const baseNode = findNode(typeNode, isDiscriminatedUnion);
43
- const typeLiteral = baseNode ? getDiscriminatedType(baseNode, {}) : findNode(typeNode, SchemaAST.isTypeLiteral);
44
- if (!typeLiteral) {
45
- return {};
46
- }
47
-
48
- return Object.fromEntries(getSchemaProperties(typeLiteral, {}).map((prop) => [prop.name, prop.defaultValue]));
49
- };
50
-
51
- const getDefaultValue = () =>
52
- type === 'object' && elementType ? getDefaultObjectValue(elementType) : SimpleType.getDefaultValue(type);
53
-
54
- const handleAdd = useCallback(() => {
55
- inputProps.onValueChange(type, [...values, getDefaultValue()]);
56
- }, [type, elementType, inputProps, values]);
57
-
58
- const handleRemove = useCallback(
59
- (index: number) => {
60
- const newValues = values.filter((_, i) => i !== index);
61
- inputProps.onValueChange(type, newValues);
62
- },
63
- [type, inputProps, values],
64
- );
65
-
66
- if (!elementType) {
67
- return null;
68
- }
69
-
70
- return readonly && values.length < 1 ? null : (
71
- <>
72
- <InputHeader readonly label={label} />
73
- <div
74
- role='none'
75
- className={
76
- readonly
77
- ? 'flex flex-wrap gap-1 mlb-1'
78
- : values.length > 0
79
- ? 'grid gap-1 grid-cols-[1fr_min-content] mlb-1'
80
- : 'hidden'
81
- }
82
- >
83
- {values.map((_value, index) => {
84
- const field = (
85
- <FormField
86
- property={{
87
- ...property,
88
- array: false, // NOTE(ZaymonFC): This breaks arrays of arrays but ¯\_(ツ)_/¯. Ping me if you need that.
89
- ast: elementType,
90
- }}
91
- path={[...(path ?? []), index]}
92
- readonly={readonly}
93
- inline
94
- Custom={Custom}
95
- lookupComponent={lookupComponent}
96
- />
97
- );
98
- return readonly ? (
99
- field
100
- ) : (
101
- <Fragment key={index}>
102
- <div role='none'>{field}</div>
103
- <IconButton
104
- icon='ph--trash--regular'
105
- iconOnly
106
- label={t('button remove')}
107
- onClick={() => handleRemove(index)}
108
- classNames='self-center'
109
- />
110
- </Fragment>
111
- );
112
- })}
113
- </div>
114
- {!readonly && (
115
- <IconButton
116
- classNames='is-full mlb-cardSpacingBlock flex'
117
- icon='ph--plus--regular'
118
- label={t('add field')}
119
- onClick={handleAdd}
120
- />
121
- )}
122
- </>
123
- );
124
- };
@@ -1,203 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import React, { useCallback, useEffect, useRef } from 'react';
6
-
7
- import { Input, Select, useTranslation } from '@dxos/react-ui';
8
-
9
- import { type InputProps, InputHeader } from './Input';
10
- import { translationKey } from '../../translations';
11
-
12
- export const TextInput = ({
13
- type,
14
- label,
15
- inputOnly,
16
- disabled,
17
- placeholder,
18
- getStatus,
19
- getValue,
20
- onValueChange,
21
- onBlur,
22
- }: InputProps) => {
23
- const { status, error } = getStatus();
24
-
25
- return disabled && !getValue() ? null : disabled && inputOnly ? (
26
- <p>{getValue() ?? ''}</p>
27
- ) : (
28
- <Input.Root validationValence={status}>
29
- {!inputOnly && <InputHeader error={error} label={label} />}
30
- {disabled ? (
31
- <p>{getValue() ?? ''}</p>
32
- ) : (
33
- <Input.TextInput
34
- disabled={disabled}
35
- placeholder={placeholder}
36
- value={getValue() ?? ''}
37
- onChange={(event) => onValueChange(type, event.target.value)}
38
- onBlur={onBlur}
39
- />
40
- )}
41
- {inputOnly && <Input.Validation>{error}</Input.Validation>}
42
- </Input.Root>
43
- );
44
- };
45
-
46
- export const NumberInput = ({
47
- type,
48
- label,
49
- inputOnly,
50
- disabled,
51
- placeholder,
52
- getStatus,
53
- getValue,
54
- onValueChange,
55
- onBlur,
56
- }: InputProps) => {
57
- const { status, error } = getStatus();
58
-
59
- return disabled && !getValue() ? null : disabled && inputOnly ? (
60
- <p>{getValue() ?? ''}</p>
61
- ) : (
62
- <Input.Root validationValence={status}>
63
- {!inputOnly && <InputHeader error={error} label={label} />}
64
- {disabled ? (
65
- <p>{getValue() ?? ''}</p>
66
- ) : (
67
- <Input.TextInput
68
- type='number'
69
- disabled={disabled}
70
- placeholder={placeholder}
71
- value={getValue() ?? 0}
72
- onChange={(event) => onValueChange(type, event.target.value)}
73
- onBlur={onBlur}
74
- />
75
- )}
76
- {inputOnly && <Input.DescriptionAndValidation>{error}</Input.DescriptionAndValidation>}
77
- </Input.Root>
78
- );
79
- };
80
-
81
- export const BooleanInput = ({ type, label, inputOnly, getStatus, getValue, onValueChange, disabled }: InputProps) => {
82
- const { status, error } = getStatus();
83
- const checked = Boolean(getValue());
84
- const { t } = useTranslation(translationKey);
85
-
86
- return (
87
- <Input.Root validationValence={status}>
88
- {!inputOnly && <InputHeader error={error} label={label} />}
89
- {disabled ? (
90
- <p>{t(checked ? 'boolean input true value' : 'boolean input false value')}</p>
91
- ) : (
92
- <Input.Switch checked={checked} onCheckedChange={(value) => onValueChange(type, value)} />
93
- )}
94
- {inputOnly && <Input.DescriptionAndValidation>{error}</Input.DescriptionAndValidation>}
95
- </Input.Root>
96
- );
97
- };
98
-
99
- export type SelectInputOptions = InputProps & {
100
- options?: Array<{ value: string | number; label?: string }>;
101
- };
102
-
103
- export const SelectInput = ({
104
- type,
105
- label,
106
- inputOnly,
107
- disabled,
108
- placeholder,
109
- options,
110
- getStatus,
111
- getValue,
112
- onValueChange,
113
- }: SelectInputOptions) => {
114
- const { status, error } = getStatus();
115
-
116
- const value = getValue() as string | undefined;
117
- const handleValueChange = useCallback((value: string | number) => onValueChange(type, value), [type, onValueChange]);
118
-
119
- return disabled && !value ? null : (
120
- <Input.Root validationValence={status}>
121
- {!inputOnly && <InputHeader error={error} label={label} />}
122
- {disabled ? (
123
- <p>{options?.find(({ value: optionValue }) => optionValue === value)?.label ?? String(value)}</p>
124
- ) : (
125
- <Select.Root value={value} onValueChange={handleValueChange}>
126
- {/* TODO(burdon): Placeholder not working? */}
127
- <Select.TriggerButton classNames='is-full' disabled={disabled} placeholder={placeholder} />
128
- <Select.Portal>
129
- <Select.Content>
130
- <Select.Viewport>
131
- {options?.map(({ value, label }) => (
132
- // NOTE: Numeric values are converted to and from strings.
133
- <Select.Option key={String(value)} value={String(value)}>
134
- {label ?? String(value)}
135
- </Select.Option>
136
- ))}
137
- </Select.Viewport>
138
- </Select.Content>
139
- </Select.Portal>
140
- </Select.Root>
141
- )}
142
- {inputOnly && <Input.DescriptionAndValidation>{error}</Input.DescriptionAndValidation>}
143
- </Input.Root>
144
- );
145
- };
146
-
147
- export const MarkdownInput = ({
148
- type,
149
- label,
150
- inputOnly,
151
- disabled,
152
- placeholder,
153
- getStatus,
154
- getValue,
155
- onValueChange,
156
- onBlur,
157
- }: InputProps) => {
158
- const { status, error } = getStatus();
159
- const textareaRef = useRef<HTMLTextAreaElement>(null);
160
-
161
- // TODO(ZaymonFC): If we start using `Enter` for form submission, we should prevent default behavior here
162
- // since we're using a textarea element and the user needs to be able to enter new lines.
163
-
164
- const adjustHeight = useCallback(() => {
165
- const textarea = textareaRef.current;
166
- if (textarea) {
167
- textarea.style.blockSize = 'auto'; // For measurement.
168
- textarea.style.blockSize = `${textarea.scrollHeight + 2}px`;
169
- }
170
- }, []);
171
-
172
- // Adjust height on initial render.
173
- useEffect(() => {
174
- adjustHeight();
175
- }, [adjustHeight]);
176
-
177
- // Adjust height when the content changes.
178
- useEffect(() => {
179
- adjustHeight();
180
- }, [getValue(), adjustHeight]);
181
-
182
- return (
183
- <Input.Root validationValence={status}>
184
- {!inputOnly && <InputHeader error={error} label={label} />}
185
- {disabled ? (
186
- <p>{getValue() ?? ''}</p>
187
- ) : (
188
- <Input.TextArea
189
- ref={textareaRef}
190
- disabled={disabled}
191
- placeholder={placeholder}
192
- value={getValue() ?? ''}
193
- classNames={'min-bs-auto max-h-40 overflow-auto'}
194
- onChange={(event) => onValueChange(type, event.target.value)}
195
- onBlur={onBlur}
196
- style={{ resize: 'none' }}
197
- spellCheck={false}
198
- />
199
- )}
200
- {inputOnly && <Input.Validation>{error}</Input.Validation>}
201
- </Input.Root>
202
- );
203
- };
@@ -1,53 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import React from 'react';
6
-
7
- import { IconButton, useTranslation } from '@dxos/react-ui';
8
- import { cardSpacing } from '@dxos/react-ui-stack';
9
- import { mx } from '@dxos/react-ui-theme';
10
-
11
- import { useFormContext } from './FormContext';
12
- import { translationKey } from '../../translations';
13
-
14
- export type FormOuterSpacing = boolean | 'blockStart-0' | 'scroll-fields';
15
-
16
- export type FormActionsProps = {
17
- readonly?: boolean;
18
- onCancel?: () => void;
19
- outerSpacing?: FormOuterSpacing;
20
- };
21
-
22
- export const FormActions = ({ readonly, onCancel, outerSpacing = true }: FormActionsProps) => {
23
- const { t } = useTranslation(translationKey);
24
- const { canSave, handleSave } = useFormContext();
25
-
26
- return (
27
- <div
28
- role='none'
29
- className={mx(
30
- outerSpacing === 'scroll-fields'
31
- ? 'pli-cardSpacingInline mbe-cardSpacingBlock'
32
- : outerSpacing
33
- ? cardSpacing
34
- : 'mbs-cardSpacingBlock',
35
- 'flex [&_button]:grow gap-1 first:mbs-0',
36
- )}
37
- >
38
- {onCancel && !readonly && (
39
- <IconButton data-testid='cancel-button' icon='ph--x--regular' label={t('button cancel')} onClick={onCancel} />
40
- )}
41
- {handleSave && (
42
- <IconButton
43
- type='submit'
44
- data-testid='save-button'
45
- disabled={!canSave}
46
- icon='ph--check--regular'
47
- label={t('button save')}
48
- onClick={handleSave}
49
- />
50
- )}
51
- </div>
52
- );
53
- };
@@ -1,240 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import { Schema, SchemaAST, pipe } from 'effect';
6
- import { capitalize } from 'effect/String';
7
- import React, { forwardRef, useMemo } from 'react';
8
-
9
- import { createJsonPath, findNode, getDiscriminatedType, isDiscriminatedUnion } from '@dxos/effect';
10
- import { type ThemedClassName } from '@dxos/react-ui';
11
- import { mx } from '@dxos/react-ui-theme';
12
- import { getSchemaProperties, type SchemaProperty } from '@dxos/schema';
13
- import { isNotFalsy } from '@dxos/util';
14
-
15
- import { ArrayField } from './ArrayField';
16
- import { SelectInput } from './Defaults';
17
- import { type ComponentLookup } from './Form';
18
- import { useInputProps, useFormValues } from './FormContext';
19
- import { type InputComponent } from './Input';
20
- import { RefField } from './RefField';
21
- import { getInputComponent } from './factory';
22
- import { type QueryRefOptions } from '../../hooks';
23
- import { getRefProps } from '../../util';
24
-
25
- export type FormFieldProps = {
26
- property: SchemaProperty<any>;
27
- path?: (string | number)[];
28
- readonly?: boolean;
29
- /** Used to indicate if input should be presented inline (e.g. for array items). */
30
- inline?: boolean;
31
- onQueryRefOptions?: QueryRefOptions;
32
- lookupComponent?: ComponentLookup;
33
- Custom?: Partial<Record<string, InputComponent>>;
34
- };
35
-
36
- export const FormField = ({
37
- property,
38
- path,
39
- readonly,
40
- inline,
41
- onQueryRefOptions,
42
- lookupComponent,
43
- Custom,
44
- }: FormFieldProps) => {
45
- const { ast, name, type, format, title, description, options, examples, array } = property;
46
- const inputProps = useInputProps(path);
47
-
48
- const label = useMemo(() => title ?? pipe(name, capitalize), [title, name]);
49
- const placeholder = useMemo(
50
- () => (examples?.length ? `Example: "${examples[0]}"` : description),
51
- [examples, description],
52
- );
53
-
54
- //
55
- // Registry and Custom.
56
- //
57
-
58
- const FoundComponent = lookupComponent?.({
59
- prop: name,
60
- schema: Schema.make(ast),
61
- inputProps: {
62
- type,
63
- format,
64
- label,
65
- disabled: readonly,
66
- placeholder,
67
- ...inputProps,
68
- },
69
- });
70
-
71
- if (FoundComponent) {
72
- return FoundComponent;
73
- }
74
-
75
- const jsonPath = createJsonPath(path ?? []);
76
- const CustomComponent = Custom?.[jsonPath];
77
- if (CustomComponent) {
78
- return (
79
- <CustomComponent
80
- type={type}
81
- format={format}
82
- label={label}
83
- inputOnly={inline}
84
- placeholder={placeholder}
85
- disabled={readonly}
86
- {...inputProps}
87
- />
88
- );
89
- }
90
-
91
- //
92
- // Refs.
93
- //
94
-
95
- const refProps = getRefProps(property);
96
- if (refProps) {
97
- return (
98
- <RefField
99
- ast={refProps.ast}
100
- array={refProps.isArray}
101
- type={type}
102
- format={format}
103
- label={label}
104
- placeholder={placeholder}
105
- disabled={readonly}
106
- inputOnly={inline}
107
- onQueryRefOptions={onQueryRefOptions}
108
- {...inputProps}
109
- />
110
- );
111
- }
112
-
113
- //
114
- // Arrays.
115
- //
116
-
117
- if (array) {
118
- return <ArrayField property={property} path={path} inputProps={inputProps} readonly={readonly} Custom={Custom} />;
119
- }
120
-
121
- //
122
- // Standard Inputs.
123
- //
124
-
125
- const InputComponent = getInputComponent(type, format);
126
- if (InputComponent) {
127
- return (
128
- <InputComponent
129
- type={type}
130
- format={format}
131
- label={label}
132
- inputOnly={inline}
133
- placeholder={placeholder}
134
- disabled={readonly}
135
- {...inputProps}
136
- />
137
- );
138
- }
139
-
140
- //
141
- // Select.
142
- //
143
-
144
- if (options) {
145
- return (
146
- <SelectInput
147
- type={type}
148
- format={format}
149
- disabled={readonly}
150
- inputOnly={inline}
151
- label={label}
152
- options={options.map((option) => ({ value: option, label: String(option) }))}
153
- placeholder={placeholder}
154
- {...inputProps}
155
- />
156
- );
157
- }
158
-
159
- //
160
- // Nested Objects.
161
- //
162
-
163
- if (type === 'object') {
164
- const baseNode = findNode(ast, isDiscriminatedUnion);
165
- const typeLiteral = baseNode
166
- ? getDiscriminatedType(baseNode, inputProps.getValue() as any)
167
- : findNode(ast, SchemaAST.isTypeLiteral);
168
-
169
- if (typeLiteral) {
170
- return (
171
- <>
172
- {!inline && <h3 className={mx('text-lg mlb-inputSpacingBlock first:mbs-0')}>{label}</h3>}
173
- <FormFields
174
- schema={Schema.make(typeLiteral)}
175
- path={path}
176
- readonly={readonly}
177
- onQueryRefOptions={onQueryRefOptions}
178
- Custom={Custom}
179
- lookupComponent={lookupComponent}
180
- />
181
- </>
182
- );
183
- }
184
- }
185
-
186
- return null;
187
- };
188
-
189
- export type FormFieldsProps = ThemedClassName<{
190
- testId?: string;
191
- readonly?: boolean;
192
- schema: Schema.Schema.All;
193
- /**
194
- * Path to the current object from the root. Used with nested forms.
195
- */
196
- path?: (string | number)[];
197
- filter?: (props: SchemaProperty<any>[]) => SchemaProperty<any>[];
198
- sort?: string[];
199
- lookupComponent?: ComponentLookup;
200
- /**
201
- * Map of custom renderers for specific properties.
202
- * Prefer lookupComponent for plugin specific input surfaces.
203
- */
204
- Custom?: Partial<Record<string, InputComponent>>;
205
- onQueryRefOptions?: QueryRefOptions;
206
- }>;
207
-
208
- export const FormFields = forwardRef<HTMLDivElement, FormFieldsProps>(
209
- (
210
- { classNames, testId, schema, path, filter, sort, readonly, lookupComponent, Custom, onQueryRefOptions },
211
- forwardRef,
212
- ) => {
213
- const values = useFormValues(path);
214
- const properties = useMemo(() => {
215
- const props = getSchemaProperties(schema.ast, values);
216
- const filtered = filter ? filter(props) : props;
217
- return sort ? filtered.sort((a, b) => sort.indexOf(a.name) - sort.indexOf(b.name)) : filtered;
218
- }, [schema, values, filter, sort]);
219
-
220
- return (
221
- <div role='form' className={mx('is-full', classNames)} ref={forwardRef}>
222
- {properties
223
- .map((property) => {
224
- return (
225
- <FormField
226
- key={property.name}
227
- property={property}
228
- path={[...(path ?? []), property.name]}
229
- readonly={readonly}
230
- onQueryRefOptions={onQueryRefOptions}
231
- lookupComponent={lookupComponent}
232
- Custom={Custom}
233
- />
234
- );
235
- })
236
- .filter(isNotFalsy)}
237
- </div>
238
- );
239
- },
240
- );