@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,93 @@
1
+ import { expect, test } from "bun:test";
2
+ import { renderToString } from "react-dom/server";
3
+ import { ThemeProvider } from "../theme/ThemeProvider.js";
4
+ import { MoreActions } from "./MoreActions.js";
5
+
6
+ // React SSR splits text nodes around dynamic children with HTML
7
+ // comments. Strip them before substring asserting so tests stay
8
+ // readable.
9
+ function stripSsrComments(html: string): string {
10
+ return html.replace(/<!--\s*-->/g, "");
11
+ }
12
+
13
+ test("MoreActions renders collapsed by default and tags state via aria + data attrs", () => {
14
+ // Default should always be closed so the disclosure doesn't show
15
+ // its hidden content unprompted (the whole point is to fight choice
16
+ // overload). The aria + data-* hooks let tests / hand-rolled CSS
17
+ // target the open state without looking at children.
18
+ const html = renderToString(
19
+ <ThemeProvider>
20
+ <MoreActions count={3}>
21
+ <button data-testid="hidden">Forfeit</button>
22
+ </MoreActions>
23
+ </ThemeProvider>,
24
+ );
25
+
26
+ expect(html).toContain('data-shell-slot="more-actions"');
27
+ expect(html).toContain('data-more-actions-open="false"');
28
+ expect(html).toContain('aria-expanded="false"');
29
+ // Toggle exposes the count so the player knows N actions are
30
+ // hidden without expanding first (ambient awareness).
31
+ expect(stripSsrComments(html)).toContain("(3)");
32
+ expect(html).not.toContain('data-testid="hidden"');
33
+ });
34
+
35
+ test("MoreActions opens by default when defaultOpen is true and exposes the disclosed region", () => {
36
+ const html = renderToString(
37
+ <ThemeProvider>
38
+ <MoreActions count={1} defaultOpen>
39
+ <button data-testid="visible">Forfeit</button>
40
+ </MoreActions>
41
+ </ThemeProvider>,
42
+ );
43
+
44
+ expect(html).toContain('data-more-actions-open="true"');
45
+ expect(html).toContain('aria-expanded="true"');
46
+ // Region is mounted with `role="region"` and an aria-label that
47
+ // matches the toggle so screen readers announce it as a labelled
48
+ // landmark.
49
+ expect(html).toContain('role="region"');
50
+ expect(html).toContain('data-testid="visible"');
51
+ });
52
+
53
+ test("MoreActions accepts a custom toggle label", () => {
54
+ // Workspace-specific copy (e.g. "Advanced", "Manage") flows through
55
+ // unchanged so authors can match their game's voice without forking
56
+ // the component.
57
+ const html = renderToString(
58
+ <ThemeProvider>
59
+ <MoreActions label="Advanced" count={2}>
60
+ <button>x</button>
61
+ </MoreActions>
62
+ </ThemeProvider>,
63
+ );
64
+
65
+ expect(html).toContain("Advanced");
66
+ expect(stripSsrComments(html)).toContain("(2)");
67
+ });
68
+
69
+ test("MoreActions hides the count badge when count is zero or undefined", () => {
70
+ // The badge would otherwise read "(0)" which is just visual noise.
71
+ // We check by counting the `tabular-nums` style hint we attach to
72
+ // the badge — the badge is the only span carrying it inside the
73
+ // toggle, so its absence proves we didn't mount the badge.
74
+ const BADGE_MARKER = "font-variant-numeric:tabular-nums";
75
+
76
+ const noCount = renderToString(
77
+ <ThemeProvider>
78
+ <MoreActions>
79
+ <button>x</button>
80
+ </MoreActions>
81
+ </ThemeProvider>,
82
+ );
83
+ const zeroCount = renderToString(
84
+ <ThemeProvider>
85
+ <MoreActions count={0}>
86
+ <button>x</button>
87
+ </MoreActions>
88
+ </ThemeProvider>,
89
+ );
90
+
91
+ expect(noCount).not.toContain(BADGE_MARKER);
92
+ expect(zeroCount).not.toContain(BADGE_MARKER);
93
+ });
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Disclosure that hides a list of low-salience actions behind a single
3
+ * "More" toggle. Sized and styled by the active theme so it looks
4
+ * consistent with neighbouring `<DefaultInteractionButton>` rows.
5
+ *
6
+ * Why not a popover or dropdown? Two reasons:
7
+ *
8
+ * 1. **Layout safety.** The default panel surface lives directly above
9
+ * the hand strip; a floating popover would be obscured by the
10
+ * hand's `overflow-x: auto` clipping. An inline expansion stays
11
+ * inside the panel container and pushes neighbouring rows down.
12
+ * 2. **Discoverability.** Players miss menus that hide behind triple
13
+ * dots / chevrons. An expanded inline list still looks like a row
14
+ * of buttons (Jakob — same affordance as the always-visible row).
15
+ *
16
+ * The toggle reports its open state via `aria-expanded` and labels
17
+ * the disclosed region via `aria-controls` so screen readers announce
18
+ * "Expanded — More actions, region containing 3 buttons" naturally.
19
+ */
20
+
21
+ import { useId, useState, type CSSProperties, type ReactNode } from "react";
22
+ import { ChevronDown, MoreHorizontal } from "lucide-react";
23
+ import { useTheme } from "../theme/ThemeProvider.js";
24
+ import { ThemedButton } from "./ThemedButton.js";
25
+
26
+ export interface MoreActionsProps {
27
+ /**
28
+ * Items rendered inside the disclosure when expanded. Typically
29
+ * `<DefaultInteractionButton>` instances for `salience: "tertiary"`
30
+ * descriptors, but any `ReactNode` works (custom panel cards, etc.).
31
+ */
32
+ children: ReactNode;
33
+ /**
34
+ * Toggle label. Defaults to `"More"`. The descriptor count gets
35
+ * appended automatically when {@link count} is supplied.
36
+ */
37
+ label?: string;
38
+ /**
39
+ * Number of hidden items, used to render the trailing "(N)" badge.
40
+ * Omit when the count is irrelevant or already implied (e.g. when
41
+ * the panel only has a fixed set of disclosed items).
42
+ */
43
+ count?: number;
44
+ /**
45
+ * Initial open state. Defaults to `false` — the disclosure is the
46
+ * point. Authors who want it open by default for a specific seat
47
+ * (e.g. tutorial mode) should pass `true`.
48
+ */
49
+ defaultOpen?: boolean;
50
+ /** Additional inline style merged after the default container. */
51
+ style?: CSSProperties;
52
+ }
53
+
54
+ export function MoreActions({
55
+ children,
56
+ label = "More",
57
+ count,
58
+ defaultOpen = false,
59
+ style,
60
+ }: MoreActionsProps) {
61
+ const theme = useTheme();
62
+ const [open, setOpen] = useState(defaultOpen);
63
+ // `useId` keeps the aria pairing stable across re-renders even when
64
+ // many `<MoreActions>` exist on screen (rare, but easy to break).
65
+ const regionId = `more-actions-${useId().replace(/:/g, "")}`;
66
+
67
+ const showCount = typeof count === "number" && count > 0;
68
+
69
+ return (
70
+ <div
71
+ data-shell-slot="more-actions"
72
+ data-more-actions-open={open ? "true" : "false"}
73
+ style={{
74
+ display: "flex",
75
+ flexDirection: "column",
76
+ gap: theme.space[2],
77
+ alignItems: "stretch",
78
+ // The disclosure is its own block so wrap behaviour upstream
79
+ // doesn't interleave the toggle and the disclosed items.
80
+ flex: "1 1 100%",
81
+ minWidth: 0,
82
+ }}
83
+ >
84
+ <ThemedButton
85
+ type="button"
86
+ variant="secondary"
87
+ size="md"
88
+ aria-expanded={open}
89
+ aria-controls={regionId}
90
+ onClick={() => setOpen((value) => !value)}
91
+ style={{
92
+ display: "inline-flex",
93
+ alignItems: "center",
94
+ gap: theme.space[1],
95
+ ...style,
96
+ }}
97
+ >
98
+ <MoreHorizontal size={16} aria-hidden />
99
+ <span>{label}</span>
100
+ {showCount ? (
101
+ <span
102
+ aria-hidden
103
+ style={{
104
+ fontVariantNumeric: "tabular-nums",
105
+ opacity: 0.78,
106
+ }}
107
+ >
108
+ ({count})
109
+ </span>
110
+ ) : null}
111
+ <ChevronDown
112
+ size={14}
113
+ aria-hidden
114
+ style={{
115
+ transform: open ? "rotate(180deg)" : "rotate(0deg)",
116
+ transition: `transform ${theme.motion.duration.fast} ${theme.motion.easing.out}`,
117
+ }}
118
+ />
119
+ </ThemedButton>
120
+ {open ? (
121
+ <div
122
+ id={regionId}
123
+ role="region"
124
+ aria-label={label}
125
+ style={{
126
+ display: "flex",
127
+ flexDirection: "row",
128
+ flexWrap: "wrap",
129
+ gap: theme.space[2],
130
+ // Inset visually so the disclosed cluster reads as a
131
+ // sub-region rather than a continuation of the main panel.
132
+ paddingInline: theme.space[3],
133
+ paddingBlock: theme.space[2],
134
+ background: theme.semantic.surface.inset,
135
+ borderRadius: theme.radius.lg,
136
+ }}
137
+ >
138
+ {children}
139
+ </div>
140
+ ) : null}
141
+ </div>
142
+ );
143
+ }
@@ -0,0 +1,341 @@
1
+ /**
2
+ * Surfaces the dominant turn-state question — "is this me?" / "who am I
3
+ * waiting for?" — as a single headline, with the phase label demoted to
4
+ * secondary copy underneath.
5
+ *
6
+ * The previous design rendered three peer chips (`Waiting`, `Roll dice`,
7
+ * `Player 3`) which fragmented one piece of information across three
8
+ * UI atoms. The redesign collapses them into a single status line:
9
+ *
10
+ * ◐ Waiting for Player 3…
11
+ * Roll dice
12
+ *
13
+ * ● Your turn
14
+ * Build, trade, or end your turn.
15
+ *
16
+ * Both states get a leading status indicator that animates so the eye
17
+ * picks up the "live" cue immediately. Animations are skipped when the
18
+ * theme reports `motion.reducedMotion === "true"`.
19
+ *
20
+ * Prop API is unchanged so existing callers (Catan, Things-in-Rings,
21
+ * tests) keep working without edits. The `variant` knob still toggles
22
+ * surface treatment (`badge` is layout-only, `bar` wraps the headline
23
+ * in an inset HUD strip, `minimal` collapses to a single underlined
24
+ * label for tight HUDs).
25
+ */
26
+
27
+ import { motion } from "framer-motion";
28
+ import { clsx } from "clsx";
29
+ import { useTheme } from "../theme/ThemeProvider.js";
30
+ import { surfaceStyle } from "../theme/derive.js";
31
+ import type { Theme } from "../theme/tokens.js";
32
+
33
+ export interface PhaseIndicatorProps {
34
+ currentPhase: string;
35
+ phaseLabels?: Record<string, string>;
36
+ isMyTurn?: boolean;
37
+ activePlayerNames?: string[];
38
+ variant?: "badge" | "bar" | "minimal";
39
+ className?: string;
40
+ }
41
+
42
+ export function PhaseIndicator({
43
+ currentPhase,
44
+ phaseLabels,
45
+ isMyTurn,
46
+ activePlayerNames,
47
+ variant = "badge",
48
+ className,
49
+ }: PhaseIndicatorProps) {
50
+ const theme = useTheme();
51
+ const reducedMotion = theme.motion.reducedMotion === "true";
52
+
53
+ const label = phaseLabels?.[currentPhase] ?? formatPhase(currentPhase);
54
+
55
+ if (variant === "minimal") {
56
+ return (
57
+ <span
58
+ className={className}
59
+ style={{
60
+ fontFamily: theme.typography.fontFamily.body,
61
+ fontSize: theme.typography.fontSize.sm,
62
+ fontWeight: theme.typography.fontWeight.bold,
63
+ color: theme.semantic.text.muted,
64
+ textDecoration: "underline",
65
+ textDecorationStyle: "wavy",
66
+ textDecorationColor: theme.semantic.border.subtle,
67
+ textUnderlineOffset: "4px",
68
+ }}
69
+ role="status"
70
+ aria-label={`Current phase: ${label}`}
71
+ >
72
+ {label}
73
+ </span>
74
+ );
75
+ }
76
+
77
+ // Decide which state we're in. Only one is active at a time so the
78
+ // headline is unambiguous.
79
+ const state: "your-turn" | "waiting" | "phase-only" =
80
+ isMyTurn === true
81
+ ? "your-turn"
82
+ : isMyTurn === false && activePlayerNames && activePlayerNames.length > 0
83
+ ? "waiting"
84
+ : "phase-only";
85
+
86
+ const containerStyle: React.CSSProperties =
87
+ variant === "bar"
88
+ ? {
89
+ ...surfaceStyle(theme, { tone: "inset", radius: "hud" }),
90
+ padding: theme.space[3],
91
+ boxShadow: theme.elevation.inset,
92
+ fontFamily: theme.typography.fontFamily.body,
93
+ }
94
+ : { fontFamily: theme.typography.fontFamily.body };
95
+
96
+ const headline = renderHeadline({
97
+ state,
98
+ activePlayerNames,
99
+ label,
100
+ theme,
101
+ reducedMotion,
102
+ });
103
+ const ariaLabel =
104
+ state === "your-turn"
105
+ ? `Your turn — ${label}`
106
+ : state === "waiting"
107
+ ? `Waiting for ${formatPlayerList(activePlayerNames ?? [])} — ${label}`
108
+ : label;
109
+
110
+ return (
111
+ <div
112
+ className={clsx("flex items-center gap-3 flex-wrap", className)}
113
+ style={containerStyle}
114
+ role="status"
115
+ aria-live="polite"
116
+ aria-atomic="true"
117
+ aria-label={ariaLabel}
118
+ >
119
+ {headline}
120
+ </div>
121
+ );
122
+ }
123
+
124
+ function formatPhase(phase: string): string {
125
+ const formatted = phase
126
+ .replace(/([A-Z])/g, " $1")
127
+ .replace(/_/g, " ")
128
+ .trim()
129
+ .replace(/^\w/, (c) => c.toUpperCase());
130
+ return formatted === "Player Turn" ? "Turn" : formatted;
131
+ }
132
+
133
+ function formatPlayerList(names: readonly string[]): string {
134
+ if (names.length === 0) return "";
135
+ if (names.length === 1) return names[0]!;
136
+ if (names.length === 2) return `${names[0]} and ${names[1]}`;
137
+ return `${names.slice(0, -1).join(", ")}, and ${names[names.length - 1]}`;
138
+ }
139
+
140
+ interface HeadlineArgs {
141
+ state: "your-turn" | "waiting" | "phase-only";
142
+ activePlayerNames: readonly string[] | undefined;
143
+ label: string;
144
+ theme: Theme;
145
+ reducedMotion: boolean;
146
+ }
147
+
148
+ function renderHeadline({
149
+ state,
150
+ activePlayerNames,
151
+ label,
152
+ theme,
153
+ reducedMotion,
154
+ }: HeadlineArgs) {
155
+ if (state === "phase-only") {
156
+ return (
157
+ <div style={textBlockStyle(theme)}>
158
+ <span style={titleTextStyle(theme)}>{label}</span>
159
+ </div>
160
+ );
161
+ }
162
+
163
+ const isYourTurn = state === "your-turn";
164
+ const intent = isYourTurn
165
+ ? theme.semantic.intent.success
166
+ : theme.semantic.intent.info;
167
+ const titleText = isYourTurn
168
+ ? "Your turn"
169
+ : `Waiting for ${formatPlayerList(activePlayerNames ?? [])}…`;
170
+
171
+ return (
172
+ <motion.div
173
+ initial={reducedMotion ? { opacity: 0 } : { opacity: 0, y: -4 }}
174
+ animate={reducedMotion ? { opacity: 1 } : { opacity: 1, y: 0 }}
175
+ style={{
176
+ display: "flex",
177
+ alignItems: "center",
178
+ gap: theme.space[3],
179
+ }}
180
+ >
181
+ <StatusIndicator
182
+ intent={intent}
183
+ kind={isYourTurn ? "pulse" : "spinner"}
184
+ reducedMotion={reducedMotion}
185
+ />
186
+ <div style={textBlockStyle(theme)}>
187
+ <span style={titleTextStyle(theme)}>{titleText}</span>
188
+ <span style={subtitleTextStyle(theme)}>{label}</span>
189
+ </div>
190
+ </motion.div>
191
+ );
192
+ }
193
+
194
+ function textBlockStyle(theme: Theme): React.CSSProperties {
195
+ return {
196
+ display: "flex",
197
+ flexDirection: "column",
198
+ alignItems: "flex-start",
199
+ gap: theme.space[0.5],
200
+ minWidth: 0,
201
+ };
202
+ }
203
+
204
+ function titleTextStyle(theme: Theme): React.CSSProperties {
205
+ return {
206
+ fontFamily: theme.typography.fontFamily.body,
207
+ fontSize: theme.typography.fontSize.lg,
208
+ fontWeight: theme.typography.fontWeight.bold,
209
+ letterSpacing: theme.typography.letterSpacing.tight,
210
+ lineHeight: theme.typography.lineHeight.tight,
211
+ color: theme.semantic.text.primary,
212
+ };
213
+ }
214
+
215
+ function subtitleTextStyle(theme: Theme): React.CSSProperties {
216
+ return {
217
+ fontFamily: theme.typography.fontFamily.body,
218
+ fontSize: theme.typography.fontSize.xs,
219
+ fontWeight: theme.typography.fontWeight.medium,
220
+ letterSpacing: theme.typography.letterSpacing.caps,
221
+ textTransform: "uppercase",
222
+ color: theme.semantic.text.muted,
223
+ };
224
+ }
225
+
226
+ interface StatusIndicatorProps {
227
+ intent: Theme["semantic"]["intent"]["success"];
228
+ kind: "pulse" | "spinner";
229
+ reducedMotion: boolean;
230
+ }
231
+
232
+ /**
233
+ * Animated status indicator sized to sit next to a body-font headline.
234
+ *
235
+ * Kept intentionally chip-less so it shares the chrome strip's
236
+ * understated treatment (the rest of the chrome uses bare typography
237
+ * and lets intent colour live in chips, dots, and borders — not in
238
+ * full-saturation surfaces). The colour comes from one
239
+ * {@link Theme}-supplied intent ramp so swapping themes keeps the
240
+ * indicator consistent with chips, toasts, and buttons that use the
241
+ * same intent.
242
+ *
243
+ * - `pulse` — soft halo radiating outward from a solid centre dot.
244
+ * Used for the player's own active turn.
245
+ * - `spinner` — thin partial ring rotating around a soft-filled
246
+ * centre. Reads as the standard "in flight / live" affordance.
247
+ */
248
+ function StatusIndicator({
249
+ intent,
250
+ kind,
251
+ reducedMotion,
252
+ }: StatusIndicatorProps) {
253
+ const size = 16;
254
+
255
+ if (kind === "pulse") {
256
+ return (
257
+ <span
258
+ aria-hidden="true"
259
+ style={{
260
+ position: "relative",
261
+ display: "inline-flex",
262
+ alignItems: "center",
263
+ justifyContent: "center",
264
+ width: size,
265
+ height: size,
266
+ flexShrink: 0,
267
+ }}
268
+ >
269
+ <motion.span
270
+ style={{
271
+ position: "absolute",
272
+ inset: 2,
273
+ borderRadius: "9999px",
274
+ background: intent.soft,
275
+ }}
276
+ animate={
277
+ reducedMotion
278
+ ? undefined
279
+ : { scale: [1, 1.6, 1], opacity: [0.7, 0, 0.7] }
280
+ }
281
+ transition={{
282
+ repeat: reducedMotion ? 0 : Infinity,
283
+ duration: 1.6,
284
+ ease: "easeOut",
285
+ }}
286
+ />
287
+ <span
288
+ style={{
289
+ position: "relative",
290
+ display: "inline-block",
291
+ width: 8,
292
+ height: 8,
293
+ borderRadius: "9999px",
294
+ background: intent.solid,
295
+ }}
296
+ />
297
+ </span>
298
+ );
299
+ }
300
+
301
+ // "spinner" — partial ring rotating around an inset soft fill.
302
+ return (
303
+ <span
304
+ aria-hidden="true"
305
+ style={{
306
+ position: "relative",
307
+ display: "inline-flex",
308
+ alignItems: "center",
309
+ justifyContent: "center",
310
+ width: size,
311
+ height: size,
312
+ flexShrink: 0,
313
+ }}
314
+ >
315
+ <span
316
+ style={{
317
+ position: "absolute",
318
+ inset: 4,
319
+ borderRadius: "9999px",
320
+ background: intent.soft,
321
+ }}
322
+ />
323
+ <motion.span
324
+ style={{
325
+ position: "absolute",
326
+ inset: 0,
327
+ borderRadius: "9999px",
328
+ border: "2px solid transparent",
329
+ borderTopColor: intent.solid,
330
+ borderRightColor: intent.solid,
331
+ }}
332
+ animate={reducedMotion ? undefined : { rotate: 360 }}
333
+ transition={{
334
+ repeat: reducedMotion ? 0 : Infinity,
335
+ duration: 1.2,
336
+ ease: "linear",
337
+ }}
338
+ />
339
+ </span>
340
+ );
341
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Central game board area for active game components (tricks, played
3
+ * cards, zones, etc.).
4
+ *
5
+ * Visual chrome (frame, empty placeholder, card entrance/exit) is
6
+ * sourced from the active {@link useTheme} so the play area re-skins
7
+ * with the rest of the shell.
8
+ */
9
+
10
+ import { motion, AnimatePresence } from "framer-motion";
11
+ import { clsx } from "clsx";
12
+ import { Card, type CardProps, type ViewCard } from "./Card.js";
13
+ import { useTheme } from "../theme/ThemeProvider.js";
14
+ import { chipStyle } from "../theme/derive.js";
15
+
16
+ export interface PlayAreaProps<CardData extends ViewCard = ViewCard> {
17
+ cards: readonly CardData[];
18
+ filter?: (card: CardData) => boolean;
19
+ cardSize?: CardProps["size"];
20
+ renderCard?: CardProps<CardData>["renderContent"];
21
+ layout?: "grid" | "row";
22
+ interactive?: boolean;
23
+ onCardClick?: (cardId: string) => void;
24
+ "aria-label"?: string;
25
+ className?: string;
26
+ }
27
+
28
+ /**
29
+ * @example
30
+ * ```tsx
31
+ * <PlayArea cards={trickCards} layout="row" renderCard={(card) => <PlayingCard card={card} />} />
32
+ * ```
33
+ */
34
+ export function PlayArea<CardData extends ViewCard = ViewCard>({
35
+ cards,
36
+ filter,
37
+ cardSize = "md",
38
+ renderCard,
39
+ layout = "row",
40
+ interactive = false,
41
+ onCardClick,
42
+ "aria-label": ariaLabel = "Play area",
43
+ className,
44
+ }: PlayAreaProps<CardData>) {
45
+ const theme = useTheme();
46
+ const reducedMotion = theme.motion.reducedMotion === "true";
47
+ const visibleCards = filter ? cards.filter(filter) : cards;
48
+
49
+ const layoutClasses = {
50
+ grid: "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 sm:gap-6",
51
+ row: "flex flex-wrap items-center justify-center gap-3 sm:gap-6",
52
+ };
53
+
54
+ return (
55
+ <div
56
+ className={clsx(
57
+ "relative w-full min-h-[200px] sm:min-h-[300px] p-6 sm:p-8",
58
+ className,
59
+ )}
60
+ style={{
61
+ background: theme.semantic.surface.inset,
62
+ border: `2px dashed ${theme.semantic.border.default}`,
63
+ borderRadius: theme.radius.hud,
64
+ fontFamily: theme.typography.fontFamily.body,
65
+ color: theme.semantic.text.primary,
66
+ }}
67
+ role="region"
68
+ aria-label={`${ariaLabel} - ${visibleCards.length} item${visibleCards.length !== 1 ? "s" : ""}`}
69
+ >
70
+ <AnimatePresence mode="popLayout">
71
+ {visibleCards.length === 0 ? (
72
+ <motion.div
73
+ className="flex items-center justify-center h-full absolute inset-0 pointer-events-none"
74
+ initial={{ opacity: 0 }}
75
+ animate={{ opacity: 1 }}
76
+ exit={{ opacity: 0 }}
77
+ role="status"
78
+ aria-live="polite"
79
+ >
80
+ <div
81
+ style={{
82
+ ...chipStyle(theme, { variant: "warning", size: "md" }),
83
+ fontSize: theme.typography.fontSize.md,
84
+ paddingBlock: theme.space[2],
85
+ paddingInline: theme.space[4],
86
+ }}
87
+ >
88
+ No cards in play
89
+ </div>
90
+ </motion.div>
91
+ ) : (
92
+ <div className={layoutClasses[layout]}>
93
+ {visibleCards.map((card, index) => (
94
+ <motion.div
95
+ key={card.id}
96
+ layout={!reducedMotion}
97
+ initial={
98
+ reducedMotion
99
+ ? { opacity: 0, scale: 1 }
100
+ : { opacity: 0, scale: 0.8 }
101
+ }
102
+ animate={{ opacity: 1, scale: 1 }}
103
+ exit={{ opacity: 0, scale: 0.8 }}
104
+ transition={{
105
+ type: "spring",
106
+ stiffness: 260,
107
+ damping: 20,
108
+ delay: reducedMotion ? 0 : index * 0.05,
109
+ }}
110
+ >
111
+ <Card
112
+ card={card}
113
+ size={cardSize}
114
+ renderContent={renderCard}
115
+ disabled={!interactive}
116
+ onCardClick={interactive ? onCardClick : undefined}
117
+ />
118
+ </motion.div>
119
+ ))}
120
+ </div>
121
+ )}
122
+ </AnimatePresence>
123
+ </div>
124
+ );
125
+ }