@dxos/plugin-thread 0.8.4-main.28f8d3d → 0.8.4-main.406dc2a

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 (246) hide show
  1. package/dist/lib/browser/ChannelContainer-BGDGZHV4.mjs +16 -0
  2. package/dist/lib/browser/ChatContainer-75KJZ4XR.mjs +15 -0
  3. package/dist/lib/browser/{app-graph-builder-AF3M7UWN.mjs → app-graph-builder-ODKIAXR7.mjs} +17 -17
  4. package/dist/lib/browser/app-graph-builder-ODKIAXR7.mjs.map +7 -0
  5. package/dist/lib/browser/{call-manager-XRRCAVLI.mjs → call-manager-BCKLI4QD.mjs} +7 -7
  6. package/dist/lib/browser/{call-manager-XRRCAVLI.mjs.map → call-manager-BCKLI4QD.mjs.map} +1 -1
  7. package/dist/lib/browser/chunk-2MUYQOQE.mjs +23 -0
  8. package/dist/lib/browser/{chunk-2CWJBXBH.mjs.map → chunk-2MUYQOQE.mjs.map} +3 -3
  9. package/dist/lib/browser/chunk-B7YQ7QR2.mjs +376 -0
  10. package/dist/lib/browser/chunk-B7YQ7QR2.mjs.map +7 -0
  11. package/dist/lib/browser/{chunk-ZJ7QBXK6.mjs → chunk-CMCVHULC.mjs} +17 -15
  12. package/dist/lib/browser/chunk-CMCVHULC.mjs.map +7 -0
  13. package/dist/lib/browser/{chunk-GERVMFQW.mjs → chunk-CP7E2HRE.mjs} +2 -2
  14. package/dist/lib/browser/chunk-CP7E2HRE.mjs.map +7 -0
  15. package/dist/lib/browser/{chunk-X5PIP5YR.mjs → chunk-HDEQRBOC.mjs} +37 -5
  16. package/dist/lib/{node-esm/chunk-SED3O2XO.mjs.map → browser/chunk-HDEQRBOC.mjs.map} +4 -4
  17. package/dist/lib/browser/chunk-L326LSNS.mjs +18 -0
  18. package/dist/lib/browser/chunk-L326LSNS.mjs.map +7 -0
  19. package/dist/lib/browser/{chunk-EV4JWI7L.mjs → chunk-T4KJ5JR4.mjs} +5 -7
  20. package/dist/lib/browser/chunk-T4KJ5JR4.mjs.map +7 -0
  21. package/dist/lib/browser/chunk-VJMKS4CZ.mjs +619 -0
  22. package/dist/lib/browser/chunk-VJMKS4CZ.mjs.map +7 -0
  23. package/dist/lib/browser/{chunk-DW7JD64V.mjs → chunk-VTYX3SIM.mjs} +26 -16
  24. package/dist/lib/browser/chunk-VTYX3SIM.mjs.map +7 -0
  25. package/dist/lib/browser/{chunk-JGKMMI65.mjs → chunk-XYUJXHYV.mjs} +54 -4
  26. package/dist/lib/browser/chunk-XYUJXHYV.mjs.map +7 -0
  27. package/dist/lib/browser/{chunk-TUFWPOVS.mjs → chunk-ZCEU5K23.mjs} +460 -268
  28. package/dist/lib/browser/chunk-ZCEU5K23.mjs.map +7 -0
  29. package/dist/lib/browser/index.mjs +22 -13
  30. package/dist/lib/browser/index.mjs.map +3 -3
  31. package/dist/lib/browser/{intent-resolver-DCY4P4OX.mjs → intent-resolver-VOQD3TQ2.mjs} +73 -15
  32. package/dist/lib/browser/intent-resolver-VOQD3TQ2.mjs.map +7 -0
  33. package/dist/lib/browser/{markdown-WZSXCEZJ.mjs → markdown-R6MGRIA6.mjs} +6 -6
  34. package/dist/lib/browser/{markdown-WZSXCEZJ.mjs.map → markdown-R6MGRIA6.mjs.map} +1 -1
  35. package/dist/lib/browser/meta.json +1 -1
  36. package/dist/lib/browser/react-root-UMSBTN6H.mjs +28 -0
  37. package/dist/lib/browser/react-root-UMSBTN6H.mjs.map +7 -0
  38. package/dist/lib/browser/{react-surface-PYAMIADB.mjs → react-surface-SXMQEWKE.mjs} +22 -22
  39. package/dist/lib/browser/react-surface-SXMQEWKE.mjs.map +7 -0
  40. package/dist/lib/browser/{settings-AC6YSASL.mjs → settings-TCNF54PN.mjs} +5 -5
  41. package/dist/lib/browser/{settings-AC6YSASL.mjs.map → settings-TCNF54PN.mjs.map} +1 -1
  42. package/dist/lib/browser/{state-2T4KDWBX.mjs → state-ZQ4IPOJT.mjs} +5 -5
  43. package/dist/lib/browser/{state-2T4KDWBX.mjs.map → state-ZQ4IPOJT.mjs.map} +1 -1
  44. package/dist/lib/browser/toolkit-JF4B4QIO.mjs +84 -0
  45. package/dist/lib/browser/toolkit-JF4B4QIO.mjs.map +7 -0
  46. package/dist/lib/browser/types/index.mjs +2 -2
  47. package/dist/lib/node-esm/ChannelContainer-OXC6BTU7.mjs +17 -0
  48. package/dist/lib/node-esm/{ChatContainer-2CT4PJ7I.mjs → ChatContainer-SH6LTFQL.mjs} +6 -6
  49. package/dist/lib/node-esm/{app-graph-builder-OLSJVCWY.mjs → app-graph-builder-DMPT3MY6.mjs} +17 -17
  50. package/dist/lib/node-esm/app-graph-builder-DMPT3MY6.mjs.map +7 -0
  51. package/dist/lib/node-esm/{call-manager-EI7FXRAU.mjs → call-manager-Q7RY2YP4.mjs} +7 -7
  52. package/dist/lib/node-esm/{call-manager-EI7FXRAU.mjs.map → call-manager-Q7RY2YP4.mjs.map} +1 -1
  53. package/dist/lib/node-esm/{chunk-YIDU3DXK.mjs → chunk-2KZ4ODHU.mjs} +17 -15
  54. package/dist/lib/node-esm/chunk-2KZ4ODHU.mjs.map +7 -0
  55. package/dist/lib/node-esm/{chunk-SED3O2XO.mjs → chunk-2MMJF7DW.mjs} +37 -5
  56. package/dist/lib/node-esm/chunk-2MMJF7DW.mjs.map +7 -0
  57. package/dist/lib/node-esm/{chunk-QBHKLWGR.mjs → chunk-2V3WSLUZ.mjs} +54 -4
  58. package/dist/lib/node-esm/chunk-2V3WSLUZ.mjs.map +7 -0
  59. package/dist/lib/node-esm/{chunk-57XR3LEF.mjs → chunk-ABUFOZVX.mjs} +2 -2
  60. package/dist/lib/node-esm/chunk-ABUFOZVX.mjs.map +7 -0
  61. package/dist/lib/node-esm/{chunk-XVKWHTH4.mjs → chunk-BAMZXJS4.mjs} +26 -16
  62. package/dist/lib/node-esm/chunk-BAMZXJS4.mjs.map +7 -0
  63. package/dist/lib/node-esm/chunk-BTGKI5IK.mjs +377 -0
  64. package/dist/lib/node-esm/chunk-BTGKI5IK.mjs.map +7 -0
  65. package/dist/lib/node-esm/chunk-GZ4WL2IS.mjs +25 -0
  66. package/dist/lib/node-esm/{chunk-6CRHHOST.mjs.map → chunk-GZ4WL2IS.mjs.map} +3 -3
  67. package/dist/lib/node-esm/chunk-LCHTNRK7.mjs +19 -0
  68. package/dist/lib/node-esm/chunk-LCHTNRK7.mjs.map +7 -0
  69. package/dist/lib/node-esm/chunk-NHSIAEDW.mjs +620 -0
  70. package/dist/lib/node-esm/chunk-NHSIAEDW.mjs.map +7 -0
  71. package/dist/lib/node-esm/{chunk-GRLRWTRU.mjs → chunk-OBVAURUU.mjs} +5 -7
  72. package/dist/lib/node-esm/chunk-OBVAURUU.mjs.map +7 -0
  73. package/dist/lib/node-esm/{chunk-4TME3BR4.mjs → chunk-TIPIOH2J.mjs} +460 -268
  74. package/dist/lib/node-esm/chunk-TIPIOH2J.mjs.map +7 -0
  75. package/dist/lib/node-esm/index.mjs +22 -13
  76. package/dist/lib/node-esm/index.mjs.map +3 -3
  77. package/dist/lib/node-esm/{intent-resolver-QGTPU6QB.mjs → intent-resolver-AKVH66PH.mjs} +73 -15
  78. package/dist/lib/node-esm/intent-resolver-AKVH66PH.mjs.map +7 -0
  79. package/dist/lib/node-esm/{markdown-3MZXUGUA.mjs → markdown-MYGTDXT6.mjs} +6 -6
  80. package/dist/lib/node-esm/{markdown-3MZXUGUA.mjs.map → markdown-MYGTDXT6.mjs.map} +1 -1
  81. package/dist/lib/node-esm/meta.json +1 -1
  82. package/dist/lib/node-esm/react-root-W6AVTL5M.mjs +29 -0
  83. package/dist/lib/node-esm/react-root-W6AVTL5M.mjs.map +7 -0
  84. package/dist/lib/node-esm/{react-surface-UJFCSFNO.mjs → react-surface-NLSZJGMO.mjs} +22 -22
  85. package/dist/lib/node-esm/react-surface-NLSZJGMO.mjs.map +7 -0
  86. package/dist/lib/node-esm/{settings-N52O7MLS.mjs → settings-ZQHGVFBY.mjs} +5 -5
  87. package/dist/lib/node-esm/{settings-N52O7MLS.mjs.map → settings-ZQHGVFBY.mjs.map} +1 -1
  88. package/dist/lib/node-esm/{state-62TQKQVB.mjs → state-FSLLJ5TZ.mjs} +5 -5
  89. package/dist/lib/node-esm/{state-62TQKQVB.mjs.map → state-FSLLJ5TZ.mjs.map} +1 -1
  90. package/dist/lib/node-esm/toolkit-DHPKJITO.mjs +85 -0
  91. package/dist/lib/node-esm/toolkit-DHPKJITO.mjs.map +7 -0
  92. package/dist/lib/node-esm/types/index.mjs +2 -2
  93. package/dist/types/src/ThreadPlugin.d.ts +1 -1
  94. package/dist/types/src/ThreadPlugin.d.ts.map +1 -1
  95. package/dist/types/src/calls/media-manager.d.ts +1 -1
  96. package/dist/types/src/calls/media-manager.d.ts.map +1 -1
  97. package/dist/types/src/calls/types.d.ts.map +1 -1
  98. package/dist/types/src/calls/util/calls-service.d.ts +3 -1
  99. package/dist/types/src/calls/util/calls-service.d.ts.map +1 -1
  100. package/dist/types/src/calls/util/stub-media-streams.d.ts.map +1 -1
  101. package/dist/types/src/calls-service.stories.d.ts +10 -9
  102. package/dist/types/src/calls-service.stories.d.ts.map +1 -1
  103. package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -1
  104. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  105. package/dist/types/src/capabilities/call-manager.d.ts +1 -1
  106. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  107. package/dist/types/src/capabilities/index.d.ts +11 -10
  108. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  109. package/dist/types/src/capabilities/intent-resolver.d.ts +1 -1
  110. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  111. package/dist/types/src/capabilities/markdown.d.ts +1 -1
  112. package/dist/types/src/capabilities/react-root.d.ts +2 -2
  113. package/dist/types/src/capabilities/react-surface.d.ts +1 -1
  114. package/dist/types/src/capabilities/settings.d.ts +1 -1
  115. package/dist/types/src/capabilities/state.d.ts +2 -2
  116. package/dist/types/src/capabilities/toolkit.d.ts +6 -0
  117. package/dist/types/src/capabilities/toolkit.d.ts.map +1 -0
  118. package/dist/types/src/components/CallDebugPanel.d.ts +7 -0
  119. package/dist/types/src/components/CallDebugPanel.d.ts.map +1 -1
  120. package/dist/types/src/components/ChannelContainer.stories.d.ts +89 -5
  121. package/dist/types/src/components/ChannelContainer.stories.d.ts.map +1 -1
  122. package/dist/types/src/components/Chat.stories.d.ts +91 -5
  123. package/dist/types/src/components/Chat.stories.d.ts.map +1 -1
  124. package/dist/types/src/components/ChatContainer.d.ts.map +1 -1
  125. package/dist/types/src/components/Comments.stories.d.ts +156 -4
  126. package/dist/types/src/components/Comments.stories.d.ts.map +1 -1
  127. package/dist/types/src/components/CommentsThreadContainer.d.ts +2 -2
  128. package/dist/types/src/components/CommentsThreadContainer.d.ts.map +1 -1
  129. package/dist/types/src/components/Media/VideoObject.d.ts +2 -2
  130. package/dist/types/src/components/Media/VideoObject.d.ts.map +1 -1
  131. package/dist/types/src/components/Media/VideoObject.stories.d.ts +18 -4
  132. package/dist/types/src/components/Media/VideoObject.stories.d.ts.map +1 -1
  133. package/dist/types/src/components/MessageContainer.d.ts +4 -1
  134. package/dist/types/src/components/MessageContainer.d.ts.map +1 -1
  135. package/dist/types/src/components/Participant/Participant.d.ts.map +1 -1
  136. package/dist/types/src/components/ResponsiveGrid/ResponsiveContainer.stories.d.ts +155 -3
  137. package/dist/types/src/components/ResponsiveGrid/ResponsiveContainer.stories.d.ts.map +1 -1
  138. package/dist/types/src/components/ResponsiveGrid/ResponsiveGrid.stories.d.ts +157 -4
  139. package/dist/types/src/components/ResponsiveGrid/ResponsiveGrid.stories.d.ts.map +1 -1
  140. package/dist/types/src/components/ThreadComplementary.d.ts.map +1 -1
  141. package/dist/types/src/components/ThreadSettings.d.ts +1 -1
  142. package/dist/types/src/components/ThreadSettings.d.ts.map +1 -1
  143. package/dist/types/src/components/command-extension.d.ts.map +1 -1
  144. package/dist/types/src/components/testing.d.ts +1 -0
  145. package/dist/types/src/components/testing.d.ts.map +1 -1
  146. package/dist/types/src/hooks/index.d.ts +1 -0
  147. package/dist/types/src/hooks/index.d.ts.map +1 -1
  148. package/dist/types/src/hooks/useBlackCanvasStreamTrack.d.ts +2 -0
  149. package/dist/types/src/hooks/useBlackCanvasStreamTrack.d.ts.map +1 -0
  150. package/dist/types/src/hooks/useVideoStreamTrack.d.ts +1 -1
  151. package/dist/types/src/hooks/useVideoStreamTrack.d.ts.map +1 -1
  152. package/dist/types/src/meta.d.ts +1 -2
  153. package/dist/types/src/meta.d.ts.map +1 -1
  154. package/dist/types/src/translations.d.ts +3 -1
  155. package/dist/types/src/translations.d.ts.map +1 -1
  156. package/dist/types/src/types/schema.d.ts +126 -111
  157. package/dist/types/src/types/schema.d.ts.map +1 -1
  158. package/dist/types/src/types/types.d.ts +259 -174
  159. package/dist/types/src/types/types.d.ts.map +1 -1
  160. package/dist/types/tsconfig.tsbuildinfo +1 -1
  161. package/package.json +60 -58
  162. package/src/ThreadPlugin.tsx +128 -122
  163. package/src/calls/call-manager.ts +1 -1
  164. package/src/calls/media-manager.ts +58 -32
  165. package/src/calls/types.ts +7 -3
  166. package/src/calls/util/calls-service.ts +61 -11
  167. package/src/calls/util/stub-media-streams.ts +60 -3
  168. package/src/calls-service.stories.tsx +76 -63
  169. package/src/capabilities/app-graph-builder.ts +13 -10
  170. package/src/capabilities/capabilities.ts +5 -5
  171. package/src/capabilities/index.ts +1 -0
  172. package/src/capabilities/intent-resolver.ts +39 -6
  173. package/src/capabilities/react-root.tsx +2 -2
  174. package/src/capabilities/react-surface.tsx +9 -9
  175. package/src/capabilities/toolkit.ts +82 -0
  176. package/src/components/Call/Toolbar.tsx +4 -4
  177. package/src/components/CallDebugPanel.tsx +108 -49
  178. package/src/components/ChannelContainer.stories.tsx +9 -9
  179. package/src/components/ChannelContainer.tsx +1 -1
  180. package/src/components/Chat.stories.tsx +31 -29
  181. package/src/components/ChatContainer.tsx +4 -1
  182. package/src/components/Comments.stories.tsx +33 -35
  183. package/src/components/CommentsThreadContainer.tsx +8 -7
  184. package/src/components/Media/VideoObject.stories.tsx +35 -28
  185. package/src/components/MessageContainer.tsx +40 -7
  186. package/src/components/Participant/Participant.tsx +13 -7
  187. package/src/components/ResponsiveGrid/ResponsiveContainer.stories.tsx +5 -6
  188. package/src/components/ResponsiveGrid/ResponsiveGrid.stories.tsx +41 -40
  189. package/src/components/ThreadComplementary.tsx +36 -6
  190. package/src/components/ThreadSettings.tsx +6 -7
  191. package/src/components/command-extension.ts +4 -1
  192. package/src/components/testing.ts +22 -0
  193. package/src/hooks/index.ts +1 -0
  194. package/src/hooks/useBlackCanvasStreamTrack.tsx +27 -0
  195. package/src/hooks/useVideoStreamTrack.tsx +6 -3
  196. package/src/meta.ts +3 -4
  197. package/src/testing/stories.ts +1 -1
  198. package/src/translations.ts +3 -1
  199. package/src/types/schema.ts +1 -1
  200. package/src/types/types.ts +21 -13
  201. package/src/typings/peermetrics-webrtc-stats.d.ts +17 -1
  202. package/src/util.ts +1 -1
  203. package/dist/lib/browser/ChannelContainer-O2SFQJU7.mjs +0 -16
  204. package/dist/lib/browser/ChatContainer-PVT3DUZJ.mjs +0 -15
  205. package/dist/lib/browser/app-graph-builder-AF3M7UWN.mjs.map +0 -7
  206. package/dist/lib/browser/chunk-2CWJBXBH.mjs +0 -21
  207. package/dist/lib/browser/chunk-4APCDRSH.mjs +0 -18
  208. package/dist/lib/browser/chunk-4APCDRSH.mjs.map +0 -7
  209. package/dist/lib/browser/chunk-DW7JD64V.mjs.map +0 -7
  210. package/dist/lib/browser/chunk-EV4JWI7L.mjs.map +0 -7
  211. package/dist/lib/browser/chunk-FY4ZRD5X.mjs +0 -367
  212. package/dist/lib/browser/chunk-FY4ZRD5X.mjs.map +0 -7
  213. package/dist/lib/browser/chunk-GERVMFQW.mjs.map +0 -7
  214. package/dist/lib/browser/chunk-J3JO5NWO.mjs +0 -477
  215. package/dist/lib/browser/chunk-J3JO5NWO.mjs.map +0 -7
  216. package/dist/lib/browser/chunk-JGKMMI65.mjs.map +0 -7
  217. package/dist/lib/browser/chunk-TUFWPOVS.mjs.map +0 -7
  218. package/dist/lib/browser/chunk-X5PIP5YR.mjs.map +0 -7
  219. package/dist/lib/browser/chunk-ZJ7QBXK6.mjs.map +0 -7
  220. package/dist/lib/browser/intent-resolver-DCY4P4OX.mjs.map +0 -7
  221. package/dist/lib/browser/react-root-QXPALOUQ.mjs +0 -28
  222. package/dist/lib/browser/react-root-QXPALOUQ.mjs.map +0 -7
  223. package/dist/lib/browser/react-surface-PYAMIADB.mjs.map +0 -7
  224. package/dist/lib/node-esm/ChannelContainer-B2ZGAE4P.mjs +0 -17
  225. package/dist/lib/node-esm/app-graph-builder-OLSJVCWY.mjs.map +0 -7
  226. package/dist/lib/node-esm/chunk-4TME3BR4.mjs.map +0 -7
  227. package/dist/lib/node-esm/chunk-57XR3LEF.mjs.map +0 -7
  228. package/dist/lib/node-esm/chunk-6CRHHOST.mjs +0 -23
  229. package/dist/lib/node-esm/chunk-BF4UKS55.mjs +0 -368
  230. package/dist/lib/node-esm/chunk-BF4UKS55.mjs.map +0 -7
  231. package/dist/lib/node-esm/chunk-GRLRWTRU.mjs.map +0 -7
  232. package/dist/lib/node-esm/chunk-PYTVBN5J.mjs +0 -19
  233. package/dist/lib/node-esm/chunk-PYTVBN5J.mjs.map +0 -7
  234. package/dist/lib/node-esm/chunk-QBHKLWGR.mjs.map +0 -7
  235. package/dist/lib/node-esm/chunk-VNG6M2IY.mjs +0 -478
  236. package/dist/lib/node-esm/chunk-VNG6M2IY.mjs.map +0 -7
  237. package/dist/lib/node-esm/chunk-XVKWHTH4.mjs.map +0 -7
  238. package/dist/lib/node-esm/chunk-YIDU3DXK.mjs.map +0 -7
  239. package/dist/lib/node-esm/intent-resolver-QGTPU6QB.mjs.map +0 -7
  240. package/dist/lib/node-esm/react-root-7ZFJYVUZ.mjs +0 -29
  241. package/dist/lib/node-esm/react-root-7ZFJYVUZ.mjs.map +0 -7
  242. package/dist/lib/node-esm/react-surface-UJFCSFNO.mjs.map +0 -7
  243. /package/dist/lib/browser/{ChannelContainer-O2SFQJU7.mjs.map → ChannelContainer-BGDGZHV4.mjs.map} +0 -0
  244. /package/dist/lib/browser/{ChatContainer-PVT3DUZJ.mjs.map → ChatContainer-75KJZ4XR.mjs.map} +0 -0
  245. /package/dist/lib/node-esm/{ChannelContainer-B2ZGAE4P.mjs.map → ChannelContainer-OXC6BTU7.mjs.map} +0 -0
  246. /package/dist/lib/node-esm/{ChatContainer-2CT4PJ7I.mjs.map → ChatContainer-SH6LTFQL.mjs.map} +0 -0
@@ -2,10 +2,8 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
- import { type Meta } from '@storybook/react-vite';
8
- import React, { useEffect, useState } from 'react';
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React, { useState } from 'react';
9
7
 
10
8
  import { Capabilities, IntentPlugin, contributes, createSurface } from '@dxos/app-framework';
11
9
  import { withPluginManager } from '@dxos/app-framework/testing';
@@ -15,9 +13,11 @@ import { useClient } from '@dxos/react-client';
15
13
  import { type Space } from '@dxos/react-client/echo';
16
14
  import { useIdentity } from '@dxos/react-client/halo';
17
15
  import { withClientProvider } from '@dxos/react-client/testing';
16
+ import { useAsyncEffect } from '@dxos/react-ui';
17
+ import { withTheme } from '@dxos/react-ui/testing';
18
18
  import { Thread } from '@dxos/react-ui-thread';
19
19
  import { DataType } from '@dxos/schema';
20
- import { withLayout, withTheme } from '@dxos/storybook-utils';
20
+ import { render } from '@dxos/storybook-utils';
21
21
 
22
22
  import { translations } from '../translations';
23
23
  import { ChannelType, ThreadType } from '../types';
@@ -26,25 +26,23 @@ import { ChatContainer } from './ChatContainer';
26
26
 
27
27
  faker.seed(1);
28
28
 
29
- const Story = () => {
29
+ const DefaultStory = () => {
30
30
  const client = useClient();
31
31
  const identity = useIdentity();
32
32
  const [space, setSpace] = useState<Space>();
33
33
  const [channel, setChannel] = useState<ChannelType | null>();
34
34
 
35
- useEffect(() => {
35
+ useAsyncEffect(async () => {
36
36
  if (identity) {
37
- setTimeout(async () => {
38
- const space = await client.spaces.create();
39
- const channel = space.db.add(
40
- Obj.make(ChannelType, {
41
- defaultThread: Ref.make(Obj.make(ThreadType, { messages: [], status: 'active' })),
42
- threads: [],
43
- }),
44
- );
45
- setSpace(space);
46
- setChannel(channel);
47
- });
37
+ const space = await client.spaces.create();
38
+ const channel = space.db.add(
39
+ Obj.make(ChannelType, {
40
+ defaultThread: Ref.make(Obj.make(ThreadType, { messages: [], status: 'active' })),
41
+ threads: [],
42
+ }),
43
+ );
44
+ setSpace(space);
45
+ setChannel(channel);
48
46
  }
49
47
  }, [identity]);
50
48
 
@@ -53,19 +51,18 @@ const Story = () => {
53
51
  }
54
52
 
55
53
  return (
56
- <main className='max-is-prose mli-auto bs-dvh overflow-hidden'>
54
+ <main className='is-full max-is-prose mli-auto bs-dvh overflow-hidden'>
57
55
  <ChatContainer space={space} thread={channel.defaultThread.target} />
58
56
  </main>
59
57
  );
60
58
  };
61
59
 
62
- export const Default = {};
63
-
64
- const meta: Meta<typeof Thread.Root> = {
60
+ const meta = {
65
61
  title: 'plugins/plugin-thread/Chat',
66
- component: Thread.Root,
67
- render: () => <Story />,
62
+ component: Thread.Root as any,
63
+ render: render(DefaultStory),
68
64
  decorators: [
65
+ withTheme,
69
66
  withPluginManager({
70
67
  plugins: [IntentPlugin()],
71
68
  capabilities: [
@@ -79,11 +76,16 @@ const meta: Meta<typeof Thread.Root> = {
79
76
  ),
80
77
  ],
81
78
  }),
82
- withTheme,
83
- withLayout({ fullscreen: true }),
84
- withClientProvider({ createSpace: true, types: [ThreadType, DataType.Message] }),
79
+ withClientProvider({ createSpace: true, types: [ThreadType, ChannelType, DataType.Message] }),
85
80
  ],
86
- parameters: { translations },
87
- };
81
+ parameters: {
82
+ layout: 'fullscreen',
83
+ translations,
84
+ },
85
+ } satisfies Meta<typeof DefaultStory>;
88
86
 
89
87
  export default meta;
88
+
89
+ type Story = StoryObj<typeof meta>;
90
+
91
+ export const Default: Story = {};
@@ -112,7 +112,10 @@ export const ChatContainer = ({ space, thread, context, current, autoFocusTextbo
112
112
  <Thread.Root
113
113
  current={current}
114
114
  id={id}
115
- classNames='bs-full grid-rows-[1fr_min-content_min-content] overflow-hidden transition-[padding-block-end] [[data-sidebar-inline-start-state=open]_&]:lg:pbe-0'
115
+ classNames={[
116
+ 'bs-full is-full max-is-prose mli-auto grid-rows-[1fr_min-content_min-content] overflow-hidden',
117
+ 'transition-[padding-block-end] [[data-sidebar-inline-start-state=open]_&]:lg:pbe-0',
118
+ ]}
116
119
  >
117
120
  <ScrollArea.Root classNames='col-span-2'>
118
121
  <ScrollArea.Viewport classNames='overflow-anchored after:overflow-anchor after:block after:bs-px after:-mbs-px [&>div]:min-bs-full [&>div]:!grid [&>div]:grid-rows-[1fr_0]'>
@@ -2,10 +2,8 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
- import { type Meta } from '@storybook/react-vite';
8
- import React, { useEffect } from 'react';
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React from 'react';
9
7
 
10
8
  import { IntentPlugin } from '@dxos/app-framework';
11
9
  import { withPluginManager } from '@dxos/app-framework/testing';
@@ -14,14 +12,16 @@ import { ClientPlugin } from '@dxos/plugin-client';
14
12
  import { faker } from '@dxos/random';
15
13
  import { useQuery, useSpace } from '@dxos/react-client/echo';
16
14
  import { useIdentity } from '@dxos/react-client/halo';
15
+ import { useAsyncEffect } from '@dxos/react-ui';
16
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
17
17
  import { AnchoredTo, DataType } from '@dxos/schema';
18
- import { layoutCentered, render, withLayout, withTheme } from '@dxos/storybook-utils';
18
+ import { render } from '@dxos/storybook-utils';
19
19
 
20
20
  import { translations } from '../translations';
21
21
  import { ThreadType } from '../types';
22
22
 
23
23
  import { CommentsContainer } from './CommentsContainer';
24
- import { createCommentThread } from './testing';
24
+ import { createCommentThread, createProposalThread } from './testing';
25
25
 
26
26
  faker.seed(1);
27
27
 
@@ -30,27 +30,25 @@ const DefaultStory = () => {
30
30
  const space = useSpace();
31
31
  const anchors = useQuery(space, Query.type(AnchoredTo));
32
32
 
33
- useEffect(() => {
33
+ useAsyncEffect(async () => {
34
34
  if (identity && space) {
35
- const t = setTimeout(async () => {
36
- const object = space.db.add(Obj.make(Type.Expando, {}));
37
- const thread1 = space.db.add(createCommentThread(identity));
38
- const thread2 = space.db.add(createCommentThread(identity));
39
- space.db.add(
40
- Relation.make(AnchoredTo, {
41
- [Relation.Source]: thread1,
42
- [Relation.Target]: object,
43
- }),
44
- );
45
- space.db.add(
46
- Relation.make(AnchoredTo, {
47
- [Relation.Source]: thread2,
48
- [Relation.Target]: object,
49
- }),
50
- );
51
- });
52
-
53
- return () => clearTimeout(t);
35
+ const object = space.db.add(Obj.make(Type.Expando, {}));
36
+ const thread1 = space.db.add(createCommentThread(identity));
37
+ const thread2 = space.db.add(createProposalThread(identity));
38
+ space.db.add(
39
+ Relation.make(AnchoredTo, {
40
+ [Relation.Source]: thread1,
41
+ [Relation.Target]: object,
42
+ anchor: 'test',
43
+ }),
44
+ );
45
+ space.db.add(
46
+ Relation.make(AnchoredTo, {
47
+ [Relation.Source]: thread2,
48
+ [Relation.Target]: object,
49
+ anchor: 'test',
50
+ }),
51
+ );
54
52
  }
55
53
  }, [identity, space]);
56
54
 
@@ -58,19 +56,17 @@ const DefaultStory = () => {
58
56
  return null;
59
57
  }
60
58
 
61
- return (
62
- <div className='w-[30rem] overflow-y-auto bg-baseSurface'>
63
- <CommentsContainer anchors={anchors} onThreadDelete={console.log} />
64
- </div>
65
- );
59
+ return <CommentsContainer anchors={anchors} onThreadDelete={console.log} onAcceptProposal={console.log} />;
66
60
  };
67
61
 
68
- const meta: Meta = {
62
+ const meta = {
69
63
  title: 'plugins/plugin-thread/Comments',
70
64
  render: render(DefaultStory),
71
65
  decorators: [
72
66
  withTheme,
73
- withLayout({ fullscreen: true, classNames: layoutCentered }),
67
+ withLayout({ container: 'column', scroll: true }),
68
+ // TODO(wittjosiah): This shouldn't depend on app framework (use withClientProvider instead).
69
+ // Currently this is required due to useOnEditAnalytics.
74
70
  withPluginManager({
75
71
  plugins: [
76
72
  IntentPlugin(),
@@ -86,8 +82,10 @@ const meta: Meta = {
86
82
  parameters: {
87
83
  translations,
88
84
  },
89
- };
85
+ } satisfies Meta<typeof DefaultStory>;
90
86
 
91
87
  export default meta;
92
88
 
93
- export const Default = {};
89
+ type Story = StoryObj<typeof meta>;
90
+
91
+ export const Default: Story = {};
@@ -20,10 +20,7 @@ import { type ThreadType } from '../types';
20
20
  import { getMessageMetadata } from '../util';
21
21
 
22
22
  import { command } from './command-extension';
23
- import { MessageContainer } from './MessageContainer';
24
-
25
- // TODO(burdon): Remove need for p-1.
26
- export const commentControlClassNames = '!p-1 transition-opacity';
23
+ import { MessageContainer, buttonClassNames, buttonGroupClassNames } from './MessageContainer';
27
24
 
28
25
  export type CommentsThreadContainerProps = {
29
26
  anchor: AnchoredTo;
@@ -32,6 +29,7 @@ export type CommentsThreadContainerProps = {
32
29
  onResolve?: (anchor: AnchoredTo) => void;
33
30
  onMessageDelete?: (anchor: AnchoredTo, messageId: string) => void;
34
31
  onThreadDelete?: (anchor: AnchoredTo) => void;
32
+ onAcceptProposal?: (anchor: AnchoredTo, messageId: string) => void;
35
33
  } & Pick<ThreadRootProps, 'current'>;
36
34
 
37
35
  export const CommentsThreadContainer = ({
@@ -42,6 +40,7 @@ export const CommentsThreadContainer = ({
42
40
  onResolve,
43
41
  onMessageDelete,
44
42
  onThreadDelete,
43
+ onAcceptProposal,
45
44
  }: CommentsThreadContainerProps) => {
46
45
  const { t } = useTranslation(meta.id);
47
46
  const identity = useIdentity()!;
@@ -76,6 +75,7 @@ export const CommentsThreadContainer = ({
76
75
  const handleResolve = useCallback(() => onResolve?.(anchor), [onResolve, anchor]);
77
76
  const handleMessageDelete = useCallback((id: string) => onMessageDelete?.(anchor, id), [onMessageDelete, anchor]);
78
77
  const handleThreadDelete = useCallback(() => onThreadDelete?.(anchor), [onThreadDelete, anchor]);
78
+ const handleAcceptProposal = useCallback((id: string) => onAcceptProposal?.(anchor, id), [onAcceptProposal, anchor]);
79
79
 
80
80
  const handleComment: MessageTextboxProps['onSend'] = useCallback(() => {
81
81
  if (!messageRef.current) {
@@ -113,7 +113,7 @@ export const CommentsThreadContainer = ({
113
113
  ) : (
114
114
  <Thread.Header>{thread.name}</Thread.Header>
115
115
  )}
116
- <div className='flex flex-row items-center gap-0.5'>
116
+ <div role='none' className={buttonGroupClassNames}>
117
117
  {thread.status === 'staged' && <Tag palette='neutral'>{t('draft button')}</Tag>}
118
118
  {onResolve && !(thread?.status === 'staged') && (
119
119
  <IconButton
@@ -122,7 +122,7 @@ export const CommentsThreadContainer = ({
122
122
  icon={thread?.status === 'resolved' ? 'ph--check--fill' : 'ph--check--regular'}
123
123
  iconOnly
124
124
  label={t('resolve thread label')}
125
- classNames={[commentControlClassNames, thread?.status !== 'resolved' && hoverableControlItem]}
125
+ classNames={[buttonClassNames, thread?.status !== 'resolved' && hoverableControlItem]}
126
126
  onClick={handleResolve}
127
127
  />
128
128
  )}
@@ -133,7 +133,7 @@ export const CommentsThreadContainer = ({
133
133
  icon='ph--x--regular'
134
134
  iconOnly
135
135
  label={t('delete thread label')}
136
- classNames={[commentControlClassNames, hoverableControlItem]}
136
+ classNames={[buttonClassNames, hoverableControlItem]}
137
137
  onClick={handleThreadDelete}
138
138
  />
139
139
  )}
@@ -148,6 +148,7 @@ export const CommentsThreadContainer = ({
148
148
  message={message}
149
149
  members={members}
150
150
  onDelete={handleMessageDelete}
151
+ onAcceptProposal={handleAcceptProposal}
151
152
  />
152
153
  ))}
153
154
 
@@ -2,48 +2,55 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
- import { type Meta, type StoryObj } from '@storybook/react-vite';
5
+ import { type StoryObj } from '@storybook/react-vite';
8
6
  import React, { useEffect, useState } from 'react';
9
7
 
10
8
  import { scheduleTask } from '@dxos/async';
11
9
  import { Context } from '@dxos/context';
12
10
  import { log } from '@dxos/log';
13
- import { withLayout, withTheme } from '@dxos/storybook-utils';
11
+ import { withTheme } from '@dxos/react-ui/testing';
14
12
 
15
13
  import { getUserMediaTrack } from '../../calls';
16
14
  import { ResponsiveContainer } from '../ResponsiveGrid';
17
15
 
18
- import { VideoObject } from './VideoObject';
16
+ import { VideoObject, type VideoObjectProps } from './VideoObject';
19
17
 
20
- const meta: Meta<typeof VideoObject> = {
21
- title: 'plugins/plugin-thread/VideoObject',
22
- component: VideoObject,
23
- render: (args) => {
24
- log.info('render');
25
- const [videoStream, setVideoStream] = useState<MediaStream>();
26
- useEffect(() => {
27
- const ctx = new Context();
28
- scheduleTask(ctx, async () => {
18
+ const DefaultStory = (props: VideoObjectProps) => {
19
+ log.info('render');
20
+ const [videoStream, setVideoStream] = useState<MediaStream>();
21
+ useEffect(() => {
22
+ const ctx = new Context();
23
+ scheduleTask(ctx, async () => {
24
+ try {
29
25
  const stream = new MediaStream();
30
26
  stream.addTrack(await getUserMediaTrack('videoinput'));
31
27
  setVideoStream(stream);
32
- });
33
-
34
- return () => {
35
- void ctx.dispose();
36
- videoStream?.getTracks().forEach((track) => track.stop());
37
- };
38
- }, []);
39
-
40
- return (
41
- <ResponsiveContainer>
42
- <VideoObject videoStream={videoStream} {...args} flip />
43
- </ResponsiveContainer>
44
- );
28
+ } catch (err) {
29
+ log.catch(err);
30
+ }
31
+ });
32
+
33
+ return () => {
34
+ void ctx.dispose();
35
+ videoStream?.getTracks().forEach((track) => track.stop());
36
+ };
37
+ }, []);
38
+
39
+ return (
40
+ <ResponsiveContainer>
41
+ <VideoObject videoStream={videoStream} flip {...props} />
42
+ </ResponsiveContainer>
43
+ );
44
+ };
45
+
46
+ const meta = {
47
+ title: 'plugins/plugin-thread/VideoObject',
48
+ component: VideoObject,
49
+ render: DefaultStory,
50
+ decorators: [withTheme],
51
+ parameters: {
52
+ layout: 'fullscreen',
45
53
  },
46
- decorators: [withTheme, withLayout({ fullscreen: true, classNames: 'justify-center' })],
47
54
  };
48
55
 
49
56
  export default meta;
@@ -21,16 +21,25 @@ import { meta } from '../meta';
21
21
  import { getMessageMetadata } from '../util';
22
22
 
23
23
  import { command } from './command-extension';
24
- import { commentControlClassNames } from './CommentsThreadContainer';
24
+
25
+ export const buttonGroupClassNames = 'flex flex-row items-center gap-0.5 pie-2';
26
+ export const buttonClassNames = '!p-1 transition-opacity';
25
27
 
26
28
  export type MessageContainerProps = {
27
29
  message: DataType.Message;
28
30
  members: SpaceMember[];
29
31
  editable?: boolean;
30
32
  onDelete?: (id: string) => void;
33
+ onAcceptProposal?: (id: string) => void;
31
34
  };
32
35
 
33
- export const MessageContainer = ({ message, members, editable = false, onDelete }: MessageContainerProps) => {
36
+ export const MessageContainer = ({
37
+ message,
38
+ members,
39
+ editable = false,
40
+ onDelete,
41
+ onAcceptProposal,
42
+ }: MessageContainerProps) => {
34
43
  const { t } = useTranslation(meta.id);
35
44
  const senderIdentity = members.find(
36
45
  (member) =>
@@ -40,8 +49,11 @@ export const MessageContainer = ({ message, members, editable = false, onDelete
40
49
  const messageMetadata = getMessageMetadata(message.id, senderIdentity);
41
50
  const userIsAuthor = useIdentity()?.did === messageMetadata.authorId;
42
51
  const [editing, setEditing] = useState(false);
52
+ const handleEdit = useCallback(() => setEditing((editing) => !editing), []);
43
53
  const handleDelete = useCallback(() => onDelete?.(message.id), [message, onDelete]);
54
+ const handleAcceptProposal = useCallback(() => onAcceptProposal?.(message.id), [message, onAcceptProposal]);
44
55
  const textBlock = message.blocks.find((block) => block._tag === 'text');
56
+ const proposalBlock = message.blocks.find((block) => block._tag === 'proposal');
45
57
  const references = message.blocks.filter((block) => block._tag === 'reference').map((block) => block.reference);
46
58
 
47
59
  useOnEditAnalytics(message, textBlock, !!editing);
@@ -49,7 +61,7 @@ export const MessageContainer = ({ message, members, editable = false, onDelete
49
61
  return (
50
62
  <MessageRoot {...messageMetadata} classNames={[hoverableControls, hoverableFocusedWithinControls]}>
51
63
  <MessageHeading authorName={messageMetadata.authorName} timestamp={messageMetadata.timestamp}>
52
- <div className='flex flex-row items-center gap-0.5'>
64
+ <div role='none' className={buttonGroupClassNames}>
53
65
  {userIsAuthor && editable && (
54
66
  <IconButton
55
67
  data-testid={editing ? 'thread.message.save' : 'thread.message.edit'}
@@ -57,8 +69,20 @@ export const MessageContainer = ({ message, members, editable = false, onDelete
57
69
  icon={editing ? 'ph--check--regular' : 'ph--pencil-simple--regular'}
58
70
  iconOnly
59
71
  label={t(editing ? 'save message label' : 'edit message label')}
60
- classNames={[commentControlClassNames, hoverableControlItem]}
61
- onClick={() => setEditing((editing) => !editing)}
72
+ classNames={[buttonClassNames, hoverableControlItem]}
73
+ onClick={handleEdit}
74
+ />
75
+ )}
76
+ {/* TODO(wittjosiah): Proposal controls should probably be hoisted to thread level. */}
77
+ {proposalBlock && onAcceptProposal && (
78
+ <IconButton
79
+ data-testid='thread.message.accept'
80
+ variant='ghost'
81
+ icon='ph--check--regular'
82
+ iconOnly
83
+ label={t('accept proposal label')}
84
+ classNames={[buttonClassNames, hoverableControlItem]}
85
+ onClick={handleAcceptProposal}
62
86
  />
63
87
  )}
64
88
  {onDelete && (
@@ -68,13 +92,14 @@ export const MessageContainer = ({ message, members, editable = false, onDelete
68
92
  icon='ph--x--regular'
69
93
  iconOnly
70
94
  label={t('delete message label')}
71
- classNames={[commentControlClassNames, hoverableControlItem]}
72
- onClick={() => handleDelete()}
95
+ classNames={[buttonClassNames, hoverableControlItem]}
96
+ onClick={handleDelete}
73
97
  />
74
98
  )}
75
99
  </div>
76
100
  </MessageHeading>
77
101
  {textBlock && <TextboxBlock block={textBlock} isAuthor={userIsAuthor} editing={editing} />}
102
+ {proposalBlock && <ProposalBlock block={proposalBlock} />}
78
103
  {Ref.Array.targets(references).map((reference, index) => (
79
104
  <MessagePart key={index} part={reference} />
80
105
  ))}
@@ -133,6 +158,14 @@ const TextboxBlock = ({
133
158
  return <div role='none' ref={parentRef} className='mie-4' {...focusAttributes} />;
134
159
  };
135
160
 
161
+ const ProposalBlock = ({ block }: { block: DataType.MessageBlock.Proposal }) => {
162
+ return (
163
+ <div role='none' className='mie-4 italic'>
164
+ {block.text}
165
+ </div>
166
+ );
167
+ };
168
+
136
169
  const MessageBlockObjectTile = forwardRef<HTMLDivElement, { subject: Obj.Any }>(({ subject }, forwardedRef) => {
137
170
  // TODO(burdon): Use annotation to get title.
138
171
  let title = (subject as any).name ?? (subject as any).title ?? (subject as any).type ?? 'Object';
@@ -17,15 +17,21 @@ export const Participant = memo(({ item: user, debug, ...props }: ResponsiveGrid
17
17
  const call = useCapability(ThreadCapabilities.CallManager);
18
18
  const isSelf: boolean = call.self.id !== undefined && user.id !== undefined && user.id.startsWith(call.self.id);
19
19
  const isScreenshare = user.id?.endsWith(SCREENSHARE_SUFFIX);
20
- const pulledVideoStream = call.getVideoStream(
21
- isScreenshare || !isSelf ? (user.tracks?.video as EncodedTrackName) : undefined,
22
- );
20
+
21
+ const pulledVideoStream =
22
+ !isSelf && isScreenshare && user.tracks?.screenshareEnabled
23
+ ? call.getVideoStream(user.tracks?.screenshare as EncodedTrackName)
24
+ : !isSelf && !isScreenshare && user.tracks?.videoEnabled
25
+ ? call.getVideoStream(user.tracks?.video as EncodedTrackName)
26
+ : undefined;
23
27
 
24
28
  const videoStream = useMemo<MediaStream | undefined>(() => {
25
- if (isSelf && !isScreenshare) {
26
- return call.media.videoStream;
27
- } else if (isSelf && isScreenshare) {
28
- return call.media.screenshareVideoStream;
29
+ if (isSelf) {
30
+ if (isScreenshare) {
31
+ return call.media.screenshareVideoStream;
32
+ } else if (!isScreenshare) {
33
+ return call.media.videoStream;
34
+ }
29
35
  } else if (!isSelf) {
30
36
  return pulledVideoStream;
31
37
  }
@@ -2,26 +2,25 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React from 'react';
9
7
 
10
- import { withLayout, withTheme } from '@dxos/storybook-utils';
8
+ import { withTheme } from '@dxos/react-ui/testing';
11
9
 
12
10
  import { translations } from '../../translations';
13
11
  import { VideoObject as VideoObjectComponent } from '../Media';
14
12
 
15
13
  import { ResponsiveContainer } from './ResponsiveContainer';
16
14
 
17
- const meta: Meta<typeof ResponsiveContainer> = {
15
+ const meta = {
18
16
  title: 'plugins/plugin-thread/ResponsiveContainer',
19
17
  component: ResponsiveContainer,
20
- decorators: [withTheme, withLayout({ fullscreen: true })],
18
+ decorators: [withTheme],
21
19
  parameters: {
20
+ layout: 'fullscreen',
22
21
  translations,
23
22
  },
24
- };
23
+ } satisfies Meta<typeof ResponsiveContainer>;
25
24
 
26
25
  export default meta;
27
26
 
@@ -2,13 +2,11 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React, { useEffect, useState } from 'react';
9
7
 
10
8
  import { faker } from '@dxos/random';
11
- import { withLayout, withTheme } from '@dxos/storybook-utils';
9
+ import { withTheme } from '@dxos/react-ui/testing';
12
10
 
13
11
  import { translations } from '../../translations';
14
12
 
@@ -57,46 +55,49 @@ const TestCell = ({ item, ...props }: ResponsiveGridItemProps<TestItem>) => {
57
55
 
58
56
  type StoryProps = ResponsiveGridProps<TestItem> & { random?: boolean; autoHideGallery?: boolean };
59
57
 
60
- const meta: Meta<StoryProps> = {
58
+ const DefaultStory = (props: StoryProps) => {
59
+ const [pinned, setPinned] = useState<string | undefined>(
60
+ (props.pinned ?? props.items.length > 1) ? props.items[0]?.id : undefined,
61
+ );
62
+ const [items, setItems] = useState<TestItem[]>(props.items);
63
+ useEffect(() => {
64
+ if (!props.random) {
65
+ return;
66
+ }
67
+
68
+ const interval = setInterval(() => {
69
+ setItems((items) => {
70
+ const p = Math.random();
71
+ if (p < 0.5) {
72
+ return items;
73
+ } else if (p < 0.99 && items.length > 1) {
74
+ return items.slice(0, -1);
75
+ } else {
76
+ return [...items, createItem(items[0]?.type)];
77
+ }
78
+ });
79
+ }, 3_000);
80
+
81
+ return () => clearInterval(interval);
82
+ }, []);
83
+
84
+ return (
85
+ <div className='grid grow p-4'>
86
+ <ResponsiveGrid {...props} Cell={TestCell} items={items} pinned={pinned} onPinnedChange={setPinned} />
87
+ </div>
88
+ );
89
+ };
90
+
91
+ const meta = {
61
92
  title: 'plugins/plugin-thread/ResponsiveGrid',
62
- component: ResponsiveGrid,
63
- render: (args) => {
64
- const [pinned, setPinned] = useState<string | undefined>(
65
- (args.pinned ?? args.items.length > 1) ? args.items[0]?.id : undefined,
66
- );
67
- const [items, setItems] = useState<TestItem[]>(args.items);
68
- useEffect(() => {
69
- if (!args.random) {
70
- return;
71
- }
72
-
73
- const interval = setInterval(() => {
74
- setItems((items) => {
75
- const p = Math.random();
76
- if (p < 0.5) {
77
- return items;
78
- } else if (p < 0.99 && items.length > 1) {
79
- return items.slice(0, -1);
80
- } else {
81
- return [...items, createItem(items[0]?.type)];
82
- }
83
- });
84
- }, 3_000);
85
-
86
- return () => clearInterval(interval);
87
- }, []);
88
-
89
- return (
90
- <div className='grid grow p-4'>
91
- <ResponsiveGrid {...args} Cell={TestCell} items={items} pinned={pinned} onPinnedChange={setPinned} />
92
- </div>
93
- );
94
- },
95
- decorators: [withTheme, withLayout({ fullscreen: true, classNames: 'justify-center' })],
93
+ component: ResponsiveGrid as any,
94
+ render: DefaultStory,
95
+ decorators: [withTheme],
96
96
  parameters: {
97
+ layout: 'fullscreen',
97
98
  translations,
98
99
  },
99
- };
100
+ } satisfies Meta<typeof DefaultStory>;
100
101
 
101
102
  const videoUrls = [
102
103
  'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4',
@@ -118,7 +119,7 @@ const createItem = (type?: 'image' | 'video') => {
118
119
 
119
120
  export default meta;
120
121
 
121
- type Story = StoryObj<StoryProps>;
122
+ type Story = StoryObj<typeof meta>;
122
123
 
123
124
  // TODO(burdon): Story to join/leave repeatedly to test stable position.
124
125