@dxos/plugin-kanban 0.8.4-main.dedc0f3 → 0.8.4-main.dfabb4ec29

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 (275) hide show
  1. package/dist/lib/neutral/KanbanContainer-QK6LNCYT.mjs +132 -0
  2. package/dist/lib/neutral/KanbanContainer-QK6LNCYT.mjs.map +7 -0
  3. package/dist/lib/neutral/KanbanPlugin.mjs +36 -0
  4. package/dist/lib/neutral/KanbanPlugin.mjs.map +7 -0
  5. package/dist/lib/neutral/KanbanPlugin.node.mjs +27 -0
  6. package/dist/lib/neutral/KanbanPlugin.node.mjs.map +7 -0
  7. package/dist/lib/neutral/KanbanSettings-G6M47NSK.mjs +83 -0
  8. package/dist/lib/neutral/KanbanSettings-G6M47NSK.mjs.map +7 -0
  9. package/dist/lib/neutral/blueprint-definition-FHVIEKTQ.mjs +15 -0
  10. package/dist/lib/neutral/blueprint-definition-FHVIEKTQ.mjs.map +7 -0
  11. package/dist/lib/neutral/blueprints/index.mjs +8 -0
  12. package/dist/lib/neutral/capabilities/index.mjs +19 -0
  13. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  14. package/dist/lib/neutral/chunk-E65AME5F.mjs +255 -0
  15. package/dist/lib/neutral/chunk-E65AME5F.mjs.map +7 -0
  16. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  17. package/dist/lib/neutral/chunk-KDPM77BD.mjs +21 -0
  18. package/dist/lib/neutral/chunk-KDPM77BD.mjs.map +7 -0
  19. package/dist/lib/neutral/chunk-Z7O5CETK.mjs +8 -0
  20. package/dist/lib/neutral/chunk-Z7O5CETK.mjs.map +7 -0
  21. package/dist/lib/neutral/chunk-ZTQW5KQS.mjs +26 -0
  22. package/dist/lib/neutral/chunk-ZTQW5KQS.mjs.map +7 -0
  23. package/dist/lib/neutral/components/index.mjs +243 -0
  24. package/dist/lib/neutral/components/index.mjs.map +7 -0
  25. package/dist/lib/neutral/containers/index.mjs +11 -0
  26. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  27. package/dist/lib/neutral/create-object-DKBSI46K.mjs +40 -0
  28. package/dist/lib/neutral/create-object-DKBSI46K.mjs.map +7 -0
  29. package/dist/lib/neutral/delete-card-QKT6OXGP.mjs +24 -0
  30. package/dist/lib/neutral/delete-card-QKT6OXGP.mjs.map +7 -0
  31. package/dist/lib/neutral/delete-card-field-XQKM7ZXE.mjs +42 -0
  32. package/dist/lib/neutral/delete-card-field-XQKM7ZXE.mjs.map +7 -0
  33. package/dist/lib/neutral/hooks/index.mjs +432 -0
  34. package/dist/lib/neutral/hooks/index.mjs.map +7 -0
  35. package/dist/lib/neutral/index.mjs +34 -0
  36. package/dist/lib/neutral/index.mjs.map +7 -0
  37. package/dist/lib/neutral/meta.json +1 -0
  38. package/dist/lib/neutral/meta.mjs +8 -0
  39. package/dist/lib/neutral/meta.mjs.map +7 -0
  40. package/dist/lib/neutral/migrations-4NS6H7U2.mjs +31 -0
  41. package/dist/lib/neutral/migrations-4NS6H7U2.mjs.map +7 -0
  42. package/dist/lib/neutral/operation-handler-B7IW6MXU.mjs +13 -0
  43. package/dist/lib/neutral/operation-handler-B7IW6MXU.mjs.map +7 -0
  44. package/dist/lib/neutral/operations/index.mjs +8 -0
  45. package/dist/lib/neutral/operations/index.mjs.map +7 -0
  46. package/dist/lib/neutral/plugin.mjs +16 -0
  47. package/dist/lib/neutral/plugin.mjs.map +7 -0
  48. package/dist/lib/neutral/react-surface-6RVSCHMJ.mjs +93 -0
  49. package/dist/lib/neutral/react-surface-6RVSCHMJ.mjs.map +7 -0
  50. package/dist/lib/neutral/restore-card-XW7AHMPO.mjs +21 -0
  51. package/dist/lib/neutral/restore-card-XW7AHMPO.mjs.map +7 -0
  52. package/dist/lib/neutral/restore-card-field-QOAUY3RJ.mjs +40 -0
  53. package/dist/lib/neutral/restore-card-field-QOAUY3RJ.mjs.map +7 -0
  54. package/dist/lib/neutral/testing/index.mjs +60 -0
  55. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  56. package/dist/lib/neutral/translations.mjs +44 -0
  57. package/dist/lib/neutral/translations.mjs.map +7 -0
  58. package/dist/lib/neutral/types/index.mjs +22 -0
  59. package/dist/lib/neutral/types/index.mjs.map +7 -0
  60. package/dist/lib/neutral/undo-mappings-6CHW6BOF.mjs +42 -0
  61. package/dist/lib/neutral/undo-mappings-6CHW6BOF.mjs.map +7 -0
  62. package/dist/types/src/KanbanPlugin.d.ts +3 -1
  63. package/dist/types/src/KanbanPlugin.d.ts.map +1 -1
  64. package/dist/types/src/KanbanPlugin.node.d.ts +4 -0
  65. package/dist/types/src/KanbanPlugin.node.d.ts.map +1 -0
  66. package/dist/types/src/KanbanPlugin.test.d.ts +2 -0
  67. package/dist/types/src/KanbanPlugin.test.d.ts.map +1 -0
  68. package/dist/types/src/blueprints/index.d.ts +2 -0
  69. package/dist/types/src/blueprints/index.d.ts.map +1 -0
  70. package/dist/types/src/blueprints/kanban-blueprint.d.ts +4 -0
  71. package/dist/types/src/blueprints/kanban-blueprint.d.ts.map +1 -0
  72. package/dist/types/src/capabilities/artifact-definition.d.ts +3 -2
  73. package/dist/types/src/capabilities/artifact-definition.d.ts.map +1 -1
  74. package/dist/types/src/capabilities/blueprint-definition.d.ts +5 -4
  75. package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -1
  76. package/dist/types/src/capabilities/create-object.d.ts +11 -0
  77. package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
  78. package/dist/types/src/capabilities/index.d.ts +13 -3
  79. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  80. package/dist/types/src/capabilities/migrations.d.ts +5 -0
  81. package/dist/types/src/capabilities/migrations.d.ts.map +1 -0
  82. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  83. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  84. package/dist/types/src/capabilities/react-surface.d.ts +3 -2
  85. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  86. package/dist/types/src/capabilities/undo-mappings.d.ts +5 -0
  87. package/dist/types/src/capabilities/undo-mappings.d.ts.map +1 -0
  88. package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts +37 -0
  89. package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts.map +1 -0
  90. package/dist/types/src/components/KanbanBoard/KanbanBoard.stories.d.ts +72 -0
  91. package/dist/types/src/components/KanbanBoard/KanbanBoard.stories.d.ts.map +1 -0
  92. package/dist/types/src/components/KanbanBoard/KanbanCard.d.ts +9 -0
  93. package/dist/types/src/components/KanbanBoard/KanbanCard.d.ts.map +1 -0
  94. package/dist/types/src/components/KanbanBoard/KanbanColumn.d.ts +8 -0
  95. package/dist/types/src/components/KanbanBoard/KanbanColumn.d.ts.map +1 -0
  96. package/dist/types/src/components/KanbanBoard/context.d.ts +38 -0
  97. package/dist/types/src/components/KanbanBoard/context.d.ts.map +1 -0
  98. package/dist/types/src/components/KanbanBoard/index.d.ts +2 -0
  99. package/dist/types/src/components/KanbanBoard/index.d.ts.map +1 -0
  100. package/dist/types/src/components/index.d.ts +1 -2
  101. package/dist/types/src/components/index.d.ts.map +1 -1
  102. package/dist/types/src/containers/KanbanContainer/KanbanContainer.d.ts +6 -0
  103. package/dist/types/src/containers/KanbanContainer/KanbanContainer.d.ts.map +1 -0
  104. package/dist/types/src/containers/KanbanContainer/KanbanContainer.stories.d.ts +79 -0
  105. package/dist/types/src/containers/KanbanContainer/KanbanContainer.stories.d.ts.map +1 -0
  106. package/dist/types/src/containers/KanbanContainer/index.d.ts +2 -0
  107. package/dist/types/src/containers/KanbanContainer/index.d.ts.map +1 -0
  108. package/dist/types/src/containers/KanbanSettings/KanbanSettings.d.ts +13 -0
  109. package/dist/types/src/containers/KanbanSettings/KanbanSettings.d.ts.map +1 -0
  110. package/dist/types/src/containers/KanbanSettings/index.d.ts +2 -0
  111. package/dist/types/src/containers/KanbanSettings/index.d.ts.map +1 -0
  112. package/dist/types/src/containers/index.d.ts +4 -0
  113. package/dist/types/src/containers/index.d.ts.map +1 -0
  114. package/dist/types/src/hooks/index.d.ts +7 -0
  115. package/dist/types/src/hooks/index.d.ts.map +1 -0
  116. package/dist/types/src/hooks/useEchoChangeCallback.d.ts +13 -0
  117. package/dist/types/src/hooks/useEchoChangeCallback.d.ts.map +1 -0
  118. package/dist/types/src/hooks/useItemsProjection.d.ts +10 -0
  119. package/dist/types/src/hooks/useItemsProjection.d.ts.map +1 -0
  120. package/dist/types/src/hooks/useKanbanBoardModel.browser.test.d.ts +2 -0
  121. package/dist/types/src/hooks/useKanbanBoardModel.browser.test.d.ts.map +1 -0
  122. package/dist/types/src/hooks/useKanbanBoardModel.d.ts +16 -0
  123. package/dist/types/src/hooks/useKanbanBoardModel.d.ts.map +1 -0
  124. package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts +22 -0
  125. package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts.map +1 -0
  126. package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts +19 -0
  127. package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts.map +1 -0
  128. package/dist/types/src/hooks/useProjectionModel.d.ts +15 -0
  129. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
  130. package/dist/types/src/index.d.ts +3 -1
  131. package/dist/types/src/index.d.ts.map +1 -1
  132. package/dist/types/src/meta.d.ts +2 -3
  133. package/dist/types/src/meta.d.ts.map +1 -1
  134. package/dist/types/src/operations/delete-card-field.d.ts +5 -0
  135. package/dist/types/src/operations/delete-card-field.d.ts.map +1 -0
  136. package/dist/types/src/operations/delete-card.d.ts +5 -0
  137. package/dist/types/src/operations/delete-card.d.ts.map +1 -0
  138. package/dist/types/src/operations/index.d.ts +3 -0
  139. package/dist/types/src/operations/index.d.ts.map +1 -0
  140. package/dist/types/src/operations/restore-card-field.d.ts +5 -0
  141. package/dist/types/src/operations/restore-card-field.d.ts.map +1 -0
  142. package/dist/types/src/operations/restore-card.d.ts +5 -0
  143. package/dist/types/src/operations/restore-card.d.ts.map +1 -0
  144. package/dist/types/src/playwright/board-manager.d.ts +5 -0
  145. package/dist/types/src/playwright/board-manager.d.ts.map +1 -0
  146. package/dist/types/src/playwright/playwright.config.d.ts +3 -0
  147. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
  148. package/dist/types/src/playwright/smoke.spec.d.ts +2 -0
  149. package/dist/types/src/playwright/smoke.spec.d.ts.map +1 -0
  150. package/dist/types/src/plugin.d.ts +4 -0
  151. package/dist/types/src/plugin.d.ts.map +1 -0
  152. package/dist/types/src/testing/KanbanCardTileSimple.d.ts +7 -0
  153. package/dist/types/src/testing/KanbanCardTileSimple.d.ts.map +1 -0
  154. package/dist/types/src/testing/index.d.ts +2 -0
  155. package/dist/types/src/testing/index.d.ts.map +1 -0
  156. package/dist/types/src/translations.d.ts +50 -22
  157. package/dist/types/src/translations.d.ts.map +1 -1
  158. package/dist/types/src/types/Kanban.d.ts +109 -0
  159. package/dist/types/src/types/Kanban.d.ts.map +1 -0
  160. package/dist/types/src/types/KanbanOperation.d.ts +52 -0
  161. package/dist/types/src/types/KanbanOperation.d.ts.map +1 -0
  162. package/dist/types/src/types/constants.d.ts +6 -0
  163. package/dist/types/src/types/constants.d.ts.map +1 -0
  164. package/dist/types/src/types/index.d.ts +3 -0
  165. package/dist/types/src/types/index.d.ts.map +1 -1
  166. package/dist/types/src/types/migrations.test.d.ts +2 -0
  167. package/dist/types/src/types/migrations.test.d.ts.map +1 -0
  168. package/dist/types/src/types/schema.d.ts +18 -52
  169. package/dist/types/src/types/schema.d.ts.map +1 -1
  170. package/dist/types/src/types/types.d.ts +28 -0
  171. package/dist/types/src/types/types.d.ts.map +1 -1
  172. package/dist/types/src/util/arrangement.d.ts +72 -0
  173. package/dist/types/src/util/arrangement.d.ts.map +1 -0
  174. package/dist/types/src/util/arrangement.test.d.ts +2 -0
  175. package/dist/types/src/util/arrangement.test.d.ts.map +1 -0
  176. package/dist/types/src/util/index.d.ts +2 -0
  177. package/dist/types/src/util/index.d.ts.map +1 -0
  178. package/dist/types/tsconfig.tsbuildinfo +1 -1
  179. package/package.json +120 -57
  180. package/src/KanbanPlugin.node.ts +21 -0
  181. package/src/KanbanPlugin.test.ts +31 -0
  182. package/src/KanbanPlugin.tsx +29 -57
  183. package/src/blueprints/index.ts +5 -0
  184. package/src/blueprints/kanban-blueprint.ts +27 -0
  185. package/src/capabilities/artifact-definition.ts +117 -113
  186. package/src/capabilities/blueprint-definition.ts +13 -24
  187. package/src/capabilities/create-object.ts +40 -0
  188. package/src/capabilities/index.ts +12 -4
  189. package/src/capabilities/migrations.ts +35 -0
  190. package/src/capabilities/operation-handler.ts +14 -0
  191. package/src/capabilities/react-surface.tsx +79 -68
  192. package/src/capabilities/undo-mappings.ts +34 -0
  193. package/src/components/KanbanBoard/KanbanBoard.stories.tsx +146 -0
  194. package/src/components/KanbanBoard/KanbanBoard.tsx +164 -0
  195. package/src/components/KanbanBoard/KanbanCard.tsx +101 -0
  196. package/src/components/KanbanBoard/KanbanColumn.tsx +72 -0
  197. package/src/components/KanbanBoard/context.ts +54 -0
  198. package/src/components/KanbanBoard/index.ts +5 -0
  199. package/src/components/index.ts +1 -2
  200. package/src/containers/KanbanContainer/KanbanContainer.stories.tsx +277 -0
  201. package/src/containers/KanbanContainer/KanbanContainer.tsx +178 -0
  202. package/src/containers/KanbanContainer/index.ts +5 -0
  203. package/src/containers/KanbanSettings/KanbanSettings.tsx +94 -0
  204. package/src/containers/KanbanSettings/index.ts +5 -0
  205. package/src/containers/index.ts +8 -0
  206. package/src/hooks/index.ts +10 -0
  207. package/src/hooks/useEchoChangeCallback.ts +30 -0
  208. package/src/hooks/useItemsProjection.ts +44 -0
  209. package/src/hooks/useKanbanBoardModel.browser.test.ts +235 -0
  210. package/src/hooks/useKanbanBoardModel.ts +157 -0
  211. package/src/hooks/useKanbanColumnEventHandler.ts +106 -0
  212. package/src/hooks/useKanbanItemEventHandler.ts +133 -0
  213. package/src/hooks/useProjectionModel.ts +58 -0
  214. package/src/index.ts +3 -2
  215. package/src/meta.ts +9 -7
  216. package/src/operations/delete-card-field.ts +47 -0
  217. package/src/operations/delete-card.ts +23 -0
  218. package/src/operations/index.ts +10 -0
  219. package/src/operations/restore-card-field.ts +41 -0
  220. package/src/operations/restore-card.ts +21 -0
  221. package/src/playwright/board-manager.ts +13 -0
  222. package/src/playwright/playwright.config.ts +19 -0
  223. package/src/playwright/smoke.spec.ts +107 -0
  224. package/src/plugin.ts +11 -0
  225. package/src/testing/KanbanCardTileSimple.tsx +82 -0
  226. package/src/testing/index.ts +5 -0
  227. package/src/translations.ts +28 -20
  228. package/src/types/Kanban.ts +151 -0
  229. package/src/types/KanbanOperation.ts +67 -0
  230. package/src/types/constants.ts +9 -0
  231. package/src/types/index.ts +4 -0
  232. package/src/types/migrations.test.ts +83 -0
  233. package/src/types/schema.ts +33 -45
  234. package/src/types/types.ts +35 -0
  235. package/src/util/arrangement.test.ts +218 -0
  236. package/src/util/arrangement.ts +177 -0
  237. package/src/util/index.ts +5 -0
  238. package/dist/lib/browser/blueprint-definition-GFG7LX2C.mjs +0 -28
  239. package/dist/lib/browser/blueprint-definition-GFG7LX2C.mjs.map +0 -7
  240. package/dist/lib/browser/chunk-NCNNL74W.mjs +0 -82
  241. package/dist/lib/browser/chunk-NCNNL74W.mjs.map +0 -7
  242. package/dist/lib/browser/index.mjs +0 -106
  243. package/dist/lib/browser/index.mjs.map +0 -7
  244. package/dist/lib/browser/intent-resolver-KHOAFPNN.mjs +0 -111
  245. package/dist/lib/browser/intent-resolver-KHOAFPNN.mjs.map +0 -7
  246. package/dist/lib/browser/meta.json +0 -1
  247. package/dist/lib/browser/react-surface-UBRYXDKS.mjs +0 -253
  248. package/dist/lib/browser/react-surface-UBRYXDKS.mjs.map +0 -7
  249. package/dist/lib/browser/types/index.mjs +0 -11
  250. package/dist/lib/node-esm/blueprint-definition-MIMDXMUM.mjs +0 -30
  251. package/dist/lib/node-esm/blueprint-definition-MIMDXMUM.mjs.map +0 -7
  252. package/dist/lib/node-esm/chunk-5B3LKGA7.mjs +0 -84
  253. package/dist/lib/node-esm/chunk-5B3LKGA7.mjs.map +0 -7
  254. package/dist/lib/node-esm/index.mjs +0 -107
  255. package/dist/lib/node-esm/index.mjs.map +0 -7
  256. package/dist/lib/node-esm/intent-resolver-MKO5HVES.mjs +0 -112
  257. package/dist/lib/node-esm/intent-resolver-MKO5HVES.mjs.map +0 -7
  258. package/dist/lib/node-esm/meta.json +0 -1
  259. package/dist/lib/node-esm/react-surface-B2FPS52G.mjs +0 -254
  260. package/dist/lib/node-esm/react-surface-B2FPS52G.mjs.map +0 -7
  261. package/dist/lib/node-esm/types/index.mjs +0 -12
  262. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  263. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  264. package/dist/types/src/components/KanbanContainer.d.ts +0 -7
  265. package/dist/types/src/components/KanbanContainer.d.ts.map +0 -1
  266. package/dist/types/src/components/KanbanContainer.stories.d.ts +0 -41
  267. package/dist/types/src/components/KanbanContainer.stories.d.ts.map +0 -1
  268. package/dist/types/src/components/KanbanViewEditor.d.ts +0 -8
  269. package/dist/types/src/components/KanbanViewEditor.d.ts.map +0 -1
  270. package/src/capabilities/intent-resolver.ts +0 -71
  271. package/src/components/KanbanContainer.stories.tsx +0 -190
  272. package/src/components/KanbanContainer.tsx +0 -95
  273. package/src/components/KanbanViewEditor.tsx +0 -62
  274. /package/dist/lib/{browser/types → neutral/blueprints}/index.mjs.map +0 -0
  275. /package/dist/lib/{node-esm/types/index.mjs.map → neutral/chunk-J5LGTIGS.mjs.map} +0 -0
@@ -0,0 +1,101 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { forwardRef, useCallback, useMemo, useState } from 'react';
6
+
7
+ import { Surface } from '@dxos/app-framework/ui';
8
+ import { AppSurface, useObjectMenuItems } from '@dxos/app-toolkit/ui';
9
+ import { Obj } from '@dxos/echo';
10
+ import { Card, Toolbar, useTranslation } from '@dxos/react-ui';
11
+ import { Menu, createMenuAction } from '@dxos/react-ui-menu';
12
+ import { Focus, Mosaic, useBoard } from '@dxos/react-ui-mosaic';
13
+
14
+ import { meta } from '#meta';
15
+
16
+ import { type KanbanCardProps, useKanbanBoard } from './context';
17
+
18
+ export { type KanbanCardProps };
19
+
20
+ const KANBAN_CARD_TILE_NAME = 'KanbanBoard.Card';
21
+
22
+ /**
23
+ * Mosaic Tile for Kanban card.
24
+ * Uses Surface for content; requires plugin manager context.
25
+ */
26
+ export const KanbanCard = forwardRef<HTMLDivElement, KanbanCardProps>(
27
+ ({ data, location, debug, draggable }, forwardedRef) => {
28
+ const { t } = useTranslation(meta.id);
29
+ const { model } = useBoard(KANBAN_CARD_TILE_NAME);
30
+ const { projection, columnFieldPath, onCardRemove } = useKanbanBoard(KANBAN_CARD_TILE_NAME);
31
+ const [dragHandle, setDragHandle] = useState<HTMLButtonElement | null>(null);
32
+ const dragHandleRef = useCallback((el: HTMLButtonElement | null) => setDragHandle(el), []);
33
+
34
+ const objectMenuItems = useObjectMenuItems(data);
35
+
36
+ const menuItems = useMemo(
37
+ () => [
38
+ ...objectMenuItems,
39
+ ...(onCardRemove
40
+ ? [
41
+ createMenuAction('remove', () => onCardRemove(data), {
42
+ label: t('remove-card.label'),
43
+ icon: 'ph--trash--regular',
44
+ }),
45
+ ]
46
+ : []),
47
+ ],
48
+ [objectMenuItems, onCardRemove, data, t],
49
+ );
50
+
51
+ return (
52
+ <Menu.Root>
53
+ <Mosaic.Tile
54
+ asChild
55
+ id={model.getItemId(data)}
56
+ data={data}
57
+ location={location}
58
+ debug={debug}
59
+ draggable={draggable}
60
+ dragHandle={dragHandle}
61
+ >
62
+ <Focus.Item asChild>
63
+ <Card.Root ref={forwardedRef} data-testid='board-item'>
64
+ <Card.Toolbar>
65
+ <Card.DragHandle ref={dragHandleRef} testId='mosaicBoard.cardDragHandle' />
66
+ <Card.Title data-testid='mosaicBoard.cardTitle'>{Obj.getLabel(data)}</Card.Title>
67
+ {/* TODO(wittjosiah): Reconcile with Card.Menu. */}
68
+ <Menu.Trigger asChild disabled={!menuItems?.length}>
69
+ <Toolbar.IconButton
70
+ iconOnly
71
+ variant='ghost'
72
+ icon='ph--dots-three-vertical--regular'
73
+ label={t('action-menu.label')}
74
+ />
75
+ </Menu.Trigger>
76
+ <Menu.Content items={menuItems} />
77
+ </Card.Toolbar>
78
+ <Card.Content>
79
+ {projection && (
80
+ <Surface.Surface
81
+ type={AppSurface.Card}
82
+ limit={1}
83
+ data={{
84
+ subject: data,
85
+ projection,
86
+ // Hide the pivot field: its value is already conveyed by
87
+ // which column the card sits in.
88
+ ignorePaths: columnFieldPath ? [columnFieldPath] : undefined,
89
+ }}
90
+ />
91
+ )}
92
+ </Card.Content>
93
+ </Card.Root>
94
+ </Focus.Item>
95
+ </Mosaic.Tile>
96
+ </Menu.Root>
97
+ );
98
+ },
99
+ );
100
+
101
+ KanbanCard.displayName = KANBAN_CARD_TILE_NAME;
@@ -0,0 +1,72 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { FC, forwardRef, useState } from 'react';
6
+
7
+ import type { Obj } from '@dxos/echo';
8
+ import { Board, type MosaicTileProps, useBoard } from '@dxos/react-ui-mosaic';
9
+
10
+ import { useKanbanItemEventHandler } from '#hooks';
11
+ import { type ColumnStructure, UNCATEGORIZED_VALUE } from '#types';
12
+
13
+ import { type KanbanColumnProps, useKanbanBoard } from './context';
14
+
15
+ export { type KanbanColumnProps };
16
+
17
+ const KANBAN_COLUMN_NAME = 'KanbanBoard.Column';
18
+
19
+ /**
20
+ * Mosaic Tile for Kanban column.
21
+ */
22
+ export const KanbanColumn = forwardRef<HTMLDivElement, KanbanColumnProps>(
23
+ ({ data: column, location, debug, draggable }, forwardedRef) => {
24
+ const { model } = useBoard<ColumnStructure, Obj.Unknown>(KANBAN_COLUMN_NAME);
25
+ const { columnFieldPath, change, onCardAdd, getPivotAttributes, itemTile } = useKanbanBoard(KANBAN_COLUMN_NAME);
26
+
27
+ const { title } = getPivotAttributes(column.columnValue);
28
+ const uncategorized = column.columnValue === UNCATEGORIZED_VALUE;
29
+ const [dragHandle, setDragHandle] = useState<HTMLButtonElement | null>(null);
30
+
31
+ const eventHandler = useKanbanItemEventHandler({
32
+ column,
33
+ columnFieldPath,
34
+ model,
35
+ change,
36
+ });
37
+
38
+ return (
39
+ <Board.Column.Root
40
+ data={column}
41
+ location={location}
42
+ classNames='grid grid-rows-[var(--dx-rail-action)_1fr_var(--dx-rail-action)]'
43
+ debug={debug}
44
+ draggable={draggable}
45
+ dragHandle={dragHandle}
46
+ ref={forwardedRef}
47
+ >
48
+ {uncategorized ? (
49
+ <div className='border-b border-separator p-2' data-testid='board-column-header'>
50
+ <span className='font-medium'>{title}</span>
51
+ </div>
52
+ ) : (
53
+ <Board.Column.Header label={title} dragHandleRef={setDragHandle} />
54
+ )}
55
+ <Board.Column.Body
56
+ data={column}
57
+ eventHandler={eventHandler}
58
+ Tile={itemTile as FC<MosaicTileProps<Obj.Unknown>>}
59
+ />
60
+ <Board.Column.Footer
61
+ onAdd={
62
+ onCardAdd
63
+ ? () => onCardAdd(column.columnValue === UNCATEGORIZED_VALUE ? undefined : column.columnValue)
64
+ : undefined
65
+ }
66
+ />
67
+ </Board.Column.Root>
68
+ );
69
+ },
70
+ );
71
+
72
+ KanbanColumn.displayName = KANBAN_COLUMN_NAME;
@@ -0,0 +1,54 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { createContext } from '@radix-ui/react-context';
6
+ import { type ComponentType } from 'react';
7
+
8
+ import { type Obj } from '@dxos/echo';
9
+ import { type MosaicTileProps } from '@dxos/react-ui-mosaic';
10
+ import { type ProjectionModel } from '@dxos/schema';
11
+
12
+ import { type ColumnStructure, type KanbanChangeCallback, UNCATEGORIZED_ATTRIBUTES, UNCATEGORIZED_VALUE } from '#types';
13
+
14
+ const KANBAN_BOARD_NAME = 'KanbanBoard.Context';
15
+
16
+ /**
17
+ * Card tile props. Defined here (rather than in `KanbanCard.tsx`) so the
18
+ * context type can reference it without forming a cycle through that
19
+ * module's runtime exports — webkit treats the resulting TDZ as a hard
20
+ * error while other engines are lenient.
21
+ */
22
+ export type KanbanCardProps = Pick<MosaicTileProps<Obj.Unknown>, 'location' | 'data' | 'debug' | 'draggable'>;
23
+
24
+ /**
25
+ * Column tile props. See note on {@link KanbanCardProps}.
26
+ */
27
+ export type KanbanColumnProps = Pick<MosaicTileProps<ColumnStructure>, 'location' | 'data' | 'debug' | 'draggable'>;
28
+
29
+ /**
30
+ * Context value for the Kanban board.
31
+ * Items are Echo objects (Obj.Unknown).
32
+ */
33
+ export type KanbanBoardContextValue = {
34
+ kanbanId: string;
35
+ projection: ProjectionModel | undefined;
36
+ columnFieldPath: string | undefined;
37
+ change: KanbanChangeCallback<Obj.Unknown>;
38
+ pivotFieldId: string | undefined;
39
+ getPivotAttributes: (columnValue: string) => { title: string; color: string };
40
+ itemTile?: ComponentType<KanbanCardProps>; // TODO(burdon): Prop.
41
+ onCardAdd?: (columnValue: string | undefined) => string | undefined;
42
+ onCardRemove?: (card: Obj.Unknown) => void;
43
+ };
44
+
45
+ export const [KanbanBoardContext, useKanbanBoard] = createContext<KanbanBoardContextValue>(KANBAN_BOARD_NAME, {
46
+ kanbanId: 'never',
47
+ projection: undefined,
48
+ columnFieldPath: undefined,
49
+ change: { kanban: () => {}, setItemField: () => {} },
50
+ pivotFieldId: undefined,
51
+ getPivotAttributes: (id: string) =>
52
+ id === UNCATEGORIZED_VALUE ? UNCATEGORIZED_ATTRIBUTES : { title: id, color: 'neutral' },
53
+ itemTile: (() => null) as ComponentType<KanbanCardProps>,
54
+ });
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './KanbanBoard';
@@ -2,5 +2,4 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export * from './KanbanContainer';
6
- export * from './KanbanViewEditor';
5
+ export * from './KanbanBoard';
@@ -0,0 +1,277 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { RegistryContext } from '@effect-atom/atom-react';
6
+ import { type Decorator, type Meta, type StoryObj } from '@storybook/react-vite';
7
+ import * as Effect from 'effect/Effect';
8
+ import React, { useCallback, useContext, useMemo } from 'react';
9
+ import { expect, waitFor, within } from 'storybook/test';
10
+
11
+ import { withPluginManager } from '@dxos/app-framework/testing';
12
+ import { Surface } from '@dxos/app-framework/ui';
13
+ import { AppSurface } from '@dxos/app-toolkit/ui';
14
+ import { Obj, type QueryAST, Type } from '@dxos/echo';
15
+ import { View } from '@dxos/echo';
16
+ import { type Mutable } from '@dxos/echo/internal';
17
+ import { invariant } from '@dxos/invariant';
18
+ // `/plugin` entrypoints used here for the same reason as `corePlugins()` —
19
+ // see `@dxos/plugin-testing/src/core.ts` for the rationale.
20
+ import { ClientPlugin } from '@dxos/plugin-client/testing';
21
+ import { initializeIdentity } from '@dxos/plugin-client/testing';
22
+ import { PreviewPlugin } from '@dxos/plugin-preview/testing';
23
+ import { SpacePlugin } from '@dxos/plugin-space/testing';
24
+ import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
25
+ import { random } from '@dxos/random';
26
+ import { Filter, type Space, useQuery, useSchema, useSpaces } from '@dxos/react-client/echo';
27
+ import { ViewEditor } from '@dxos/react-ui-form';
28
+ import { Syntax } from '@dxos/react-ui-syntax-highlighter';
29
+ import { withLayout } from '@dxos/react-ui/testing';
30
+ import { ViewModel, getTypenameFromQuery } from '@dxos/schema';
31
+ // TODO(wittjosiah): Replace with echo/testing.
32
+ import { Organization, Person } from '@dxos/types';
33
+
34
+ import { useProjectionModel } from '#hooks';
35
+ import { translations } from '#translations';
36
+ import { Kanban } from '#types';
37
+
38
+ import { KanbanPlugin } from '../../KanbanPlugin';
39
+
40
+ random.seed(0);
41
+
42
+ const createOrg = (status?: Organization.Organization['status']) => ({
43
+ name: random.commerce.productName(),
44
+ description: random.lorem.paragraph(),
45
+ image: random.image.url(),
46
+ website: random.internet.url(),
47
+ status: (status ?? random.helpers.arrayElement(Organization.StatusOptions).id) as Organization.Organization['status'],
48
+ });
49
+
50
+ //
51
+ // Story setup helpers.
52
+ //
53
+
54
+ type ClientSetupOptions = {
55
+ types?: Type.AnyEntity[];
56
+ onSpaceCreated?: (space: Space) => Promise<void>;
57
+ };
58
+
59
+ /**
60
+ * Creates the standard plugin manager decorator with client configuration.
61
+ * Includes KanbanPlugin so the Surface resolves to KanbanContainer.
62
+ */
63
+ const withKanbanPlugins = ({ types = [], onSpaceCreated }: ClientSetupOptions): Decorator =>
64
+ withPluginManager({
65
+ plugins: [
66
+ ...corePlugins(),
67
+ ClientPlugin({
68
+ types: [...types, View.View, Kanban.Kanban],
69
+ onClientInitialized: ({ client }) =>
70
+ Effect.gen(function* () {
71
+ yield* initializeIdentity(client);
72
+ const space = yield* Effect.promise(() => client.spaces.create());
73
+ yield* Effect.promise(() => space.waitUntilReady());
74
+ yield* Effect.promise(() => onSpaceCreated?.(space) ?? Promise.resolve());
75
+ }),
76
+ }),
77
+ PreviewPlugin(),
78
+ SpacePlugin({}),
79
+ StorybookPlugin({}),
80
+ KanbanPlugin(),
81
+ ],
82
+ });
83
+
84
+ /**
85
+ * Renders the first Kanban in the space via Surface (resolves to KanbanContainer),
86
+ * with a sidebar containing ViewEditor and Json filter.
87
+ */
88
+ const DefaultComponent = () => {
89
+ const registry = useContext(RegistryContext);
90
+ const spaces = useSpaces();
91
+ const space = spaces[spaces.length - 1];
92
+ const [kanban] = useQuery(space?.db, Filter.type(Kanban.Kanban));
93
+ const viewRef = kanban && kanban.spec.kind === 'view' ? kanban.spec.view : undefined;
94
+ const view = viewRef?.target;
95
+ const typename = view?.query ? getTypenameFromQuery(view.query.ast) : undefined;
96
+ const schema = useSchema(space?.db, typename);
97
+ const projection = useProjectionModel(schema, kanban, registry);
98
+
99
+ const data = useMemo(() => (kanban ? { subject: kanban, attendableId: 'story' } : undefined), [kanban]);
100
+
101
+ const handleUpdateQuery = useCallback(
102
+ (newQuery: QueryAST.Query) => {
103
+ invariant(schema);
104
+ invariant(view);
105
+ if (Type.isMutable(schema)) {
106
+ schema.updateTypename(getTypenameFromQuery(newQuery));
107
+ }
108
+ Obj.update(view, (view) => {
109
+ view.query.ast = newQuery as Mutable<QueryAST.Query>;
110
+ });
111
+ },
112
+ [view, schema],
113
+ );
114
+
115
+ const handleDeleteField = useCallback(
116
+ (fieldId: string) => {
117
+ if (schema && Type.isMutable(schema) && projection) {
118
+ projection.deleteFieldProjection(fieldId);
119
+ }
120
+ },
121
+ [schema, projection],
122
+ );
123
+
124
+ if (!schema || !view) {
125
+ return null;
126
+ }
127
+
128
+ return (
129
+ <div className='grow grid grid-cols-[1fr_350px] overflow-hidden h-full w-full'>
130
+ <Surface.Surface type={AppSurface.Article} data={data} limit={1} />
131
+ <div className='flex flex-col h-full overflow-hidden border-l border-separator'>
132
+ <ViewEditor
133
+ registry={space?.db.schemaRegistry}
134
+ schema={schema}
135
+ view={view}
136
+ onQueryChanged={handleUpdateQuery}
137
+ onDelete={schema && Type.isMutable(schema) ? handleDeleteField : undefined}
138
+ />
139
+ <Syntax.Root data={{ view, schema }}>
140
+ <Syntax.Content>
141
+ <Syntax.Filter />
142
+ <Syntax.Viewport>
143
+ <Syntax.Code classNames='text-xs' />
144
+ </Syntax.Viewport>
145
+ </Syntax.Content>
146
+ </Syntax.Root>
147
+ </div>
148
+ </div>
149
+ );
150
+ };
151
+
152
+ //
153
+ // Story definitions.
154
+ //
155
+
156
+ const meta = {
157
+ title: 'plugins/plugin-kanban/containers/Kanban',
158
+ component: DefaultComponent,
159
+ render: () => <DefaultComponent />,
160
+ decorators: [withLayout({ layout: 'fullscreen' })],
161
+ parameters: {
162
+ layout: 'fullscreen',
163
+ translations,
164
+ },
165
+ } satisfies Meta<typeof DefaultComponent>;
166
+
167
+ export default meta;
168
+
169
+ type Story = StoryObj<typeof meta>;
170
+
171
+ /**
172
+ * Default story using static runtime schema (immutable).
173
+ * Schema mutations are not allowed.
174
+ */
175
+ export const Default: Story = {
176
+ decorators: [
177
+ withKanbanPlugins({
178
+ types: [Organization.Organization, Person.Person],
179
+ onSpaceCreated: async (space) => {
180
+ const { view } = await ViewModel.makeFromDatabase({
181
+ db: space.db,
182
+ typename: Organization.Organization.typename,
183
+ pivotFieldName: 'status',
184
+ });
185
+ const kanban = Kanban.make({ view });
186
+ space.db.add(kanban);
187
+
188
+ Array.from({ length: 10 }).map(() => {
189
+ return space.db.add(Obj.make(Organization.Organization, createOrg()));
190
+ });
191
+ },
192
+ }),
193
+ ],
194
+ play: async ({ canvasElement }) => {
195
+ const canvas = within(canvasElement);
196
+
197
+ // Wait for the kanban columns to render by finding the status tags.
198
+ // Organization.StatusOptions: prospect, qualified, active, commit, reject.
199
+ const activeTag = await canvas.findByText('Active', undefined, { timeout: 12_000 });
200
+ const prospectTag = await canvas.findByText('Prospect', undefined, { timeout: 12_000 });
201
+ const commitTag = await canvas.findByText('Commit', undefined, { timeout: 12_000 });
202
+
203
+ // Verify all expected columns are rendered.
204
+ await expect(activeTag).toBeTruthy();
205
+ await expect(prospectTag).toBeTruthy();
206
+ await expect(commitTag).toBeTruthy();
207
+
208
+ // Find the column containers (Board uses data-testid="board-column").
209
+ const activeColumn = activeTag.closest('[data-testid="board-column"]') as HTMLElement;
210
+ const prospectColumn = prospectTag.closest('[data-testid="board-column"]') as HTMLElement;
211
+ await expect(activeColumn).toBeTruthy();
212
+ await expect(prospectColumn).toBeTruthy();
213
+
214
+ // Wait for cards to render in the columns (Board items use data-testid="board-item").
215
+ const getColumnCards = (column: HTMLElement) =>
216
+ Array.from(column.querySelectorAll('[data-testid="board-item"]')) as HTMLElement[];
217
+
218
+ await waitFor(() => expect(getColumnCards(activeColumn).length).toBeGreaterThan(0));
219
+
220
+ // Verify cards are distributed across columns.
221
+ const activeCards = getColumnCards(activeColumn);
222
+ const prospectCards = getColumnCards(prospectColumn);
223
+ await expect(activeCards.length).toBeGreaterThan(0);
224
+ await expect(prospectCards.length).toBeGreaterThan(0);
225
+
226
+ // Verify cards have drag handles (Card.Toolbar includes drag handle).
227
+ const firstActiveCard = activeCards[0];
228
+ const buttons = firstActiveCard.querySelectorAll('button');
229
+ await expect(buttons.length).toBeGreaterThan(0);
230
+
231
+ // Verify add-card action exists in columns (optional footer).
232
+ const activeAddItem = activeColumn.querySelector('[data-testid="board-column-add-item"]');
233
+ const prospectAddItem = prospectColumn.querySelector('[data-testid="board-column-add-item"]');
234
+ await expect(activeAddItem).toBeTruthy();
235
+ await expect(prospectAddItem).toBeTruthy();
236
+
237
+ // TODO(wittjosiah): Get drag & drop tests working.
238
+ // See packages/apps/composer-app/src/playwright/stack.spec.ts for reference.
239
+ },
240
+ };
241
+
242
+ /**
243
+ * Story variant that uses a mutable database schema (EchoSchema).
244
+ * This allows testing schema mutations like adding/removing fields.
245
+ */
246
+ // TODO(wittjosiah): Card previews (e.g., OrganizationCard) are type-specific and hard-coded.
247
+ // They don't use the projection to determine which fields to display, so deleting a field
248
+ // from the schema won't remove it from the card preview. To fix this, the type-specific
249
+ // cards in PreviewPlugin would need to accept and respect the projection prop.
250
+ export const MutableSchema: Story = {
251
+ decorators: [
252
+ withKanbanPlugins({
253
+ onSpaceCreated: async (space) => {
254
+ // Register schema in the database to make it mutable (EchoSchema).
255
+ const [schema] = await space.db.schemaRegistry.register([Organization.Organization]);
256
+
257
+ const { view } = await ViewModel.makeFromDatabase({
258
+ db: space.db,
259
+ typename: schema.typename,
260
+ pivotFieldName: 'status',
261
+ });
262
+ const kanban = Kanban.make({ view });
263
+ space.db.add(kanban);
264
+
265
+ // Create test data using the registered schema.
266
+ const requiredOrgs = [
267
+ ...Array.from({ length: 2 }, () => createOrg('prospect')),
268
+ ...Array.from({ length: 5 }, () => createOrg('qualified')),
269
+ ...Array.from({ length: 1 }, () => createOrg('active')),
270
+ ...Array.from({ length: 1 }, () => createOrg('commit')),
271
+ ...Array.from({ length: 1 }, () => createOrg('reject')),
272
+ ];
273
+ requiredOrgs.forEach((org) => space.db.add(Obj.make(schema, org)));
274
+ },
275
+ }),
276
+ ],
277
+ };