@dreamboard-games/ui-sdk 0.0.41

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 (533) hide show
  1. package/LICENSE +89 -0
  2. package/NOTICE +1 -0
  3. package/README.md +154 -0
  4. package/dist/components/ActionButton.d.ts +13 -0
  5. package/dist/components/ActionButton.d.ts.map +1 -0
  6. package/dist/components/ActionButton.js +14 -0
  7. package/dist/components/ActionPanel.d.ts +33 -0
  8. package/dist/components/ActionPanel.d.ts.map +1 -0
  9. package/dist/components/ActionPanel.js +148 -0
  10. package/dist/components/Card.d.ts +29 -0
  11. package/dist/components/Card.d.ts.map +1 -0
  12. package/dist/components/Card.js +220 -0
  13. package/dist/components/ChromeSuppressionContext.d.ts +7 -0
  14. package/dist/components/ChromeSuppressionContext.d.ts.map +1 -0
  15. package/dist/components/ChromeSuppressionContext.js +34 -0
  16. package/dist/components/CostDisplay.d.ts +22 -0
  17. package/dist/components/CostDisplay.d.ts.map +1 -0
  18. package/dist/components/CostDisplay.js +41 -0
  19. package/dist/components/DiceRoller.d.ts +30 -0
  20. package/dist/components/DiceRoller.d.ts.map +1 -0
  21. package/dist/components/DiceRoller.js +319 -0
  22. package/dist/components/Drawer.d.ts +19 -0
  23. package/dist/components/Drawer.d.ts.map +1 -0
  24. package/dist/components/Drawer.js +55 -0
  25. package/dist/components/ErrorBoundary.d.ts +24 -0
  26. package/dist/components/ErrorBoundary.d.ts.map +1 -0
  27. package/dist/components/ErrorBoundary.js +37 -0
  28. package/dist/components/GameEndDisplay.d.ts +27 -0
  29. package/dist/components/GameEndDisplay.d.ts.map +1 -0
  30. package/dist/components/GameEndDisplay.js +185 -0
  31. package/dist/components/GameSkeleton.d.ts +12 -0
  32. package/dist/components/GameSkeleton.d.ts.map +1 -0
  33. package/dist/components/GameSkeleton.js +54 -0
  34. package/dist/components/Hand.d.ts +99 -0
  35. package/dist/components/Hand.d.ts.map +1 -0
  36. package/dist/components/Hand.js +162 -0
  37. package/dist/components/HandDock.d.ts +35 -0
  38. package/dist/components/HandDock.d.ts.map +1 -0
  39. package/dist/components/HandDock.js +124 -0
  40. package/dist/components/InteractionForm.d.ts +50 -0
  41. package/dist/components/InteractionForm.d.ts.map +1 -0
  42. package/dist/components/InteractionForm.js +402 -0
  43. package/dist/components/MoreActions.d.ts +49 -0
  44. package/dist/components/MoreActions.d.ts.map +1 -0
  45. package/dist/components/MoreActions.js +64 -0
  46. package/dist/components/PhaseIndicator.d.ts +35 -0
  47. package/dist/components/PhaseIndicator.d.ts.map +1 -0
  48. package/dist/components/PhaseIndicator.js +212 -0
  49. package/dist/components/PlayArea.d.ts +28 -0
  50. package/dist/components/PlayArea.d.ts.map +1 -0
  51. package/dist/components/PlayArea.js +48 -0
  52. package/dist/components/PluginRuntime.d.ts +37 -0
  53. package/dist/components/PluginRuntime.d.ts.map +1 -0
  54. package/dist/components/PluginRuntime.js +47 -0
  55. package/dist/components/PrimaryActionButton.d.ts +98 -0
  56. package/dist/components/PrimaryActionButton.d.ts.map +1 -0
  57. package/dist/components/PrimaryActionButton.js +183 -0
  58. package/dist/components/PrimaryButton.d.ts +20 -0
  59. package/dist/components/PrimaryButton.d.ts.map +1 -0
  60. package/dist/components/PrimaryButton.js +5 -0
  61. package/dist/components/PromptDialogHost.d.ts +15 -0
  62. package/dist/components/PromptDialogHost.d.ts.map +1 -0
  63. package/dist/components/PromptDialogHost.js +22 -0
  64. package/dist/components/ResourceCounter.d.ts +38 -0
  65. package/dist/components/ResourceCounter.d.ts.map +1 -0
  66. package/dist/components/ResourceCounter.js +118 -0
  67. package/dist/components/ThemedButton.d.ts +12 -0
  68. package/dist/components/ThemedButton.d.ts.map +1 -0
  69. package/dist/components/ThemedButton.js +38 -0
  70. package/dist/components/Toast.d.ts +35 -0
  71. package/dist/components/Toast.d.ts.map +1 -0
  72. package/dist/components/Toast.js +116 -0
  73. package/dist/components/board/HexGrid.d.ts +344 -0
  74. package/dist/components/board/HexGrid.d.ts.map +1 -0
  75. package/dist/components/board/HexGrid.js +340 -0
  76. package/dist/components/board/NetworkGraph.d.ts +100 -0
  77. package/dist/components/board/NetworkGraph.d.ts.map +1 -0
  78. package/dist/components/board/NetworkGraph.js +123 -0
  79. package/dist/components/board/SlotSystem.d.ts +71 -0
  80. package/dist/components/board/SlotSystem.d.ts.map +1 -0
  81. package/dist/components/board/SlotSystem.js +87 -0
  82. package/dist/components/board/SquareGrid.d.ts +188 -0
  83. package/dist/components/board/SquareGrid.d.ts.map +1 -0
  84. package/dist/components/board/SquareGrid.js +328 -0
  85. package/dist/components/board/TrackBoard.d.ts +113 -0
  86. package/dist/components/board/TrackBoard.d.ts.map +1 -0
  87. package/dist/components/board/TrackBoard.js +135 -0
  88. package/dist/components/board/ZoneMap.d.ts +88 -0
  89. package/dist/components/board/ZoneMap.d.ts.map +1 -0
  90. package/dist/components/board/ZoneMap.js +133 -0
  91. package/dist/components/board/hex-board-view.d.ts +69 -0
  92. package/dist/components/board/hex-board-view.d.ts.map +1 -0
  93. package/dist/components/board/hex-board-view.js +60 -0
  94. package/dist/components/board/index.d.ts +23 -0
  95. package/dist/components/board/index.d.ts.map +1 -0
  96. package/dist/components/board/index.js +40 -0
  97. package/dist/components/board/interaction-accessibility.d.ts +5 -0
  98. package/dist/components/board/interaction-accessibility.d.ts.map +1 -0
  99. package/dist/components/board/interaction-accessibility.js +13 -0
  100. package/dist/components/board/target-layer.d.ts +13 -0
  101. package/dist/components/board/target-layer.d.ts.map +1 -0
  102. package/dist/components/board/target-layer.js +10 -0
  103. package/dist/components/card-render-content.type-test.d.ts +2 -0
  104. package/dist/components/card-render-content.type-test.d.ts.map +1 -0
  105. package/dist/components/card-render-content.type-test.js +1 -0
  106. package/dist/components/index.d.ts +34 -0
  107. package/dist/components/index.d.ts.map +1 -0
  108. package/dist/components/index.js +35 -0
  109. package/dist/components/interaction-dialog-behavior.d.ts +15 -0
  110. package/dist/components/interaction-dialog-behavior.d.ts.map +1 -0
  111. package/dist/components/interaction-dialog-behavior.js +9 -0
  112. package/dist/components/surfaces/BlockerSurface.d.ts +27 -0
  113. package/dist/components/surfaces/BlockerSurface.d.ts.map +1 -0
  114. package/dist/components/surfaces/BlockerSurface.js +38 -0
  115. package/dist/components/surfaces/BoardSurface.d.ts +77 -0
  116. package/dist/components/surfaces/BoardSurface.d.ts.map +1 -0
  117. package/dist/components/surfaces/BoardSurface.js +180 -0
  118. package/dist/components/surfaces/ChromeSurface.d.ts +29 -0
  119. package/dist/components/surfaces/ChromeSurface.d.ts.map +1 -0
  120. package/dist/components/surfaces/ChromeSurface.js +34 -0
  121. package/dist/components/surfaces/ExhaustivenessAudit.d.ts +32 -0
  122. package/dist/components/surfaces/ExhaustivenessAudit.d.ts.map +1 -0
  123. package/dist/components/surfaces/ExhaustivenessAudit.js +65 -0
  124. package/dist/components/surfaces/InboxSurface.d.ts +40 -0
  125. package/dist/components/surfaces/InboxSurface.d.ts.map +1 -0
  126. package/dist/components/surfaces/InboxSurface.js +99 -0
  127. package/dist/components/surfaces/MarketSurface.d.ts +62 -0
  128. package/dist/components/surfaces/MarketSurface.d.ts.map +1 -0
  129. package/dist/components/surfaces/MarketSurface.js +242 -0
  130. package/dist/components/surfaces/PanelSurface.d.ts +111 -0
  131. package/dist/components/surfaces/PanelSurface.d.ts.map +1 -0
  132. package/dist/components/surfaces/PanelSurface.js +180 -0
  133. package/dist/components/surfaces/PlayerCardsSurface.d.ts +104 -0
  134. package/dist/components/surfaces/PlayerCardsSurface.d.ts.map +1 -0
  135. package/dist/components/surfaces/PlayerCardsSurface.js +178 -0
  136. package/dist/components/surfaces/internal/CardZoneFollowUpForm.d.ts +7 -0
  137. package/dist/components/surfaces/internal/CardZoneFollowUpForm.d.ts.map +1 -0
  138. package/dist/components/surfaces/internal/CardZoneFollowUpForm.js +9 -0
  139. package/dist/components/surfaces/internal/DefaultInteractionButton.d.ts +71 -0
  140. package/dist/components/surfaces/internal/DefaultInteractionButton.d.ts.map +1 -0
  141. package/dist/components/surfaces/internal/DefaultInteractionButton.js +82 -0
  142. package/dist/components/surfaces/internal/useCardZoneInteractions.d.ts +21 -0
  143. package/dist/components/surfaces/internal/useCardZoneInteractions.d.ts.map +1 -0
  144. package/dist/components/surfaces/internal/useCardZoneInteractions.js +202 -0
  145. package/dist/components/surfaces/types.d.ts +59 -0
  146. package/dist/components/surfaces/types.d.ts.map +1 -0
  147. package/dist/components/surfaces/types.js +1 -0
  148. package/dist/context/ClientParamSchemaContext.d.ts +21 -0
  149. package/dist/context/ClientParamSchemaContext.d.ts.map +1 -0
  150. package/dist/context/ClientParamSchemaContext.js +12 -0
  151. package/dist/context/InteractionDraftContext.d.ts +69 -0
  152. package/dist/context/InteractionDraftContext.d.ts.map +1 -0
  153. package/dist/context/InteractionDraftContext.js +145 -0
  154. package/dist/context/PluginSessionContext.d.ts +33 -0
  155. package/dist/context/PluginSessionContext.d.ts.map +1 -0
  156. package/dist/context/PluginSessionContext.js +38 -0
  157. package/dist/context/PluginStateContext.d.ts +116 -0
  158. package/dist/context/PluginStateContext.d.ts.map +1 -0
  159. package/dist/context/PluginStateContext.js +186 -0
  160. package/dist/context/RuntimeContext.d.ts +49 -0
  161. package/dist/context/RuntimeContext.d.ts.map +1 -0
  162. package/dist/context/RuntimeContext.js +67 -0
  163. package/dist/defaults/components.d.ts +52 -0
  164. package/dist/defaults/components.d.ts.map +1 -0
  165. package/dist/defaults/components.js +159 -0
  166. package/dist/defaults/index.d.ts +2 -0
  167. package/dist/defaults/index.d.ts.map +1 -0
  168. package/dist/defaults/index.js +1 -0
  169. package/dist/errors/ValidationError.d.ts +10 -0
  170. package/dist/errors/ValidationError.d.ts.map +1 -0
  171. package/dist/errors/ValidationError.js +23 -0
  172. package/dist/helpers/cards.d.ts +3 -0
  173. package/dist/helpers/cards.d.ts.map +1 -0
  174. package/dist/helpers/cards.js +11 -0
  175. package/dist/helpers/track-board.d.ts +79 -0
  176. package/dist/helpers/track-board.d.ts.map +1 -0
  177. package/dist/helpers/track-board.js +56 -0
  178. package/dist/hooks/useActivePlayers.d.ts +16 -0
  179. package/dist/hooks/useActivePlayers.d.ts.map +1 -0
  180. package/dist/hooks/useActivePlayers.js +17 -0
  181. package/dist/hooks/useBoardInteractions.d.ts +110 -0
  182. package/dist/hooks/useBoardInteractions.d.ts.map +1 -0
  183. package/dist/hooks/useBoardInteractions.js +248 -0
  184. package/dist/hooks/useBoardTopology.d.ts +23 -0
  185. package/dist/hooks/useBoardTopology.d.ts.map +1 -0
  186. package/dist/hooks/useBoardTopology.js +128 -0
  187. package/dist/hooks/useCards.d.ts +3 -0
  188. package/dist/hooks/useCards.d.ts.map +1 -0
  189. package/dist/hooks/useCards.js +5 -0
  190. package/dist/hooks/useGameSelector.d.ts +13 -0
  191. package/dist/hooks/useGameSelector.d.ts.map +1 -0
  192. package/dist/hooks/useGameSelector.js +67 -0
  193. package/dist/hooks/useGameView.d.ts +6 -0
  194. package/dist/hooks/useGameView.d.ts.map +1 -0
  195. package/dist/hooks/useGameView.js +7 -0
  196. package/dist/hooks/useHandLayout.d.ts +120 -0
  197. package/dist/hooks/useHandLayout.d.ts.map +1 -0
  198. package/dist/hooks/useHandLayout.js +235 -0
  199. package/dist/hooks/useHexBoard.d.ts +19 -0
  200. package/dist/hooks/useHexBoard.d.ts.map +1 -0
  201. package/dist/hooks/useHexBoard.js +28 -0
  202. package/dist/hooks/useHexGrid.d.ts +56 -0
  203. package/dist/hooks/useHexGrid.d.ts.map +1 -0
  204. package/dist/hooks/useHexGrid.js +112 -0
  205. package/dist/hooks/useInteractionByKey.d.ts +29 -0
  206. package/dist/hooks/useInteractionByKey.d.ts.map +1 -0
  207. package/dist/hooks/useInteractionByKey.js +263 -0
  208. package/dist/hooks/useInteractionHandle.d.ts +103 -0
  209. package/dist/hooks/useInteractionHandle.d.ts.map +1 -0
  210. package/dist/hooks/useInteractionHandle.js +254 -0
  211. package/dist/hooks/useIsMobile.d.ts +7 -0
  212. package/dist/hooks/useIsMobile.d.ts.map +1 -0
  213. package/dist/hooks/useIsMobile.js +29 -0
  214. package/dist/hooks/useIsMyTurn.d.ts +6 -0
  215. package/dist/hooks/useIsMyTurn.d.ts.map +1 -0
  216. package/dist/hooks/useIsMyTurn.js +11 -0
  217. package/dist/hooks/useLobby.d.ts +28 -0
  218. package/dist/hooks/useLobby.d.ts.map +1 -0
  219. package/dist/hooks/useLobby.js +60 -0
  220. package/dist/hooks/useMe.d.ts +11 -0
  221. package/dist/hooks/useMe.d.ts.map +1 -0
  222. package/dist/hooks/useMe.js +32 -0
  223. package/dist/hooks/usePanZoom.d.ts +113 -0
  224. package/dist/hooks/usePanZoom.d.ts.map +1 -0
  225. package/dist/hooks/usePanZoom.js +165 -0
  226. package/dist/hooks/usePlayerInfo.d.ts +4 -0
  227. package/dist/hooks/usePlayerInfo.d.ts.map +1 -0
  228. package/dist/hooks/usePlayerInfo.js +21 -0
  229. package/dist/hooks/usePlayerTurnOrder.d.ts +15 -0
  230. package/dist/hooks/usePlayerTurnOrder.d.ts.map +1 -0
  231. package/dist/hooks/usePlayerTurnOrder.js +22 -0
  232. package/dist/hooks/usePluginRuntime.d.ts +45 -0
  233. package/dist/hooks/usePluginRuntime.d.ts.map +1 -0
  234. package/dist/hooks/usePluginRuntime.js +92 -0
  235. package/dist/hooks/useSeatInbox.d.ts +22 -0
  236. package/dist/hooks/useSeatInbox.d.ts.map +1 -0
  237. package/dist/hooks/useSeatInbox.js +43 -0
  238. package/dist/hooks/useSimultaneousPhase.d.ts +7 -0
  239. package/dist/hooks/useSimultaneousPhase.d.ts.map +1 -0
  240. package/dist/hooks/useSimultaneousPhase.js +8 -0
  241. package/dist/hooks/useSquareBoard.d.ts +21 -0
  242. package/dist/hooks/useSquareBoard.d.ts.map +1 -0
  243. package/dist/hooks/useSquareBoard.js +67 -0
  244. package/dist/hooks/useSquareGrid.d.ts +96 -0
  245. package/dist/hooks/useSquareGrid.d.ts.map +1 -0
  246. package/dist/hooks/useSquareGrid.js +152 -0
  247. package/dist/index.d.ts +30 -0
  248. package/dist/index.d.ts.map +1 -0
  249. package/dist/index.js +20 -0
  250. package/dist/internal/ui/alert.d.ts +8 -0
  251. package/dist/internal/ui/alert.d.ts.map +1 -0
  252. package/dist/internal/ui/alert.js +11 -0
  253. package/dist/internal/ui/button.d.ts +10 -0
  254. package/dist/internal/ui/button.d.ts.map +1 -0
  255. package/dist/internal/ui/button.js +21 -0
  256. package/dist/internal/ui/dialog.d.ts +16 -0
  257. package/dist/internal/ui/dialog.d.ts.map +1 -0
  258. package/dist/internal/ui/dialog.js +35 -0
  259. package/dist/internal/ui/input.d.ts +3 -0
  260. package/dist/internal/ui/input.d.ts.map +1 -0
  261. package/dist/internal/ui/input.js +5 -0
  262. package/dist/internal/ui/label.d.ts +4 -0
  263. package/dist/internal/ui/label.d.ts.map +1 -0
  264. package/dist/internal/ui/label.js +7 -0
  265. package/dist/internal/ui/select.d.ts +9 -0
  266. package/dist/internal/ui/select.d.ts.map +1 -0
  267. package/dist/internal/ui/select.js +23 -0
  268. package/dist/internal/ui/tooltip.d.ts +7 -0
  269. package/dist/internal/ui/tooltip.d.ts.map +1 -0
  270. package/dist/internal/ui/tooltip.js +16 -0
  271. package/dist/internal/ui/utils.d.ts +3 -0
  272. package/dist/internal/ui/utils.d.ts.map +1 -0
  273. package/dist/internal/ui/utils.js +4 -0
  274. package/dist/internal.d.ts +7 -0
  275. package/dist/internal.d.ts.map +1 -0
  276. package/dist/internal.js +4 -0
  277. package/dist/plugin-styles.css +246 -0
  278. package/dist/primitives/board.d.ts +29 -0
  279. package/dist/primitives/board.d.ts.map +1 -0
  280. package/dist/primitives/board.js +163 -0
  281. package/dist/primitives/game-ui-provider.d.ts +12 -0
  282. package/dist/primitives/game-ui-provider.d.ts.map +1 -0
  283. package/dist/primitives/game-ui-provider.js +7 -0
  284. package/dist/primitives/index.d.ts +8 -0
  285. package/dist/primitives/index.d.ts.map +1 -0
  286. package/dist/primitives/index.js +7 -0
  287. package/dist/primitives/interaction.d.ts +52 -0
  288. package/dist/primitives/interaction.d.ts.map +1 -0
  289. package/dist/primitives/interaction.js +250 -0
  290. package/dist/primitives/phase.d.ts +15 -0
  291. package/dist/primitives/phase.d.ts.map +1 -0
  292. package/dist/primitives/phase.js +18 -0
  293. package/dist/primitives/player-roster.d.ts +64 -0
  294. package/dist/primitives/player-roster.d.ts.map +1 -0
  295. package/dist/primitives/player-roster.js +149 -0
  296. package/dist/primitives/primitive-props.d.ts +15 -0
  297. package/dist/primitives/primitive-props.d.ts.map +1 -0
  298. package/dist/primitives/primitive-props.js +39 -0
  299. package/dist/primitives/prompt.d.ts +44 -0
  300. package/dist/primitives/prompt.d.ts.map +1 -0
  301. package/dist/primitives/prompt.js +101 -0
  302. package/dist/primitives/zone.d.ts +31 -0
  303. package/dist/primitives/zone.d.ts.map +1 -0
  304. package/dist/primitives/zone.js +58 -0
  305. package/dist/reducer.d.ts +21 -0
  306. package/dist/reducer.d.ts.map +1 -0
  307. package/dist/reducer.js +14 -0
  308. package/dist/runtime/createPluginRuntimeAPI.d.ts +67 -0
  309. package/dist/runtime/createPluginRuntimeAPI.d.ts.map +1 -0
  310. package/dist/runtime/createPluginRuntimeAPI.js +419 -0
  311. package/dist/theme/ThemeProvider.d.ts +98 -0
  312. package/dist/theme/ThemeProvider.d.ts.map +1 -0
  313. package/dist/theme/ThemeProvider.js +148 -0
  314. package/dist/theme/board.d.ts +42 -0
  315. package/dist/theme/board.d.ts.map +1 -0
  316. package/dist/theme/board.js +34 -0
  317. package/dist/theme/css-vars.d.ts +31 -0
  318. package/dist/theme/css-vars.d.ts.map +1 -0
  319. package/dist/theme/css-vars.js +88 -0
  320. package/dist/theme/derive.d.ts +66 -0
  321. package/dist/theme/derive.d.ts.map +1 -0
  322. package/dist/theme/derive.js +161 -0
  323. package/dist/theme/index.d.ts +22 -0
  324. package/dist/theme/index.d.ts.map +1 -0
  325. package/dist/theme/index.js +20 -0
  326. package/dist/theme/presets/arcade.d.ts +10 -0
  327. package/dist/theme/presets/arcade.d.ts.map +1 -0
  328. package/dist/theme/presets/arcade.js +257 -0
  329. package/dist/theme/presets/studio.d.ts +10 -0
  330. package/dist/theme/presets/studio.d.ts.map +1 -0
  331. package/dist/theme/presets/studio.js +257 -0
  332. package/dist/theme/presets/tabletop.d.ts +15 -0
  333. package/dist/theme/presets/tabletop.d.ts.map +1 -0
  334. package/dist/theme/presets/tabletop.js +262 -0
  335. package/dist/theme/tokens.d.ts +345 -0
  336. package/dist/theme/tokens.d.ts.map +1 -0
  337. package/dist/theme/tokens.js +57 -0
  338. package/dist/types/player-state.d.ts +337 -0
  339. package/dist/types/player-state.d.ts.map +1 -0
  340. package/dist/types/player-state.js +1 -0
  341. package/dist/types/plugin-state.d.ts +324 -0
  342. package/dist/types/plugin-state.d.ts.map +1 -0
  343. package/dist/types/plugin-state.js +1 -0
  344. package/dist/types/reducer-state.d.ts +10 -0
  345. package/dist/types/reducer-state.d.ts.map +1 -0
  346. package/dist/types/reducer-state.js +1 -0
  347. package/dist/types/runtime-api.d.ts +99 -0
  348. package/dist/types/runtime-api.d.ts.map +1 -0
  349. package/dist/types/runtime-api.js +1 -0
  350. package/dist/types/tiled-board.d.ts +187 -0
  351. package/dist/types/tiled-board.d.ts.map +1 -0
  352. package/dist/types/tiled-board.js +226 -0
  353. package/dist/ui-contract.d.ts +78 -0
  354. package/dist/ui-contract.d.ts.map +1 -0
  355. package/dist/ui-contract.js +15 -0
  356. package/dist/ui-sdk.d.ts +3409 -0
  357. package/dist/utils/interaction-inputs.d.ts +22 -0
  358. package/dist/utils/interaction-inputs.d.ts.map +1 -0
  359. package/dist/utils/interaction-inputs.js +219 -0
  360. package/dist/utils/interaction-labels.d.ts +4 -0
  361. package/dist/utils/interaction-labels.d.ts.map +1 -0
  362. package/dist/utils/interaction-labels.js +18 -0
  363. package/dist/utils/interaction-status.d.ts +15 -0
  364. package/dist/utils/interaction-status.d.ts.map +1 -0
  365. package/dist/utils/interaction-status.js +31 -0
  366. package/package.json +101 -0
  367. package/src/components/ActionButton.tsx +48 -0
  368. package/src/components/ActionPanel.tsx +310 -0
  369. package/src/components/Card.tsx +385 -0
  370. package/src/components/ChromeSuppressionContext.tsx +70 -0
  371. package/src/components/CostDisplay.test.tsx +23 -0
  372. package/src/components/CostDisplay.tsx +145 -0
  373. package/src/components/DiceRoller.tsx +601 -0
  374. package/src/components/Drawer.tsx +179 -0
  375. package/src/components/ErrorBoundary.tsx +119 -0
  376. package/src/components/GameEndDisplay.test.tsx +19 -0
  377. package/src/components/GameEndDisplay.tsx +398 -0
  378. package/src/components/GameSkeleton.tsx +260 -0
  379. package/src/components/Hand.tsx +387 -0
  380. package/src/components/HandDock.tsx +257 -0
  381. package/src/components/InteractionForm.test.tsx +303 -0
  382. package/src/components/InteractionForm.tsx +1029 -0
  383. package/src/components/MoreActions.test.tsx +93 -0
  384. package/src/components/MoreActions.tsx +143 -0
  385. package/src/components/PhaseIndicator.tsx +341 -0
  386. package/src/components/PlayArea.tsx +125 -0
  387. package/src/components/PluginRuntime.tsx +92 -0
  388. package/src/components/PrimaryActionButton.test.tsx +138 -0
  389. package/src/components/PrimaryActionButton.tsx +351 -0
  390. package/src/components/PrimaryButton.tsx +44 -0
  391. package/src/components/PromptDialogHost.tsx +92 -0
  392. package/src/components/ResourceCounter.test.tsx +29 -0
  393. package/src/components/ResourceCounter.tsx +275 -0
  394. package/src/components/ThemedButton.tsx +78 -0
  395. package/src/components/Toast.tsx +251 -0
  396. package/src/components/__fixtures__/ActionButton.fixture.tsx +234 -0
  397. package/src/components/__fixtures__/ActionPanel.fixture.tsx +298 -0
  398. package/src/components/__fixtures__/Card.fixture.tsx +185 -0
  399. package/src/components/__fixtures__/CostDisplay.fixture.tsx +156 -0
  400. package/src/components/__fixtures__/DiceRoller.fixture.tsx +435 -0
  401. package/src/components/__fixtures__/Drawer.fixture.tsx +113 -0
  402. package/src/components/__fixtures__/ErrorBoundary.fixture.tsx +82 -0
  403. package/src/components/__fixtures__/GameEndDisplay.fixture.tsx +188 -0
  404. package/src/components/__fixtures__/GameSkeleton.fixture.tsx +46 -0
  405. package/src/components/__fixtures__/Hand.fixture.tsx +522 -0
  406. package/src/components/__fixtures__/HexGrid.fixture.tsx +1181 -0
  407. package/src/components/__fixtures__/NetworkGraph.fixture.tsx +599 -0
  408. package/src/components/__fixtures__/PhaseIndicator.fixture.tsx +181 -0
  409. package/src/components/__fixtures__/PlayArea.fixture.tsx +221 -0
  410. package/src/components/__fixtures__/ResourceCounter.fixture.tsx +227 -0
  411. package/src/components/__fixtures__/SlotSystem.fixture.tsx +824 -0
  412. package/src/components/__fixtures__/SquareGrid.fixture.tsx +764 -0
  413. package/src/components/__fixtures__/Toast.fixture.tsx +97 -0
  414. package/src/components/__fixtures__/TrackBoard.fixture.tsx +685 -0
  415. package/src/components/__fixtures__/ZoneMap.fixture.tsx +754 -0
  416. package/src/components/board/HexGrid.tsx +1294 -0
  417. package/src/components/board/NetworkGraph.tsx +476 -0
  418. package/src/components/board/SlotSystem.tsx +339 -0
  419. package/src/components/board/SquareGrid.tsx +1165 -0
  420. package/src/components/board/TrackBoard.tsx +496 -0
  421. package/src/components/board/ZoneMap.tsx +448 -0
  422. package/src/components/board/hex-board-view.test.tsx +114 -0
  423. package/src/components/board/hex-board-view.ts +123 -0
  424. package/src/components/board/index.ts +142 -0
  425. package/src/components/board/interaction-accessibility.ts +21 -0
  426. package/src/components/board/target-layer-grids.test.tsx +420 -0
  427. package/src/components/board/target-layer.ts +30 -0
  428. package/src/components/card-render-content.type-test.ts +27 -0
  429. package/src/components/index.ts +208 -0
  430. package/src/components/interaction-dialog-behavior.test.ts +23 -0
  431. package/src/components/interaction-dialog-behavior.ts +22 -0
  432. package/src/components/surfaces/BlockerSurface.test.tsx +158 -0
  433. package/src/components/surfaces/BlockerSurface.tsx +127 -0
  434. package/src/components/surfaces/BoardSurface.tsx +340 -0
  435. package/src/components/surfaces/ChromeSurface.tsx +123 -0
  436. package/src/components/surfaces/ExhaustivenessAudit.tsx +91 -0
  437. package/src/components/surfaces/InboxSurface.test.tsx +149 -0
  438. package/src/components/surfaces/InboxSurface.tsx +245 -0
  439. package/src/components/surfaces/MarketSurface.tsx +544 -0
  440. package/src/components/surfaces/PanelSurface.test.tsx +496 -0
  441. package/src/components/surfaces/PanelSurface.tsx +458 -0
  442. package/src/components/surfaces/PlayerCardsSurface.tsx +525 -0
  443. package/src/components/surfaces/internal/CardZoneFollowUpForm.tsx +35 -0
  444. package/src/components/surfaces/internal/DefaultInteractionButton.tsx +219 -0
  445. package/src/components/surfaces/internal/useCardZoneInteractions.ts +311 -0
  446. package/src/components/surfaces/types.ts +100 -0
  447. package/src/context/ClientParamSchemaContext.tsx +44 -0
  448. package/src/context/InteractionDraftContext.tsx +204 -0
  449. package/src/context/PluginSessionContext.tsx +47 -0
  450. package/src/context/PluginStateContext.tsx +254 -0
  451. package/src/context/RuntimeContext.tsx +96 -0
  452. package/src/defaults/components.tsx +442 -0
  453. package/src/defaults/defaults.test.tsx +230 -0
  454. package/src/defaults/index.ts +1 -0
  455. package/src/errors/ValidationError.ts +29 -0
  456. package/src/helpers/cards.ts +19 -0
  457. package/src/helpers/track-board.ts +211 -0
  458. package/src/hooks/useActivePlayers.ts +19 -0
  459. package/src/hooks/useBoardInteractions.test.tsx +622 -0
  460. package/src/hooks/useBoardInteractions.ts +434 -0
  461. package/src/hooks/useBoardTopology.ts +316 -0
  462. package/src/hooks/useCards.test.tsx +129 -0
  463. package/src/hooks/useCards.ts +10 -0
  464. package/src/hooks/useGameSelector.ts +105 -0
  465. package/src/hooks/useGameView.ts +9 -0
  466. package/src/hooks/useHandLayout.ts +349 -0
  467. package/src/hooks/useHexBoard.ts +74 -0
  468. package/src/hooks/useHexGrid.ts +185 -0
  469. package/src/hooks/useInteractionByKey.ts +349 -0
  470. package/src/hooks/useInteractionHandle.ts +437 -0
  471. package/src/hooks/useIsMobile.ts +35 -0
  472. package/src/hooks/useIsMyTurn.test.tsx +99 -0
  473. package/src/hooks/useIsMyTurn.ts +15 -0
  474. package/src/hooks/useLobby.ts +76 -0
  475. package/src/hooks/useMe.ts +48 -0
  476. package/src/hooks/usePanZoom.ts +278 -0
  477. package/src/hooks/usePlayerInfo.ts +28 -0
  478. package/src/hooks/usePlayerTurnOrder.ts +23 -0
  479. package/src/hooks/usePluginRuntime.test.tsx +102 -0
  480. package/src/hooks/usePluginRuntime.ts +130 -0
  481. package/src/hooks/useSeatInbox.ts +61 -0
  482. package/src/hooks/useSimultaneousPhase.ts +10 -0
  483. package/src/hooks/useSquareBoard.ts +124 -0
  484. package/src/hooks/useSquareGrid.ts +328 -0
  485. package/src/index.test.ts +474 -0
  486. package/src/index.ts +148 -0
  487. package/src/internal/ui/alert.tsx +51 -0
  488. package/src/internal/ui/button.tsx +58 -0
  489. package/src/internal/ui/dialog.tsx +134 -0
  490. package/src/internal/ui/input.tsx +21 -0
  491. package/src/internal/ui/label.tsx +21 -0
  492. package/src/internal/ui/select.tsx +129 -0
  493. package/src/internal/ui/tooltip.tsx +54 -0
  494. package/src/internal/ui/utils.ts +5 -0
  495. package/src/internal.ts +18 -0
  496. package/src/plugin-styles.css +246 -0
  497. package/src/primitives/board.test.tsx +139 -0
  498. package/src/primitives/board.tsx +267 -0
  499. package/src/primitives/game-ui-provider.tsx +35 -0
  500. package/src/primitives/index.ts +83 -0
  501. package/src/primitives/interaction.test.tsx +420 -0
  502. package/src/primitives/interaction.tsx +405 -0
  503. package/src/primitives/phase.test.tsx +82 -0
  504. package/src/primitives/phase.tsx +43 -0
  505. package/src/primitives/player-roster.test.tsx +168 -0
  506. package/src/primitives/player-roster.tsx +301 -0
  507. package/src/primitives/primitive-props.tsx +82 -0
  508. package/src/primitives/prompt.test.tsx +159 -0
  509. package/src/primitives/prompt.tsx +203 -0
  510. package/src/primitives/zone.tsx +113 -0
  511. package/src/reducer.ts +42 -0
  512. package/src/runtime/createPluginRuntimeAPI.ts +605 -0
  513. package/src/theme/ThemeProvider.test.tsx +36 -0
  514. package/src/theme/ThemeProvider.tsx +252 -0
  515. package/src/theme/board.ts +61 -0
  516. package/src/theme/css-vars.ts +105 -0
  517. package/src/theme/derive.ts +240 -0
  518. package/src/theme/index.ts +61 -0
  519. package/src/theme/presets/arcade.ts +261 -0
  520. package/src/theme/presets/studio.ts +261 -0
  521. package/src/theme/presets/tabletop.ts +266 -0
  522. package/src/theme/theme.test.ts +258 -0
  523. package/src/theme/tokens.ts +392 -0
  524. package/src/types/player-state.ts +445 -0
  525. package/src/types/plugin-state.ts +407 -0
  526. package/src/types/reducer-state.ts +24 -0
  527. package/src/types/runtime-api.ts +114 -0
  528. package/src/types/tiled-board.ts +785 -0
  529. package/src/ui-contract.ts +168 -0
  530. package/src/utils/interaction-inputs.test.ts +109 -0
  531. package/src/utils/interaction-inputs.ts +331 -0
  532. package/src/utils/interaction-labels.ts +23 -0
  533. package/src/utils/interaction-status.ts +59 -0
@@ -0,0 +1,92 @@
1
+ import React from "react";
2
+ import { InteractionUiProvider } from "../context/InteractionDraftContext.js";
3
+ import { RuntimeProvider } from "../context/RuntimeContext.js";
4
+ import { usePluginSession } from "../context/PluginSessionContext.js";
5
+ import { usePluginRuntime } from "../hooks/usePluginRuntime.js";
6
+ import { GameSkeleton } from "./GameSkeleton.js";
7
+
8
+ export interface PluginRuntimeProps {
9
+ /** Child components to render after state sync has started */
10
+ children: React.ReactNode;
11
+ /**
12
+ * Timeout in milliseconds to wait for the first state-sync snapshot.
13
+ * @default 10000 (10 seconds)
14
+ */
15
+ timeout?: number;
16
+ /** Custom loading component to show while waiting for state sync */
17
+ loadingComponent?: React.ReactNode;
18
+ /** Custom error component to show when initialization fails */
19
+ errorComponent?: (error: string) => React.ReactNode;
20
+ }
21
+
22
+ /**
23
+ * PluginRuntime provides the RuntimeContext for plugin components.
24
+ *
25
+ * This component:
26
+ * - Creates a RuntimeAPI instance using the SDK-provided implementation
27
+ * - Waits for the first reducer-native state-sync snapshot before rendering children
28
+ * - Provides RuntimeAPI and session state to all child components
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * // In your plugin's index.tsx
33
+ * import { PluginRuntime } from "./components/dreamboard";
34
+ * import App from './App';
35
+ *
36
+ * ReactDOM.createRoot(document.getElementById('root')!).render(
37
+ * <PluginRuntime>
38
+ * <App />
39
+ * </PluginRuntime>
40
+ * );
41
+ * ```
42
+ */
43
+ export function PluginRuntime({
44
+ children,
45
+ timeout = 10000,
46
+ loadingComponent,
47
+ errorComponent,
48
+ }: PluginRuntimeProps) {
49
+ const { runtime, isReady, error } = usePluginRuntime({ timeout });
50
+
51
+ if (error) {
52
+ if (errorComponent) {
53
+ return <>{errorComponent(error)}</>;
54
+ }
55
+ return (
56
+ <div className="flex h-full w-full items-center justify-center bg-gray-50">
57
+ <div className="text-center p-6">
58
+ <p className="text-red-600 font-medium mb-2">Failed to load game</p>
59
+ <p className="text-gray-600 text-sm">{error}</p>
60
+ </div>
61
+ </div>
62
+ );
63
+ }
64
+
65
+ if (!isReady) {
66
+ if (loadingComponent) {
67
+ return <>{loadingComponent}</>;
68
+ }
69
+ return <GameSkeleton message="Waiting for game state..." />;
70
+ }
71
+
72
+ return (
73
+ <RuntimeProvider runtime={runtime}>
74
+ <SessionScopedInteractionUiProvider>
75
+ {children}
76
+ </SessionScopedInteractionUiProvider>
77
+ </RuntimeProvider>
78
+ );
79
+ }
80
+
81
+ function SessionScopedInteractionUiProvider({
82
+ children,
83
+ }: {
84
+ children: React.ReactNode;
85
+ }) {
86
+ const { controllingPlayerId } = usePluginSession();
87
+ return (
88
+ <InteractionUiProvider key={controllingPlayerId ?? "__no_player__"}>
89
+ {children}
90
+ </InteractionUiProvider>
91
+ );
92
+ }
@@ -0,0 +1,138 @@
1
+ import { expect, test } from "bun:test";
2
+ import { renderToString } from "react-dom/server";
3
+ import { ThemeProvider } from "../theme/ThemeProvider.js";
4
+ import type { InteractionHandle } from "../hooks/useInteractionHandle.js";
5
+ import type { InteractionDescriptor } from "../types/plugin-state.js";
6
+ import { PrimaryActionButton } from "./PrimaryActionButton.js";
7
+
8
+ function makeHandle(
9
+ overrides: Partial<InteractionDescriptor> = {},
10
+ handleOverrides: Partial<InteractionHandle> = {},
11
+ ): InteractionHandle {
12
+ const descriptor: InteractionDescriptor = {
13
+ phaseName: "play",
14
+ interactionKey: "play.rollDice",
15
+ interactionId: "rollDice",
16
+ kind: "action",
17
+ inputs: [],
18
+ commit: { mode: "manual" },
19
+ available: true,
20
+ ...overrides,
21
+ };
22
+ return {
23
+ descriptor,
24
+ commit: descriptor.commit,
25
+ submit: async () => {},
26
+ validate: async () => {},
27
+ validateDraft: () => ({
28
+ ok: true,
29
+ params: {},
30
+ fieldErrors: {},
31
+ formErrors: [],
32
+ missing: [],
33
+ }),
34
+ validateDraftServer: async () => {},
35
+ submitDraft: async () => {},
36
+ available: descriptor.available,
37
+ unavailableReason: descriptor.unavailableReason,
38
+ draft: {},
39
+ values: {},
40
+ setInput: () => {},
41
+ clearInput: () => {},
42
+ isReady: true,
43
+ isArmed: false,
44
+ arm: () => {},
45
+ disarm: () => {},
46
+ ...handleOverrides,
47
+ };
48
+ }
49
+
50
+ test("PrimaryActionButton renders fallback label and interaction id", () => {
51
+ const html = renderToString(
52
+ <ThemeProvider>
53
+ <PrimaryActionButton handle={makeHandle()} />
54
+ </ThemeProvider>,
55
+ );
56
+
57
+ expect(html).toContain("data-dreamboard-primary-action");
58
+ expect(html).toContain('data-available="true"');
59
+ expect(html).toContain('data-interaction-id="rollDice"');
60
+ expect(html).toContain("Roll Dice");
61
+ });
62
+
63
+ test("PrimaryActionButton disables and surfaces unavailableReason", () => {
64
+ const handle = makeHandle({
65
+ available: false,
66
+ unavailableReason: "Wait for your turn",
67
+ });
68
+ const html = renderToString(
69
+ <ThemeProvider>
70
+ <PrimaryActionButton handle={handle} />
71
+ </ThemeProvider>,
72
+ );
73
+
74
+ expect(html).toContain('data-available="false"');
75
+ // Native `disabled` and `aria-disabled` both flip on so screen
76
+ // readers and pointer devices both see the disabled state.
77
+ expect(html).toContain("disabled=");
78
+ expect(html).toContain('aria-disabled="true"');
79
+ // The unavailable reason becomes the `title` tooltip so the
80
+ // player can see why the action is blocked.
81
+ expect(html).toContain("Wait for your turn");
82
+ });
83
+
84
+ test("PrimaryActionButton supports a label override", () => {
85
+ const html = renderToString(
86
+ <ThemeProvider>
87
+ <PrimaryActionButton handle={makeHandle()} label="Confirm" />
88
+ </ThemeProvider>,
89
+ );
90
+
91
+ // Override wins over the generated fallback label.
92
+ expect(html).toContain("Confirm");
93
+ expect(html).not.toContain("Roll Dice");
94
+ });
95
+
96
+ test("PrimaryActionButton suppresses the halo when attention='off'", () => {
97
+ // The halo is implemented via `motion.span` siblings of the
98
+ // `<button>`. With `attention="off"` they should be absent so the
99
+ // button stands alone — useful for screens where the author does
100
+ // not want a pulse competing with a richer custom indicator.
101
+ const htmlWithHalo = renderToString(
102
+ <ThemeProvider>
103
+ <PrimaryActionButton handle={makeHandle()} attention="always" />
104
+ </ThemeProvider>,
105
+ );
106
+ const htmlWithoutHalo = renderToString(
107
+ <ThemeProvider>
108
+ <PrimaryActionButton handle={makeHandle()} attention="off" />
109
+ </ThemeProvider>,
110
+ );
111
+
112
+ // The halo wrappers are detected via the `boxShadow` keyframe
113
+ // that the announce pulse renders inline. With the halo off the
114
+ // wrapper still mounts but the inner motion spans are gone.
115
+ expect(htmlWithHalo).toContain("box-shadow");
116
+ // Sanity: the button itself still mounts in both cases.
117
+ expect(htmlWithoutHalo).toContain('data-interaction-id="rollDice"');
118
+ // The announce-pulse motion span uses an inset:0 absolute layer.
119
+ // It is present with the halo on, absent with attention="off".
120
+ expect(htmlWithHalo.split("position:absolute").length).toBeGreaterThan(
121
+ htmlWithoutHalo.split("position:absolute").length,
122
+ );
123
+ });
124
+
125
+ test("PrimaryActionButton skips the halo entirely when reduced motion is requested", () => {
126
+ // Reduced-motion pulses would defeat the accessibility contract.
127
+ // The button must still render — only the decorative halo is
128
+ // suppressed.
129
+ const html = renderToString(
130
+ <ThemeProvider reducedMotion="force">
131
+ <PrimaryActionButton handle={makeHandle()} attention="always" />
132
+ </ThemeProvider>,
133
+ );
134
+
135
+ expect(html).toContain('data-interaction-id="rollDice"');
136
+ // No motion spans — only the static button frame.
137
+ expect(html.split("position:absolute").length).toBe(1);
138
+ });
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Theme-aware primary CTA button — the dominant call-to-action on
3
+ * the screen at any given moment ("Roll dice", "End turn", "Confirm
4
+ * trade", "Place settlement").
5
+ *
6
+ * The shell's `chrome.primaryAction` slot accepts either a
7
+ * pre-rendered React node *or* an {@link InteractionHandle}. When a
8
+ * handle is supplied, the shell wraps it in this component so the
9
+ * call site collapses from ~10 lines to one. Authors who need a
10
+ * fully bespoke CTA can still pass any node.
11
+ *
12
+ * Visual contract (Laws of UX cross-references):
13
+ *
14
+ * - **Fitts** — defaults to `lg` size (min 56px tall, generous
15
+ * horizontal padding) so the dock target is easy to land on.
16
+ * Authors can opt down to `md`.
17
+ * - **Von Restorff (isolation)** — uses `intent.primary.solid` with
18
+ * `elevation.lifted` and an animated halo when `attention="auto"`
19
+ * and the descriptor is available, so the button outranks every
20
+ * other element in its peripheral neighbourhood.
21
+ * - **Peak-end** — when the action becomes available (handle flips
22
+ * from disabled → enabled), the halo pulses for one breath cycle
23
+ * so the eye finds the change without re-scanning the screen.
24
+ * - **Doherty / responsiveness** — clicks set an internal `pending`
25
+ * flag the moment submit fires so the button visibly absorbs the
26
+ * tap, even on slow networks. Throwing submitters are swallowed
27
+ * here for the same reason `<DefaultInteractionButton>` does:
28
+ * descriptor availability is authoritative.
29
+ * - **Accessibility** — minimum 56×56 hit area satisfies WCAG 2.5.5.
30
+ * `prefers-reduced-motion` zeroes out the halo and press
31
+ * transitions through the theme's `motion.reducedMotion` token.
32
+ */
33
+
34
+ import {
35
+ useEffect,
36
+ useRef,
37
+ useState,
38
+ type CSSProperties,
39
+ type ReactNode,
40
+ } from "react";
41
+ import { motion } from "framer-motion";
42
+ import { useTheme } from "../theme/ThemeProvider.js";
43
+ import {
44
+ intentForVariant,
45
+ type ButtonSize,
46
+ type ButtonVariant,
47
+ } from "../theme/derive.js";
48
+ import type {
49
+ InteractionHandle,
50
+ InteractionParamsShape,
51
+ } from "../hooks/useInteractionHandle.js";
52
+ import { interactionLabel } from "../utils/interaction-labels.js";
53
+ import { ThemedButton } from "./ThemedButton.js";
54
+ import type { SubmittedActionConfig } from "./surfaces/internal/DefaultInteractionButton.js";
55
+
56
+ /** Attention-pulse policy for the trailing halo. */
57
+ export type PrimaryActionAttention = "auto" | "always" | "off";
58
+
59
+ export interface PrimaryActionButtonProps<
60
+ Params extends InteractionParamsShape = InteractionParamsShape,
61
+ > {
62
+ /**
63
+ * Bound interaction handle (typically from `useInteractionByKey`).
64
+ * The button mirrors `descriptor.label`, disables itself when the
65
+ * descriptor is unavailable, surfaces `unavailableReason` as a
66
+ * tooltip, and submits explicit `params` or the current draft on click.
67
+ */
68
+ handle: InteractionHandle<Params>;
69
+ /**
70
+ * Override the visual variant. Defaults to `primary` (which maps
71
+ * to `intent.primary` regardless of the descriptor's `emphasis`
72
+ * hint — the shell's primary slot is, by definition, primary).
73
+ */
74
+ variant?: ButtonVariant;
75
+ /**
76
+ * Sizing. Defaults to `lg` so the dock target is comfortable on
77
+ * touch and visually outranks panel buttons sized `md`.
78
+ */
79
+ size?: ButtonSize;
80
+ /**
81
+ * Override the label inferred from `descriptor.label`. Use only
82
+ * when the descriptor's label needs phase-specific copy that the
83
+ * authoring layer can't express.
84
+ */
85
+ label?: ReactNode;
86
+ /** Client-side draft readiness. Reducer availability remains authoritative. */
87
+ ready?: boolean;
88
+ /** Copy and visual overrides once this interaction has been submitted. */
89
+ whenSubmitted?: SubmittedActionConfig;
90
+ /**
91
+ * Optional leading icon override. When omitted, falls back to
92
+ * `descriptor.icon` (an emoji glyph from the authoring spec).
93
+ */
94
+ icon?: ReactNode;
95
+ /**
96
+ * Submit params forwarded to `handle.submit`. When omitted, the button
97
+ * submits the handle's current draft via `handle.submitDraft()`.
98
+ */
99
+ params?: Record<string, unknown>;
100
+ /**
101
+ * Attention-halo policy. `auto` (default) pulses the halo for one
102
+ * breath when the button transitions from disabled → enabled (so
103
+ * the user sees the moment the action becomes available), then
104
+ * settles into a slow ambient breath while the action remains
105
+ * available. `always` keeps the breath running unconditionally.
106
+ * `off` suppresses the halo entirely.
107
+ *
108
+ * Ignored when `theme.motion.reducedMotion === "true"`.
109
+ */
110
+ attention?: PrimaryActionAttention;
111
+ /** Additional inline style merged after the resolved button style. */
112
+ style?: CSSProperties;
113
+ /** Optional className for downstream styling hooks. */
114
+ className?: string;
115
+ }
116
+
117
+ /**
118
+ * @see PrimaryActionButtonProps
119
+ */
120
+ export function PrimaryActionButton<
121
+ Params extends InteractionParamsShape = InteractionParamsShape,
122
+ >({
123
+ handle,
124
+ variant = "primary",
125
+ size = "lg",
126
+ label,
127
+ ready = true,
128
+ whenSubmitted,
129
+ icon,
130
+ params,
131
+ attention = "auto",
132
+ style,
133
+ className,
134
+ }: PrimaryActionButtonProps<Params>) {
135
+ const theme = useTheme();
136
+ const reducedMotion = theme.motion.reducedMotion === "true";
137
+ const [pending, setPending] = useState(false);
138
+
139
+ const descriptor = handle.descriptor;
140
+ const submitted = handle.status === "submitted";
141
+ const submitting = handle.status === "submitting" || pending;
142
+ const available = descriptor.available && ready && !submitted && !submitting;
143
+ const resolvedVariant = submitted
144
+ ? (whenSubmitted?.variant ?? "submitted")
145
+ : variant;
146
+ const disabled = !available;
147
+ const intent = intentForVariant(theme, resolvedVariant);
148
+
149
+ // Pulse the halo for one breath when availability flips on. After
150
+ // the breath we settle into the ambient cadence (or stop, when
151
+ // `attention` is `off`). Tracking the previous availability lets
152
+ // us catch the transition without re-mounting the component.
153
+ const previouslyAvailableRef = useRef(available);
154
+ const [pulseKey, setPulseKey] = useState(0);
155
+ useEffect(() => {
156
+ if (!previouslyAvailableRef.current && available) {
157
+ setPulseKey((n) => n + 1);
158
+ }
159
+ previouslyAvailableRef.current = available;
160
+ }, [available]);
161
+
162
+ const haloEnabled =
163
+ !reducedMotion && available && attention !== "off" && !submitted;
164
+
165
+ const fallbackLabel = interactionLabel(descriptor);
166
+ const tooltip = available
167
+ ? undefined
168
+ : formatUnavailableReason(descriptor.unavailableReason);
169
+
170
+ const resolvedLabel: ReactNode = submitted
171
+ ? (whenSubmitted?.label ?? label ?? fallbackLabel)
172
+ : (label ?? fallbackLabel);
173
+ const resolvedIcon: ReactNode =
174
+ submitted && whenSubmitted?.icon ? (
175
+ <span aria-hidden style={{ fontSize: "1.15em" }}>
176
+ {whenSubmitted.icon}
177
+ </span>
178
+ ) : (
179
+ (icon ?? null)
180
+ );
181
+
182
+ return (
183
+ <span
184
+ data-dreamboard-primary-action
185
+ data-available={available ? "true" : "false"}
186
+ data-pending={submitting ? "true" : undefined}
187
+ data-action-state={
188
+ submitted
189
+ ? "submitted"
190
+ : submitting
191
+ ? "submitting"
192
+ : available
193
+ ? "available"
194
+ : "unavailable"
195
+ }
196
+ style={{
197
+ position: "relative",
198
+ display: "inline-flex",
199
+ alignItems: "center",
200
+ justifyContent: "center",
201
+ // The halo overflows the button bounds; the wrapper reserves
202
+ // a transparent buffer so it doesn't get clipped by the
203
+ // dock's safe-area frame.
204
+ padding: theme.space[1],
205
+ }}
206
+ >
207
+ {haloEnabled ? (
208
+ <>
209
+ {/*
210
+ Ambient breath — slow, low-amplitude, runs as long as the
211
+ action is available. The outer `haloEnabled` already
212
+ short-circuits when `attention === "off"`, so this layer
213
+ is gated purely on the availability + reduced-motion
214
+ checks.
215
+ */}
216
+ <motion.span
217
+ aria-hidden
218
+ style={{
219
+ position: "absolute",
220
+ inset: 0,
221
+ borderRadius: theme.radius.md,
222
+ background: intent.soft,
223
+ opacity: 0.55,
224
+ pointerEvents: "none",
225
+ }}
226
+ animate={{
227
+ scale: [1, 1.06, 1],
228
+ opacity: [0.45, 0.18, 0.45],
229
+ }}
230
+ transition={{
231
+ repeat: Infinity,
232
+ duration: 2.4,
233
+ ease: "easeInOut",
234
+ }}
235
+ />
236
+ {/*
237
+ One-shot announce pulse keyed on `pulseKey` — re-mounts
238
+ (and thus re-runs) every time availability flips on so the
239
+ eye registers the change. We use a separate layer (rather
240
+ than retriggering the ambient breath) so the announce is
241
+ visibly louder than the steady-state cadence.
242
+ */}
243
+ <motion.span
244
+ key={pulseKey}
245
+ aria-hidden
246
+ style={{
247
+ position: "absolute",
248
+ inset: 0,
249
+ borderRadius: theme.radius.md,
250
+ boxShadow: `0 0 0 0 ${intent.solid}`,
251
+ pointerEvents: "none",
252
+ }}
253
+ initial={{ opacity: 0.7 }}
254
+ animate={{
255
+ boxShadow: [
256
+ `0 0 0 0 ${withAlpha(intent.solid, 0.55)}`,
257
+ `0 0 0 14px ${withAlpha(intent.solid, 0)}`,
258
+ ],
259
+ opacity: [0.7, 0],
260
+ }}
261
+ transition={{ duration: 0.9, ease: "easeOut" }}
262
+ />
263
+ </>
264
+ ) : null}
265
+ <ThemedButton
266
+ type="button"
267
+ variant={resolvedVariant}
268
+ size={size}
269
+ pressed={submitting}
270
+ className={className}
271
+ aria-label={
272
+ typeof resolvedLabel === "string" ? resolvedLabel : fallbackLabel
273
+ }
274
+ aria-disabled={disabled || undefined}
275
+ data-interaction-id={descriptor.interactionId}
276
+ data-emphasis="primary"
277
+ title={tooltip}
278
+ disabled={disabled}
279
+ style={{
280
+ // Sit above the halo so clicks land on the button.
281
+ position: "relative",
282
+ zIndex: 1,
283
+ boxShadow: disabled || submitted ? undefined : theme.elevation.lifted,
284
+ ...style,
285
+ }}
286
+ onClick={async (event) => {
287
+ event.preventDefault();
288
+ if (disabled) return;
289
+ setPending(true);
290
+ try {
291
+ if (params !== undefined) {
292
+ await handle.submit(params as Params);
293
+ } else {
294
+ await handle.submitDraft();
295
+ }
296
+ } catch {
297
+ // Descriptor availability is authoritative; submit errors
298
+ // surface via the runtime's error channel.
299
+ } finally {
300
+ setPending(false);
301
+ }
302
+ }}
303
+ >
304
+ {resolvedIcon}
305
+ <span>{resolvedLabel}</span>
306
+ </ThemedButton>
307
+ </span>
308
+ );
309
+ }
310
+
311
+ function formatUnavailableReason(
312
+ reason: string | undefined,
313
+ ): string | undefined {
314
+ if (reason === "INSUFFICIENT_RESOURCES") {
315
+ return "Insufficient resources";
316
+ }
317
+ return reason;
318
+ }
319
+
320
+ /**
321
+ * Add an alpha channel to a CSS colour string. Supports `#rgb`,
322
+ * `#rrggbb`, and any colour the browser can paint via a fallback to
323
+ * `color-mix` (modern Safari/Chrome/Firefox all support this; older
324
+ * runtimes get the original colour without alpha which is still
325
+ * visible — the halo is decorative).
326
+ */
327
+ function withAlpha(color: string, alpha: number): string {
328
+ const trimmed = color.trim();
329
+ if (trimmed.startsWith("#")) {
330
+ const hex = trimmed.slice(1);
331
+ if (hex.length === 3) {
332
+ // `String.prototype.slice` always returns a string (possibly
333
+ // empty) — never `undefined` — so duplicating each digit is
334
+ // safe to feed to `parseInt` without further narrowing.
335
+ const r = parseInt(hex.slice(0, 1).repeat(2), 16);
336
+ const g = parseInt(hex.slice(1, 2).repeat(2), 16);
337
+ const b = parseInt(hex.slice(2, 3).repeat(2), 16);
338
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
339
+ }
340
+ if (hex.length === 6) {
341
+ const r = parseInt(hex.slice(0, 2), 16);
342
+ const g = parseInt(hex.slice(2, 4), 16);
343
+ const b = parseInt(hex.slice(4, 6), 16);
344
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
345
+ }
346
+ }
347
+ // Best-effort fallback for non-hex colours (rgb/hsl/named): use
348
+ // `color-mix` with transparent. Safe to land in inline style — the
349
+ // halo is purely decorative and will gracefully degrade.
350
+ return `color-mix(in srgb, ${trimmed} ${Math.round(alpha * 100)}%, transparent)`;
351
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Minimally-styled primary action button.
3
+ *
4
+ * Visual styling now flows through {@link buttonStyle}: the background,
5
+ * border, foreground, radius, typography and elevation all derive from
6
+ * the active {@link useTheme}'s `intent.primary` slot. Override the
7
+ * variant when a non-primary call site needs a different emphasis (the
8
+ * underlying `<DefaultInteractionButton>` is the canonical button for
9
+ * interaction-bound submission).
10
+ */
11
+
12
+ import type { ButtonHTMLAttributes } from "react";
13
+ import type { ButtonSize, ButtonVariant } from "../theme/derive.js";
14
+ import { ThemedButton } from "./ThemedButton.js";
15
+
16
+ export interface PrimaryButtonProps
17
+ extends ButtonHTMLAttributes<HTMLButtonElement> {
18
+ /** Intent slot — defaults to `primary`. */
19
+ variant?: ButtonVariant;
20
+ /** Sizing token — defaults to `md`. */
21
+ size?: ButtonSize;
22
+ }
23
+
24
+ export function PrimaryButton({
25
+ children,
26
+ disabled,
27
+ style,
28
+ variant = "primary",
29
+ size = "md",
30
+ ...rest
31
+ }: PrimaryButtonProps) {
32
+ return (
33
+ <ThemedButton
34
+ type="button"
35
+ disabled={disabled}
36
+ variant={variant}
37
+ size={size}
38
+ style={style}
39
+ {...rest}
40
+ >
41
+ {children}
42
+ </ThemedButton>
43
+ );
44
+ }