@dxos/plugin-preview 0.10.0 → 0.11.0

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 (68) hide show
  1. package/dist/lib/PreviewPlugin.mjs +16 -0
  2. package/dist/lib/PreviewPlugin.mjs.map +1 -0
  3. package/dist/lib/PreviewPlugin.node.mjs +10 -0
  4. package/dist/lib/PreviewPlugin.node.mjs.map +1 -0
  5. package/dist/lib/PreviewPlugin.workerd.mjs +10 -0
  6. package/dist/lib/PreviewPlugin.workerd.mjs.map +1 -0
  7. package/dist/lib/capabilities.mjs +8 -0
  8. package/dist/lib/capabilities.mjs.map +1 -0
  9. package/dist/lib/chunk-meta.mjs +23 -0
  10. package/dist/lib/chunk-meta.mjs.map +1 -0
  11. package/dist/lib/chunk-preview-popover.mjs +82 -0
  12. package/dist/lib/chunk-preview-popover.mjs.map +1 -0
  13. package/dist/lib/chunk-react-surface.mjs +366 -0
  14. package/dist/lib/chunk-react-surface.mjs.map +1 -0
  15. package/dist/lib/index.mjs +2 -0
  16. package/dist/lib/meta.mjs +2 -0
  17. package/dist/lib/plugin.mjs +8 -0
  18. package/dist/lib/plugin.mjs.map +1 -0
  19. package/dist/lib/testing.mjs +2 -0
  20. package/dist/lib/translations.mjs +15 -0
  21. package/dist/lib/translations.mjs.map +1 -0
  22. package/dist/types/src/capabilities/RelatedCards.d.ts +14 -0
  23. package/dist/types/src/capabilities/RelatedCards.d.ts.map +1 -0
  24. package/dist/types/src/capabilities/preview-popover.d.ts +1 -1
  25. package/dist/types/src/capabilities/react-surface.d.ts +1 -1
  26. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  27. package/dist/types/src/cards/PersonCard.d.ts.map +1 -1
  28. package/dist/types/src/stories/Card.stories.d.ts.map +1 -1
  29. package/dist/types/src/stories/fixtures.d.ts +48 -0
  30. package/dist/types/src/stories/fixtures.d.ts.map +1 -0
  31. package/dist/types/src/stories/testing.d.ts +1 -47
  32. package/dist/types/src/stories/testing.d.ts.map +1 -1
  33. package/dist/types/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +37 -36
  35. package/src/capabilities/RelatedCards.tsx +35 -0
  36. package/src/capabilities/preview-popover.ts +2 -2
  37. package/src/capabilities/{react-surface.tsx → react-surface.ts} +43 -61
  38. package/src/cards/PersonCard.tsx +12 -6
  39. package/src/stories/Card.stories.tsx +2 -2
  40. package/src/stories/fixtures.ts +109 -0
  41. package/src/stories/testing.tsx +2 -101
  42. package/dist/lib/neutral/PreviewPlugin.mjs +0 -9
  43. package/dist/lib/neutral/PreviewPlugin.mjs.map +0 -7
  44. package/dist/lib/neutral/PreviewPlugin.node.mjs +0 -17
  45. package/dist/lib/neutral/PreviewPlugin.node.mjs.map +0 -7
  46. package/dist/lib/neutral/PreviewPlugin.workerd.mjs +0 -17
  47. package/dist/lib/neutral/PreviewPlugin.workerd.mjs.map +0 -7
  48. package/dist/lib/neutral/capabilities/index.mjs +0 -9
  49. package/dist/lib/neutral/capabilities/index.mjs.map +0 -7
  50. package/dist/lib/neutral/chunk-3SWUVHXJ.mjs +0 -28
  51. package/dist/lib/neutral/chunk-3SWUVHXJ.mjs.map +0 -7
  52. package/dist/lib/neutral/chunk-SXDHWUBO.mjs +0 -32
  53. package/dist/lib/neutral/chunk-SXDHWUBO.mjs.map +0 -7
  54. package/dist/lib/neutral/index.mjs +0 -7
  55. package/dist/lib/neutral/index.mjs.map +0 -7
  56. package/dist/lib/neutral/meta.json +0 -1
  57. package/dist/lib/neutral/meta.mjs +0 -7
  58. package/dist/lib/neutral/meta.mjs.map +0 -7
  59. package/dist/lib/neutral/plugin.mjs +0 -11
  60. package/dist/lib/neutral/plugin.mjs.map +0 -7
  61. package/dist/lib/neutral/preview-popover-EEQQJAH5.mjs +0 -101
  62. package/dist/lib/neutral/preview-popover-EEQQJAH5.mjs.map +0 -7
  63. package/dist/lib/neutral/react-surface-2CN3DRU6.mjs +0 -432
  64. package/dist/lib/neutral/react-surface-2CN3DRU6.mjs.map +0 -7
  65. package/dist/lib/neutral/testing.mjs +0 -7
  66. package/dist/lib/neutral/testing.mjs.map +0 -7
  67. package/dist/lib/neutral/translations.mjs +0 -21
  68. package/dist/lib/neutral/translations.mjs.map +0 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-preview",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Preview plugin",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -16,12 +16,12 @@
16
16
  "#capabilities": {
17
17
  "source": "./src/capabilities/index.ts",
18
18
  "types": "./dist/types/src/capabilities/index.d.ts",
19
- "default": "./dist/lib/neutral/capabilities/index.mjs"
19
+ "default": "./dist/lib/capabilities.mjs"
20
20
  },
21
21
  "#meta": {
22
22
  "source": "./src/meta.ts",
23
23
  "types": "./dist/types/src/meta.d.ts",
24
- "default": "./dist/lib/neutral/meta.mjs"
24
+ "default": "./dist/lib/meta.mjs"
25
25
  },
26
26
  "#plugin": {
27
27
  "source": {
@@ -30,36 +30,36 @@
30
30
  "default": "./src/PreviewPlugin.tsx"
31
31
  },
32
32
  "types": "./dist/types/src/PreviewPlugin.d.ts",
33
- "workerd": "./dist/lib/neutral/PreviewPlugin.workerd.mjs",
34
- "node": "./dist/lib/neutral/PreviewPlugin.node.mjs",
35
- "default": "./dist/lib/neutral/PreviewPlugin.mjs"
33
+ "workerd": "./dist/lib/PreviewPlugin.workerd.mjs",
34
+ "node": "./dist/lib/PreviewPlugin.node.mjs",
35
+ "default": "./dist/lib/PreviewPlugin.mjs"
36
36
  },
37
37
  "#translations": {
38
38
  "source": "./src/translations.ts",
39
39
  "types": "./dist/types/src/translations.d.ts",
40
- "default": "./dist/lib/neutral/translations.mjs"
40
+ "default": "./dist/lib/translations.mjs"
41
41
  }
42
42
  },
43
43
  "exports": {
44
44
  ".": {
45
45
  "source": "./src/index.ts",
46
46
  "types": "./dist/types/src/index.d.ts",
47
- "default": "./dist/lib/neutral/index.mjs"
47
+ "import": "./dist/lib/index.mjs"
48
48
  },
49
49
  "./plugin": {
50
50
  "source": "./src/plugin.ts",
51
51
  "types": "./dist/types/src/plugin.d.ts",
52
- "default": "./dist/lib/neutral/plugin.mjs"
52
+ "import": "./dist/lib/plugin.mjs"
53
53
  },
54
54
  "./testing": {
55
55
  "source": "./src/testing.ts",
56
56
  "types": "./dist/types/src/testing.d.ts",
57
- "default": "./dist/lib/neutral/testing.mjs"
57
+ "import": "./dist/lib/testing.mjs"
58
58
  },
59
59
  "./translations": {
60
60
  "source": "./src/translations.ts",
61
61
  "types": "./dist/types/src/translations.d.ts",
62
- "default": "./dist/lib/neutral/translations.mjs"
62
+ "import": "./dist/lib/translations.mjs"
63
63
  }
64
64
  },
65
65
  "types": "dist/types/src/index.d.ts",
@@ -69,36 +69,37 @@
69
69
  "src"
70
70
  ],
71
71
  "dependencies": {
72
- "@dxos/app-framework": "0.10.0",
73
- "@dxos/app-toolkit": "0.10.0",
74
- "@dxos/async": "0.10.0",
75
- "@dxos/client": "0.10.0",
76
- "@dxos/echo-react": "0.10.0",
77
- "@dxos/echo": "0.10.0",
78
- "@dxos/effect": "0.10.0",
79
- "@dxos/keys": "0.10.0",
80
- "@dxos/log": "0.10.0",
81
- "@dxos/react-ui-form": "0.10.0",
82
- "@dxos/plugin-client": "0.10.0",
83
- "@dxos/react-ui": "0.10.0",
84
- "@dxos/react-ui-mosaic": "0.10.0",
85
- "@dxos/react-ui-syntax-highlighter": "0.10.0",
86
- "@dxos/schema": "0.10.0",
87
- "@dxos/ui-editor": "0.10.0",
88
- "@dxos/types": "0.10.0",
89
- "@dxos/util": "0.10.0",
90
- "@dxos/ui-types": "0.10.0"
72
+ "@dxos/app-toolkit": "0.11.0",
73
+ "@dxos/async": "0.11.0",
74
+ "@dxos/client": "0.11.0",
75
+ "@dxos/echo": "0.11.0",
76
+ "@dxos/echo-react": "0.11.0",
77
+ "@dxos/keys": "0.11.0",
78
+ "@dxos/plugin-client": "0.11.0",
79
+ "@dxos/react-ui": "0.11.0",
80
+ "@dxos/log": "0.11.0",
81
+ "@dxos/react-ui-form": "0.11.0",
82
+ "@dxos/react-ui-syntax-highlighter": "0.11.0",
83
+ "@dxos/react-ui-attention": "0.11.0",
84
+ "@dxos/schema": "0.11.0",
85
+ "@dxos/react-ui-mosaic": "0.11.0",
86
+ "@dxos/ui-types": "0.11.0",
87
+ "@dxos/types": "0.11.0",
88
+ "@dxos/util": "0.11.0",
89
+ "@dxos/effect": "0.11.0",
90
+ "@dxos/app-framework": "0.11.0",
91
+ "@dxos/ui-editor": "0.11.0"
91
92
  },
92
93
  "devDependencies": {
93
94
  "effect": "3.21.4",
94
95
  "react": "~19.2.7",
95
96
  "react-dom": "~19.2.7",
96
- "vite": "^8.0.16",
97
- "@dxos/echo-react": "0.10.0",
98
- "@dxos/random": "0.10.0",
99
- "@dxos/plugin-testing": "0.10.0",
100
- "@dxos/react-ui-components": "0.10.0",
101
- "@dxos/storybook-utils": "0.10.0"
97
+ "vite": "^8.1.4",
98
+ "@dxos/react-ui-components": "0.11.0",
99
+ "@dxos/plugin-testing": "0.11.0",
100
+ "@dxos/echo-react": "0.11.0",
101
+ "@dxos/random": "0.11.0",
102
+ "@dxos/storybook-utils": "0.11.0"
102
103
  },
103
104
  "peerDependencies": {
104
105
  "effect": "3.21.4",
@@ -0,0 +1,35 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { Surface } from '@dxos/app-framework/ui';
8
+ import { AppSurface } from '@dxos/app-toolkit/ui';
9
+ import { type Organization, type Person } from '@dxos/types';
10
+
11
+ import { OrganizationCard, PersonCard } from '../cards';
12
+
13
+ export type OrganizationCardContentProps = AppSurface.ObjectCardData<Organization.Organization> & {
14
+ role: string;
15
+ };
16
+
17
+ /** Organization and Person cards append a nested `Related` surface, so they compose rather than map. */
18
+ export const OrganizationCardContent = ({ role, ...data }: OrganizationCardContentProps) => (
19
+ <>
20
+ <OrganizationCard role={role} subject={data.subject} />
21
+ <Surface.Surface type={AppSurface.Related} data={data} limit={1} />
22
+ </>
23
+ );
24
+
25
+ export type PersonCardContentProps = AppSurface.ObjectCardData<Person.Person> & {
26
+ role: string;
27
+ };
28
+
29
+ /** Renders a person card with one nested `Related` surface. */
30
+ export const PersonCardContent = ({ role, ...data }: PersonCardContentProps) => (
31
+ <>
32
+ <PersonCard role={role} subject={data.subject} />
33
+ <Surface.Surface type={AppSurface.Related} data={data} limit={1} />
34
+ </>
35
+ );
@@ -5,7 +5,7 @@
5
5
  import * as Effect from 'effect/Effect';
6
6
 
7
7
  import { Capabilities, Capability } from '@dxos/app-framework';
8
- import { AppCapabilities, AppSpace, LayoutOperation, Paths } from '@dxos/app-toolkit';
8
+ import { AppCapabilities, AppSpace, GraphPath, LayoutOperation } from '@dxos/app-toolkit';
9
9
  import { addEventListener } from '@dxos/async';
10
10
  import { type Space } from '@dxos/client/echo';
11
11
  import { Obj } from '@dxos/echo';
@@ -76,7 +76,7 @@ export default Capability.makeModule(
76
76
  // would crash inside Atom's identity check (`'~atom/Serializable' in undefined`). When layout
77
77
  // isn't available, fall through to the personal-space default.
78
78
  const [layoutAtom] = capabilities.getAll(AppCapabilities.Layout);
79
- const spaceId = layoutAtom && Paths.getSpaceIdFromPath(registry.get(layoutAtom).workspace);
79
+ const spaceId = layoutAtom && GraphPath.getSpaceIdFromPath(registry.get(layoutAtom).workspace);
80
80
  const space = (spaceId && client.spaces.get(spaceId)) ?? AppSpace.getPersonalSpace(client);
81
81
  if (!space) {
82
82
  return;
@@ -3,18 +3,17 @@
3
3
  //
4
4
 
5
5
  import * as Effect from 'effect/Effect';
6
- import React from 'react';
7
6
 
8
7
  import { Capabilities, Capability } from '@dxos/app-framework';
9
8
  import { Surface } from '@dxos/app-framework/ui';
10
9
  import { AppSurface } from '@dxos/app-toolkit/ui';
11
10
  import { Obj, Type } from '@dxos/echo';
12
- import { Card } from '@dxos/react-ui';
13
11
  import { Expando } from '@dxos/schema';
14
12
  import { Organization, Person, Pipeline, Task } from '@dxos/types';
15
13
  import { Position } from '@dxos/util';
16
14
 
17
- import { ExpandoCard, FormCard, JsonCard, OrganizationCard, PersonCard, ProjectCard, TaskCard } from '../cards';
15
+ import { ExpandoCard, FormCard, JsonCard, ProjectCard, TaskCard } from '../cards';
16
+ import { OrganizationCardContent, PersonCardContent } from './RelatedCards';
18
17
 
19
18
  export default Capability.makeModule(() =>
20
19
  Effect.succeed(
@@ -24,54 +23,40 @@ export default Capability.makeModule(() =>
24
23
  // TODO(burdon): Create helpers and factor out.
25
24
  //
26
25
 
27
- Surface.create<{ subject: Person.Person }>({
28
- id: 'schemaPopoverContact',
26
+ Surface.create({
27
+ id: 'organizationContent',
29
28
  position: Position.first,
30
- filter: AppSurface.object(AppSurface.CardContent, Person.Person),
31
- component: ({ data, role }) => {
32
- return (
33
- <>
34
- <PersonCard role={role} subject={data.subject} />
35
- <Surface.Surface type={AppSurface.Related} data={data} limit={1} />
36
- </>
37
- );
38
- },
29
+ filter: AppSurface.object(AppSurface.CardContent, Organization.Organization),
30
+ component: OrganizationCardContent,
31
+ props: ({ role, data }) => ({ role, ...data }),
39
32
  }),
40
33
  Surface.create({
41
- id: 'schemaPopoverOrganization',
34
+ id: 'contactContent',
42
35
  position: Position.first,
43
- filter: AppSurface.object(AppSurface.CardContent, Organization.Organization),
44
- component: ({ data, role }) => {
45
- return (
46
- <>
47
- <OrganizationCard role={role} subject={data.subject} />
48
- <Surface.Surface type={AppSurface.Related} data={data} limit={1} />
49
- </>
50
- );
51
- },
36
+ filter: AppSurface.object(AppSurface.CardContent, Person.Person),
37
+ component: PersonCardContent,
38
+ props: ({ role, data }) => ({ role, ...data }),
52
39
  }),
40
+
53
41
  Surface.create({
54
42
  id: 'schemaPopoverProject',
55
43
  position: Position.first,
56
44
  filter: AppSurface.object(AppSurface.CardContent, Pipeline.Pipeline),
57
- component: ({ data, role }) => {
58
- return <ProjectCard role={role} subject={data.subject} />;
59
- },
45
+ component: ProjectCard,
46
+ props: ({ role, data: { subject } }) => ({ role, subject }),
60
47
  }),
61
48
  Surface.create({
62
49
  id: 'schemaPopoverTask',
63
50
  position: Position.first,
64
51
  filter: AppSurface.object(AppSurface.CardContent, Task.Task),
65
- component: ({ data, role }) => {
66
- return <TaskCard role={role} subject={data.subject} />;
67
- },
52
+ component: TaskCard,
53
+ props: ({ role, data: { subject } }) => ({ role, subject }),
68
54
  }),
69
- Surface.create<AppSurface.ObjectCardData<Expando.Expando>>({
55
+ Surface.create({
70
56
  id: 'schemaPopoverExpando',
71
57
  filter: AppSurface.object(AppSurface.CardContent, Expando.Expando),
72
- component: ({ data, role }) => {
73
- return <ExpandoCard role={role} subject={data.subject} ignorePaths={data.ignorePaths} />;
74
- },
58
+ component: ExpandoCard,
59
+ props: ({ role, data: { subject, ignorePaths } }) => ({ role, subject, ignorePaths }),
75
60
  }),
76
61
 
77
62
  Surface.create({
@@ -100,11 +85,10 @@ export default Capability.makeModule(() =>
100
85
  return false;
101
86
  }
102
87
  }),
103
- component: ({ data, role }) => {
104
- // Dynamic/mutable schemas render an editable, full-layout form;
105
- // FormCard handles both static and runtime schema resolution internally.
106
- return <FormCard role={role} subject={data.subject} readonly={false} layout='full' />;
107
- },
88
+ // Dynamic/mutable schemas render an editable, full-layout form;
89
+ // FormCard handles both static and runtime schema resolution internally.
90
+ component: FormCard,
91
+ props: ({ role, data: { subject } }) => ({ role, subject, readonly: false, layout: 'full' as const }),
108
92
  }),
109
93
 
110
94
  //
@@ -115,36 +99,34 @@ export default Capability.makeModule(() =>
115
99
  id: 'fallbackPopover',
116
100
  position: Position.last,
117
101
  filter: AppSurface.subject(AppSurface.CardContent, Obj.isObject),
118
- component: ({ data, role }) => {
119
- return <FormCard role={role} subject={data.subject} projection={data.projection} />;
120
- },
102
+ component: FormCard,
103
+ props: ({ role, data: { subject, projection } }) => ({ role, subject, projection }),
121
104
  }),
122
105
 
123
106
  Surface.create({
124
107
  id: 'fallbackJson',
125
108
  filter: Surface.makeFilter(AppSurface.CardContent),
126
109
  position: Position.last,
127
- component: ({ data }) => {
128
- return <JsonCard data={data} />;
129
- },
110
+ component: JsonCard,
111
+ props: ({ data }) => ({ data }),
130
112
  }),
131
113
 
132
- Surface.create({
133
- id: 'section',
134
- position: Position.last,
135
- filter: AppSurface.subject(AppSurface.Section, Obj.isObject),
136
- component: ({ data }) => {
137
- return (
138
- <div className='flex w-full justify-center'>
139
- <div className='py-2 dx-card-min-width dx-card-max-width'>
140
- <Card.Root>
141
- <Surface.Surface type={AppSurface.CardContent} data={data} limit={1} />
142
- </Card.Root>
143
- </div>
144
- </div>
145
- );
146
- },
147
- }),
114
+ // Surface.create({
115
+ // id: 'fallbackSection',
116
+ // position: Position.last,
117
+ // filter: AppSurface.subject(AppSurface.Section, Obj.isObject),
118
+ // component: ({ data }) => {
119
+ // return (
120
+ // <div className='flex w-full justify-center'>
121
+ // <div className='py-2 dx-card-min-width dx-card-max-width'>
122
+ // <Card.Root>
123
+ // <Surface.Surface type={AppSurface.CardContent} data={data} limit={1} />
124
+ // </Card.Root>
125
+ // </div>
126
+ // </div>
127
+ // );
128
+ // },
129
+ // }),
148
130
  ]),
149
131
  ),
150
132
  );
@@ -3,19 +3,22 @@
3
3
  //
4
4
 
5
5
  import * as Effect from 'effect/Effect';
6
- import React, { useCallback } from 'react';
6
+ import React, { useCallback, useRef } from 'react';
7
7
 
8
8
  import { useOperationInvoker } from '@dxos/app-framework/ui';
9
- import { LayoutOperation, Paths } from '@dxos/app-toolkit';
9
+ import { GraphPath, LayoutOperation } from '@dxos/app-toolkit';
10
10
  import { type AppSurface } from '@dxos/app-toolkit/ui';
11
11
  import { Obj } from '@dxos/echo';
12
12
  import { EffectEx } from '@dxos/effect';
13
13
  import { Avatar } from '@dxos/react-ui';
14
14
  import { Card, Icon } from '@dxos/react-ui';
15
+ import { Attention } from '@dxos/react-ui-attention';
15
16
  import { type Person } from '@dxos/types';
16
17
 
17
18
  export const PersonCard = ({ subject }: AppSurface.ObjectCardProps<Person.Person>) => {
18
19
  const { invoke } = useOperationInvoker();
20
+ // Card.Action's onClick carries no event, so resolve the origin plank from the card element itself.
21
+ const cardRef = useRef<HTMLDivElement>(null);
19
22
  const { image, organization: { target: organization } = {}, emails = [] } = subject;
20
23
 
21
24
  const handleOrganizationClick = useCallback(() => {
@@ -23,19 +26,22 @@ export const PersonCard = ({ subject }: AppSurface.ObjectCardProps<Person.Person
23
26
  return;
24
27
  }
25
28
 
29
+ const pivotId = cardRef.current ? Attention.getRootAttendableId(cardRef.current) : undefined;
26
30
  return Effect.gen(function* () {
27
- const organizationPath = Paths.getObjectPathFromObject(organization);
31
+ const organizationPath = GraphPath.getObjectPathFromObject(organization);
28
32
  const db = Obj.getDatabase(organization);
29
33
  yield* invoke(LayoutOperation.UpdatePopover, { state: false, anchorId: '' });
30
34
  yield* invoke(LayoutOperation.Open, {
31
35
  subject: [organizationPath],
32
- workspace: db ? Paths.getSpacePath(db.spaceId) : undefined,
36
+ pivotId,
37
+ disposition: 'add',
38
+ workspace: db ? GraphPath.getSpacePath(db.spaceId) : undefined,
33
39
  });
34
40
  }).pipe(EffectEx.runAndForwardErrors);
35
41
  }, [invoke, organization]);
36
42
 
37
43
  return (
38
- <Card.Body>
44
+ <Card.Body ref={cardRef}>
39
45
  {image && (
40
46
  <Card.Row>
41
47
  <Avatar.Root>
@@ -58,7 +64,7 @@ export const PersonCard = ({ subject }: AppSurface.ObjectCardProps<Person.Person
58
64
  <Card.Block>
59
65
  <Icon icon='ph--at--regular' />
60
66
  </Card.Block>
61
- <Card.Text truncate className='text-sky-text text-sm'>
67
+ <Card.Text truncate classNames='text-sky-text text-sm'>
62
68
  {emails.map(({ value }) => (
63
69
  <div key={value}>{value}</div>
64
70
  ))}
@@ -15,7 +15,6 @@ import { translations } from '#translations';
15
15
 
16
16
  import { ExpandoCard, FormCard, OrganizationCard, PersonCard, ProjectCard, TaskCard } from '../cards';
17
17
  import {
18
- DefaultStory,
19
18
  createExpando,
20
19
  createOrganization,
21
20
  createPerson,
@@ -24,7 +23,8 @@ import {
24
23
  createTableEmpty,
25
24
  createTask,
26
25
  createUnknown,
27
- } from './testing';
26
+ } from './fixtures';
27
+ import { DefaultStory } from './testing';
28
28
 
29
29
  random.seed(999);
30
30
 
@@ -0,0 +1,109 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Schema from 'effect/Schema';
6
+
7
+ import { DXN, Obj, Ref, Type } from '@dxos/echo';
8
+ import { Annotation } from '@dxos/echo';
9
+ import { random } from '@dxos/random';
10
+ import { Expando } from '@dxos/schema';
11
+ import { Organization, Person, Pipeline, Task } from '@dxos/types';
12
+
13
+ // Kept out of `testing.tsx`: react-refresh only fast-refreshes a module whose exports are all
14
+ // components, so the object factories exported beside the story force a full page reload on every edit.
15
+
16
+ export const omitImage = ({ image: _, ...rest }: any) => rest;
17
+
18
+ /**
19
+ * Factory functions for creating test objects at render time.
20
+ */
21
+ export const createOrganization = (): Organization.Organization => {
22
+ return Obj.make(Organization.Organization, {
23
+ name: random.company.name(),
24
+ image:
25
+ 'https://plus.unsplash.com/premium_photo-1672116452571-896980a801c8?q=80&w=2671&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
26
+ website: random.internet.url(),
27
+ description: random.lorem.paragraph(),
28
+ });
29
+ };
30
+
31
+ export const createPerson = (): Person.Person => {
32
+ const organization = createOrganization();
33
+ return Obj.make(Person.Person, {
34
+ fullName: random.person.fullName(),
35
+ image:
36
+ 'https://plus.unsplash.com/premium_photo-1664536392779-049ba8fde933?q=80&w=2574&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
37
+ organization: Ref.make(organization),
38
+ emails: [
39
+ { label: 'Work', value: random.internet.email() },
40
+ { label: 'Work', value: random.internet.email() },
41
+ { label: 'Work', value: random.internet.email() },
42
+ ],
43
+ });
44
+ };
45
+
46
+ export const createProject = (): Pipeline.Pipeline => {
47
+ return Obj.make(Pipeline.Pipeline, {
48
+ name: random.person.fullName(),
49
+ image: 'https://dxos.network/dxos-logotype-blue.png',
50
+ description: random.lorem.paragraph(),
51
+ columns: [],
52
+ });
53
+ };
54
+
55
+ export const createTask = (): Task.Task => {
56
+ return Obj.make(Task.Task, {
57
+ title: random.lorem.sentence(),
58
+ status: random.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
59
+ });
60
+ };
61
+
62
+ /**
63
+ * Plain object cast as `Obj.Any` — has no schema, typename, or database, so cards
64
+ * like `FormCard` fall through to their empty/unsupported state. Use to story-pin
65
+ * fallback rendering.
66
+ */
67
+ export const createUnknown = (): Obj.Any => ({}) as unknown as Obj.Any;
68
+
69
+ /**
70
+ * Typed object with no user-facing values (only `id`). All Person fields are
71
+ * optional, so `Obj.make(Person, {})` is valid and triggers the FormCard
72
+ * "no values" branch in readonly mode.
73
+ */
74
+ export const createPersonEmpty = (): Person.Person => Obj.make(Person.Person, {});
75
+
76
+ /**
77
+ * Mock of the `Table` schema (org.dxos.type.table): an optional `name` plus
78
+ * `view` and `sizes` fields annotated with `FormInputAnnotation.set(false)`.
79
+ * When constructed without a `name`, no form-renderable field has a value so
80
+ * FormCard should fall through to its empty state — even though
81
+ * `Object.keys(subject)` includes `view`/`sizes`.
82
+ */
83
+ const TableLike = Schema.Struct({
84
+ name: Schema.String.pipe(Schema.optional),
85
+ view: Schema.String.pipe(Annotation.FormInputAnnotation.set(false)),
86
+ sizes: Schema.Record({ key: Schema.String, value: Schema.Number }).pipe(
87
+ Schema.mutable,
88
+ Annotation.FormInputAnnotation.set(false),
89
+ ),
90
+ }).pipe(Type.makeObject(DXN.make('org.dxos.test.tableLike', '0.1.0')));
91
+ type TableLike = Type.InstanceType<typeof TableLike>;
92
+
93
+ /**
94
+ * Table-like object with values only in form-hidden fields (`view`, `sizes`).
95
+ * `Object.keys` includes them, but a naive `hasValues` check would
96
+ * misclassify this as having data. FormCard must consult the schema's
97
+ * form-input annotations to correctly fall through to the empty state.
98
+ */
99
+ export const createTableEmpty = (): TableLike =>
100
+ Obj.make(TableLike, { view: 'view-dxn:placeholder', sizes: {} } as Obj.MakeProps<typeof TableLike>);
101
+
102
+ export const createExpando = (): Expando.Expando => {
103
+ return Expando.make({
104
+ name: random.person.fullName(),
105
+ email: random.internet.email(),
106
+ age: random.number.int({ min: 18, max: 80 }),
107
+ active: random.datatype.boolean(),
108
+ });
109
+ };
@@ -2,19 +2,15 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import * as Schema from 'effect/Schema';
6
5
  import React, { type FC, useMemo } from 'react';
7
6
 
8
7
  import { type AppSurface } from '@dxos/app-toolkit/ui';
9
- import { DXN, Obj, Ref, Type } from '@dxos/echo';
10
- import { Annotation } from '@dxos/echo';
11
- import { random } from '@dxos/random';
8
+ import { Obj } from '@dxos/echo';
12
9
  import { Card } from '@dxos/react-ui';
13
10
  import { CardContainer, type CardContainerProps } from '@dxos/react-ui-mosaic/testing';
14
- import { Expando } from '@dxos/schema';
15
- import { Organization, Person, Pipeline, Task } from '@dxos/types';
16
11
 
17
12
  import { JsonCard } from '../cards';
13
+ import { omitImage } from './fixtures';
18
14
 
19
15
  export type StoryArgs<T extends Obj.Any, P extends {} = {}> = {
20
16
  Component: FC<AppSurface.ObjectCardProps<T> & P>;
@@ -61,98 +57,3 @@ export const DefaultStory = <T extends Obj.Any, P extends {} = {}>({
61
57
  </div>
62
58
  );
63
59
  };
64
-
65
- export const omitImage = ({ image: _, ...rest }: any) => rest;
66
-
67
- /**
68
- * Factory functions for creating test objects at render time.
69
- */
70
- export const createOrganization = (): Organization.Organization => {
71
- return Obj.make(Organization.Organization, {
72
- name: random.company.name(),
73
- image:
74
- 'https://plus.unsplash.com/premium_photo-1672116452571-896980a801c8?q=80&w=2671&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
75
- website: random.internet.url(),
76
- description: random.lorem.paragraph(),
77
- });
78
- };
79
-
80
- export const createPerson = (): Person.Person => {
81
- const organization = createOrganization();
82
- return Obj.make(Person.Person, {
83
- fullName: random.person.fullName(),
84
- image:
85
- 'https://plus.unsplash.com/premium_photo-1664536392779-049ba8fde933?q=80&w=2574&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
86
- organization: Ref.make(organization),
87
- emails: [
88
- { label: 'Work', value: random.internet.email() },
89
- { label: 'Work', value: random.internet.email() },
90
- { label: 'Work', value: random.internet.email() },
91
- ],
92
- });
93
- };
94
-
95
- export const createProject = (): Pipeline.Pipeline => {
96
- return Obj.make(Pipeline.Pipeline, {
97
- name: random.person.fullName(),
98
- image: 'https://dxos.network/dxos-logotype-blue.png',
99
- description: random.lorem.paragraph(),
100
- columns: [],
101
- });
102
- };
103
-
104
- export const createTask = (): Task.Task => {
105
- return Obj.make(Task.Task, {
106
- title: random.lorem.sentence(),
107
- status: random.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
108
- });
109
- };
110
-
111
- /**
112
- * Plain object cast as `Obj.Any` — has no schema, typename, or database, so cards
113
- * like `FormCard` fall through to their empty/unsupported state. Use to story-pin
114
- * fallback rendering.
115
- */
116
- export const createUnknown = (): Obj.Any => ({}) as unknown as Obj.Any;
117
-
118
- /**
119
- * Typed object with no user-facing values (only `id`). All Person fields are
120
- * optional, so `Obj.make(Person, {})` is valid and triggers the FormCard
121
- * "no values" branch in readonly mode.
122
- */
123
- export const createPersonEmpty = (): Person.Person => Obj.make(Person.Person, {});
124
-
125
- /**
126
- * Mock of the `Table` schema (org.dxos.type.table): an optional `name` plus
127
- * `view` and `sizes` fields annotated with `FormInputAnnotation.set(false)`.
128
- * When constructed without a `name`, no form-renderable field has a value so
129
- * FormCard should fall through to its empty state — even though
130
- * `Object.keys(subject)` includes `view`/`sizes`.
131
- */
132
- const TableLike = Schema.Struct({
133
- name: Schema.String.pipe(Schema.optional),
134
- view: Schema.String.pipe(Annotation.FormInputAnnotation.set(false)),
135
- sizes: Schema.Record({ key: Schema.String, value: Schema.Number }).pipe(
136
- Schema.mutable,
137
- Annotation.FormInputAnnotation.set(false),
138
- ),
139
- }).pipe(Type.makeObject(DXN.make('org.dxos.test.tableLike', '0.1.0')));
140
- type TableLike = Type.InstanceType<typeof TableLike>;
141
-
142
- /**
143
- * Table-like object with values only in form-hidden fields (`view`, `sizes`).
144
- * `Object.keys` includes them, but a naive `hasValues` check would
145
- * misclassify this as having data. FormCard must consult the schema's
146
- * form-input annotations to correctly fall through to the empty state.
147
- */
148
- export const createTableEmpty = (): TableLike =>
149
- Obj.make(TableLike, { view: 'view-dxn:placeholder', sizes: {} } as Obj.MakeProps<typeof TableLike>);
150
-
151
- export const createExpando = (): Expando.Expando => {
152
- return Expando.make({
153
- name: random.person.fullName(),
154
- email: random.internet.email(),
155
- age: random.number.int({ min: 18, max: 80 }),
156
- active: random.datatype.boolean(),
157
- });
158
- };