@dreamboard-games/sdk 0.2.0

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 (369) hide show
  1. package/LICENSE.md +96 -0
  2. package/README.md +12 -0
  3. package/dist/HandView-ncJIVLhN.d.ts +193 -0
  4. package/dist/ResourceCounter-CTREyF73.d.ts +102 -0
  5. package/dist/ThemeProvider-fy0_QzgO.d.ts +99 -0
  6. package/dist/bundle-TIZcw8LB.d.ts +281 -0
  7. package/dist/cards-Sl3b40Mv.d.ts +13 -0
  8. package/dist/chunk-7YAHLYBR.js +481 -0
  9. package/dist/chunk-7YAHLYBR.js.map +1 -0
  10. package/dist/chunk-FDNZTDD6.js +8085 -0
  11. package/dist/chunk-FDNZTDD6.js.map +1 -0
  12. package/dist/chunk-GKKBPPSW.js +598 -0
  13. package/dist/chunk-GKKBPPSW.js.map +1 -0
  14. package/dist/chunk-I46YJSOD.js +1 -0
  15. package/dist/chunk-I46YJSOD.js.map +1 -0
  16. package/dist/chunk-KAELH4KC.js +104 -0
  17. package/dist/chunk-KAELH4KC.js.map +1 -0
  18. package/dist/chunk-PZ5AY32C.js +10 -0
  19. package/dist/chunk-PZ5AY32C.js.map +1 -0
  20. package/dist/chunk-T3ZKNUZ7.js +1 -0
  21. package/dist/chunk-T3ZKNUZ7.js.map +1 -0
  22. package/dist/chunk-T52J5RMF.js +1 -0
  23. package/dist/chunk-T52J5RMF.js.map +1 -0
  24. package/dist/chunk-TDSWKVZ4.js +5401 -0
  25. package/dist/chunk-TDSWKVZ4.js.map +1 -0
  26. package/dist/chunk-U5C6BONG.js +34 -0
  27. package/dist/chunk-U5C6BONG.js.map +1 -0
  28. package/dist/chunk-VDXOF4FW.js +69 -0
  29. package/dist/chunk-VDXOF4FW.js.map +1 -0
  30. package/dist/chunk-VFTAA4WO.js +115 -0
  31. package/dist/chunk-VFTAA4WO.js.map +1 -0
  32. package/dist/chunk-WN74KVNY.js +17 -0
  33. package/dist/chunk-WN74KVNY.js.map +1 -0
  34. package/dist/chunk-WYPQ3GG5.js +10990 -0
  35. package/dist/chunk-WYPQ3GG5.js.map +1 -0
  36. package/dist/components-D5ZRE2Hl.d.ts +1451 -0
  37. package/dist/generated/runtime/primitives.d.ts +12 -0
  38. package/dist/generated/runtime/primitives.js +180 -0
  39. package/dist/generated/runtime/primitives.js.map +1 -0
  40. package/dist/generated/runtime-api.d.ts +3 -0
  41. package/dist/generated/runtime-api.js +2 -0
  42. package/dist/generated/runtime-api.js.map +1 -0
  43. package/dist/generated/runtime.d.ts +14 -0
  44. package/dist/generated/runtime.js +18 -0
  45. package/dist/generated/runtime.js.map +1 -0
  46. package/dist/generated/workspace-contract.d.ts +14 -0
  47. package/dist/generated/workspace-contract.js +14 -0
  48. package/dist/generated/workspace-contract.js.map +1 -0
  49. package/dist/hex-board-view-D_07hO6O.d.ts +933 -0
  50. package/dist/hex-color-MhOyuY-o.d.ts +8 -0
  51. package/dist/index-BwqPQtBu.d.ts +1433 -0
  52. package/dist/index.d.ts +1 -0
  53. package/dist/index.js +12 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/infrastructure/reducer-bundle-abi.d.ts +1083 -0
  56. package/dist/infrastructure/reducer-bundle-abi.js +14 -0
  57. package/dist/infrastructure/reducer-bundle-abi.js.map +1 -0
  58. package/dist/infrastructure/workspace-codegen.d.ts +53 -0
  59. package/dist/infrastructure/workspace-codegen.js +44 -0
  60. package/dist/infrastructure/workspace-codegen.js.map +1 -0
  61. package/dist/manifest-contract-BNHVGFtU.d.ts +9 -0
  62. package/dist/package-set.d.ts +13 -0
  63. package/dist/package-set.js +12 -0
  64. package/dist/package-set.js.map +1 -0
  65. package/dist/primitive-props-DpKs-GCr.d.ts +11 -0
  66. package/dist/reducer.d.ts +3786 -0
  67. package/dist/reducer.js +8131 -0
  68. package/dist/reducer.js.map +1 -0
  69. package/dist/runtime/primitives.d.ts +226 -0
  70. package/dist/runtime/primitives.js +180 -0
  71. package/dist/runtime/primitives.js.map +1 -0
  72. package/dist/runtime/types/runtime-api.d.ts +1 -0
  73. package/dist/runtime/types/runtime-api.js +2 -0
  74. package/dist/runtime/types/runtime-api.js.map +1 -0
  75. package/dist/runtime/workspace-contract.d.ts +172 -0
  76. package/dist/runtime/workspace-contract.js +14 -0
  77. package/dist/runtime/workspace-contract.js.map +1 -0
  78. package/dist/runtime-api-3dshj6kK.d.ts +101 -0
  79. package/dist/runtime-api-DWxvTr-O.d.ts +379 -0
  80. package/dist/runtime.d.ts +58 -0
  81. package/dist/runtime.js +13 -0
  82. package/dist/runtime.js.map +1 -0
  83. package/dist/slots-1GPGihk8.d.ts +8 -0
  84. package/dist/testing.d.ts +149 -0
  85. package/dist/testing.js +513 -0
  86. package/dist/testing.js.map +1 -0
  87. package/dist/types.d.ts +496 -0
  88. package/dist/types.js +28 -0
  89. package/dist/types.js.map +1 -0
  90. package/dist/ui/components.d.ts +16 -0
  91. package/dist/ui/components.js +192 -0
  92. package/dist/ui/components.js.map +1 -0
  93. package/dist/ui/defaults.d.ts +19 -0
  94. package/dist/ui/defaults.js +104 -0
  95. package/dist/ui/defaults.js.map +1 -0
  96. package/dist/ui/plugin-styles.css +250 -0
  97. package/dist/ui/types/player-state.d.ts +365 -0
  98. package/dist/ui/types/player-state.js +1 -0
  99. package/dist/ui/types/player-state.js.map +1 -0
  100. package/dist/ui-contract-iQfTtUSL.d.ts +1161 -0
  101. package/dist/ui.d.ts +320 -0
  102. package/dist/ui.js +253 -0
  103. package/dist/ui.js.map +1 -0
  104. package/package.json +199 -0
  105. package/src/generated/reducer-contract/builders.ts +90 -0
  106. package/src/generated/reducer-contract/version.ts +9 -0
  107. package/src/generated/reducer-contract/wire.ts +100 -0
  108. package/src/generated/reducer-contract/zod.ts +101 -0
  109. package/src/generated/runtime/primitives.ts +2 -0
  110. package/src/generated/runtime-api.ts +5 -0
  111. package/src/generated/runtime.ts +35 -0
  112. package/src/generated/workspace-contract.ts +2 -0
  113. package/src/index.ts +7 -0
  114. package/src/infrastructure/reducer-bundle-abi.ts +8 -0
  115. package/src/infrastructure/reducer-contract/bundle.ts +37 -0
  116. package/src/infrastructure/workspace-codegen/hex-geometry.ts +69 -0
  117. package/src/infrastructure/workspace-codegen/index.ts +64 -0
  118. package/src/infrastructure/workspace-codegen/manifest-contract.ts +6632 -0
  119. package/src/infrastructure/workspace-codegen/manifest-validation.ts +795 -0
  120. package/src/infrastructure/workspace-codegen/ownership.ts +131 -0
  121. package/src/infrastructure/workspace-codegen/preset-card-sets.ts +169 -0
  122. package/src/infrastructure/workspace-codegen/seeds.ts +1705 -0
  123. package/src/infrastructure/workspace-codegen.ts +1 -0
  124. package/src/package-set.ts +19 -0
  125. package/src/reducer/authoring/contract.ts +157 -0
  126. package/src/reducer/authoring/effect.ts +224 -0
  127. package/src/reducer/authoring/game.ts +23 -0
  128. package/src/reducer/authoring/interaction.ts +98 -0
  129. package/src/reducer/authoring/phase.ts +300 -0
  130. package/src/reducer/authoring/types.ts +70 -0
  131. package/src/reducer/authoring/validation.ts +382 -0
  132. package/src/reducer/authoring/view-stage.ts +68 -0
  133. package/src/reducer/authoring.ts +29 -0
  134. package/src/reducer/bundle/ingress-bundle.ts +491 -0
  135. package/src/reducer/bundle/trusted/engine-instruction-resolver.ts +254 -0
  136. package/src/reducer/bundle/trusted/flow-instruction-resolver.ts +73 -0
  137. package/src/reducer/bundle/trusted/instruction-runner.ts +414 -0
  138. package/src/reducer/bundle/trusted/interaction-authorization.ts +137 -0
  139. package/src/reducer/bundle/trusted/interaction-collectors.ts +859 -0
  140. package/src/reducer/bundle/trusted/interaction-decision.ts +747 -0
  141. package/src/reducer/bundle/trusted/interaction-resolver.ts +95 -0
  142. package/src/reducer/bundle/trusted/interaction-types.ts +171 -0
  143. package/src/reducer/bundle/trusted/lifecycle-runner.ts +427 -0
  144. package/src/reducer/bundle/trusted/projection-builder.ts +356 -0
  145. package/src/reducer/bundle/trusted/projection-context.ts +39 -0
  146. package/src/reducer/bundle/trusted/rng-sampler.ts +150 -0
  147. package/src/reducer/bundle/trusted/runtime-registry.ts +120 -0
  148. package/src/reducer/bundle/trusted/runtime-scope.ts +336 -0
  149. package/src/reducer/bundle/trusted/simultaneous-player.ts +97 -0
  150. package/src/reducer/bundle/trusted/stage-resolver.ts +87 -0
  151. package/src/reducer/bundle/trusted/static-projection.ts +116 -0
  152. package/src/reducer/bundle/trusted/trusted-runtime-args.ts +97 -0
  153. package/src/reducer/bundle/trusted/trusted-runtime-result.ts +39 -0
  154. package/src/reducer/bundle/trusted/trusted-setup-profiles.ts +43 -0
  155. package/src/reducer/bundle/trusted/trusted-state-codec.ts +48 -0
  156. package/src/reducer/bundle/trusted-bundle.ts +97 -0
  157. package/src/reducer/bundle/types.ts +171 -0
  158. package/src/reducer/bundle.ts +2 -0
  159. package/src/reducer/client-param-schemas.ts +57 -0
  160. package/src/reducer/compose.ts +34 -0
  161. package/src/reducer/core/runtime-input.ts +30 -0
  162. package/src/reducer/core/runtime-instruction.ts +59 -0
  163. package/src/reducer/core/types.ts +62 -0
  164. package/src/reducer/definition-index.ts +277 -0
  165. package/src/reducer/derived.ts +106 -0
  166. package/src/reducer/effects.ts +92 -0
  167. package/src/reducer/engine/runtime-instruction-engine.ts +155 -0
  168. package/src/reducer/ingress/decode-runtime-input.ts +7 -0
  169. package/src/reducer/ingress/decode-session-state.ts +9 -0
  170. package/src/reducer/ingress/encode-session-state.ts +6 -0
  171. package/src/reducer/ingress/input-codec.ts +18 -0
  172. package/src/reducer/ingress/phase-schemas.ts +62 -0
  173. package/src/reducer/ingress/raw-types.ts +107 -0
  174. package/src/reducer/ingress/runtime-codec.ts +14 -0
  175. package/src/reducer/ingress/runtime-payload.ts +13 -0
  176. package/src/reducer/ingress/session-codec.ts +392 -0
  177. package/src/reducer/ingress/types.ts +6 -0
  178. package/src/reducer/inputs/boardInput.ts +217 -0
  179. package/src/reducer/inputs/boardTarget.ts +190 -0
  180. package/src/reducer/inputs/cardInput.ts +86 -0
  181. package/src/reducer/inputs/cardTarget.ts +101 -0
  182. package/src/reducer/inputs/choiceTarget.ts +104 -0
  183. package/src/reducer/inputs/defineInputs.ts +71 -0
  184. package/src/reducer/inputs/formInput.ts +809 -0
  185. package/src/reducer/inputs/many.ts +120 -0
  186. package/src/reducer/inputs/promptInput.ts +87 -0
  187. package/src/reducer/inputs/rngInput.ts +58 -0
  188. package/src/reducer/inputs/targetRule.ts +123 -0
  189. package/src/reducer/inputs.ts +41 -0
  190. package/src/reducer/model/definition.ts +1072 -0
  191. package/src/reducer/model/extract.ts +745 -0
  192. package/src/reducer/model/manifest.ts +570 -0
  193. package/src/reducer/model/queries.ts +641 -0
  194. package/src/reducer/model/runtime.ts +264 -0
  195. package/src/reducer/model/spec.ts +1386 -0
  196. package/src/reducer/model/table.ts +260 -0
  197. package/src/reducer/model.ts +7 -0
  198. package/src/reducer/ops.ts +1034 -0
  199. package/src/reducer/parse-utils.ts +28 -0
  200. package/src/reducer/per-player.ts +422 -0
  201. package/src/reducer/rng.ts +69 -0
  202. package/src/reducer/schema-helpers.ts +185 -0
  203. package/src/reducer/setup-bootstrap-helpers.ts +171 -0
  204. package/src/reducer/setup-bootstrap.ts +481 -0
  205. package/src/reducer/table-ops.ts +2671 -0
  206. package/src/reducer/table-queries.ts +372 -0
  207. package/src/reducer/transaction.ts +120 -0
  208. package/src/reducer.ts +314 -0
  209. package/src/runtime/primitives.ts +1 -0
  210. package/src/runtime/types/runtime-api.ts +1 -0
  211. package/src/runtime/workspace-contract.ts +32 -0
  212. package/src/runtime-internal/components/InteractionForm.tsx +1309 -0
  213. package/src/runtime-internal/components/PluginRuntime.tsx +103 -0
  214. package/src/runtime-internal/components/board/target-layer.ts +70 -0
  215. package/src/runtime-internal/context/ClientParamSchemaContext.tsx +44 -0
  216. package/src/runtime-internal/context/InteractionDraftContext.tsx +279 -0
  217. package/src/runtime-internal/context/PluginSessionContext.tsx +47 -0
  218. package/src/runtime-internal/context/PluginStateContext.tsx +262 -0
  219. package/src/runtime-internal/context/RuntimeContext.tsx +96 -0
  220. package/src/runtime-internal/defaults/components.tsx +409 -0
  221. package/src/runtime-internal/defaults/index.ts +11 -0
  222. package/src/runtime-internal/errors/ValidationError.ts +29 -0
  223. package/src/runtime-internal/hooks/useActivePlayers.ts +33 -0
  224. package/src/runtime-internal/hooks/useBoardInteractions.ts +665 -0
  225. package/src/runtime-internal/hooks/useGameSelector.ts +105 -0
  226. package/src/runtime-internal/hooks/useGameView.ts +9 -0
  227. package/src/runtime-internal/hooks/useInteractionByKey.ts +354 -0
  228. package/src/runtime-internal/hooks/useInteractionHandle.ts +438 -0
  229. package/src/runtime-internal/hooks/useIsMyTurn.ts +20 -0
  230. package/src/runtime-internal/hooks/useLobby.ts +76 -0
  231. package/src/runtime-internal/hooks/useMe.ts +48 -0
  232. package/src/runtime-internal/hooks/usePlayerInfo.ts +28 -0
  233. package/src/runtime-internal/hooks/usePlayerTurnOrder.ts +23 -0
  234. package/src/runtime-internal/hooks/usePluginRuntime.ts +147 -0
  235. package/src/runtime-internal/hooks/useSeatInbox.ts +61 -0
  236. package/src/runtime-internal/hooks/useSimultaneousPhase.ts +10 -0
  237. package/src/runtime-internal/index.ts +42 -0
  238. package/src/runtime-internal/internal.ts +43 -0
  239. package/src/runtime-internal/plugin-styles.css +250 -0
  240. package/src/runtime-internal/primitives/board.tsx +459 -0
  241. package/src/runtime-internal/primitives/dialog-lifecycle.ts +58 -0
  242. package/src/runtime-internal/primitives/dice.tsx +79 -0
  243. package/src/runtime-internal/primitives/game-ui-provider.tsx +35 -0
  244. package/src/runtime-internal/primitives/game.tsx +387 -0
  245. package/src/runtime-internal/primitives/hand-intent-adapter.ts +147 -0
  246. package/src/runtime-internal/primitives/hand-surface.tsx +594 -0
  247. package/src/runtime-internal/primitives/index.ts +196 -0
  248. package/src/runtime-internal/primitives/interaction-form-binding.tsx +56 -0
  249. package/src/runtime-internal/primitives/interaction-submit.ts +90 -0
  250. package/src/runtime-internal/primitives/interaction.tsx +987 -0
  251. package/src/runtime-internal/primitives/phase.tsx +43 -0
  252. package/src/runtime-internal/primitives/player-roster.tsx +302 -0
  253. package/src/runtime-internal/primitives/primitive-props.tsx +101 -0
  254. package/src/runtime-internal/primitives/prompt.tsx +255 -0
  255. package/src/runtime-internal/primitives/ui.tsx +60 -0
  256. package/src/runtime-internal/primitives/zone.tsx +791 -0
  257. package/src/runtime-internal/reducer.ts +30 -0
  258. package/src/runtime-internal/runtime/createPluginRuntimeAPI.ts +605 -0
  259. package/src/runtime-internal/types/plugin-state.ts +508 -0
  260. package/src/runtime-internal/types/reducer-state.ts +24 -0
  261. package/src/runtime-internal/types/runtime-api.ts +114 -0
  262. package/src/runtime-internal/ui-contract.ts +519 -0
  263. package/src/runtime-internal/utils/card-intent-adapter.ts +546 -0
  264. package/src/runtime-internal/utils/interaction-inputs.ts +492 -0
  265. package/src/runtime-internal/utils/interaction-labels.ts +23 -0
  266. package/src/runtime-internal/utils/interaction-router.ts +273 -0
  267. package/src/runtime-internal/utils/interaction-status.ts +74 -0
  268. package/src/runtime-internal/workspace-contract.ts +1170 -0
  269. package/src/runtime.ts +34 -0
  270. package/src/testing/create-expect-api.ts +352 -0
  271. package/src/testing/create-test-runtime.ts +381 -0
  272. package/src/testing/definitions.ts +127 -0
  273. package/src/testing/index.ts +3 -0
  274. package/src/testing.ts +1 -0
  275. package/src/type-stubs/manifest-contract.d.ts +42 -0
  276. package/src/type-stubs/manifest-contract.js +72 -0
  277. package/src/type-stubs/ui-contract.d.ts +5 -0
  278. package/src/type-stubs/ui-contract.js +1 -0
  279. package/src/types/authoring-card-properties.type-test.ts +266 -0
  280. package/src/types/authoring.ts +1282 -0
  281. package/src/types/cards.ts +19 -0
  282. package/src/types/contracts.ts +1550 -0
  283. package/src/types/generated-helpers.ts +35 -0
  284. package/src/types/index.ts +147 -0
  285. package/src/types/slots.ts +11 -0
  286. package/src/types.ts +1 -0
  287. package/src/ui/components/ActionButton.tsx +97 -0
  288. package/src/ui/components/ActionPanel.tsx +315 -0
  289. package/src/ui/components/Card.tsx +378 -0
  290. package/src/ui/components/CardDragSurface.tsx +1076 -0
  291. package/src/ui/components/ChromeSuppressionContext.tsx +70 -0
  292. package/src/ui/components/CostDisplay.tsx +145 -0
  293. package/src/ui/components/DiceRoller.tsx +581 -0
  294. package/src/ui/components/Drawer.tsx +180 -0
  295. package/src/ui/components/ErrorBoundary.tsx +275 -0
  296. package/src/ui/components/GameEndDisplay.tsx +398 -0
  297. package/src/ui/components/GameSkeleton.tsx +260 -0
  298. package/src/ui/components/Hand.tsx +468 -0
  299. package/src/ui/components/HandDock.tsx +299 -0
  300. package/src/ui/components/HandView.tsx +441 -0
  301. package/src/ui/components/MobileHandTray.tsx +381 -0
  302. package/src/ui/components/MoreActions.tsx +143 -0
  303. package/src/ui/components/PhaseIndicator.tsx +341 -0
  304. package/src/ui/components/PlayArea.tsx +146 -0
  305. package/src/ui/components/PrimaryActionButton.tsx +336 -0
  306. package/src/ui/components/PrimaryButton.tsx +45 -0
  307. package/src/ui/components/ResourceCounter.tsx +270 -0
  308. package/src/ui/components/StagingZone.tsx +134 -0
  309. package/src/ui/components/ThemedButton.tsx +113 -0
  310. package/src/ui/components/Toast.tsx +264 -0
  311. package/src/ui/components/board/HexGrid.tsx +1294 -0
  312. package/src/ui/components/board/NetworkGraph.tsx +476 -0
  313. package/src/ui/components/board/SlotSystem.tsx +388 -0
  314. package/src/ui/components/board/SquareGrid.tsx +1165 -0
  315. package/src/ui/components/board/TrackBoard.tsx +496 -0
  316. package/src/ui/components/board/ZoneMap.tsx +448 -0
  317. package/src/ui/components/board/hex-board-view.ts +123 -0
  318. package/src/ui/components/board/index.ts +142 -0
  319. package/src/ui/components/board/interaction-accessibility.ts +21 -0
  320. package/src/ui/components/board/target-layer.ts +66 -0
  321. package/src/ui/components/card-render-content.type-test.ts +27 -0
  322. package/src/ui/components/hand-layout-math.ts +163 -0
  323. package/src/ui/components/hand-pointer-engine.ts +413 -0
  324. package/src/ui/components/index.ts +245 -0
  325. package/src/ui/components.ts +1 -0
  326. package/src/ui/defaults/components.tsx +106 -0
  327. package/src/ui/defaults/index.ts +8 -0
  328. package/src/ui/defaults.ts +1 -0
  329. package/src/ui/errors/ValidationError.ts +29 -0
  330. package/src/ui/helpers/cards.ts +19 -0
  331. package/src/ui/helpers/track-board.ts +211 -0
  332. package/src/ui/hooks/useBoardTopology.ts +316 -0
  333. package/src/ui/hooks/useCards.ts +10 -0
  334. package/src/ui/hooks/useHandCardPointer.ts +381 -0
  335. package/src/ui/hooks/useHandLayout.ts +378 -0
  336. package/src/ui/hooks/useHandPresentation.ts +121 -0
  337. package/src/ui/hooks/useHexBoard.ts +74 -0
  338. package/src/ui/hooks/useHexGrid.ts +185 -0
  339. package/src/ui/hooks/useIsMobile.ts +35 -0
  340. package/src/ui/hooks/usePanZoom.ts +278 -0
  341. package/src/ui/hooks/useSquareBoard.ts +124 -0
  342. package/src/ui/hooks/useSquareGrid.ts +328 -0
  343. package/src/ui/index.ts +98 -0
  344. package/src/ui/internal/ui/alert.tsx +51 -0
  345. package/src/ui/internal/ui/button.tsx +58 -0
  346. package/src/ui/internal/ui/dialog.tsx +134 -0
  347. package/src/ui/internal/ui/input.tsx +21 -0
  348. package/src/ui/internal/ui/label.tsx +21 -0
  349. package/src/ui/internal/ui/select.tsx +129 -0
  350. package/src/ui/internal/ui/tooltip.tsx +54 -0
  351. package/src/ui/internal/ui/utils.ts +5 -0
  352. package/src/ui/plugin-styles.css +250 -0
  353. package/src/ui/primitives/dialog-lifecycle.ts +58 -0
  354. package/src/ui/primitives/dice.tsx +79 -0
  355. package/src/ui/primitives/primitive-props.tsx +101 -0
  356. package/src/ui/theme/ThemeProvider.tsx +252 -0
  357. package/src/ui/theme/board.ts +61 -0
  358. package/src/ui/theme/css-vars.ts +105 -0
  359. package/src/ui/theme/derive.ts +240 -0
  360. package/src/ui/theme/index.ts +61 -0
  361. package/src/ui/theme/presets/arcade.ts +261 -0
  362. package/src/ui/theme/presets/studio.ts +261 -0
  363. package/src/ui/theme/presets/tabletop.ts +266 -0
  364. package/src/ui/theme/tokens.ts +392 -0
  365. package/src/ui/types/hex-color.ts +20 -0
  366. package/src/ui/types/player-state.ts +463 -0
  367. package/src/ui/types/tiled-board.ts +785 -0
  368. package/src/ui/types/visual-state.ts +137 -0
  369. package/src/ui.ts +1 -0
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Theme-aware controlled 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
+ * Visual contract (Laws of UX cross-references):
7
+ *
8
+ * - **Fitts** — defaults to `lg` size (min 56px tall, generous
9
+ * horizontal padding) so the dock target is easy to land on.
10
+ * Authors can opt down to `md`.
11
+ * - **Von Restorff (isolation)** — uses `intent.primary.solid` with
12
+ * `elevation.lifted` and an animated halo when `attention="auto"`
13
+ * and `available` is true, so the button outranks every other
14
+ * element in its peripheral neighbourhood.
15
+ * - **Peak-end** — when the action becomes available, the halo pulses for one breath cycle
16
+ * so the eye finds the change without re-scanning the screen.
17
+ * - **Doherty / responsiveness** — clicks set an internal `pending`
18
+ * flag the moment submit fires so the button visibly absorbs the
19
+ * tap, even on slow networks. Throwing submitters are swallowed
20
+ * here for the same reason `<DefaultInteractionButton>` does:
21
+ * descriptor availability is authoritative.
22
+ * - **Accessibility** — minimum 56×56 hit area satisfies WCAG 2.5.5.
23
+ * `prefers-reduced-motion` zeroes out the halo and press
24
+ * transitions through the theme's `motion.reducedMotion` token.
25
+ */
26
+
27
+ import {
28
+ useEffect,
29
+ useRef,
30
+ useState,
31
+ type CSSProperties,
32
+ type MouseEvent,
33
+ type ReactNode,
34
+ } from "react";
35
+ import { motion } from "framer-motion";
36
+ import { useTheme } from "../theme/ThemeProvider.js";
37
+ import {
38
+ intentForVariant,
39
+ type ButtonSize,
40
+ type ButtonVariant,
41
+ } from "../theme/derive.js";
42
+ import type { InteractionVisualState } from "../types/visual-state.js";
43
+ import { ThemedButton } from "./ThemedButton.js";
44
+
45
+ /** Attention-pulse policy for the trailing halo. */
46
+ export type PrimaryActionAttention = "auto" | "always" | "off";
47
+
48
+ export interface SubmittedActionConfig {
49
+ label?: ReactNode;
50
+ icon?: ReactNode;
51
+ variant?: ButtonVariant;
52
+ }
53
+
54
+ export interface PrimaryActionButtonProps extends InteractionVisualState {
55
+ /**
56
+ * Override the visual variant. Defaults to `primary` (which maps
57
+ * to `intent.primary` regardless of the descriptor's `emphasis`
58
+ * hint — the shell's primary slot is, by definition, primary).
59
+ */
60
+ variant?: ButtonVariant;
61
+ /**
62
+ * Sizing. Defaults to `lg` so the dock target is comfortable on
63
+ * touch and visually outranks panel buttons sized `md`.
64
+ */
65
+ size?: ButtonSize;
66
+ /**
67
+ * Override the label inferred from `descriptor.label`. Use only
68
+ * when the descriptor's label needs phase-specific copy that the
69
+ * authoring layer can't express.
70
+ */
71
+ label?: ReactNode;
72
+ /** Client-side draft readiness. Reducer availability remains authoritative. */
73
+ ready?: boolean;
74
+ /** Whether the action is currently available according to the caller. */
75
+ available?: boolean;
76
+ /** Optional reason rendered as the disabled tooltip. */
77
+ unavailableReason?: string;
78
+ /** External submission state. */
79
+ submitting?: boolean;
80
+ /** External submitted state. */
81
+ submitted?: boolean;
82
+ /** Copy and visual overrides once this interaction has been submitted. */
83
+ whenSubmitted?: SubmittedActionConfig;
84
+ /**
85
+ * Optional leading icon override. When omitted, falls back to
86
+ * `descriptor.icon` (an emoji glyph from the authoring spec).
87
+ */
88
+ icon?: ReactNode;
89
+ /** Stable identifier for diagnostics and tests. */
90
+ actionId?: string;
91
+ /** Called when the controlled action is activated. */
92
+ onAction?: () => void | Promise<void>;
93
+ /**
94
+ * Attention-halo policy. `auto` (default) pulses the halo for one
95
+ * breath when the button transitions from disabled → enabled (so
96
+ * the user sees the moment the action becomes available), then
97
+ * settles into a slow ambient breath while the action remains
98
+ * available. `always` keeps the breath running unconditionally.
99
+ * `off` suppresses the halo entirely.
100
+ *
101
+ * Ignored when `theme.motion.reducedMotion === "true"`.
102
+ */
103
+ attention?: PrimaryActionAttention;
104
+ /** Additional inline style merged after the resolved button style. */
105
+ style?: CSSProperties;
106
+ /** Optional className for downstream styling hooks. */
107
+ className?: string;
108
+ }
109
+
110
+ /**
111
+ * @see PrimaryActionButtonProps
112
+ */
113
+ export function PrimaryActionButton({
114
+ variant = "primary",
115
+ size = "lg",
116
+ label = "Action",
117
+ ready = true,
118
+ available: availableProp = true,
119
+ unavailableReason,
120
+ submitting: submittingProp = false,
121
+ submitted = false,
122
+ whenSubmitted,
123
+ icon,
124
+ actionId,
125
+ onAction,
126
+ attention = "auto",
127
+ style,
128
+ className,
129
+ }: PrimaryActionButtonProps) {
130
+ const theme = useTheme();
131
+ const reducedMotion = theme.motion.reducedMotion === "true";
132
+ const [pending, setPending] = useState(false);
133
+
134
+ const submitting = submittingProp || pending;
135
+ const available = availableProp && ready && !submitted && !submitting;
136
+ const resolvedVariant = submitted
137
+ ? (whenSubmitted?.variant ?? "submitted")
138
+ : variant;
139
+ const disabled = !available;
140
+ const intent = intentForVariant(theme, resolvedVariant);
141
+
142
+ // Pulse the halo for one breath when availability flips on. After
143
+ // the breath we settle into the ambient cadence (or stop, when
144
+ // `attention` is `off`). Tracking the previous availability lets
145
+ // us catch the transition without re-mounting the component.
146
+ const previouslyAvailableRef = useRef(available);
147
+ const [pulseKey, setPulseKey] = useState(0);
148
+ useEffect(() => {
149
+ if (!previouslyAvailableRef.current && available) {
150
+ setPulseKey((n) => n + 1);
151
+ }
152
+ previouslyAvailableRef.current = available;
153
+ }, [available]);
154
+
155
+ const haloEnabled =
156
+ !reducedMotion && available && attention !== "off" && !submitted;
157
+
158
+ const tooltip = available
159
+ ? undefined
160
+ : formatUnavailableReason(unavailableReason);
161
+
162
+ const resolvedLabel: ReactNode = submitted
163
+ ? (whenSubmitted?.label ?? label)
164
+ : label;
165
+ const resolvedIcon: ReactNode =
166
+ submitted && whenSubmitted?.icon ? (
167
+ <span aria-hidden style={{ fontSize: "1.15em" }}>
168
+ {whenSubmitted.icon}
169
+ </span>
170
+ ) : (
171
+ (icon ?? null)
172
+ );
173
+
174
+ return (
175
+ <span
176
+ data-dreamboard-primary-action
177
+ data-available={available ? "true" : "false"}
178
+ data-pending={submitting ? "true" : undefined}
179
+ data-action-state={
180
+ submitted
181
+ ? "submitted"
182
+ : submitting
183
+ ? "submitting"
184
+ : available
185
+ ? "available"
186
+ : "unavailable"
187
+ }
188
+ style={{
189
+ position: "relative",
190
+ display: "inline-flex",
191
+ alignItems: "center",
192
+ justifyContent: "center",
193
+ // The halo overflows the button bounds; the wrapper reserves
194
+ // a transparent buffer so it doesn't get clipped by the
195
+ // dock's safe-area frame.
196
+ padding: theme.space[1],
197
+ }}
198
+ >
199
+ {haloEnabled ? (
200
+ <>
201
+ {/*
202
+ Ambient breath — slow, low-amplitude, runs as long as the
203
+ action is available. The outer `haloEnabled` already
204
+ short-circuits when `attention === "off"`, so this layer
205
+ is gated purely on the availability + reduced-motion
206
+ checks.
207
+ */}
208
+ <motion.span
209
+ aria-hidden
210
+ style={{
211
+ position: "absolute",
212
+ inset: 0,
213
+ borderRadius: theme.radius.md,
214
+ background: intent.soft,
215
+ opacity: 0.55,
216
+ pointerEvents: "none",
217
+ }}
218
+ animate={{
219
+ scale: [1, 1.06, 1],
220
+ opacity: [0.45, 0.18, 0.45],
221
+ }}
222
+ transition={{
223
+ repeat: Infinity,
224
+ duration: 2.4,
225
+ ease: "easeInOut",
226
+ }}
227
+ />
228
+ {/*
229
+ One-shot announce pulse keyed on `pulseKey` — re-mounts
230
+ (and thus re-runs) every time availability flips on so the
231
+ eye registers the change. We use a separate layer (rather
232
+ than retriggering the ambient breath) so the announce is
233
+ visibly louder than the steady-state cadence.
234
+ */}
235
+ <motion.span
236
+ key={pulseKey}
237
+ aria-hidden
238
+ style={{
239
+ position: "absolute",
240
+ inset: 0,
241
+ borderRadius: theme.radius.md,
242
+ boxShadow: `0 0 0 0 ${intent.solid}`,
243
+ pointerEvents: "none",
244
+ }}
245
+ initial={{ opacity: 0.7 }}
246
+ animate={{
247
+ boxShadow: [
248
+ `0 0 0 0 ${withAlpha(intent.solid, 0.55)}`,
249
+ `0 0 0 14px ${withAlpha(intent.solid, 0)}`,
250
+ ],
251
+ opacity: [0.7, 0],
252
+ }}
253
+ transition={{ duration: 0.9, ease: "easeOut" }}
254
+ />
255
+ </>
256
+ ) : null}
257
+ <ThemedButton
258
+ type="button"
259
+ variant={resolvedVariant}
260
+ size={size}
261
+ pressed={submitting}
262
+ className={className}
263
+ aria-label={
264
+ typeof resolvedLabel === "string" ? resolvedLabel : "Primary action"
265
+ }
266
+ aria-disabled={disabled || undefined}
267
+ data-interaction-id={actionId}
268
+ data-emphasis="primary"
269
+ title={tooltip}
270
+ disabled={disabled}
271
+ style={{
272
+ // Sit above the halo so clicks land on the button.
273
+ position: "relative",
274
+ zIndex: 1,
275
+ boxShadow: disabled || submitted ? undefined : theme.elevation.lifted,
276
+ ...style,
277
+ }}
278
+ onClick={async (event: MouseEvent<HTMLButtonElement>) => {
279
+ event.preventDefault();
280
+ if (disabled) return;
281
+ setPending(true);
282
+ try {
283
+ await onAction?.();
284
+ } finally {
285
+ setPending(false);
286
+ }
287
+ }}
288
+ >
289
+ {resolvedIcon}
290
+ <span>{resolvedLabel}</span>
291
+ </ThemedButton>
292
+ </span>
293
+ );
294
+ }
295
+
296
+ function formatUnavailableReason(
297
+ reason: string | undefined,
298
+ ): string | undefined {
299
+ if (reason === "INSUFFICIENT_RESOURCES") {
300
+ return "Insufficient resources";
301
+ }
302
+ return reason;
303
+ }
304
+
305
+ /**
306
+ * Add an alpha channel to a CSS colour string. Supports `#rgb`,
307
+ * `#rrggbb`, and any colour the browser can paint via a fallback to
308
+ * `color-mix` (modern Safari/Chrome/Firefox all support this; older
309
+ * runtimes get the original colour without alpha which is still
310
+ * visible — the halo is decorative).
311
+ */
312
+ function withAlpha(color: string, alpha: number): string {
313
+ const trimmed = color.trim();
314
+ if (trimmed.startsWith("#")) {
315
+ const hex = trimmed.slice(1);
316
+ if (hex.length === 3) {
317
+ // `String.prototype.slice` always returns a string (possibly
318
+ // empty) — never `undefined` — so duplicating each digit is
319
+ // safe to feed to `parseInt` without further narrowing.
320
+ const r = parseInt(hex.slice(0, 1).repeat(2), 16);
321
+ const g = parseInt(hex.slice(1, 2).repeat(2), 16);
322
+ const b = parseInt(hex.slice(2, 3).repeat(2), 16);
323
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
324
+ }
325
+ if (hex.length === 6) {
326
+ const r = parseInt(hex.slice(0, 2), 16);
327
+ const g = parseInt(hex.slice(2, 4), 16);
328
+ const b = parseInt(hex.slice(4, 6), 16);
329
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
330
+ }
331
+ }
332
+ // Best-effort fallback for non-hex colours (rgb/hsl/named): use
333
+ // `color-mix` with transparent. Safe to land in inline style — the
334
+ // halo is purely decorative and will gracefully degrade.
335
+ return `color-mix(in srgb, ${trimmed} ${Math.round(alpha * 100)}%, transparent)`;
336
+ }
@@ -0,0 +1,45 @@
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 type { InteractionVisualState } from "../types/visual-state.js";
15
+ import { ThemedButton } from "./ThemedButton.js";
16
+
17
+ export interface PrimaryButtonProps
18
+ extends ButtonHTMLAttributes<HTMLButtonElement>, InteractionVisualState {
19
+ /** Intent slot — defaults to `primary`. */
20
+ variant?: ButtonVariant;
21
+ /** Sizing token — defaults to `md`. */
22
+ size?: ButtonSize;
23
+ }
24
+
25
+ export function PrimaryButton({
26
+ children,
27
+ disabled,
28
+ style,
29
+ variant = "primary",
30
+ size = "md",
31
+ ...rest
32
+ }: PrimaryButtonProps) {
33
+ return (
34
+ <ThemedButton
35
+ type="button"
36
+ disabled={disabled}
37
+ variant={variant}
38
+ size={size}
39
+ style={style}
40
+ {...rest}
41
+ >
42
+ {children}
43
+ </ThemedButton>
44
+ );
45
+ }
@@ -0,0 +1,270 @@
1
+ import {
2
+ createContext,
3
+ createElement,
4
+ useContext,
5
+ useMemo,
6
+ type ComponentType,
7
+ type HTMLAttributes,
8
+ type ReactElement,
9
+ type ReactNode,
10
+ } from "react";
11
+ import {
12
+ composeEventHandlers,
13
+ renderPrimitive,
14
+ type PrimitiveCommonProps,
15
+ } from "../primitives/primitive-props.js";
16
+
17
+ export type ResourceId = string;
18
+
19
+ export interface ResourceDisplayConfig<Resource extends string = ResourceId> {
20
+ type: Resource;
21
+ label: string;
22
+ icon:
23
+ | ReactNode
24
+ | ComponentType<{
25
+ className?: string;
26
+ strokeWidth?: number;
27
+ "aria-hidden"?: boolean | "true" | "false";
28
+ }>;
29
+ iconColor?: string;
30
+ bgColor?: string;
31
+ textColor?: string;
32
+ }
33
+
34
+ export interface ResourceCounterItemState<
35
+ Resource extends string = ResourceId,
36
+ > {
37
+ type: Resource;
38
+ label: string;
39
+ icon: ResourceDisplayConfig<Resource>["icon"];
40
+ iconColor?: string;
41
+ bgColor?: string;
42
+ textColor?: string;
43
+ count: number;
44
+ isZero: boolean;
45
+ interactive: boolean;
46
+ select: () => void;
47
+ renderIcon: (props?: ResourceIconProps) => ReactNode;
48
+ dataAttributes: {
49
+ "data-resource-id": Resource;
50
+ "data-resource-count": number;
51
+ "data-resource-zero": boolean | undefined;
52
+ "data-interactive": boolean | undefined;
53
+ };
54
+ }
55
+
56
+ export interface ResourceIconProps {
57
+ className?: string;
58
+ strokeWidth?: number;
59
+ "aria-hidden"?: boolean | "true" | "false";
60
+ }
61
+
62
+ export type ResourceCounterRootProps<Resource extends string = ResourceId> =
63
+ Omit<PrimitiveCommonProps, "children"> &
64
+ Omit<HTMLAttributes<HTMLElement>, "children"> & {
65
+ resources: ReadonlyArray<ResourceDisplayConfig<Resource>>;
66
+ counts: Partial<Record<Resource, number>>;
67
+ zero?: "show" | "hide";
68
+ onResourceClick?: (resourceType: Resource) => void;
69
+ children: ReactNode;
70
+ };
71
+
72
+ export type BoundResourceCounterRootProps<
73
+ Resource extends string = ResourceId,
74
+ > = Omit<ResourceCounterRootProps<Resource>, "resources">;
75
+
76
+ export type ResourceCounterProps<Resource extends string = ResourceId> =
77
+ ResourceCounterRootProps<Resource>;
78
+
79
+ export type ResourceCounterPartProps<Resource extends string = ResourceId> =
80
+ Omit<PrimitiveCommonProps, "children"> &
81
+ Omit<HTMLAttributes<HTMLElement>, "children"> & {
82
+ children?:
83
+ | ReactNode
84
+ | ((resource: ResourceCounterItemState<Resource>) => ReactNode);
85
+ };
86
+
87
+ const ResourceCounterItemContext =
88
+ createContext<ResourceCounterItemState<string> | null>(null);
89
+
90
+ function useResourceCounterItemContext<Resource extends string>() {
91
+ const value = useContext(ResourceCounterItemContext);
92
+ if (!value) {
93
+ throw new Error(
94
+ "ResourceCounter item primitives must be rendered inside <ResourceCounter.Item>.",
95
+ );
96
+ }
97
+ return value as ResourceCounterItemState<Resource>;
98
+ }
99
+
100
+ function renderResourceIcon(
101
+ icon: ResourceDisplayConfig<string>["icon"],
102
+ props: ResourceIconProps = {},
103
+ ) {
104
+ if (typeof icon === "function") {
105
+ return createElement(icon, {
106
+ "aria-hidden": true,
107
+ strokeWidth: 2.5,
108
+ ...props,
109
+ });
110
+ }
111
+ const {
112
+ strokeWidth: _strokeWidth,
113
+ "aria-hidden": ariaHidden,
114
+ ...spanProps
115
+ } = props;
116
+ return (
117
+ <span
118
+ aria-hidden={ariaHidden === undefined ? true : ariaHidden !== "false"}
119
+ {...spanProps}
120
+ >
121
+ {icon}
122
+ </span>
123
+ );
124
+ }
125
+
126
+ function resolveResourceChildren<Resource extends string>(
127
+ children: ResourceCounterPartProps<Resource>["children"],
128
+ resource: ResourceCounterItemState<Resource>,
129
+ ) {
130
+ return typeof children === "function" ? children(resource) : children;
131
+ }
132
+
133
+ export function ResourceCounterRoot<Resource extends string = ResourceId>({
134
+ resources,
135
+ counts,
136
+ zero = "show",
137
+ onResourceClick,
138
+ children,
139
+ "aria-label": ariaLabel,
140
+ ...props
141
+ }: ResourceCounterRootProps<Resource>) {
142
+ const items = useMemo(
143
+ () =>
144
+ resources
145
+ .map((resource) => {
146
+ const count = counts[resource.type] ?? 0;
147
+ return {
148
+ ...resource,
149
+ count,
150
+ isZero: count === 0,
151
+ interactive: Boolean(onResourceClick),
152
+ select: () => onResourceClick?.(resource.type),
153
+ renderIcon: (iconProps) =>
154
+ renderResourceIcon(resource.icon, iconProps),
155
+ dataAttributes: {
156
+ "data-resource-id": resource.type,
157
+ "data-resource-count": count,
158
+ "data-resource-zero": count === 0 || undefined,
159
+ "data-interactive": onResourceClick ? true : undefined,
160
+ },
161
+ } satisfies ResourceCounterItemState<Resource>;
162
+ })
163
+ .filter((resource) => zero === "show" || !resource.isZero),
164
+ [counts, onResourceClick, resources, zero],
165
+ );
166
+
167
+ return renderPrimitive("div", {
168
+ role: "list",
169
+ "aria-label": ariaLabel ?? "Resource counts",
170
+ "data-dreamboard-resource-counter": "",
171
+ ...props,
172
+ children: items.map((resource) => (
173
+ <ResourceCounterItemContext.Provider key={resource.type} value={resource}>
174
+ {children}
175
+ </ResourceCounterItemContext.Provider>
176
+ )),
177
+ });
178
+ }
179
+
180
+ export function ResourceCounterItem<Resource extends string = ResourceId>({
181
+ children,
182
+ onClick,
183
+ "aria-label": ariaLabel,
184
+ ...props
185
+ }: ResourceCounterPartProps<Resource>) {
186
+ const resource = useResourceCounterItemContext<Resource>();
187
+ return renderPrimitive("span", {
188
+ role: "listitem",
189
+ "aria-label": ariaLabel ?? `${resource.label}: ${resource.count}`,
190
+ ...resource.dataAttributes,
191
+ ...props,
192
+ onClick: composeEventHandlers(
193
+ onClick,
194
+ resource.interactive ? resource.select : undefined,
195
+ ),
196
+ children: resolveResourceChildren(children, resource),
197
+ });
198
+ }
199
+
200
+ export function ResourceCounterIcon<Resource extends string = ResourceId>({
201
+ className,
202
+ strokeWidth,
203
+ "aria-hidden": ariaHidden,
204
+ }: ResourceIconProps): ReactNode {
205
+ const resource = useResourceCounterItemContext<Resource>();
206
+ return resource.renderIcon({
207
+ className,
208
+ strokeWidth,
209
+ "aria-hidden": ariaHidden,
210
+ });
211
+ }
212
+
213
+ export function ResourceCounterCount<Resource extends string = ResourceId>({
214
+ children,
215
+ ...props
216
+ }: ResourceCounterPartProps<Resource>) {
217
+ const resource = useResourceCounterItemContext<Resource>();
218
+ return renderPrimitive("span", {
219
+ ...props,
220
+ "data-dreamboard-resource-count": "",
221
+ children: resolveResourceChildren(children ?? resource.count, resource),
222
+ });
223
+ }
224
+
225
+ export function ResourceCounterLabel<Resource extends string = ResourceId>({
226
+ children,
227
+ ...props
228
+ }: ResourceCounterPartProps<Resource>) {
229
+ const resource = useResourceCounterItemContext<Resource>();
230
+ return renderPrimitive("span", {
231
+ ...props,
232
+ "data-dreamboard-resource-label": "",
233
+ children: resolveResourceChildren(children ?? resource.label, resource),
234
+ });
235
+ }
236
+
237
+ export interface ResourceCounterComponents<
238
+ Resource extends string = ResourceId,
239
+ > {
240
+ Root(props: BoundResourceCounterRootProps<Resource>): ReactElement;
241
+ Item(props: ResourceCounterPartProps<Resource>): ReactElement;
242
+ Icon(props: ResourceIconProps): ReactNode;
243
+ Count(props: ResourceCounterPartProps<Resource>): ReactElement;
244
+ Label(props: ResourceCounterPartProps<Resource>): ReactElement;
245
+ }
246
+
247
+ export function createResourceCounter<Resource extends string>(
248
+ resources: ReadonlyArray<ResourceDisplayConfig<Resource>>,
249
+ ): ResourceCounterComponents<Resource> {
250
+ return {
251
+ Root(props) {
252
+ return createElement(ResourceCounterRoot<Resource>, {
253
+ ...props,
254
+ resources,
255
+ });
256
+ },
257
+ Item: ResourceCounterItem,
258
+ Icon: ResourceCounterIcon,
259
+ Count: ResourceCounterCount,
260
+ Label: ResourceCounterLabel,
261
+ } satisfies ResourceCounterComponents<Resource>;
262
+ }
263
+
264
+ export const ResourceCounter = {
265
+ Root: ResourceCounterRoot,
266
+ Item: ResourceCounterItem,
267
+ Icon: ResourceCounterIcon,
268
+ Count: ResourceCounterCount,
269
+ Label: ResourceCounterLabel,
270
+ };