@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.
- package/dist/lib/PreviewPlugin.mjs +16 -0
- package/dist/lib/PreviewPlugin.mjs.map +1 -0
- package/dist/lib/PreviewPlugin.node.mjs +10 -0
- package/dist/lib/PreviewPlugin.node.mjs.map +1 -0
- package/dist/lib/PreviewPlugin.workerd.mjs +10 -0
- package/dist/lib/PreviewPlugin.workerd.mjs.map +1 -0
- package/dist/lib/capabilities.mjs +8 -0
- package/dist/lib/capabilities.mjs.map +1 -0
- package/dist/lib/chunk-meta.mjs +23 -0
- package/dist/lib/chunk-meta.mjs.map +1 -0
- package/dist/lib/chunk-preview-popover.mjs +82 -0
- package/dist/lib/chunk-preview-popover.mjs.map +1 -0
- package/dist/lib/chunk-react-surface.mjs +366 -0
- package/dist/lib/chunk-react-surface.mjs.map +1 -0
- package/dist/lib/index.mjs +2 -0
- package/dist/lib/meta.mjs +2 -0
- package/dist/lib/plugin.mjs +8 -0
- package/dist/lib/plugin.mjs.map +1 -0
- package/dist/lib/testing.mjs +2 -0
- package/dist/lib/translations.mjs +15 -0
- package/dist/lib/translations.mjs.map +1 -0
- package/dist/types/src/capabilities/RelatedCards.d.ts +14 -0
- package/dist/types/src/capabilities/RelatedCards.d.ts.map +1 -0
- package/dist/types/src/capabilities/preview-popover.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/cards/PersonCard.d.ts.map +1 -1
- package/dist/types/src/stories/Card.stories.d.ts.map +1 -1
- package/dist/types/src/stories/fixtures.d.ts +48 -0
- package/dist/types/src/stories/fixtures.d.ts.map +1 -0
- package/dist/types/src/stories/testing.d.ts +1 -47
- package/dist/types/src/stories/testing.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +37 -36
- package/src/capabilities/RelatedCards.tsx +35 -0
- package/src/capabilities/preview-popover.ts +2 -2
- package/src/capabilities/{react-surface.tsx → react-surface.ts} +43 -61
- package/src/cards/PersonCard.tsx +12 -6
- package/src/stories/Card.stories.tsx +2 -2
- package/src/stories/fixtures.ts +109 -0
- package/src/stories/testing.tsx +2 -101
- package/dist/lib/neutral/PreviewPlugin.mjs +0 -9
- package/dist/lib/neutral/PreviewPlugin.mjs.map +0 -7
- package/dist/lib/neutral/PreviewPlugin.node.mjs +0 -17
- package/dist/lib/neutral/PreviewPlugin.node.mjs.map +0 -7
- package/dist/lib/neutral/PreviewPlugin.workerd.mjs +0 -17
- package/dist/lib/neutral/PreviewPlugin.workerd.mjs.map +0 -7
- package/dist/lib/neutral/capabilities/index.mjs +0 -9
- package/dist/lib/neutral/capabilities/index.mjs.map +0 -7
- package/dist/lib/neutral/chunk-3SWUVHXJ.mjs +0 -28
- package/dist/lib/neutral/chunk-3SWUVHXJ.mjs.map +0 -7
- package/dist/lib/neutral/chunk-SXDHWUBO.mjs +0 -32
- package/dist/lib/neutral/chunk-SXDHWUBO.mjs.map +0 -7
- package/dist/lib/neutral/index.mjs +0 -7
- package/dist/lib/neutral/index.mjs.map +0 -7
- package/dist/lib/neutral/meta.json +0 -1
- package/dist/lib/neutral/meta.mjs +0 -7
- package/dist/lib/neutral/meta.mjs.map +0 -7
- package/dist/lib/neutral/plugin.mjs +0 -11
- package/dist/lib/neutral/plugin.mjs.map +0 -7
- package/dist/lib/neutral/preview-popover-EEQQJAH5.mjs +0 -101
- package/dist/lib/neutral/preview-popover-EEQQJAH5.mjs.map +0 -7
- package/dist/lib/neutral/react-surface-2CN3DRU6.mjs +0 -432
- package/dist/lib/neutral/react-surface-2CN3DRU6.mjs.map +0 -7
- package/dist/lib/neutral/testing.mjs +0 -7
- package/dist/lib/neutral/testing.mjs.map +0 -7
- package/dist/lib/neutral/translations.mjs +0 -21
- 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.
|
|
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/
|
|
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/
|
|
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/
|
|
34
|
-
"node": "./dist/lib/
|
|
35
|
-
"default": "./dist/lib/
|
|
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/
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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-
|
|
73
|
-
"@dxos/
|
|
74
|
-
"@dxos/
|
|
75
|
-
"@dxos/
|
|
76
|
-
"@dxos/echo-react": "0.
|
|
77
|
-
"@dxos/
|
|
78
|
-
"@dxos/
|
|
79
|
-
"@dxos/
|
|
80
|
-
"@dxos/log": "0.
|
|
81
|
-
"@dxos/react-ui-form": "0.
|
|
82
|
-
"@dxos/
|
|
83
|
-
"@dxos/react-ui": "0.
|
|
84
|
-
"@dxos/
|
|
85
|
-
"@dxos/react-ui-
|
|
86
|
-
"@dxos/
|
|
87
|
-
"@dxos/
|
|
88
|
-
"@dxos/
|
|
89
|
-
"@dxos/
|
|
90
|
-
"@dxos/
|
|
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.
|
|
97
|
-
"@dxos/
|
|
98
|
-
"@dxos/
|
|
99
|
-
"@dxos/
|
|
100
|
-
"@dxos/
|
|
101
|
-
"@dxos/storybook-utils": "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,
|
|
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 &&
|
|
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,
|
|
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
|
|
28
|
-
id: '
|
|
26
|
+
Surface.create({
|
|
27
|
+
id: 'organizationContent',
|
|
29
28
|
position: Position.first,
|
|
30
|
-
filter: AppSurface.object(AppSurface.CardContent,
|
|
31
|
-
component:
|
|
32
|
-
|
|
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: '
|
|
34
|
+
id: 'contactContent',
|
|
42
35
|
position: Position.first,
|
|
43
|
-
filter: AppSurface.object(AppSurface.CardContent,
|
|
44
|
-
component:
|
|
45
|
-
|
|
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:
|
|
58
|
-
|
|
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:
|
|
66
|
-
|
|
67
|
-
},
|
|
52
|
+
component: TaskCard,
|
|
53
|
+
props: ({ role, data: { subject } }) => ({ role, subject }),
|
|
68
54
|
}),
|
|
69
|
-
Surface.create
|
|
55
|
+
Surface.create({
|
|
70
56
|
id: 'schemaPopoverExpando',
|
|
71
57
|
filter: AppSurface.object(AppSurface.CardContent, Expando.Expando),
|
|
72
|
-
component:
|
|
73
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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:
|
|
119
|
-
|
|
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:
|
|
128
|
-
|
|
129
|
-
},
|
|
110
|
+
component: JsonCard,
|
|
111
|
+
props: ({ data }) => ({ data }),
|
|
130
112
|
}),
|
|
131
113
|
|
|
132
|
-
Surface.create({
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
);
|
package/src/cards/PersonCard.tsx
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 './
|
|
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
|
+
};
|
package/src/stories/testing.tsx
CHANGED
|
@@ -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 {
|
|
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
|
-
};
|