@dxos/plugin-kanban 0.8.4-main.ae835ea → 0.8.4-main.bc2380dfbc

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 (282) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/neutral/KanbanArticle-NEJ3LNBO.mjs +132 -0
  4. package/dist/lib/neutral/KanbanArticle-NEJ3LNBO.mjs.map +7 -0
  5. package/dist/lib/neutral/KanbanPlugin.mjs +36 -0
  6. package/dist/lib/neutral/KanbanPlugin.mjs.map +7 -0
  7. package/dist/lib/neutral/KanbanPlugin.node.mjs +27 -0
  8. package/dist/lib/neutral/KanbanPlugin.node.mjs.map +7 -0
  9. package/dist/lib/neutral/KanbanPlugin.workerd.mjs +21 -0
  10. package/dist/lib/neutral/KanbanPlugin.workerd.mjs.map +7 -0
  11. package/dist/lib/neutral/KanbanSettings-G6M47NSK.mjs +83 -0
  12. package/dist/lib/neutral/KanbanSettings-G6M47NSK.mjs.map +7 -0
  13. package/dist/lib/neutral/blueprint-definition-FHVIEKTQ.mjs +15 -0
  14. package/dist/lib/neutral/blueprint-definition-FHVIEKTQ.mjs.map +7 -0
  15. package/dist/lib/neutral/blueprints/index.mjs +8 -0
  16. package/dist/lib/neutral/capabilities/index.mjs +19 -0
  17. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  18. package/dist/lib/neutral/chunk-6FPBLOS3.mjs +8 -0
  19. package/dist/lib/neutral/chunk-6FPBLOS3.mjs.map +7 -0
  20. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  21. package/dist/lib/neutral/chunk-KDPM77BD.mjs +21 -0
  22. package/dist/lib/neutral/chunk-KDPM77BD.mjs.map +7 -0
  23. package/dist/lib/neutral/chunk-OQ72EEGU.mjs +254 -0
  24. package/dist/lib/neutral/chunk-OQ72EEGU.mjs.map +7 -0
  25. package/dist/lib/neutral/chunk-ZTQW5KQS.mjs +26 -0
  26. package/dist/lib/neutral/chunk-ZTQW5KQS.mjs.map +7 -0
  27. package/dist/lib/neutral/components/index.mjs +243 -0
  28. package/dist/lib/neutral/components/index.mjs.map +7 -0
  29. package/dist/lib/neutral/containers/index.mjs +11 -0
  30. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  31. package/dist/lib/neutral/create-object-DKBSI46K.mjs +40 -0
  32. package/dist/lib/neutral/create-object-DKBSI46K.mjs.map +7 -0
  33. package/dist/lib/neutral/delete-card-356CBACE.mjs +24 -0
  34. package/dist/lib/neutral/delete-card-356CBACE.mjs.map +7 -0
  35. package/dist/lib/neutral/delete-card-field-IRCZL2BR.mjs +42 -0
  36. package/dist/lib/neutral/delete-card-field-IRCZL2BR.mjs.map +7 -0
  37. package/dist/lib/neutral/hooks/index.mjs +432 -0
  38. package/dist/lib/neutral/hooks/index.mjs.map +7 -0
  39. package/dist/lib/neutral/index.mjs +34 -0
  40. package/dist/lib/neutral/index.mjs.map +7 -0
  41. package/dist/lib/neutral/meta.json +1 -0
  42. package/dist/lib/neutral/meta.mjs +8 -0
  43. package/dist/lib/neutral/meta.mjs.map +7 -0
  44. package/dist/lib/neutral/migrations-IWBT35UT.mjs +31 -0
  45. package/dist/lib/neutral/migrations-IWBT35UT.mjs.map +7 -0
  46. package/dist/lib/neutral/operation-handler-B7IW6MXU.mjs +13 -0
  47. package/dist/lib/neutral/operation-handler-B7IW6MXU.mjs.map +7 -0
  48. package/dist/lib/neutral/operations/index.mjs +8 -0
  49. package/dist/lib/neutral/operations/index.mjs.map +7 -0
  50. package/dist/lib/neutral/plugin.mjs +16 -0
  51. package/dist/lib/neutral/plugin.mjs.map +7 -0
  52. package/dist/lib/neutral/react-surface-QROEHBRW.mjs +93 -0
  53. package/dist/lib/neutral/react-surface-QROEHBRW.mjs.map +7 -0
  54. package/dist/lib/neutral/restore-card-P25Y4YSE.mjs +21 -0
  55. package/dist/lib/neutral/restore-card-P25Y4YSE.mjs.map +7 -0
  56. package/dist/lib/neutral/restore-card-field-NHR3R4XI.mjs +40 -0
  57. package/dist/lib/neutral/restore-card-field-NHR3R4XI.mjs.map +7 -0
  58. package/dist/lib/neutral/testing/index.mjs +60 -0
  59. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  60. package/dist/lib/neutral/translations.mjs +44 -0
  61. package/dist/lib/neutral/translations.mjs.map +7 -0
  62. package/dist/lib/neutral/types/index.mjs +22 -0
  63. package/dist/lib/neutral/types/index.mjs.map +7 -0
  64. package/dist/lib/neutral/undo-mappings-6CHW6BOF.mjs +42 -0
  65. package/dist/lib/neutral/undo-mappings-6CHW6BOF.mjs.map +7 -0
  66. package/dist/types/src/KanbanPlugin.d.ts +3 -1
  67. package/dist/types/src/KanbanPlugin.d.ts.map +1 -1
  68. package/dist/types/src/KanbanPlugin.node.d.ts +4 -0
  69. package/dist/types/src/KanbanPlugin.node.d.ts.map +1 -0
  70. package/dist/types/src/KanbanPlugin.test.d.ts +2 -0
  71. package/dist/types/src/KanbanPlugin.test.d.ts.map +1 -0
  72. package/dist/types/src/KanbanPlugin.workerd.d.ts +4 -0
  73. package/dist/types/src/KanbanPlugin.workerd.d.ts.map +1 -0
  74. package/dist/types/src/blueprints/index.d.ts +2 -0
  75. package/dist/types/src/blueprints/index.d.ts.map +1 -0
  76. package/dist/types/src/blueprints/kanban-blueprint.d.ts +4 -0
  77. package/dist/types/src/blueprints/kanban-blueprint.d.ts.map +1 -0
  78. package/dist/types/src/capabilities/artifact-definition.d.ts +3 -2
  79. package/dist/types/src/capabilities/artifact-definition.d.ts.map +1 -1
  80. package/dist/types/src/capabilities/blueprint-definition.d.ts +5 -4
  81. package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -1
  82. package/dist/types/src/capabilities/create-object.d.ts +11 -0
  83. package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
  84. package/dist/types/src/capabilities/index.d.ts +13 -3
  85. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  86. package/dist/types/src/capabilities/migrations.d.ts +6 -0
  87. package/dist/types/src/capabilities/migrations.d.ts.map +1 -0
  88. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  89. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  90. package/dist/types/src/capabilities/react-surface.d.ts +3 -2
  91. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  92. package/dist/types/src/capabilities/undo-mappings.d.ts +5 -0
  93. package/dist/types/src/capabilities/undo-mappings.d.ts.map +1 -0
  94. package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts +37 -0
  95. package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts.map +1 -0
  96. package/dist/types/src/components/KanbanBoard/KanbanBoard.stories.d.ts +72 -0
  97. package/dist/types/src/components/KanbanBoard/KanbanBoard.stories.d.ts.map +1 -0
  98. package/dist/types/src/components/KanbanBoard/KanbanCard.d.ts +9 -0
  99. package/dist/types/src/components/KanbanBoard/KanbanCard.d.ts.map +1 -0
  100. package/dist/types/src/components/KanbanBoard/KanbanColumn.d.ts +8 -0
  101. package/dist/types/src/components/KanbanBoard/KanbanColumn.d.ts.map +1 -0
  102. package/dist/types/src/components/KanbanBoard/context.d.ts +38 -0
  103. package/dist/types/src/components/KanbanBoard/context.d.ts.map +1 -0
  104. package/dist/types/src/components/KanbanBoard/index.d.ts +2 -0
  105. package/dist/types/src/components/KanbanBoard/index.d.ts.map +1 -0
  106. package/dist/types/src/components/index.d.ts +1 -2
  107. package/dist/types/src/components/index.d.ts.map +1 -1
  108. package/dist/types/src/containers/KanbanArticle/KanbanArticle.d.ts +6 -0
  109. package/dist/types/src/containers/KanbanArticle/KanbanArticle.d.ts.map +1 -0
  110. package/dist/types/src/containers/KanbanArticle/KanbanArticle.stories.d.ts +79 -0
  111. package/dist/types/src/containers/KanbanArticle/KanbanArticle.stories.d.ts.map +1 -0
  112. package/dist/types/src/containers/KanbanArticle/index.d.ts +2 -0
  113. package/dist/types/src/containers/KanbanArticle/index.d.ts.map +1 -0
  114. package/dist/types/src/containers/KanbanSettings/KanbanSettings.d.ts +13 -0
  115. package/dist/types/src/containers/KanbanSettings/KanbanSettings.d.ts.map +1 -0
  116. package/dist/types/src/containers/KanbanSettings/index.d.ts +2 -0
  117. package/dist/types/src/containers/KanbanSettings/index.d.ts.map +1 -0
  118. package/dist/types/src/containers/index.d.ts +4 -0
  119. package/dist/types/src/containers/index.d.ts.map +1 -0
  120. package/dist/types/src/hooks/index.d.ts +7 -0
  121. package/dist/types/src/hooks/index.d.ts.map +1 -0
  122. package/dist/types/src/hooks/useEchoChangeCallback.d.ts +13 -0
  123. package/dist/types/src/hooks/useEchoChangeCallback.d.ts.map +1 -0
  124. package/dist/types/src/hooks/useItemsProjection.d.ts +10 -0
  125. package/dist/types/src/hooks/useItemsProjection.d.ts.map +1 -0
  126. package/dist/types/src/hooks/useKanbanBoardModel.browser.test.d.ts +2 -0
  127. package/dist/types/src/hooks/useKanbanBoardModel.browser.test.d.ts.map +1 -0
  128. package/dist/types/src/hooks/useKanbanBoardModel.d.ts +16 -0
  129. package/dist/types/src/hooks/useKanbanBoardModel.d.ts.map +1 -0
  130. package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts +22 -0
  131. package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts.map +1 -0
  132. package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts +19 -0
  133. package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts.map +1 -0
  134. package/dist/types/src/hooks/useProjectionModel.d.ts +15 -0
  135. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
  136. package/dist/types/src/index.d.ts +3 -1
  137. package/dist/types/src/index.d.ts.map +1 -1
  138. package/dist/types/src/meta.d.ts +2 -2
  139. package/dist/types/src/meta.d.ts.map +1 -1
  140. package/dist/types/src/operations/delete-card-field.d.ts +5 -0
  141. package/dist/types/src/operations/delete-card-field.d.ts.map +1 -0
  142. package/dist/types/src/operations/delete-card.d.ts +5 -0
  143. package/dist/types/src/operations/delete-card.d.ts.map +1 -0
  144. package/dist/types/src/operations/index.d.ts +3 -0
  145. package/dist/types/src/operations/index.d.ts.map +1 -0
  146. package/dist/types/src/operations/restore-card-field.d.ts +5 -0
  147. package/dist/types/src/operations/restore-card-field.d.ts.map +1 -0
  148. package/dist/types/src/operations/restore-card.d.ts +5 -0
  149. package/dist/types/src/operations/restore-card.d.ts.map +1 -0
  150. package/dist/types/src/playwright/board-manager.d.ts +5 -0
  151. package/dist/types/src/playwright/board-manager.d.ts.map +1 -0
  152. package/dist/types/src/playwright/playwright.config.d.ts +3 -0
  153. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
  154. package/dist/types/src/playwright/smoke.spec.d.ts +2 -0
  155. package/dist/types/src/playwright/smoke.spec.d.ts.map +1 -0
  156. package/dist/types/src/plugin.d.ts +4 -0
  157. package/dist/types/src/plugin.d.ts.map +1 -0
  158. package/dist/types/src/testing/KanbanCardTileSimple.d.ts +7 -0
  159. package/dist/types/src/testing/KanbanCardTileSimple.d.ts.map +1 -0
  160. package/dist/types/src/testing/index.d.ts +2 -0
  161. package/dist/types/src/testing/index.d.ts.map +1 -0
  162. package/dist/types/src/translations.d.ts +50 -22
  163. package/dist/types/src/translations.d.ts.map +1 -1
  164. package/dist/types/src/types/Kanban.d.ts +108 -0
  165. package/dist/types/src/types/Kanban.d.ts.map +1 -0
  166. package/dist/types/src/types/KanbanOperation.d.ts +52 -0
  167. package/dist/types/src/types/KanbanOperation.d.ts.map +1 -0
  168. package/dist/types/src/types/constants.d.ts +6 -0
  169. package/dist/types/src/types/constants.d.ts.map +1 -0
  170. package/dist/types/src/types/index.d.ts +3 -0
  171. package/dist/types/src/types/index.d.ts.map +1 -1
  172. package/dist/types/src/types/migrations.test.d.ts +2 -0
  173. package/dist/types/src/types/migrations.test.d.ts.map +1 -0
  174. package/dist/types/src/types/schema.d.ts +17 -51
  175. package/dist/types/src/types/schema.d.ts.map +1 -1
  176. package/dist/types/src/types/types.d.ts +28 -0
  177. package/dist/types/src/types/types.d.ts.map +1 -1
  178. package/dist/types/src/util/arrangement.d.ts +72 -0
  179. package/dist/types/src/util/arrangement.d.ts.map +1 -0
  180. package/dist/types/src/util/arrangement.test.d.ts +2 -0
  181. package/dist/types/src/util/arrangement.test.d.ts.map +1 -0
  182. package/dist/types/src/util/index.d.ts +2 -0
  183. package/dist/types/src/util/index.d.ts.map +1 -0
  184. package/dist/types/tsconfig.tsbuildinfo +1 -1
  185. package/package.json +123 -57
  186. package/src/KanbanPlugin.node.ts +21 -0
  187. package/src/KanbanPlugin.test.ts +31 -0
  188. package/src/KanbanPlugin.tsx +28 -56
  189. package/src/KanbanPlugin.workerd.ts +18 -0
  190. package/src/blueprints/index.ts +5 -0
  191. package/src/blueprints/kanban-blueprint.ts +27 -0
  192. package/src/capabilities/artifact-definition.ts +116 -114
  193. package/src/capabilities/blueprint-definition.ts +13 -24
  194. package/src/capabilities/create-object.ts +40 -0
  195. package/src/capabilities/index.ts +12 -4
  196. package/src/capabilities/migrations.ts +35 -0
  197. package/src/capabilities/operation-handler.ts +14 -0
  198. package/src/capabilities/react-surface.tsx +77 -67
  199. package/src/capabilities/undo-mappings.ts +34 -0
  200. package/src/components/KanbanBoard/KanbanBoard.stories.tsx +145 -0
  201. package/src/components/KanbanBoard/KanbanBoard.tsx +164 -0
  202. package/src/components/KanbanBoard/KanbanCard.tsx +101 -0
  203. package/src/components/KanbanBoard/KanbanColumn.tsx +72 -0
  204. package/src/components/KanbanBoard/context.ts +54 -0
  205. package/src/components/KanbanBoard/index.ts +5 -0
  206. package/src/components/index.ts +1 -2
  207. package/src/containers/KanbanArticle/KanbanArticle.stories.tsx +276 -0
  208. package/src/containers/KanbanArticle/KanbanArticle.tsx +178 -0
  209. package/src/containers/KanbanArticle/index.ts +5 -0
  210. package/src/containers/KanbanSettings/KanbanSettings.tsx +94 -0
  211. package/src/containers/KanbanSettings/index.ts +5 -0
  212. package/src/containers/index.ts +8 -0
  213. package/src/hooks/index.ts +10 -0
  214. package/src/hooks/useEchoChangeCallback.ts +30 -0
  215. package/src/hooks/useItemsProjection.ts +44 -0
  216. package/src/hooks/useKanbanBoardModel.browser.test.ts +234 -0
  217. package/src/hooks/useKanbanBoardModel.ts +157 -0
  218. package/src/hooks/useKanbanColumnEventHandler.ts +106 -0
  219. package/src/hooks/useKanbanItemEventHandler.ts +133 -0
  220. package/src/hooks/useProjectionModel.ts +58 -0
  221. package/src/index.ts +3 -2
  222. package/src/meta.ts +3 -3
  223. package/src/operations/delete-card-field.ts +47 -0
  224. package/src/operations/delete-card.ts +23 -0
  225. package/src/operations/index.ts +10 -0
  226. package/src/operations/restore-card-field.ts +41 -0
  227. package/src/operations/restore-card.ts +21 -0
  228. package/src/playwright/board-manager.ts +13 -0
  229. package/src/playwright/playwright.config.ts +19 -0
  230. package/src/playwright/smoke.spec.ts +107 -0
  231. package/src/plugin.ts +11 -0
  232. package/src/testing/KanbanCardTileSimple.tsx +82 -0
  233. package/src/testing/index.ts +5 -0
  234. package/src/translations.ts +28 -20
  235. package/src/types/Kanban.ts +150 -0
  236. package/src/types/KanbanOperation.ts +67 -0
  237. package/src/types/constants.ts +9 -0
  238. package/src/types/index.ts +4 -0
  239. package/src/types/migrations.test.ts +82 -0
  240. package/src/types/schema.ts +32 -44
  241. package/src/types/types.ts +35 -0
  242. package/src/util/arrangement.test.ts +217 -0
  243. package/src/util/arrangement.ts +177 -0
  244. package/src/util/index.ts +5 -0
  245. package/dist/lib/browser/blueprint-definition-UYVX622Q.mjs +0 -28
  246. package/dist/lib/browser/blueprint-definition-UYVX622Q.mjs.map +0 -7
  247. package/dist/lib/browser/chunk-3UDST345.mjs +0 -85
  248. package/dist/lib/browser/chunk-3UDST345.mjs.map +0 -7
  249. package/dist/lib/browser/index.mjs +0 -105
  250. package/dist/lib/browser/index.mjs.map +0 -7
  251. package/dist/lib/browser/intent-resolver-VVBNS2TO.mjs +0 -111
  252. package/dist/lib/browser/intent-resolver-VVBNS2TO.mjs.map +0 -7
  253. package/dist/lib/browser/meta.json +0 -1
  254. package/dist/lib/browser/react-surface-FNXJ6VJX.mjs +0 -255
  255. package/dist/lib/browser/react-surface-FNXJ6VJX.mjs.map +0 -7
  256. package/dist/lib/browser/types/index.mjs +0 -11
  257. package/dist/lib/node-esm/blueprint-definition-42P47FUY.mjs +0 -30
  258. package/dist/lib/node-esm/blueprint-definition-42P47FUY.mjs.map +0 -7
  259. package/dist/lib/node-esm/chunk-JBOARUAT.mjs +0 -87
  260. package/dist/lib/node-esm/chunk-JBOARUAT.mjs.map +0 -7
  261. package/dist/lib/node-esm/index.mjs +0 -106
  262. package/dist/lib/node-esm/index.mjs.map +0 -7
  263. package/dist/lib/node-esm/intent-resolver-ACN7UALP.mjs +0 -112
  264. package/dist/lib/node-esm/intent-resolver-ACN7UALP.mjs.map +0 -7
  265. package/dist/lib/node-esm/meta.json +0 -1
  266. package/dist/lib/node-esm/react-surface-ZHYHCV5N.mjs +0 -256
  267. package/dist/lib/node-esm/react-surface-ZHYHCV5N.mjs.map +0 -7
  268. package/dist/lib/node-esm/types/index.mjs +0 -12
  269. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  270. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  271. package/dist/types/src/components/KanbanContainer.d.ts +0 -7
  272. package/dist/types/src/components/KanbanContainer.d.ts.map +0 -1
  273. package/dist/types/src/components/KanbanContainer.stories.d.ts +0 -41
  274. package/dist/types/src/components/KanbanContainer.stories.d.ts.map +0 -1
  275. package/dist/types/src/components/KanbanViewEditor.d.ts +0 -8
  276. package/dist/types/src/components/KanbanViewEditor.d.ts.map +0 -1
  277. package/src/capabilities/intent-resolver.ts +0 -71
  278. package/src/components/KanbanContainer.stories.tsx +0 -193
  279. package/src/components/KanbanContainer.tsx +0 -95
  280. package/src/components/KanbanViewEditor.tsx +0 -64
  281. /package/dist/lib/{browser/types → neutral/blueprints}/index.mjs.map +0 -0
  282. /package/dist/lib/{node-esm/types/index.mjs.map → neutral/chunk-J5LGTIGS.mjs.map} +0 -0
@@ -0,0 +1,145 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Atom, RegistryContext } from '@effect-atom/atom-react';
6
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
7
+ import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
8
+
9
+ import { Filter, JsonSchema, Obj, Query, type View } from '@dxos/echo';
10
+ import { random } from '@dxos/random';
11
+ import { withMosaic } from '@dxos/react-ui-mosaic/testing';
12
+ import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
13
+ import { ProjectionModel, ViewModel, createEchoChangeCallback } from '@dxos/schema';
14
+ import { withRegistry } from '@dxos/storybook-utils';
15
+ import { Organization } from '@dxos/types';
16
+
17
+ import { createEchoChangeCallback as createKanbanChangeCallback } from '#hooks';
18
+ import { KanbanCardTileSimple } from '#testing';
19
+ import { translations } from '#translations';
20
+ import { Kanban } from '#types';
21
+
22
+ import { KanbanBoard } from './KanbanBoard';
23
+
24
+ random.seed(1);
25
+
26
+ const createOrg = () => ({
27
+ name: random.commerce.productName(),
28
+ description: random.lorem.sentence(),
29
+ image: random.image.url(),
30
+ website: random.internet.url(),
31
+ status: random.helpers.arrayElement(Organization.StatusOptions).id as Organization.Organization['status'],
32
+ });
33
+
34
+ /**
35
+ * In-memory Kanban board: View + ProjectionModel + Kanban + items, no plugin manager or Space.
36
+ * Similar to react-ui-kanban Kanban.stories.tsx.
37
+ */
38
+ const DefaultStory = () => {
39
+ const registry = useContext(RegistryContext);
40
+ const items = useMemo(() => Atom.make<Obj.Unknown[]>([]), []);
41
+ const [state, setState] = useState<{
42
+ view: View.View;
43
+ kanban: Kanban.Kanban;
44
+ projection: ProjectionModel;
45
+ change: ReturnType<typeof createKanbanChangeCallback>;
46
+ }>();
47
+
48
+ useEffect(() => {
49
+ const view = ViewModel.make({
50
+ query: Query.select(Filter.typename(Organization.Organization.typename)),
51
+ jsonSchema: JsonSchema.toJsonSchema(Organization.Organization),
52
+ pivotFieldName: 'status',
53
+ });
54
+ const kanban = Kanban.make({ view });
55
+ const change = createKanbanChangeCallback(kanban);
56
+ const projection = new ProjectionModel({
57
+ registry,
58
+ view,
59
+ baseSchema: JsonSchema.toJsonSchema(Organization.Organization),
60
+ change: createEchoChangeCallback(view),
61
+ });
62
+ projection.normalizeView();
63
+
64
+ const statuses = Organization.StatusOptions.map((o) => o.id);
65
+ const initialItems = Array.from({ length: 12 }, () =>
66
+ Obj.make(Organization.Organization, {
67
+ ...createOrg(),
68
+ status: random.helpers.arrayElement(statuses) as Organization.Organization['status'],
69
+ }),
70
+ );
71
+
72
+ setState({ view, kanban, projection, change });
73
+ registry.set(items, initialItems);
74
+ }, [registry, items]);
75
+
76
+ const columnFieldPath =
77
+ state?.projection.tryGetFieldProjection(state.projection.getFieldId('status') ?? '')?.props.property ?? 'status';
78
+
79
+ const handleCardAdd = useCallback(
80
+ (columnValue: string | undefined) => {
81
+ if (!state || !columnFieldPath || !registry) {
82
+ return undefined;
83
+ }
84
+ const card = Obj.make(Organization.Organization, {
85
+ ...createOrg(),
86
+ ...(columnFieldPath ? { [columnFieldPath]: columnValue } : {}),
87
+ });
88
+ const current = registry.get(items) ?? [];
89
+ registry.set(items, [...current, card]);
90
+ return card.id;
91
+ },
92
+ [state, columnFieldPath, registry, items],
93
+ );
94
+
95
+ const handleCardRemove = useCallback(
96
+ (card: Obj.Unknown) => {
97
+ if (!registry) {
98
+ return;
99
+ }
100
+ const current = registry.get(items) ?? [];
101
+ registry.set(
102
+ items,
103
+ current.filter((i) => i.id !== card.id),
104
+ );
105
+ },
106
+ [registry, items],
107
+ );
108
+
109
+ if (!state) {
110
+ return <Loading />;
111
+ }
112
+
113
+ return (
114
+ <KanbanBoard.Root
115
+ kanban={state.kanban}
116
+ projection={state.projection}
117
+ items={items}
118
+ itemTile={KanbanCardTileSimple}
119
+ change={state.change}
120
+ onCardAdd={handleCardAdd}
121
+ onCardRemove={handleCardRemove}
122
+ >
123
+ <KanbanBoard.Content />
124
+ </KanbanBoard.Root>
125
+ );
126
+ };
127
+
128
+ const meta = {
129
+ title: 'plugins/plugin-kanban/components/KanbanBoard',
130
+ component: DefaultStory,
131
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' }), withMosaic(), withRegistry],
132
+ parameters: {
133
+ layout: 'fullscreen',
134
+ translations,
135
+ },
136
+ } satisfies Meta<typeof DefaultStory>;
137
+
138
+ export default meta;
139
+
140
+ type Story = StoryObj<typeof meta>;
141
+
142
+ /**
143
+ * In-memory board with Echo-shaped objects. No plugin manager, client, or Space.
144
+ */
145
+ export const Default: Story = {};
@@ -0,0 +1,164 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Atom, RegistryContext } from '@effect-atom/atom-react';
6
+ import React, { type ComponentPropsWithoutRef, type PropsWithChildren, useCallback, useContext, useMemo } from 'react';
7
+
8
+ import { Obj } from '@dxos/echo';
9
+ import { useTranslation } from '@dxos/react-ui';
10
+ import { Board, useBoard } from '@dxos/react-ui-mosaic';
11
+ import type { ProjectionModel } from '@dxos/schema';
12
+ import { composable, composableProps } from '@dxos/ui-theme';
13
+
14
+ import { useKanbanBoardModel, useKanbanColumnEventHandler } from '#hooks';
15
+ import { meta } from '#meta';
16
+ import { type Kanban, UNCATEGORIZED_ATTRIBUTES, UNCATEGORIZED_VALUE } from '#types';
17
+
18
+ import {
19
+ KanbanBoardContext,
20
+ type KanbanBoardContextValue,
21
+ type KanbanCardProps,
22
+ type KanbanColumnProps,
23
+ useKanbanBoard,
24
+ } from './context';
25
+ import { KanbanCard } from './KanbanCard';
26
+ import { KanbanColumn } from './KanbanColumn';
27
+
28
+ // TODO(burdon): Rename Kanban.
29
+
30
+ //
31
+ // Root
32
+ //
33
+
34
+ const KANBAN_BOARD_ROOT = 'KanbanBoard.Root';
35
+
36
+ type KanbanBoardRootProps = PropsWithChildren<
37
+ {
38
+ kanban: Kanban.Kanban;
39
+ /** Required when providing context; Root derives columnFieldPath, pivotFieldId, getPivotAttributes from kanban + projection. */
40
+ projection: ProjectionModel;
41
+ /** Atom of items (e.g. from AtomQuery for DB, or Atom.make([]) for in-memory). */
42
+ items: Atom.Atom<Obj.Unknown[]>;
43
+ onCardAdd?: (columnValue: string | undefined) => string | undefined;
44
+ onCardRemove?: (card: Obj.Unknown) => void;
45
+ } & Pick<KanbanBoardContextValue, 'change' | 'itemTile'> &
46
+ ComponentPropsWithoutRef<'div'>
47
+ >;
48
+
49
+ export const KanbanBoardRoot = ({
50
+ children,
51
+ kanban,
52
+ projection,
53
+ items,
54
+ change,
55
+ itemTile = KanbanCard,
56
+ onCardAdd,
57
+ onCardRemove,
58
+ }: KanbanBoardRootProps) => {
59
+ const registry = useContext(RegistryContext);
60
+ const { t } = useTranslation(meta.id);
61
+ const model = useKanbanBoardModel(kanban, projection, items, registry);
62
+ const columns = model?.getColumns?.() ?? [];
63
+ const view = kanban?.spec.kind === 'view' ? kanban.spec.view.target : undefined;
64
+ const pivotFieldId = view?.projection?.pivotFieldId;
65
+ const columnFieldPath = useMemo(() => {
66
+ // Items-variant kanbans use the property name itself as the pivot field
67
+ // (no view/projection translation layer).
68
+ if (kanban?.spec.kind === 'items') {
69
+ return kanban.spec.pivotField;
70
+ }
71
+ if (pivotFieldId === undefined || !projection) {
72
+ return undefined;
73
+ }
74
+
75
+ return projection.tryGetFieldProjection(pivotFieldId)?.props.property;
76
+ }, [kanban?.spec, projection, pivotFieldId]);
77
+
78
+ const getPivotAttributes = useCallback<KanbanBoardContextValue['getPivotAttributes']>(
79
+ (columnValue) => {
80
+ if (columnValue === UNCATEGORIZED_VALUE) {
81
+ return UNCATEGORIZED_ATTRIBUTES;
82
+ }
83
+
84
+ const options = projection?.tryGetFieldProjection(pivotFieldId ?? '')?.props.options ?? [];
85
+ const option = options.find((option) => option.id === columnValue);
86
+ return option ?? ({ title: columnValue, color: 'neutral' } as const);
87
+ },
88
+ [projection, pivotFieldId],
89
+ );
90
+
91
+ if (columns.length === 0) {
92
+ return (
93
+ <div className='flex flex-1 items-center justify-center p-8 text-center text-description'>
94
+ {t('select-pivot.placeholder')}
95
+ </div>
96
+ );
97
+ }
98
+
99
+ return (
100
+ <KanbanBoardContext
101
+ kanbanId={Obj.getDXN(kanban).toString()}
102
+ projection={projection}
103
+ columnFieldPath={columnFieldPath}
104
+ pivotFieldId={pivotFieldId}
105
+ getPivotAttributes={getPivotAttributes}
106
+ itemTile={itemTile}
107
+ change={change}
108
+ onCardAdd={onCardAdd}
109
+ onCardRemove={onCardRemove}
110
+ >
111
+ <Board.Root model={model}>{children}</Board.Root>
112
+ </KanbanBoardContext>
113
+ );
114
+ };
115
+
116
+ KanbanBoardRoot.displayName = KANBAN_BOARD_ROOT;
117
+
118
+ //
119
+ // KanbanBoardContent
120
+ //
121
+
122
+ const KANBAN_BOARD_CONTENT = 'KanbanBoard.Content';
123
+
124
+ type KanbanBoardContentProps = {};
125
+
126
+ export const KanbanBoardContent = composable<HTMLDivElement, KanbanBoardContentProps>((props, forwardedRef) => {
127
+ const { model } = useBoard(KANBAN_BOARD_CONTENT);
128
+ const { kanbanId, projection, pivotFieldId, change } = useKanbanBoard(KANBAN_BOARD_CONTENT);
129
+
130
+ const columnEventHandler = useKanbanColumnEventHandler({
131
+ id: `${kanbanId}-columns`,
132
+ model,
133
+ projection: projection ?? undefined,
134
+ pivotFieldId: pivotFieldId ?? undefined,
135
+ change,
136
+ });
137
+
138
+ return (
139
+ <Board.Content
140
+ {...composableProps(props)}
141
+ ref={forwardedRef}
142
+ id={kanbanId}
143
+ eventHandler={columnEventHandler}
144
+ Tile={KanbanColumn}
145
+ />
146
+ );
147
+ });
148
+
149
+ KanbanBoardContent.displayName = KANBAN_BOARD_CONTENT;
150
+
151
+ //
152
+ // KanbanBoard
153
+ //
154
+
155
+ export const KanbanBoard = {
156
+ Root: KanbanBoardRoot,
157
+ Content: KanbanBoardContent,
158
+ Column: KanbanColumn,
159
+ Card: KanbanCard,
160
+ };
161
+
162
+ export { useKanbanBoard };
163
+
164
+ export type { KanbanBoardRootProps as KanbanBoardProps, KanbanCardProps, KanbanColumnProps };
@@ -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';