@dxos/plugin-testing 0.8.4-main.6fa680abb7 → 0.8.4-main.7996785055

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 (134) hide show
  1. package/dist/lib/browser/add-toast-M6ZVP2PQ.mjs +22 -0
  2. package/dist/lib/browser/add-toast-M6ZVP2PQ.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-NWOKPVNQ.mjs → chunk-F2MMDTKN.mjs} +1 -1
  4. package/dist/lib/browser/chunk-F2MMDTKN.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-M4TYMLOE.mjs +21 -0
  6. package/dist/lib/browser/chunk-M4TYMLOE.mjs.map +7 -0
  7. package/dist/lib/browser/close-M6YHTWUZ.mjs +10 -0
  8. package/dist/lib/browser/close-M6YHTWUZ.mjs.map +7 -0
  9. package/dist/lib/browser/index.mjs +47 -17
  10. package/dist/lib/browser/index.mjs.map +4 -4
  11. package/dist/lib/browser/meta.json +1 -1
  12. package/dist/lib/browser/open-ND4QPYWD.mjs +10 -0
  13. package/dist/lib/browser/open-ND4QPYWD.mjs.map +7 -0
  14. package/dist/lib/browser/operation-handler-DUWMXELV.mjs +16 -0
  15. package/dist/lib/browser/operation-handler-DUWMXELV.mjs.map +7 -0
  16. package/dist/lib/browser/scroll-into-view-UAINWR6E.mjs +10 -0
  17. package/dist/lib/browser/scroll-into-view-UAINWR6E.mjs.map +7 -0
  18. package/dist/lib/browser/set-layout-mode-E6SJ6ATN.mjs +10 -0
  19. package/dist/lib/browser/set-layout-mode-E6SJ6ATN.mjs.map +7 -0
  20. package/dist/lib/browser/{state-TCYYH5JN.mjs → state-MRRN7LZE.mjs} +3 -2
  21. package/dist/lib/browser/state-MRRN7LZE.mjs.map +7 -0
  22. package/dist/lib/browser/switch-workspace-RPYF6M6L.mjs +19 -0
  23. package/dist/lib/browser/switch-workspace-RPYF6M6L.mjs.map +7 -0
  24. package/dist/lib/browser/update-complementary-HNS4FIWF.mjs +25 -0
  25. package/dist/lib/browser/update-complementary-HNS4FIWF.mjs.map +7 -0
  26. package/dist/lib/browser/update-dialog-W7QOCQ2N.mjs +27 -0
  27. package/dist/lib/browser/update-dialog-W7QOCQ2N.mjs.map +7 -0
  28. package/dist/lib/browser/update-popover-UELZDJW6.mjs +45 -0
  29. package/dist/lib/browser/update-popover-UELZDJW6.mjs.map +7 -0
  30. package/dist/lib/browser/update-sidebar-RJZTREDE.mjs +25 -0
  31. package/dist/lib/browser/update-sidebar-RJZTREDE.mjs.map +7 -0
  32. package/dist/lib/node-esm/add-toast-VMEADC7Y.mjs +23 -0
  33. package/dist/lib/node-esm/add-toast-VMEADC7Y.mjs.map +7 -0
  34. package/dist/lib/node-esm/{chunk-NWN7D2LS.mjs → chunk-VDX7GMAD.mjs} +1 -1
  35. package/dist/lib/node-esm/chunk-VDX7GMAD.mjs.map +7 -0
  36. package/dist/lib/node-esm/chunk-ZLM7QUEK.mjs +22 -0
  37. package/dist/lib/node-esm/chunk-ZLM7QUEK.mjs.map +7 -0
  38. package/dist/lib/node-esm/close-B54TW3OK.mjs +12 -0
  39. package/dist/lib/node-esm/close-B54TW3OK.mjs.map +7 -0
  40. package/dist/lib/node-esm/index.mjs +47 -17
  41. package/dist/lib/node-esm/index.mjs.map +4 -4
  42. package/dist/lib/node-esm/meta.json +1 -1
  43. package/dist/lib/node-esm/open-OLJQ7VR3.mjs +12 -0
  44. package/dist/lib/node-esm/open-OLJQ7VR3.mjs.map +7 -0
  45. package/dist/lib/node-esm/operation-handler-TCIEZXBS.mjs +18 -0
  46. package/dist/lib/node-esm/operation-handler-TCIEZXBS.mjs.map +7 -0
  47. package/dist/lib/node-esm/scroll-into-view-C3XK4YMK.mjs +12 -0
  48. package/dist/lib/node-esm/scroll-into-view-C3XK4YMK.mjs.map +7 -0
  49. package/dist/lib/node-esm/set-layout-mode-AEQI6AVR.mjs +12 -0
  50. package/dist/lib/node-esm/set-layout-mode-AEQI6AVR.mjs.map +7 -0
  51. package/dist/lib/node-esm/{state-ZJEK6SP7.mjs → state-PVDE6Q6G.mjs} +3 -2
  52. package/dist/lib/node-esm/state-PVDE6Q6G.mjs.map +7 -0
  53. package/dist/lib/node-esm/switch-workspace-ZDUCWF6E.mjs +20 -0
  54. package/dist/lib/node-esm/switch-workspace-ZDUCWF6E.mjs.map +7 -0
  55. package/dist/lib/node-esm/update-complementary-LAPUAZ5I.mjs +26 -0
  56. package/dist/lib/node-esm/update-complementary-LAPUAZ5I.mjs.map +7 -0
  57. package/dist/lib/node-esm/update-dialog-5M7FL4XG.mjs +28 -0
  58. package/dist/lib/node-esm/update-dialog-5M7FL4XG.mjs.map +7 -0
  59. package/dist/lib/node-esm/update-popover-2TDGFHL3.mjs +46 -0
  60. package/dist/lib/node-esm/update-popover-2TDGFHL3.mjs.map +7 -0
  61. package/dist/lib/node-esm/update-sidebar-CU6TUDK5.mjs +26 -0
  62. package/dist/lib/node-esm/update-sidebar-CU6TUDK5.mjs.map +7 -0
  63. package/dist/types/src/capabilities/index.d.ts +1 -1
  64. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  65. package/dist/types/src/capabilities/operation-handler/index.d.ts +4 -0
  66. package/dist/types/src/capabilities/operation-handler/index.d.ts.map +1 -0
  67. package/dist/types/src/capabilities/operation-handler/operation-handler.d.ts +6 -0
  68. package/dist/types/src/capabilities/operation-handler/operation-handler.d.ts.map +1 -0
  69. package/dist/types/src/capabilities/state/state.d.ts.map +1 -1
  70. package/dist/types/src/components/Layout/Layout.d.ts.map +1 -1
  71. package/dist/types/src/core.d.ts +1 -7
  72. package/dist/types/src/core.d.ts.map +1 -1
  73. package/dist/types/src/operations/add-toast.d.ts +5 -0
  74. package/dist/types/src/operations/add-toast.d.ts.map +1 -0
  75. package/dist/types/src/operations/close.d.ts +5 -0
  76. package/dist/types/src/operations/close.d.ts.map +1 -0
  77. package/dist/types/src/operations/index.d.ts +3 -0
  78. package/dist/types/src/operations/index.d.ts.map +1 -0
  79. package/dist/types/src/operations/open.d.ts +5 -0
  80. package/dist/types/src/operations/open.d.ts.map +1 -0
  81. package/dist/types/src/operations/scroll-into-view.d.ts +5 -0
  82. package/dist/types/src/operations/scroll-into-view.d.ts.map +1 -0
  83. package/dist/types/src/operations/set-layout-mode.d.ts +5 -0
  84. package/dist/types/src/operations/set-layout-mode.d.ts.map +1 -0
  85. package/dist/types/src/operations/switch-workspace.d.ts +5 -0
  86. package/dist/types/src/operations/switch-workspace.d.ts.map +1 -0
  87. package/dist/types/src/operations/update-complementary.d.ts +5 -0
  88. package/dist/types/src/operations/update-complementary.d.ts.map +1 -0
  89. package/dist/types/src/operations/update-dialog.d.ts +5 -0
  90. package/dist/types/src/operations/update-dialog.d.ts.map +1 -0
  91. package/dist/types/src/operations/update-popover.d.ts +5 -0
  92. package/dist/types/src/operations/update-popover.d.ts.map +1 -0
  93. package/dist/types/src/operations/update-sidebar.d.ts +5 -0
  94. package/dist/types/src/operations/update-sidebar.d.ts.map +1 -0
  95. package/dist/types/src/operations/update-state.d.ts +5 -0
  96. package/dist/types/src/operations/update-state.d.ts.map +1 -0
  97. package/dist/types/src/types/capabilities.d.ts +2 -0
  98. package/dist/types/src/types/capabilities.d.ts.map +1 -1
  99. package/dist/types/tsconfig.tsbuildinfo +1 -1
  100. package/package.json +16 -17
  101. package/src/StorybookPlugin.ts +3 -3
  102. package/src/capabilities/index.ts +1 -1
  103. package/src/capabilities/operation-handler/index.ts +11 -0
  104. package/src/capabilities/operation-handler/operation-handler.ts +16 -0
  105. package/src/capabilities/state/state.tsx +1 -0
  106. package/src/components/Layout/Layout.tsx +124 -70
  107. package/src/core.ts +0 -5
  108. package/src/operations/add-toast.ts +22 -0
  109. package/src/operations/close.ts +14 -0
  110. package/src/operations/index.ts +18 -0
  111. package/src/operations/open.ts +14 -0
  112. package/src/operations/scroll-into-view.ts +14 -0
  113. package/src/operations/set-layout-mode.ts +14 -0
  114. package/src/operations/switch-workspace.ts +20 -0
  115. package/src/operations/update-complementary.ts +27 -0
  116. package/src/operations/update-dialog.ts +27 -0
  117. package/src/operations/update-popover.ts +37 -0
  118. package/src/operations/update-sidebar.ts +26 -0
  119. package/src/operations/update-state.ts +17 -0
  120. package/src/types/capabilities.ts +3 -0
  121. package/dist/lib/browser/chunk-NWOKPVNQ.mjs.map +0 -7
  122. package/dist/lib/browser/operation-resolver-SR4GZ7Q5.mjs +0 -112
  123. package/dist/lib/browser/operation-resolver-SR4GZ7Q5.mjs.map +0 -7
  124. package/dist/lib/browser/state-TCYYH5JN.mjs.map +0 -7
  125. package/dist/lib/node-esm/chunk-NWN7D2LS.mjs.map +0 -7
  126. package/dist/lib/node-esm/operation-resolver-A6XLCTWX.mjs +0 -113
  127. package/dist/lib/node-esm/operation-resolver-A6XLCTWX.mjs.map +0 -7
  128. package/dist/lib/node-esm/state-ZJEK6SP7.mjs.map +0 -7
  129. package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
  130. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
  131. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
  132. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
  133. package/src/capabilities/operation-resolver/index.ts +0 -7
  134. package/src/capabilities/operation-resolver/operation-resolver.ts +0 -99
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-testing",
3
- "version": "0.8.4-main.6fa680abb7",
3
+ "version": "0.8.4-main.7996785055",
4
4
  "description": "Plugin testing utils",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -27,33 +27,32 @@
27
27
  "dependencies": {
28
28
  "@effect-atom/atom": "^0.5.1",
29
29
  "@effect-atom/atom-react": "^0.5.0",
30
- "@dxos/app-framework": "0.8.4-main.6fa680abb7",
31
- "@dxos/app-toolkit": "0.8.4-main.6fa680abb7",
32
- "@dxos/operation": "0.8.4-main.6fa680abb7",
33
- "@dxos/plugin-client": "0.8.4-main.6fa680abb7",
34
- "@dxos/plugin-graph": "0.8.4-main.6fa680abb7",
35
- "@dxos/plugin-attention": "0.8.4-main.6fa680abb7",
36
- "@dxos/plugin-settings": "0.8.4-main.6fa680abb7",
37
- "@dxos/react-ui-mosaic": "0.8.4-main.6fa680abb7",
38
- "@dxos/plugin-theme": "0.8.4-main.6fa680abb7",
39
- "@dxos/util": "0.8.4-main.6fa680abb7"
30
+ "@dxos/app-framework": "0.8.4-main.7996785055",
31
+ "@dxos/operation": "0.8.4-main.7996785055",
32
+ "@dxos/plugin-attention": "0.8.4-main.7996785055",
33
+ "@dxos/plugin-graph": "0.8.4-main.7996785055",
34
+ "@dxos/plugin-settings": "0.8.4-main.7996785055",
35
+ "@dxos/app-toolkit": "0.8.4-main.7996785055",
36
+ "@dxos/plugin-theme": "0.8.4-main.7996785055",
37
+ "@dxos/react-ui-mosaic": "0.8.4-main.7996785055",
38
+ "@dxos/util": "0.8.4-main.7996785055"
40
39
  },
41
40
  "devDependencies": {
42
41
  "@types/react": "~19.2.7",
43
42
  "@types/react-dom": "~19.2.3",
44
- "effect": "3.19.16",
43
+ "effect": "3.20.0",
45
44
  "react": "~19.2.3",
46
45
  "react-dom": "~19.2.3",
47
46
  "vite": "^7.1.11",
48
- "@dxos/ui-theme": "0.8.4-main.6fa680abb7",
49
- "@dxos/react-ui": "0.8.4-main.6fa680abb7"
47
+ "@dxos/ui-theme": "0.8.4-main.7996785055",
48
+ "@dxos/react-ui": "0.8.4-main.7996785055"
50
49
  },
51
50
  "peerDependencies": {
52
- "effect": "3.19.16",
51
+ "effect": "3.20.0",
53
52
  "react": "~19.2.3",
54
53
  "react-dom": "~19.2.3",
55
- "@dxos/ui-theme": "0.8.4-main.6fa680abb7",
56
- "@dxos/react-ui": "0.8.4-main.6fa680abb7"
54
+ "@dxos/react-ui": "0.8.4-main.7996785055",
55
+ "@dxos/ui-theme": "0.8.4-main.7996785055"
57
56
  },
58
57
  "publishConfig": {
59
58
  "access": "public"
@@ -7,7 +7,7 @@ import * as Effect from 'effect/Effect';
7
7
  import { ActivationEvents, Capabilities, Capability, Plugin } from '@dxos/app-framework';
8
8
  import { AppActivationEvents, AppPlugin } from '@dxos/app-toolkit';
9
9
 
10
- import { OperationResolver, State } from './capabilities';
10
+ import { OperationHandler, State } from './capabilities';
11
11
  import { Layout } from './components';
12
12
  import { meta } from './meta';
13
13
  import { type LayoutStateProps } from './types';
@@ -17,8 +17,8 @@ export type StorybookPluginOptions = {
17
17
  };
18
18
 
19
19
  export const StorybookPlugin = Plugin.define<StorybookPluginOptions>(meta).pipe(
20
- AppPlugin.addOperationResolverModule({
21
- activate: OperationResolver,
20
+ AppPlugin.addOperationHandlerModule({
21
+ activate: OperationHandler,
22
22
  }),
23
23
  AppPlugin.addReactContextModule({
24
24
  activate: () =>
@@ -2,5 +2,5 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './operation-resolver';
5
+ export * from './operation-handler';
6
6
  export * from './state';
@@ -0,0 +1,11 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Capability } from '@dxos/app-framework';
6
+ import { OperationHandlerSet } from '@dxos/operation';
7
+
8
+ export const OperationHandler = Capability.lazy<OperationHandlerSet.OperationHandlerSet>(
9
+ 'OperationHandler',
10
+ () => import('./operation-handler'),
11
+ );
@@ -0,0 +1,16 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capabilities, Capability } from '@dxos/app-framework';
8
+ import type { OperationHandlerSet } from '@dxos/operation';
9
+
10
+ import { TestingOperationHandlerSet } from '../../operations';
11
+
12
+ export default Capability.makeModule<OperationHandlerSet.OperationHandlerSet>(
13
+ Effect.fnUntraced(function* () {
14
+ return Capability.contributes(Capabilities.OperationHandler, TestingOperationHandlerSet);
15
+ }),
16
+ );
@@ -14,6 +14,7 @@ const defaultState: LayoutStateProps = {
14
14
  sidebarState: 'closed',
15
15
  complementarySidebarState: 'closed',
16
16
  dialogOpen: false,
17
+ toasts: [],
17
18
  workspace: 'default',
18
19
  };
19
20
 
@@ -6,12 +6,16 @@ import { RegistryContext, useAtomValue } from '@effect-atom/atom-react';
6
6
  import React, { type PropsWithChildren, useCallback, useContext, useEffect, useRef, useState } from 'react';
7
7
 
8
8
  import { Surface, useCapability } from '@dxos/app-framework/ui';
9
+ import { type LayoutOperation } from '@dxos/app-toolkit';
9
10
  import {
10
11
  AlertDialog,
12
+ Button,
11
13
  Dialog,
14
+ Icon,
12
15
  Main,
13
16
  Popover,
14
17
  type PopoverContentInteractOutsideEvent,
18
+ Toast,
15
19
  toLocalizedString,
16
20
  useTranslation,
17
21
  } from '@dxos/react-ui';
@@ -24,7 +28,44 @@ import { LayoutState, type LayoutStateProps } from '../../types';
24
28
 
25
29
  const debounce_delay = 100;
26
30
 
27
- // TODO(wittjosiah): Support dialogs, tooltips, maybe toast.
31
+ const StoryToast = ({ toast, onDismiss }: { toast: LayoutOperation.Toast; onDismiss: (id: string) => void }) => {
32
+ const { t } = useTranslation(meta.id);
33
+ return (
34
+ <Toast.Root
35
+ data-testid={toast.id}
36
+ defaultOpen
37
+ duration={toast.duration}
38
+ onOpenChange={(open) => {
39
+ if (!open) {
40
+ onDismiss(toast.id);
41
+ }
42
+ }}
43
+ >
44
+ <Toast.Body>
45
+ <Toast.Title classNames='items-center'>
46
+ {toast.icon && <Icon icon={toast.icon} classNames='inline mr-1' />}
47
+ {toast.title && <span>{toLocalizedString(toast.title, t)}</span>}
48
+ </Toast.Title>
49
+ {toast.description && <Toast.Description>{toLocalizedString(toast.description, t)}</Toast.Description>}
50
+ </Toast.Body>
51
+ <Toast.Actions>
52
+ {toast.onAction && toast.actionAlt && toast.actionLabel && (
53
+ <Toast.Action altText={toLocalizedString(toast.actionAlt, t)} asChild>
54
+ <Button variant='primary' onClick={() => toast.onAction?.()}>
55
+ {toLocalizedString(toast.actionLabel, t)}
56
+ </Button>
57
+ </Toast.Action>
58
+ )}
59
+ {toast.closeLabel && (
60
+ <Toast.Close asChild>
61
+ <Button>{toLocalizedString(toast.closeLabel, t)}</Button>
62
+ </Toast.Close>
63
+ )}
64
+ </Toast.Actions>
65
+ </Toast.Root>
66
+ );
67
+ };
68
+
28
69
  export const Layout = ({ children }: PropsWithChildren<{}>) => {
29
70
  const { t } = useTranslation(meta.id);
30
71
  const trigger = useRef<HTMLButtonElement | null>(null);
@@ -81,81 +122,94 @@ export const Layout = ({ children }: PropsWithChildren<{}>) => {
81
122
  [handleClose],
82
123
  );
83
124
 
125
+ const handleDismissToast = useCallback(
126
+ (id: string) => {
127
+ updateState({ toasts: layout.toasts.filter((toast) => toast.id !== id) });
128
+ },
129
+ [updateState, layout.toasts],
130
+ );
131
+
84
132
  const DialogRoot = layout.dialogType === 'alert' ? AlertDialog.Root : Dialog.Root;
85
133
  const DialogOverlay = layout.dialogType === 'alert' ? AlertDialog.Overlay : Dialog.Overlay;
86
134
 
87
135
  return (
88
- <div role='none' className='fixed inset-0 flex overflow-hidden'>
89
- <Mosaic.Root>
90
- <Popover.Root open={open}>
91
- <Main.Root
92
- navigationSidebarState={layout.sidebarState}
93
- complementarySidebarState={layout.complementarySidebarState}
94
- onNavigationSidebarStateChange={(next) => updateState({ sidebarState: next })}
95
- onComplementarySidebarStateChange={(next) => updateState({ complementarySidebarState: next })}
96
- >
97
- {children}
98
- </Main.Root>
99
-
100
- <DialogRoot
101
- modal={layout.dialogBlockAlign !== 'end'}
102
- open={layout.dialogOpen}
103
- onOpenChange={(nextOpen) => updateState({ dialogOpen: nextOpen })}
104
- >
105
- {layout.dialogBlockAlign === 'end' ? (
106
- <Surface.Surface
107
- role='dialog'
108
- data={layout.dialogContent}
109
- limit={1}
110
- fallback={ErrorFallback}
111
- placeholder={<div />}
112
- />
113
- ) : (
114
- <DialogOverlay
115
- blockAlign={layout.dialogBlockAlign}
116
- classNames={layout.dialogOverlayClasses}
117
- style={layout.dialogOverlayStyle}
118
- >
119
- <Surface.Surface role='dialog' data={layout.dialogContent} limit={1} fallback={ErrorFallback} />
120
- </DialogOverlay>
121
- )}
122
- </DialogRoot>
123
-
124
- <Popover.VirtualTrigger key={iter} virtualRef={trigger} />
125
- <Popover.Portal>
126
- <Popover.Content
127
- side={layout.popoverSide}
128
- onInteractOutside={handleInteractOutside}
129
- onEscapeKeyDown={handleInteractOutside}
130
- sticky='always'
131
- hideWhenDetached
136
+ <Toast.Provider>
137
+ <div role='none' className='fixed inset-0 flex overflow-hidden'>
138
+ <Mosaic.Root>
139
+ <Popover.Root open={open}>
140
+ <Main.Root
141
+ navigationSidebarState={layout.sidebarState}
142
+ complementarySidebarState={layout.complementarySidebarState}
143
+ onNavigationSidebarStateChange={(next) => updateState({ sidebarState: next })}
144
+ onComplementarySidebarStateChange={(next) => updateState({ complementarySidebarState: next })}
145
+ >
146
+ {children}
147
+ </Main.Root>
148
+
149
+ <DialogRoot
150
+ modal={layout.dialogBlockAlign !== 'end'}
151
+ open={layout.dialogOpen}
152
+ onOpenChange={(nextOpen) => updateState({ dialogOpen: nextOpen })}
132
153
  >
133
- <Popover.Viewport>
134
- {layout.popoverKind === 'card' && (
135
- <Card.Root>
136
- <Card.Toolbar>
137
- {/* TODO(wittjosiah): Cleaner way to handle no drag handle in toolbar? */}
138
- <span />
139
- {layout.popoverTitle ? (
140
- <Card.Title>{toLocalizedString(layout.popoverTitle, t)}</Card.Title>
141
- ) : (
154
+ {layout.dialogBlockAlign === 'end' ? (
155
+ <Surface.Surface
156
+ role='dialog'
157
+ data={layout.dialogContent}
158
+ limit={1}
159
+ fallback={ErrorFallback}
160
+ placeholder={<div />}
161
+ />
162
+ ) : (
163
+ <DialogOverlay
164
+ blockAlign={layout.dialogBlockAlign}
165
+ classNames={layout.dialogOverlayClasses}
166
+ style={layout.dialogOverlayStyle}
167
+ >
168
+ <Surface.Surface role='dialog' data={layout.dialogContent} limit={1} fallback={ErrorFallback} />
169
+ </DialogOverlay>
170
+ )}
171
+ </DialogRoot>
172
+
173
+ <Popover.VirtualTrigger key={iter} virtualRef={trigger} />
174
+ <Popover.Portal>
175
+ <Popover.Content
176
+ side={layout.popoverSide}
177
+ onInteractOutside={handleInteractOutside}
178
+ onEscapeKeyDown={handleInteractOutside}
179
+ sticky='always'
180
+ hideWhenDetached
181
+ >
182
+ <Popover.Viewport>
183
+ {layout.popoverKind === 'card' && (
184
+ <Card.Root>
185
+ <Card.Toolbar>
186
+ {/* TODO(wittjosiah): Cleaner way to handle no drag handle in toolbar? */}
142
187
  <span />
143
- )}
144
- <Card.CloseIconButton onClick={handleClose} />
145
- </Card.Toolbar>
146
- <Surface.Surface role='card--content' data={layout.popoverContent} limit={1} />
147
- </Card.Root>
148
- )}
149
- {layout.popoverKind === 'base' && (
150
- <Surface.Surface role='popover' data={layout.popoverContent} limit={1} />
151
- )}
152
- </Popover.Viewport>
153
- <Popover.Arrow />
154
- </Popover.Content>
155
- </Popover.Portal>
156
- </Popover.Root>
157
- </Mosaic.Root>
158
- </div>
188
+ {layout.popoverTitle ? (
189
+ <Card.Title>{toLocalizedString(layout.popoverTitle, t)}</Card.Title>
190
+ ) : (
191
+ <span />
192
+ )}
193
+ <Card.CloseIconButton onClick={handleClose} />
194
+ </Card.Toolbar>
195
+ <Surface.Surface role='card--content' data={layout.popoverContent} limit={1} />
196
+ </Card.Root>
197
+ )}
198
+ {layout.popoverKind === 'base' && (
199
+ <Surface.Surface role='popover' data={layout.popoverContent} limit={1} />
200
+ )}
201
+ </Popover.Viewport>
202
+ <Popover.Arrow />
203
+ </Popover.Content>
204
+ </Popover.Portal>
205
+ </Popover.Root>
206
+ </Mosaic.Root>
207
+ {layout.toasts.map((toast) => (
208
+ <StoryToast key={toast.id} toast={toast} onDismiss={handleDismissToast} />
209
+ ))}
210
+ <Toast.Viewport />
211
+ </div>
212
+ </Toast.Provider>
159
213
  );
160
214
  };
161
215
 
package/src/core.ts CHANGED
@@ -4,16 +4,11 @@
4
4
 
5
5
  import { OperationPlugin, type Plugin, RuntimePlugin } from '@dxos/app-framework';
6
6
  import { AttentionPlugin } from '@dxos/plugin-attention';
7
- import { ClientPlugin } from '@dxos/plugin-client';
8
7
  import { GraphPlugin } from '@dxos/plugin-graph';
9
8
  import { SettingsPlugin } from '@dxos/plugin-settings';
10
9
  import { ThemePlugin } from '@dxos/plugin-theme';
11
10
  import { defaultTx } from '@dxos/ui-theme';
12
11
 
13
- // TODO(burdon): Remove this.
14
- // Re-export common framework plugins.
15
- export { AttentionPlugin, ClientPlugin, GraphPlugin, OperationPlugin, RuntimePlugin, SettingsPlugin, ThemePlugin };
16
-
17
12
  /**
18
13
  * Core plugins for testing/storybook environments.
19
14
  * NOTE: Does not include SpacePlugin to avoid circular dependencies.
@@ -0,0 +1,22 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ import { updateState } from './update-state';
11
+
12
+ const handler: Operation.WithHandler<typeof LayoutOperation.AddToast> = LayoutOperation.AddToast.pipe(
13
+ Operation.withHandler(
14
+ Effect.fnUntraced(function* (input) {
15
+ yield* updateState((state) => ({
16
+ toasts: [...state.toasts, input as LayoutOperation.Toast],
17
+ }));
18
+ }),
19
+ ),
20
+ );
21
+
22
+ export default handler;
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ const handler: Operation.WithHandler<typeof LayoutOperation.Close> = LayoutOperation.Close.pipe(
11
+ Operation.withHandler(() => Effect.void),
12
+ );
13
+
14
+ export default handler;
@@ -0,0 +1,18 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { OperationHandlerSet } from '@dxos/operation';
6
+
7
+ export const TestingOperationHandlerSet = OperationHandlerSet.lazy(
8
+ () => import('./add-toast'),
9
+ () => import('./close'),
10
+ () => import('./open'),
11
+ () => import('./scroll-into-view'),
12
+ () => import('./set-layout-mode'),
13
+ () => import('./switch-workspace'),
14
+ () => import('./update-complementary'),
15
+ () => import('./update-dialog'),
16
+ () => import('./update-popover'),
17
+ () => import('./update-sidebar'),
18
+ );
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ const handler: Operation.WithHandler<typeof LayoutOperation.Open> = LayoutOperation.Open.pipe(
11
+ Operation.withHandler(() => Effect.void),
12
+ );
13
+
14
+ export default handler;
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ const handler: Operation.WithHandler<typeof LayoutOperation.ScrollIntoView> = LayoutOperation.ScrollIntoView.pipe(
11
+ Operation.withHandler(() => Effect.void),
12
+ );
13
+
14
+ export default handler;
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ const handler: Operation.WithHandler<typeof LayoutOperation.SetLayoutMode> = LayoutOperation.SetLayoutMode.pipe(
11
+ Operation.withHandler(() => Effect.void),
12
+ );
13
+
14
+ export default handler;
@@ -0,0 +1,20 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ import { updateState } from './update-state';
11
+
12
+ const handler: Operation.WithHandler<typeof LayoutOperation.SwitchWorkspace> = LayoutOperation.SwitchWorkspace.pipe(
13
+ Operation.withHandler(
14
+ Effect.fnUntraced(function* ({ subject }) {
15
+ yield* updateState(() => ({ workspace: subject }));
16
+ }),
17
+ ),
18
+ );
19
+
20
+ export default handler;
@@ -0,0 +1,27 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ import { updateState } from './update-state';
11
+
12
+ const handler: Operation.WithHandler<typeof LayoutOperation.UpdateComplementary> =
13
+ LayoutOperation.UpdateComplementary.pipe(
14
+ Operation.withHandler(
15
+ Effect.fnUntraced(function* ({ state }) {
16
+ yield* updateState((layout) => {
17
+ const next = state ?? layout.complementarySidebarState;
18
+ if (next !== layout.complementarySidebarState) {
19
+ return { complementarySidebarState: next };
20
+ }
21
+ return {};
22
+ });
23
+ }),
24
+ ),
25
+ );
26
+
27
+ export default handler;
@@ -0,0 +1,27 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ import { updateState } from './update-state';
11
+
12
+ const handler: Operation.WithHandler<typeof LayoutOperation.UpdateDialog> = LayoutOperation.UpdateDialog.pipe(
13
+ Operation.withHandler(
14
+ Effect.fnUntraced(function* ({ subject, state, type, blockAlign, overlayClasses, overlayStyle, props }) {
15
+ yield* updateState(() => ({
16
+ dialogOpen: state ?? Boolean(subject),
17
+ dialogType: type ?? 'default',
18
+ dialogBlockAlign: blockAlign ?? 'center',
19
+ dialogOverlayClasses: overlayClasses,
20
+ dialogOverlayStyle: overlayStyle,
21
+ dialogContent: subject ? { component: subject, props } : null,
22
+ }));
23
+ }),
24
+ ),
25
+ );
26
+
27
+ export default handler;
@@ -0,0 +1,37 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ import { type LayoutStateProps } from '../types';
11
+ import { updateState } from './update-state';
12
+
13
+ const handler: Operation.WithHandler<typeof LayoutOperation.UpdatePopover> = LayoutOperation.UpdatePopover.pipe(
14
+ Operation.withHandler(
15
+ Effect.fnUntraced(function* (input) {
16
+ const { subject, state, side, kind, props } = input;
17
+ yield* updateState(() => {
18
+ const base: Partial<LayoutStateProps> = {
19
+ popoverKind: kind ?? 'base',
20
+ popoverTitle: kind === 'card' ? input.title : undefined,
21
+ popoverContent:
22
+ typeof subject === 'string' ? { component: subject, props } : subject ? { subject } : undefined,
23
+ popoverOpen: state ?? Boolean(subject),
24
+ popoverSide: side,
25
+ };
26
+ if ('variant' in input && input.variant === 'virtual') {
27
+ return { ...base, popoverVariant: 'virtual', popoverAnchor: input.anchor };
28
+ } else if ('anchorId' in input) {
29
+ return { ...base, popoverVariant: 'react', popoverAnchorId: input.anchorId };
30
+ }
31
+ return base;
32
+ });
33
+ }),
34
+ ),
35
+ );
36
+
37
+ export default handler;
@@ -0,0 +1,26 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ import { updateState } from './update-state';
11
+
12
+ const handler: Operation.WithHandler<typeof LayoutOperation.UpdateSidebar> = LayoutOperation.UpdateSidebar.pipe(
13
+ Operation.withHandler(
14
+ Effect.fnUntraced(function* ({ state }) {
15
+ yield* updateState((layout) => {
16
+ const next = state ?? layout.sidebarState;
17
+ if (next !== layout.sidebarState) {
18
+ return { sidebarState: next };
19
+ }
20
+ return {};
21
+ });
22
+ }),
23
+ ),
24
+ );
25
+
26
+ export default handler;
@@ -0,0 +1,17 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capabilities, Capability } from '@dxos/app-framework';
8
+
9
+ import { LayoutState, type LayoutStateProps } from '../types';
10
+
11
+ export const updateState = (fn: (state: LayoutStateProps) => Partial<LayoutStateProps>) =>
12
+ Effect.gen(function* () {
13
+ const registry = yield* Capability.get(Capabilities.AtomRegistry);
14
+ const stateAtom = yield* Capability.get(LayoutState);
15
+ const current = registry.get(stateAtom);
16
+ registry.set(stateAtom, { ...current, ...fn(current) });
17
+ });
@@ -5,6 +5,7 @@
5
5
  import { type Atom } from '@effect-atom/atom-react';
6
6
 
7
7
  import { Capability } from '@dxos/app-framework';
8
+ import { type LayoutOperation } from '@dxos/app-toolkit';
8
9
  import { type Label } from '@dxos/react-ui';
9
10
 
10
11
  import { meta } from '../meta';
@@ -30,6 +31,8 @@ export type LayoutStateProps = {
30
31
  popoverTitle?: Label;
31
32
  popoverContent?: any;
32
33
 
34
+ toasts: LayoutOperation.Toast[];
35
+
33
36
  workspace: string;
34
37
  };
35
38