@dxos/react-ui-form 0.8.4-main.3c1ae3b → 0.8.4-main.3eb6e50203

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 (95) hide show
  1. package/dist/lib/browser/index.mjs +1610 -1825
  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 +1610 -1825
  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/FieldEditor/FieldEditor.d.ts +3 -2
  8. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  9. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +2 -2
  10. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  11. package/dist/types/src/components/Form/Form-old.stories.d.ts +1 -42
  12. package/dist/types/src/components/Form/Form-old.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/Form/Form.d.ts +9 -6
  14. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/Form.stories.d.ts +32 -32
  16. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/FormField.d.ts +2 -2
  18. package/dist/types/src/components/Form/FormField.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -1
  20. package/dist/types/src/components/Form/FormFieldSet.d.ts +3 -3
  21. package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -1
  22. package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -1
  23. package/dist/types/src/components/Form/fields/RefField.d.ts +8 -12
  24. package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -1
  25. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +1 -1
  26. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +14 -9
  27. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
  28. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +1 -1
  29. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
  30. package/dist/types/src/components/Settings/Settings.d.ts +56 -0
  31. package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
  32. package/dist/types/src/components/{ControlSection/ControlSection.stories.d.ts → Settings/Settings.stories.d.ts} +3 -9
  33. package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
  34. package/dist/types/src/components/Settings/index.d.ts +2 -0
  35. package/dist/types/src/components/Settings/index.d.ts.map +1 -0
  36. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +1 -1
  37. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  38. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +1 -1
  39. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  40. package/dist/types/src/components/index.d.ts +1 -1
  41. package/dist/types/src/components/index.d.ts.map +1 -1
  42. package/dist/types/src/components/testing.d.ts.map +1 -1
  43. package/dist/types/src/hooks/index.d.ts +0 -2
  44. package/dist/types/src/hooks/index.d.ts.map +1 -1
  45. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
  46. package/dist/types/src/translations.d.ts +2 -2
  47. package/dist/types/src/translations.d.ts.map +1 -1
  48. package/dist/types/src/util/echo.d.ts +6 -0
  49. package/dist/types/src/util/echo.d.ts.map +1 -0
  50. package/dist/types/src/util/index.d.ts +1 -0
  51. package/dist/types/src/util/index.d.ts.map +1 -1
  52. package/dist/types/tsconfig.tsbuildinfo +1 -1
  53. package/package.json +49 -45
  54. package/src/components/FieldEditor/FieldEditor.stories.tsx +39 -18
  55. package/src/components/FieldEditor/FieldEditor.tsx +13 -6
  56. package/src/components/Form/Form-old.stories.tsx +42 -42
  57. package/src/components/Form/Form.stories.tsx +12 -17
  58. package/src/components/Form/Form.tsx +52 -27
  59. package/src/components/Form/FormField.tsx +23 -17
  60. package/src/components/Form/FormFieldComponent.stories.tsx +1 -1
  61. package/src/components/Form/FormFieldComponent.tsx +17 -16
  62. package/src/components/Form/FormFieldSet.tsx +8 -5
  63. package/src/components/Form/fields/ArrayField.tsx +11 -11
  64. package/src/components/Form/fields/RefField.tsx +82 -99
  65. package/src/components/Form/fields/SelectOptionField.stories.tsx +1 -1
  66. package/src/components/Form/fields/SelectOptionField.tsx +2 -2
  67. package/src/components/Form/fields/TupleField.tsx +1 -1
  68. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +12 -5
  69. package/src/components/ObjectPicker/ObjectPicker.tsx +66 -57
  70. package/src/components/Settings/Settings.stories.tsx +56 -0
  71. package/src/components/Settings/Settings.tsx +234 -0
  72. package/src/components/{ControlSection → Settings}/index.ts +1 -1
  73. package/src/components/ViewEditor/ViewEditor.stories.tsx +18 -12
  74. package/src/components/ViewEditor/ViewEditor.test.tsx +1 -5
  75. package/src/components/ViewEditor/ViewEditor.tsx +77 -44
  76. package/src/components/index.ts +1 -1
  77. package/src/components/testing.tsx +3 -5
  78. package/src/hooks/index.ts +0 -2
  79. package/src/hooks/useFormHandler.ts +20 -6
  80. package/src/translations.ts +1 -1
  81. package/src/util/echo.ts +25 -0
  82. package/src/util/index.ts +1 -0
  83. package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
  84. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
  85. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
  86. package/dist/types/src/components/ControlSection/index.d.ts +0 -2
  87. package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
  88. package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -30
  89. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
  90. package/dist/types/src/hooks/useRefQueryOptions.d.ts +0 -8
  91. package/dist/types/src/hooks/useRefQueryOptions.d.ts.map +0 -1
  92. package/src/components/ControlSection/ControlSection.stories.tsx +0 -38
  93. package/src/components/ControlSection/ControlSection.tsx +0 -153
  94. package/src/hooks/useQueryRefOptions.ts +0 -70
  95. package/src/hooks/useRefQueryOptions.ts +0 -37
@@ -7,13 +7,22 @@ import type * as Schema from 'effect/Schema';
7
7
  import type * as SchemaAST from 'effect/SchemaAST';
8
8
  import { useCallback, useEffect, useMemo, useState } from 'react';
9
9
 
10
- import { type AnyProperties, getValue as getValue$, setValue as setValue$ } from '@dxos/echo/internal';
11
- import { type JsonPath, createJsonPath, fromEffectValidationPath } from '@dxos/effect';
10
+ import { Obj } from '@dxos/echo';
11
+ import { type AnyProperties } from '@dxos/echo/internal';
12
+ import {
13
+ type JsonPath,
14
+ createJsonPath,
15
+ fromEffectValidationPath,
16
+ getValue as getValue$,
17
+ setValue as setValue$,
18
+ } from '@dxos/effect';
12
19
  import { log } from '@dxos/log';
13
20
  import { useDefaultValue } from '@dxos/react-ui';
14
21
  import { type ValidationError, validateSchema } from '@dxos/schema';
15
22
  import { type MaybePromise } from '@dxos/util';
16
23
 
24
+ import { setValueEchoAware } from '../util';
25
+
17
26
  export type FormFieldStatus = {
18
27
  status?: 'error';
19
28
  error?: string;
@@ -126,7 +135,7 @@ export const useFormHandler = <T extends AnyProperties>({
126
135
  const [errors, setErrors] = useState<Record<JsonPath, string>>({});
127
136
  const [saving, setSaving] = useState(false);
128
137
  const defaultValues = useDefaultValue<Partial<T>>(defaultValuesProp, () => ({}));
129
- const [values, setValues] = useControllableState<Partial<T>>({
138
+ const [values$, setValues] = useControllableState<Partial<T>>({
130
139
  prop: valuesProp,
131
140
  defaultProp: defaultValues,
132
141
  onChange: () => {
@@ -137,6 +146,8 @@ export const useFormHandler = <T extends AnyProperties>({
137
146
  setSaving(false);
138
147
  },
139
148
  });
149
+ // TODO(wittjosiah): Upgrade @radix-ui/react-use-controllable-state.
150
+ const values = values$ as Partial<T>;
140
151
 
141
152
  // Validate.
142
153
  const validate = useCallback(
@@ -259,12 +270,15 @@ export const useFormHandler = <T extends AnyProperties>({
259
270
  }
260
271
 
261
272
  // Update.
262
- const newValues = { ...setValue$(values, jsonPath, parsedValue) };
273
+ // For ECHO objects, mutate in place via Obj.change(); for plain objects, spread to create new reference.
274
+ const newValues = Obj.isObject(values)
275
+ ? (setValueEchoAware(values, jsonPath, parsedValue), values)
276
+ : { ...setValue$(values, jsonPath, parsedValue) };
263
277
  setValues(newValues);
264
278
 
265
279
  // TODO(burdon): Check value has changed from original.
266
- const newChanged = { ...changed, [jsonPath]: true };
267
- setChanged({ ...changed, [jsonPath]: true });
280
+ const newChanged = { [jsonPath]: true };
281
+ setChanged({ ...changed, ...newChanged });
268
282
 
269
283
  // Validate.
270
284
  const isValid = validate(newValues);
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { type Resource } from '@dxos/react-ui';
6
6
 
7
- export const translationKey = 'react-ui-form';
7
+ export const translationKey = '@dxos/react-ui-form';
8
8
 
9
9
  export const translations = [
10
10
  {
@@ -0,0 +1,25 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Obj } from '@dxos/echo';
6
+ import { type JsonPath, setValue as setValuePlain, splitJsonPath } from '@dxos/effect';
7
+
8
+ /**
9
+ * Sets a value on an object, using Obj.change() if it's an ECHO object.
10
+ */
11
+ // TODO(wittjosiah): Consider refactoring Form to not know about ECHO at all.
12
+ // Instead of directly mutating the values object, Form could work with a plain
13
+ // object copy and communicate all mutations through callbacks (e.g., onValuesChanged).
14
+ // The caller would then be responsible for applying changes to ECHO objects via
15
+ // Obj.change(). This would keep Form agnostic to the underlying data layer and
16
+ // avoid coupling react-ui-form to @dxos/echo.
17
+ export const setValueEchoAware = <T extends object>(obj: T, path: JsonPath, value: any): T => {
18
+ if (Obj.isObject(obj)) {
19
+ Obj.change(obj, (mutableObj) => {
20
+ Obj.setValue(mutableObj, splitJsonPath(path), value);
21
+ });
22
+ return obj;
23
+ }
24
+ return setValuePlain(obj, path, value);
25
+ };
package/src/util/index.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
+ export * from './echo';
5
6
  export * from './field';
6
7
  export * from './formatting';
7
8
  export * from './properties';
@@ -1,23 +0,0 @@
1
- import React, { type ComponentPropsWithoutRef, type PropsWithChildren } from 'react';
2
- import { type ButtonProps, type Label, type ThemedClassName } from '@dxos/react-ui';
3
- export type ControlPageProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;
4
- export declare const ControlPage: ({ children, classNames, ...props }: ControlPageProps) => React.JSX.Element;
5
- export type ControlSectionProps = PropsWithChildren<{
6
- title: Label;
7
- description?: Label;
8
- }>;
9
- export declare const ControlSection: ({ title, description, children }: ControlSectionProps) => React.JSX.Element;
10
- export declare const ControlSectionHeading: ({ title, description }: Omit<ControlSectionProps, "children">) => React.JSX.Element;
11
- export declare const ControlGroupButton: ({ classNames, ...props }: ButtonProps) => React.JSX.Element;
12
- export type ControlGroupProps = ThemedClassName<PropsWithChildren>;
13
- export declare const ControlGroup: ({ children, classNames }: ControlGroupProps) => React.JSX.Element;
14
- export declare const ControlFrame: ({ children }: ControlGroupProps) => React.JSX.Element;
15
- export declare const controlItemClasses: string;
16
- export type ControlItemProps = PropsWithChildren<{
17
- title: Label;
18
- description?: Label;
19
- }>;
20
- export declare const ControlItem: ({ title, description, children }: ControlItemProps) => React.JSX.Element;
21
- export declare const ControlItemInput: ({ title, description, children }: ControlItemProps) => React.JSX.Element;
22
- export declare const ControlFrameItem: ({ title, description, children }: ControlItemProps) => React.JSX.Element;
23
- //# sourceMappingURL=ControlSection.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ControlSection.d.ts","sourceRoot":"","sources":["../../../../../src/components/ControlSection/ControlSection.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAAE,KAAK,wBAAwB,EAAE,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAErF,OAAO,EAEL,KAAK,WAAW,EAEhB,KAAK,KAAK,EACV,KAAK,eAAe,EAGrB,MAAM,gBAAgB,CAAC;AAMxB,MAAM,MAAM,gBAAgB,GAAG,eAAe,CAAC,wBAAwB,CAAC,KAAK,CAAC,CAAC,CAAC;AAEhF,eAAO,MAAM,WAAW,GAAI,oCAAoC,gBAAgB,sBAQ/E,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;IAClD,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,CAAC,EAAE,KAAK,CAAC;CACrB,CAAC,CAAC;AAEH,eAAO,MAAM,cAAc,GAAI,kCAAkC,mBAAmB,sBAOnF,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAI,wBAAwB,IAAI,CAAC,mBAAmB,EAAE,UAAU,CAAC,sBAYlG,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,0BAA0B,WAAW,sBAEvE,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,eAAe,CAAC,iBAAiB,CAAC,CAAC;AAEnE,eAAO,MAAM,YAAY,GAAI,0BAA0B,iBAAiB,sBAWvE,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,cAAc,iBAAiB,sBAU3D,CAAC;AAEF,eAAO,MAAM,kBAAkB,QAK7B,CAAC;AAKH,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;IAC/C,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,CAAC,EAAE,KAAK,CAAC;CACrB,CAAC,CAAC;AAEH,eAAO,MAAM,WAAW,GAAI,kCAAkC,gBAAgB,sBAY7E,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,kCAAkC,gBAAgB,sBAoBlF,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,kCAAkC,gBAAgB,sBAUlF,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ControlSection.stories.d.ts","sourceRoot":"","sources":["../../../../../src/components/ControlSection/ControlSection.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B,QAAA,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAa6B,CAAC;AAExC,eAAe,IAAI,CAAC;AAEpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,OAAO,EAAE,KAKrB,CAAC"}
@@ -1,2 +0,0 @@
1
- export * from './ControlSection';
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/ControlSection/index.ts"],"names":[],"mappings":"AAIA,cAAc,kBAAkB,CAAC"}
@@ -1,30 +0,0 @@
1
- import { type DXN } from '@dxos/keys';
2
- import { type Palette } from '@dxos/react-ui-types';
3
- import { type MaybePromise } from '@dxos/util';
4
- export type RefOption = {
5
- dxn: DXN;
6
- label?: string;
7
- };
8
- export type QueryRefOptions = ({ typename }: {
9
- typename: string;
10
- }) => MaybePromise<RefOption[]>;
11
- export type QueryTag = {
12
- id: string;
13
- label: string;
14
- hue?: Palette;
15
- };
16
- type UseQueryRefOptionsProps = {
17
- typename?: string;
18
- onQueryRefOptions?: QueryRefOptions;
19
- };
20
- /**
21
- * Hook to query reference options based on type information.
22
- * Used internally within forms to fetch and format reference options for reference fields.
23
- */
24
- export declare const useQueryRefOptions: ({ typename, onQueryRefOptions }: UseQueryRefOptionsProps) => {
25
- options: QueryTag[];
26
- update: () => void;
27
- loading: boolean;
28
- };
29
- export {};
30
- //# sourceMappingURL=useQueryRefOptions.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useQueryRefOptions.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useQueryRefOptions.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,YAAY,CAAC;AAGtC,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,EAAE,GAAG,CAAC;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,KAAK,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC;AAEhG,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,KAAK,uBAAuB,GAAG;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,eAAe,CAAC;CACrC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,iCAAiC,uBAAuB;;;;CAmC1F,CAAC"}
@@ -1,8 +0,0 @@
1
- import { type Space } from '@dxos/react-client/echo';
2
- import { type QueryRefOptions } from './useQueryRefOptions';
3
- type UseRefQueryOptionsProps = {
4
- space?: Space;
5
- };
6
- export declare const useRefQueryOptions: ({ space }: UseRefQueryOptionsProps) => QueryRefOptions;
7
- export {};
8
- //# sourceMappingURL=useRefQueryOptions.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useRefQueryOptions.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useRefQueryOptions.ts"],"names":[],"mappings":"AAOA,OAAO,EAAU,KAAK,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAG7D,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,KAAK,uBAAuB,GAAG;IAAE,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAEjD,eAAO,MAAM,kBAAkB,GAAI,WAAW,uBAAuB,KAAG,eAsBvE,CAAC"}
@@ -1,38 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { type Meta, type StoryObj } from '@storybook/react-vite';
6
- import React from 'react';
7
-
8
- import { withTheme } from '@dxos/react-ui/testing';
9
-
10
- import { translations } from '../../translations';
11
-
12
- import { ControlPage, ControlSection } from './ControlSection';
13
-
14
- const meta = {
15
- title: 'ui/react-ui-form/ControlSection',
16
- component: ControlSection,
17
- render: (args) => (
18
- <ControlPage classNames='is-[40rem]'>
19
- <ControlSection {...args} />
20
- </ControlPage>
21
- ),
22
- decorators: [withTheme],
23
- parameters: {
24
- layout: 'centered',
25
- translations,
26
- },
27
- } satisfies Meta<typeof ControlSection>;
28
-
29
- export default meta;
30
-
31
- type Story = StoryObj<typeof meta>;
32
-
33
- export const Default: Story = {
34
- args: {
35
- title: 'Control Section',
36
- description: 'This is a control section',
37
- },
38
- };
@@ -1,153 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import React, { type ComponentPropsWithoutRef, type PropsWithChildren } from 'react';
6
-
7
- import {
8
- Button,
9
- type ButtonProps,
10
- Input,
11
- type Label,
12
- type ThemedClassName,
13
- toLocalizedString,
14
- useTranslation,
15
- } from '@dxos/react-ui';
16
- import { StackItem } from '@dxos/react-ui-stack';
17
- import { mx } from '@dxos/react-ui-theme';
18
-
19
- import { translationKey } from '../../translations';
20
-
21
- export type ControlPageProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;
22
-
23
- export const ControlPage = ({ children, classNames, ...props }: ControlPageProps) => {
24
- return (
25
- <StackItem.Content scrollable classNames='[--control-spacing:var(--dx-trimMd)]'>
26
- <div role='none' className={mx('pli-cardSpacingInline pbe-trimLg', classNames)} {...props}>
27
- {children}
28
- </div>
29
- </StackItem.Content>
30
- );
31
- };
32
-
33
- export type ControlSectionProps = PropsWithChildren<{
34
- title: Label;
35
- description?: Label;
36
- }>;
37
-
38
- export const ControlSection = ({ title, description, children }: ControlSectionProps) => {
39
- return (
40
- <>
41
- <ControlSectionHeading title={title} description={description} />
42
- {children}
43
- </>
44
- );
45
- };
46
-
47
- export const ControlSectionHeading = ({ title, description }: Omit<ControlSectionProps, 'children'>) => {
48
- const { t } = useTranslation(translationKey);
49
- return (
50
- <>
51
- <h2 className='pli-trimMd container-max-width text-xl mbs-trimLg mbe-trimMd'>{toLocalizedString(title, t)}</h2>
52
- {description && (
53
- <p className='pli-trimMd mlb-trimMd container-max-width text-description'>
54
- {toLocalizedString(description, t)}
55
- </p>
56
- )}
57
- </>
58
- );
59
- };
60
-
61
- export const ControlGroupButton = ({ classNames, ...props }: ButtonProps) => {
62
- return <Button {...props} classNames={['md:col-span-2', classNames]} />;
63
- };
64
-
65
- export type ControlGroupProps = ThemedClassName<PropsWithChildren>;
66
-
67
- export const ControlGroup = ({ children, classNames }: ControlGroupProps) => (
68
- <div
69
- role='none'
70
- className={mx(
71
- 'group container-max-width grid grid-cols-1 md:grid-cols-[1fr_min-content] gap-trimMd',
72
- '[--control-spacing:0px] [&_input]:justify-self-end [&_button]:justify-self-end',
73
- classNames,
74
- )}
75
- >
76
- {children}
77
- </div>
78
- );
79
-
80
- export const ControlFrame = ({ children }: ControlGroupProps) => (
81
- <div
82
- role='none'
83
- className={mx(
84
- 'container-max-width grid grid-cols-1 md:grid-cols-[1fr_1fr] gap-trimSm md:gap-trimMd p-trimMd',
85
- 'border border-separator rounded-md',
86
- )}
87
- >
88
- {children}
89
- </div>
90
- );
91
-
92
- export const controlItemClasses = mx([
93
- 'container-max-width grid md:col-span-2 grid-cols-subgrid gap-trimSm items-center',
94
- // TODO(burdon): Use grid gap consistently or apply margins consistently?
95
- 'mlb-[--control-spacing] *:first:!mbs-0 *:last:!mbe-0 pli-trimMd plb-trimMd',
96
- 'border border-separator rounded-md',
97
- ]);
98
-
99
- const controlItemTitleClasses = 'mbe-0 text-lg text-baseText font-normal';
100
- const controlItemDescriptionClasses = 'mlb-trimSm md:mbe-0 text-base text-description';
101
-
102
- export type ControlItemProps = PropsWithChildren<{
103
- title: Label;
104
- description?: Label;
105
- }>;
106
-
107
- export const ControlItem = ({ title, description, children }: ControlItemProps) => {
108
- const { t } = useTranslation(translationKey);
109
-
110
- return (
111
- <div className={controlItemClasses}>
112
- <div role='none'>
113
- <h3 className={controlItemTitleClasses}>{toLocalizedString(title, t)}</h3>
114
- {description && <p className={controlItemDescriptionClasses}>{toLocalizedString(description, t)}</p>}
115
- </div>
116
- {children}
117
- </div>
118
- );
119
- };
120
-
121
- export const ControlItemInput = ({ title, description, children }: ControlItemProps) => {
122
- const { t } = useTranslation(translationKey);
123
-
124
- return (
125
- <Input.Root>
126
- <div className={controlItemClasses}>
127
- <div role='none'>
128
- <Input.Label classNames={controlItemTitleClasses}>{toLocalizedString(title, t)}</Input.Label>
129
- {description && (
130
- <Input.DescriptionAndValidation>
131
- <Input.Description classNames={controlItemDescriptionClasses}>
132
- {toLocalizedString(description, t)}
133
- </Input.Description>
134
- </Input.DescriptionAndValidation>
135
- )}
136
- </div>
137
- {children}
138
- </div>
139
- </Input.Root>
140
- );
141
- };
142
-
143
- export const ControlFrameItem = ({ title, description, children }: ControlItemProps) => {
144
- const { t } = useTranslation(translationKey);
145
-
146
- return (
147
- <div role='group' className='min-is-0'>
148
- <h3 className='text-lg mbe-2'>{toLocalizedString(title, t)}</h3>
149
- {description && <p className='mlb-trimSm md:mbe-0 text-description'>{toLocalizedString(description, t)}</p>}
150
- {children}
151
- </div>
152
- );
153
- };
@@ -1,70 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import { useCallback, useState } from 'react';
6
-
7
- import { type DXN } from '@dxos/keys';
8
- import { log } from '@dxos/log';
9
- import { useAsyncEffect } from '@dxos/react-ui';
10
- import { type Palette } from '@dxos/react-ui-types';
11
- import { type MaybePromise } from '@dxos/util';
12
-
13
- export type RefOption = {
14
- dxn: DXN;
15
- label?: string;
16
- };
17
-
18
- export type QueryRefOptions = ({ typename }: { typename: string }) => MaybePromise<RefOption[]>;
19
-
20
- export type QueryTag = {
21
- id: string;
22
- label: string;
23
- hue?: Palette;
24
- };
25
-
26
- type UseQueryRefOptionsProps = {
27
- typename?: string;
28
- onQueryRefOptions?: QueryRefOptions;
29
- };
30
-
31
- /**
32
- * Hook to query reference options based on type information.
33
- * Used internally within forms to fetch and format reference options for reference fields.
34
- */
35
- export const useQueryRefOptions = ({ typename, onQueryRefOptions }: UseQueryRefOptionsProps) => {
36
- const [options, setOptions] = useState<QueryTag[]>([]);
37
- const [loading, setLoading] = useState(false);
38
- const [state, setState] = useState({});
39
- const update = useCallback(() => setState({}), []);
40
-
41
- useAsyncEffect(async () => {
42
- if (!typename || !onQueryRefOptions) {
43
- return;
44
- }
45
-
46
- setLoading(true);
47
- try {
48
- // TODO(wittjosiah): This should be a reactive query so that the options are always up to date.
49
- const options = await onQueryRefOptions({ typename });
50
- log('options', { options: options.length });
51
- setOptions(
52
- options.map((option) => {
53
- const dxn = option.dxn.toString() as string;
54
- return {
55
- id: dxn,
56
- label: option.label ?? dxn,
57
- hue: 'neutral' as any,
58
- };
59
- }),
60
- );
61
- } catch (error) {
62
- log.error('Failed to fetch ref options:', error);
63
- setOptions([]);
64
- } finally {
65
- setLoading(false);
66
- }
67
- }, [typename, onQueryRefOptions, state]);
68
-
69
- return { options, update, loading };
70
- };
@@ -1,37 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import { useCallback } from 'react';
6
-
7
- import { Obj } from '@dxos/echo';
8
- import { Filter, type Space } from '@dxos/react-client/echo';
9
- import { isNonNullable } from '@dxos/util';
10
-
11
- import { type QueryRefOptions } from './useQueryRefOptions';
12
-
13
- type UseRefQueryOptionsProps = { space?: Space };
14
-
15
- export const useRefQueryOptions = ({ space }: UseRefQueryOptionsProps): QueryRefOptions => {
16
- return useCallback<QueryRefOptions>(
17
- async ({ typename }) => {
18
- if (!space) {
19
- return [];
20
- }
21
-
22
- const query = space.db.query(Filter.typename(typename));
23
- const objects = await query.run();
24
- return objects
25
- .map((object) => {
26
- const dxn = Obj.getDXN(object);
27
- if (!dxn) {
28
- return undefined;
29
- }
30
-
31
- return { dxn, label: Obj.getLabel(object) ?? object?.id ?? '' };
32
- })
33
- .filter(isNonNullable);
34
- },
35
- [space],
36
- );
37
- };