@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.
- package/LICENSE +89 -0
- package/NOTICE +1 -0
- package/README.md +154 -0
- package/dist/components/ActionButton.d.ts +13 -0
- package/dist/components/ActionButton.d.ts.map +1 -0
- package/dist/components/ActionButton.js +14 -0
- package/dist/components/ActionPanel.d.ts +33 -0
- package/dist/components/ActionPanel.d.ts.map +1 -0
- package/dist/components/ActionPanel.js +148 -0
- package/dist/components/Card.d.ts +29 -0
- package/dist/components/Card.d.ts.map +1 -0
- package/dist/components/Card.js +220 -0
- package/dist/components/ChromeSuppressionContext.d.ts +7 -0
- package/dist/components/ChromeSuppressionContext.d.ts.map +1 -0
- package/dist/components/ChromeSuppressionContext.js +34 -0
- package/dist/components/CostDisplay.d.ts +22 -0
- package/dist/components/CostDisplay.d.ts.map +1 -0
- package/dist/components/CostDisplay.js +41 -0
- package/dist/components/DiceRoller.d.ts +30 -0
- package/dist/components/DiceRoller.d.ts.map +1 -0
- package/dist/components/DiceRoller.js +319 -0
- package/dist/components/Drawer.d.ts +19 -0
- package/dist/components/Drawer.d.ts.map +1 -0
- package/dist/components/Drawer.js +55 -0
- package/dist/components/ErrorBoundary.d.ts +24 -0
- package/dist/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/components/ErrorBoundary.js +37 -0
- package/dist/components/GameEndDisplay.d.ts +27 -0
- package/dist/components/GameEndDisplay.d.ts.map +1 -0
- package/dist/components/GameEndDisplay.js +185 -0
- package/dist/components/GameSkeleton.d.ts +12 -0
- package/dist/components/GameSkeleton.d.ts.map +1 -0
- package/dist/components/GameSkeleton.js +54 -0
- package/dist/components/Hand.d.ts +99 -0
- package/dist/components/Hand.d.ts.map +1 -0
- package/dist/components/Hand.js +162 -0
- package/dist/components/HandDock.d.ts +35 -0
- package/dist/components/HandDock.d.ts.map +1 -0
- package/dist/components/HandDock.js +124 -0
- package/dist/components/InteractionForm.d.ts +50 -0
- package/dist/components/InteractionForm.d.ts.map +1 -0
- package/dist/components/InteractionForm.js +402 -0
- package/dist/components/MoreActions.d.ts +49 -0
- package/dist/components/MoreActions.d.ts.map +1 -0
- package/dist/components/MoreActions.js +64 -0
- package/dist/components/PhaseIndicator.d.ts +35 -0
- package/dist/components/PhaseIndicator.d.ts.map +1 -0
- package/dist/components/PhaseIndicator.js +212 -0
- package/dist/components/PlayArea.d.ts +28 -0
- package/dist/components/PlayArea.d.ts.map +1 -0
- package/dist/components/PlayArea.js +48 -0
- package/dist/components/PluginRuntime.d.ts +37 -0
- package/dist/components/PluginRuntime.d.ts.map +1 -0
- package/dist/components/PluginRuntime.js +47 -0
- package/dist/components/PrimaryActionButton.d.ts +98 -0
- package/dist/components/PrimaryActionButton.d.ts.map +1 -0
- package/dist/components/PrimaryActionButton.js +183 -0
- package/dist/components/PrimaryButton.d.ts +20 -0
- package/dist/components/PrimaryButton.d.ts.map +1 -0
- package/dist/components/PrimaryButton.js +5 -0
- package/dist/components/PromptDialogHost.d.ts +15 -0
- package/dist/components/PromptDialogHost.d.ts.map +1 -0
- package/dist/components/PromptDialogHost.js +22 -0
- package/dist/components/ResourceCounter.d.ts +38 -0
- package/dist/components/ResourceCounter.d.ts.map +1 -0
- package/dist/components/ResourceCounter.js +118 -0
- package/dist/components/ThemedButton.d.ts +12 -0
- package/dist/components/ThemedButton.d.ts.map +1 -0
- package/dist/components/ThemedButton.js +38 -0
- package/dist/components/Toast.d.ts +35 -0
- package/dist/components/Toast.d.ts.map +1 -0
- package/dist/components/Toast.js +116 -0
- package/dist/components/board/HexGrid.d.ts +344 -0
- package/dist/components/board/HexGrid.d.ts.map +1 -0
- package/dist/components/board/HexGrid.js +340 -0
- package/dist/components/board/NetworkGraph.d.ts +100 -0
- package/dist/components/board/NetworkGraph.d.ts.map +1 -0
- package/dist/components/board/NetworkGraph.js +123 -0
- package/dist/components/board/SlotSystem.d.ts +71 -0
- package/dist/components/board/SlotSystem.d.ts.map +1 -0
- package/dist/components/board/SlotSystem.js +87 -0
- package/dist/components/board/SquareGrid.d.ts +188 -0
- package/dist/components/board/SquareGrid.d.ts.map +1 -0
- package/dist/components/board/SquareGrid.js +328 -0
- package/dist/components/board/TrackBoard.d.ts +113 -0
- package/dist/components/board/TrackBoard.d.ts.map +1 -0
- package/dist/components/board/TrackBoard.js +135 -0
- package/dist/components/board/ZoneMap.d.ts +88 -0
- package/dist/components/board/ZoneMap.d.ts.map +1 -0
- package/dist/components/board/ZoneMap.js +133 -0
- package/dist/components/board/hex-board-view.d.ts +69 -0
- package/dist/components/board/hex-board-view.d.ts.map +1 -0
- package/dist/components/board/hex-board-view.js +60 -0
- package/dist/components/board/index.d.ts +23 -0
- package/dist/components/board/index.d.ts.map +1 -0
- package/dist/components/board/index.js +40 -0
- package/dist/components/board/interaction-accessibility.d.ts +5 -0
- package/dist/components/board/interaction-accessibility.d.ts.map +1 -0
- package/dist/components/board/interaction-accessibility.js +13 -0
- package/dist/components/board/target-layer.d.ts +13 -0
- package/dist/components/board/target-layer.d.ts.map +1 -0
- package/dist/components/board/target-layer.js +10 -0
- package/dist/components/card-render-content.type-test.d.ts +2 -0
- package/dist/components/card-render-content.type-test.d.ts.map +1 -0
- package/dist/components/card-render-content.type-test.js +1 -0
- package/dist/components/index.d.ts +34 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +35 -0
- package/dist/components/interaction-dialog-behavior.d.ts +15 -0
- package/dist/components/interaction-dialog-behavior.d.ts.map +1 -0
- package/dist/components/interaction-dialog-behavior.js +9 -0
- package/dist/components/surfaces/BlockerSurface.d.ts +27 -0
- package/dist/components/surfaces/BlockerSurface.d.ts.map +1 -0
- package/dist/components/surfaces/BlockerSurface.js +38 -0
- package/dist/components/surfaces/BoardSurface.d.ts +77 -0
- package/dist/components/surfaces/BoardSurface.d.ts.map +1 -0
- package/dist/components/surfaces/BoardSurface.js +180 -0
- package/dist/components/surfaces/ChromeSurface.d.ts +29 -0
- package/dist/components/surfaces/ChromeSurface.d.ts.map +1 -0
- package/dist/components/surfaces/ChromeSurface.js +34 -0
- package/dist/components/surfaces/ExhaustivenessAudit.d.ts +32 -0
- package/dist/components/surfaces/ExhaustivenessAudit.d.ts.map +1 -0
- package/dist/components/surfaces/ExhaustivenessAudit.js +65 -0
- package/dist/components/surfaces/InboxSurface.d.ts +40 -0
- package/dist/components/surfaces/InboxSurface.d.ts.map +1 -0
- package/dist/components/surfaces/InboxSurface.js +99 -0
- package/dist/components/surfaces/MarketSurface.d.ts +62 -0
- package/dist/components/surfaces/MarketSurface.d.ts.map +1 -0
- package/dist/components/surfaces/MarketSurface.js +242 -0
- package/dist/components/surfaces/PanelSurface.d.ts +111 -0
- package/dist/components/surfaces/PanelSurface.d.ts.map +1 -0
- package/dist/components/surfaces/PanelSurface.js +180 -0
- package/dist/components/surfaces/PlayerCardsSurface.d.ts +104 -0
- package/dist/components/surfaces/PlayerCardsSurface.d.ts.map +1 -0
- package/dist/components/surfaces/PlayerCardsSurface.js +178 -0
- package/dist/components/surfaces/internal/CardZoneFollowUpForm.d.ts +7 -0
- package/dist/components/surfaces/internal/CardZoneFollowUpForm.d.ts.map +1 -0
- package/dist/components/surfaces/internal/CardZoneFollowUpForm.js +9 -0
- package/dist/components/surfaces/internal/DefaultInteractionButton.d.ts +71 -0
- package/dist/components/surfaces/internal/DefaultInteractionButton.d.ts.map +1 -0
- package/dist/components/surfaces/internal/DefaultInteractionButton.js +82 -0
- package/dist/components/surfaces/internal/useCardZoneInteractions.d.ts +21 -0
- package/dist/components/surfaces/internal/useCardZoneInteractions.d.ts.map +1 -0
- package/dist/components/surfaces/internal/useCardZoneInteractions.js +202 -0
- package/dist/components/surfaces/types.d.ts +59 -0
- package/dist/components/surfaces/types.d.ts.map +1 -0
- package/dist/components/surfaces/types.js +1 -0
- package/dist/context/ClientParamSchemaContext.d.ts +21 -0
- package/dist/context/ClientParamSchemaContext.d.ts.map +1 -0
- package/dist/context/ClientParamSchemaContext.js +12 -0
- package/dist/context/InteractionDraftContext.d.ts +69 -0
- package/dist/context/InteractionDraftContext.d.ts.map +1 -0
- package/dist/context/InteractionDraftContext.js +145 -0
- package/dist/context/PluginSessionContext.d.ts +33 -0
- package/dist/context/PluginSessionContext.d.ts.map +1 -0
- package/dist/context/PluginSessionContext.js +38 -0
- package/dist/context/PluginStateContext.d.ts +116 -0
- package/dist/context/PluginStateContext.d.ts.map +1 -0
- package/dist/context/PluginStateContext.js +186 -0
- package/dist/context/RuntimeContext.d.ts +49 -0
- package/dist/context/RuntimeContext.d.ts.map +1 -0
- package/dist/context/RuntimeContext.js +67 -0
- package/dist/defaults/components.d.ts +52 -0
- package/dist/defaults/components.d.ts.map +1 -0
- package/dist/defaults/components.js +159 -0
- package/dist/defaults/index.d.ts +2 -0
- package/dist/defaults/index.d.ts.map +1 -0
- package/dist/defaults/index.js +1 -0
- package/dist/errors/ValidationError.d.ts +10 -0
- package/dist/errors/ValidationError.d.ts.map +1 -0
- package/dist/errors/ValidationError.js +23 -0
- package/dist/helpers/cards.d.ts +3 -0
- package/dist/helpers/cards.d.ts.map +1 -0
- package/dist/helpers/cards.js +11 -0
- package/dist/helpers/track-board.d.ts +79 -0
- package/dist/helpers/track-board.d.ts.map +1 -0
- package/dist/helpers/track-board.js +56 -0
- package/dist/hooks/useActivePlayers.d.ts +16 -0
- package/dist/hooks/useActivePlayers.d.ts.map +1 -0
- package/dist/hooks/useActivePlayers.js +17 -0
- package/dist/hooks/useBoardInteractions.d.ts +110 -0
- package/dist/hooks/useBoardInteractions.d.ts.map +1 -0
- package/dist/hooks/useBoardInteractions.js +248 -0
- package/dist/hooks/useBoardTopology.d.ts +23 -0
- package/dist/hooks/useBoardTopology.d.ts.map +1 -0
- package/dist/hooks/useBoardTopology.js +128 -0
- package/dist/hooks/useCards.d.ts +3 -0
- package/dist/hooks/useCards.d.ts.map +1 -0
- package/dist/hooks/useCards.js +5 -0
- package/dist/hooks/useGameSelector.d.ts +13 -0
- package/dist/hooks/useGameSelector.d.ts.map +1 -0
- package/dist/hooks/useGameSelector.js +67 -0
- package/dist/hooks/useGameView.d.ts +6 -0
- package/dist/hooks/useGameView.d.ts.map +1 -0
- package/dist/hooks/useGameView.js +7 -0
- package/dist/hooks/useHandLayout.d.ts +120 -0
- package/dist/hooks/useHandLayout.d.ts.map +1 -0
- package/dist/hooks/useHandLayout.js +235 -0
- package/dist/hooks/useHexBoard.d.ts +19 -0
- package/dist/hooks/useHexBoard.d.ts.map +1 -0
- package/dist/hooks/useHexBoard.js +28 -0
- package/dist/hooks/useHexGrid.d.ts +56 -0
- package/dist/hooks/useHexGrid.d.ts.map +1 -0
- package/dist/hooks/useHexGrid.js +112 -0
- package/dist/hooks/useInteractionByKey.d.ts +29 -0
- package/dist/hooks/useInteractionByKey.d.ts.map +1 -0
- package/dist/hooks/useInteractionByKey.js +263 -0
- package/dist/hooks/useInteractionHandle.d.ts +103 -0
- package/dist/hooks/useInteractionHandle.d.ts.map +1 -0
- package/dist/hooks/useInteractionHandle.js +254 -0
- package/dist/hooks/useIsMobile.d.ts +7 -0
- package/dist/hooks/useIsMobile.d.ts.map +1 -0
- package/dist/hooks/useIsMobile.js +29 -0
- package/dist/hooks/useIsMyTurn.d.ts +6 -0
- package/dist/hooks/useIsMyTurn.d.ts.map +1 -0
- package/dist/hooks/useIsMyTurn.js +11 -0
- package/dist/hooks/useLobby.d.ts +28 -0
- package/dist/hooks/useLobby.d.ts.map +1 -0
- package/dist/hooks/useLobby.js +60 -0
- package/dist/hooks/useMe.d.ts +11 -0
- package/dist/hooks/useMe.d.ts.map +1 -0
- package/dist/hooks/useMe.js +32 -0
- package/dist/hooks/usePanZoom.d.ts +113 -0
- package/dist/hooks/usePanZoom.d.ts.map +1 -0
- package/dist/hooks/usePanZoom.js +165 -0
- package/dist/hooks/usePlayerInfo.d.ts +4 -0
- package/dist/hooks/usePlayerInfo.d.ts.map +1 -0
- package/dist/hooks/usePlayerInfo.js +21 -0
- package/dist/hooks/usePlayerTurnOrder.d.ts +15 -0
- package/dist/hooks/usePlayerTurnOrder.d.ts.map +1 -0
- package/dist/hooks/usePlayerTurnOrder.js +22 -0
- package/dist/hooks/usePluginRuntime.d.ts +45 -0
- package/dist/hooks/usePluginRuntime.d.ts.map +1 -0
- package/dist/hooks/usePluginRuntime.js +92 -0
- package/dist/hooks/useSeatInbox.d.ts +22 -0
- package/dist/hooks/useSeatInbox.d.ts.map +1 -0
- package/dist/hooks/useSeatInbox.js +43 -0
- package/dist/hooks/useSimultaneousPhase.d.ts +7 -0
- package/dist/hooks/useSimultaneousPhase.d.ts.map +1 -0
- package/dist/hooks/useSimultaneousPhase.js +8 -0
- package/dist/hooks/useSquareBoard.d.ts +21 -0
- package/dist/hooks/useSquareBoard.d.ts.map +1 -0
- package/dist/hooks/useSquareBoard.js +67 -0
- package/dist/hooks/useSquareGrid.d.ts +96 -0
- package/dist/hooks/useSquareGrid.d.ts.map +1 -0
- package/dist/hooks/useSquareGrid.js +152 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/internal/ui/alert.d.ts +8 -0
- package/dist/internal/ui/alert.d.ts.map +1 -0
- package/dist/internal/ui/alert.js +11 -0
- package/dist/internal/ui/button.d.ts +10 -0
- package/dist/internal/ui/button.d.ts.map +1 -0
- package/dist/internal/ui/button.js +21 -0
- package/dist/internal/ui/dialog.d.ts +16 -0
- package/dist/internal/ui/dialog.d.ts.map +1 -0
- package/dist/internal/ui/dialog.js +35 -0
- package/dist/internal/ui/input.d.ts +3 -0
- package/dist/internal/ui/input.d.ts.map +1 -0
- package/dist/internal/ui/input.js +5 -0
- package/dist/internal/ui/label.d.ts +4 -0
- package/dist/internal/ui/label.d.ts.map +1 -0
- package/dist/internal/ui/label.js +7 -0
- package/dist/internal/ui/select.d.ts +9 -0
- package/dist/internal/ui/select.d.ts.map +1 -0
- package/dist/internal/ui/select.js +23 -0
- package/dist/internal/ui/tooltip.d.ts +7 -0
- package/dist/internal/ui/tooltip.d.ts.map +1 -0
- package/dist/internal/ui/tooltip.js +16 -0
- package/dist/internal/ui/utils.d.ts +3 -0
- package/dist/internal/ui/utils.d.ts.map +1 -0
- package/dist/internal/ui/utils.js +4 -0
- package/dist/internal.d.ts +7 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +4 -0
- package/dist/plugin-styles.css +246 -0
- package/dist/primitives/board.d.ts +29 -0
- package/dist/primitives/board.d.ts.map +1 -0
- package/dist/primitives/board.js +163 -0
- package/dist/primitives/game-ui-provider.d.ts +12 -0
- package/dist/primitives/game-ui-provider.d.ts.map +1 -0
- package/dist/primitives/game-ui-provider.js +7 -0
- package/dist/primitives/index.d.ts +8 -0
- package/dist/primitives/index.d.ts.map +1 -0
- package/dist/primitives/index.js +7 -0
- package/dist/primitives/interaction.d.ts +52 -0
- package/dist/primitives/interaction.d.ts.map +1 -0
- package/dist/primitives/interaction.js +250 -0
- package/dist/primitives/phase.d.ts +15 -0
- package/dist/primitives/phase.d.ts.map +1 -0
- package/dist/primitives/phase.js +18 -0
- package/dist/primitives/player-roster.d.ts +64 -0
- package/dist/primitives/player-roster.d.ts.map +1 -0
- package/dist/primitives/player-roster.js +149 -0
- package/dist/primitives/primitive-props.d.ts +15 -0
- package/dist/primitives/primitive-props.d.ts.map +1 -0
- package/dist/primitives/primitive-props.js +39 -0
- package/dist/primitives/prompt.d.ts +44 -0
- package/dist/primitives/prompt.d.ts.map +1 -0
- package/dist/primitives/prompt.js +101 -0
- package/dist/primitives/zone.d.ts +31 -0
- package/dist/primitives/zone.d.ts.map +1 -0
- package/dist/primitives/zone.js +58 -0
- package/dist/reducer.d.ts +21 -0
- package/dist/reducer.d.ts.map +1 -0
- package/dist/reducer.js +14 -0
- package/dist/runtime/createPluginRuntimeAPI.d.ts +67 -0
- package/dist/runtime/createPluginRuntimeAPI.d.ts.map +1 -0
- package/dist/runtime/createPluginRuntimeAPI.js +419 -0
- package/dist/theme/ThemeProvider.d.ts +98 -0
- package/dist/theme/ThemeProvider.d.ts.map +1 -0
- package/dist/theme/ThemeProvider.js +148 -0
- package/dist/theme/board.d.ts +42 -0
- package/dist/theme/board.d.ts.map +1 -0
- package/dist/theme/board.js +34 -0
- package/dist/theme/css-vars.d.ts +31 -0
- package/dist/theme/css-vars.d.ts.map +1 -0
- package/dist/theme/css-vars.js +88 -0
- package/dist/theme/derive.d.ts +66 -0
- package/dist/theme/derive.d.ts.map +1 -0
- package/dist/theme/derive.js +161 -0
- package/dist/theme/index.d.ts +22 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +20 -0
- package/dist/theme/presets/arcade.d.ts +10 -0
- package/dist/theme/presets/arcade.d.ts.map +1 -0
- package/dist/theme/presets/arcade.js +257 -0
- package/dist/theme/presets/studio.d.ts +10 -0
- package/dist/theme/presets/studio.d.ts.map +1 -0
- package/dist/theme/presets/studio.js +257 -0
- package/dist/theme/presets/tabletop.d.ts +15 -0
- package/dist/theme/presets/tabletop.d.ts.map +1 -0
- package/dist/theme/presets/tabletop.js +262 -0
- package/dist/theme/tokens.d.ts +345 -0
- package/dist/theme/tokens.d.ts.map +1 -0
- package/dist/theme/tokens.js +57 -0
- package/dist/types/player-state.d.ts +337 -0
- package/dist/types/player-state.d.ts.map +1 -0
- package/dist/types/player-state.js +1 -0
- package/dist/types/plugin-state.d.ts +324 -0
- package/dist/types/plugin-state.d.ts.map +1 -0
- package/dist/types/plugin-state.js +1 -0
- package/dist/types/reducer-state.d.ts +10 -0
- package/dist/types/reducer-state.d.ts.map +1 -0
- package/dist/types/reducer-state.js +1 -0
- package/dist/types/runtime-api.d.ts +99 -0
- package/dist/types/runtime-api.d.ts.map +1 -0
- package/dist/types/runtime-api.js +1 -0
- package/dist/types/tiled-board.d.ts +187 -0
- package/dist/types/tiled-board.d.ts.map +1 -0
- package/dist/types/tiled-board.js +226 -0
- package/dist/ui-contract.d.ts +78 -0
- package/dist/ui-contract.d.ts.map +1 -0
- package/dist/ui-contract.js +15 -0
- package/dist/ui-sdk.d.ts +3409 -0
- package/dist/utils/interaction-inputs.d.ts +22 -0
- package/dist/utils/interaction-inputs.d.ts.map +1 -0
- package/dist/utils/interaction-inputs.js +219 -0
- package/dist/utils/interaction-labels.d.ts +4 -0
- package/dist/utils/interaction-labels.d.ts.map +1 -0
- package/dist/utils/interaction-labels.js +18 -0
- package/dist/utils/interaction-status.d.ts +15 -0
- package/dist/utils/interaction-status.d.ts.map +1 -0
- package/dist/utils/interaction-status.js +31 -0
- package/package.json +101 -0
- package/src/components/ActionButton.tsx +48 -0
- package/src/components/ActionPanel.tsx +310 -0
- package/src/components/Card.tsx +385 -0
- package/src/components/ChromeSuppressionContext.tsx +70 -0
- package/src/components/CostDisplay.test.tsx +23 -0
- package/src/components/CostDisplay.tsx +145 -0
- package/src/components/DiceRoller.tsx +601 -0
- package/src/components/Drawer.tsx +179 -0
- package/src/components/ErrorBoundary.tsx +119 -0
- package/src/components/GameEndDisplay.test.tsx +19 -0
- package/src/components/GameEndDisplay.tsx +398 -0
- package/src/components/GameSkeleton.tsx +260 -0
- package/src/components/Hand.tsx +387 -0
- package/src/components/HandDock.tsx +257 -0
- package/src/components/InteractionForm.test.tsx +303 -0
- package/src/components/InteractionForm.tsx +1029 -0
- package/src/components/MoreActions.test.tsx +93 -0
- package/src/components/MoreActions.tsx +143 -0
- package/src/components/PhaseIndicator.tsx +341 -0
- package/src/components/PlayArea.tsx +125 -0
- package/src/components/PluginRuntime.tsx +92 -0
- package/src/components/PrimaryActionButton.test.tsx +138 -0
- package/src/components/PrimaryActionButton.tsx +351 -0
- package/src/components/PrimaryButton.tsx +44 -0
- package/src/components/PromptDialogHost.tsx +92 -0
- package/src/components/ResourceCounter.test.tsx +29 -0
- package/src/components/ResourceCounter.tsx +275 -0
- package/src/components/ThemedButton.tsx +78 -0
- package/src/components/Toast.tsx +251 -0
- package/src/components/__fixtures__/ActionButton.fixture.tsx +234 -0
- package/src/components/__fixtures__/ActionPanel.fixture.tsx +298 -0
- package/src/components/__fixtures__/Card.fixture.tsx +185 -0
- package/src/components/__fixtures__/CostDisplay.fixture.tsx +156 -0
- package/src/components/__fixtures__/DiceRoller.fixture.tsx +435 -0
- package/src/components/__fixtures__/Drawer.fixture.tsx +113 -0
- package/src/components/__fixtures__/ErrorBoundary.fixture.tsx +82 -0
- package/src/components/__fixtures__/GameEndDisplay.fixture.tsx +188 -0
- package/src/components/__fixtures__/GameSkeleton.fixture.tsx +46 -0
- package/src/components/__fixtures__/Hand.fixture.tsx +522 -0
- package/src/components/__fixtures__/HexGrid.fixture.tsx +1181 -0
- package/src/components/__fixtures__/NetworkGraph.fixture.tsx +599 -0
- package/src/components/__fixtures__/PhaseIndicator.fixture.tsx +181 -0
- package/src/components/__fixtures__/PlayArea.fixture.tsx +221 -0
- package/src/components/__fixtures__/ResourceCounter.fixture.tsx +227 -0
- package/src/components/__fixtures__/SlotSystem.fixture.tsx +824 -0
- package/src/components/__fixtures__/SquareGrid.fixture.tsx +764 -0
- package/src/components/__fixtures__/Toast.fixture.tsx +97 -0
- package/src/components/__fixtures__/TrackBoard.fixture.tsx +685 -0
- package/src/components/__fixtures__/ZoneMap.fixture.tsx +754 -0
- package/src/components/board/HexGrid.tsx +1294 -0
- package/src/components/board/NetworkGraph.tsx +476 -0
- package/src/components/board/SlotSystem.tsx +339 -0
- package/src/components/board/SquareGrid.tsx +1165 -0
- package/src/components/board/TrackBoard.tsx +496 -0
- package/src/components/board/ZoneMap.tsx +448 -0
- package/src/components/board/hex-board-view.test.tsx +114 -0
- package/src/components/board/hex-board-view.ts +123 -0
- package/src/components/board/index.ts +142 -0
- package/src/components/board/interaction-accessibility.ts +21 -0
- package/src/components/board/target-layer-grids.test.tsx +420 -0
- package/src/components/board/target-layer.ts +30 -0
- package/src/components/card-render-content.type-test.ts +27 -0
- package/src/components/index.ts +208 -0
- package/src/components/interaction-dialog-behavior.test.ts +23 -0
- package/src/components/interaction-dialog-behavior.ts +22 -0
- package/src/components/surfaces/BlockerSurface.test.tsx +158 -0
- package/src/components/surfaces/BlockerSurface.tsx +127 -0
- package/src/components/surfaces/BoardSurface.tsx +340 -0
- package/src/components/surfaces/ChromeSurface.tsx +123 -0
- package/src/components/surfaces/ExhaustivenessAudit.tsx +91 -0
- package/src/components/surfaces/InboxSurface.test.tsx +149 -0
- package/src/components/surfaces/InboxSurface.tsx +245 -0
- package/src/components/surfaces/MarketSurface.tsx +544 -0
- package/src/components/surfaces/PanelSurface.test.tsx +496 -0
- package/src/components/surfaces/PanelSurface.tsx +458 -0
- package/src/components/surfaces/PlayerCardsSurface.tsx +525 -0
- package/src/components/surfaces/internal/CardZoneFollowUpForm.tsx +35 -0
- package/src/components/surfaces/internal/DefaultInteractionButton.tsx +219 -0
- package/src/components/surfaces/internal/useCardZoneInteractions.ts +311 -0
- package/src/components/surfaces/types.ts +100 -0
- package/src/context/ClientParamSchemaContext.tsx +44 -0
- package/src/context/InteractionDraftContext.tsx +204 -0
- package/src/context/PluginSessionContext.tsx +47 -0
- package/src/context/PluginStateContext.tsx +254 -0
- package/src/context/RuntimeContext.tsx +96 -0
- package/src/defaults/components.tsx +442 -0
- package/src/defaults/defaults.test.tsx +230 -0
- package/src/defaults/index.ts +1 -0
- package/src/errors/ValidationError.ts +29 -0
- package/src/helpers/cards.ts +19 -0
- package/src/helpers/track-board.ts +211 -0
- package/src/hooks/useActivePlayers.ts +19 -0
- package/src/hooks/useBoardInteractions.test.tsx +622 -0
- package/src/hooks/useBoardInteractions.ts +434 -0
- package/src/hooks/useBoardTopology.ts +316 -0
- package/src/hooks/useCards.test.tsx +129 -0
- package/src/hooks/useCards.ts +10 -0
- package/src/hooks/useGameSelector.ts +105 -0
- package/src/hooks/useGameView.ts +9 -0
- package/src/hooks/useHandLayout.ts +349 -0
- package/src/hooks/useHexBoard.ts +74 -0
- package/src/hooks/useHexGrid.ts +185 -0
- package/src/hooks/useInteractionByKey.ts +349 -0
- package/src/hooks/useInteractionHandle.ts +437 -0
- package/src/hooks/useIsMobile.ts +35 -0
- package/src/hooks/useIsMyTurn.test.tsx +99 -0
- package/src/hooks/useIsMyTurn.ts +15 -0
- package/src/hooks/useLobby.ts +76 -0
- package/src/hooks/useMe.ts +48 -0
- package/src/hooks/usePanZoom.ts +278 -0
- package/src/hooks/usePlayerInfo.ts +28 -0
- package/src/hooks/usePlayerTurnOrder.ts +23 -0
- package/src/hooks/usePluginRuntime.test.tsx +102 -0
- package/src/hooks/usePluginRuntime.ts +130 -0
- package/src/hooks/useSeatInbox.ts +61 -0
- package/src/hooks/useSimultaneousPhase.ts +10 -0
- package/src/hooks/useSquareBoard.ts +124 -0
- package/src/hooks/useSquareGrid.ts +328 -0
- package/src/index.test.ts +474 -0
- package/src/index.ts +148 -0
- package/src/internal/ui/alert.tsx +51 -0
- package/src/internal/ui/button.tsx +58 -0
- package/src/internal/ui/dialog.tsx +134 -0
- package/src/internal/ui/input.tsx +21 -0
- package/src/internal/ui/label.tsx +21 -0
- package/src/internal/ui/select.tsx +129 -0
- package/src/internal/ui/tooltip.tsx +54 -0
- package/src/internal/ui/utils.ts +5 -0
- package/src/internal.ts +18 -0
- package/src/plugin-styles.css +246 -0
- package/src/primitives/board.test.tsx +139 -0
- package/src/primitives/board.tsx +267 -0
- package/src/primitives/game-ui-provider.tsx +35 -0
- package/src/primitives/index.ts +83 -0
- package/src/primitives/interaction.test.tsx +420 -0
- package/src/primitives/interaction.tsx +405 -0
- package/src/primitives/phase.test.tsx +82 -0
- package/src/primitives/phase.tsx +43 -0
- package/src/primitives/player-roster.test.tsx +168 -0
- package/src/primitives/player-roster.tsx +301 -0
- package/src/primitives/primitive-props.tsx +82 -0
- package/src/primitives/prompt.test.tsx +159 -0
- package/src/primitives/prompt.tsx +203 -0
- package/src/primitives/zone.tsx +113 -0
- package/src/reducer.ts +42 -0
- package/src/runtime/createPluginRuntimeAPI.ts +605 -0
- package/src/theme/ThemeProvider.test.tsx +36 -0
- package/src/theme/ThemeProvider.tsx +252 -0
- package/src/theme/board.ts +61 -0
- package/src/theme/css-vars.ts +105 -0
- package/src/theme/derive.ts +240 -0
- package/src/theme/index.ts +61 -0
- package/src/theme/presets/arcade.ts +261 -0
- package/src/theme/presets/studio.ts +261 -0
- package/src/theme/presets/tabletop.ts +266 -0
- package/src/theme/theme.test.ts +258 -0
- package/src/theme/tokens.ts +392 -0
- package/src/types/player-state.ts +445 -0
- package/src/types/plugin-state.ts +407 -0
- package/src/types/reducer-state.ts +24 -0
- package/src/types/runtime-api.ts +114 -0
- package/src/types/tiled-board.ts +785 -0
- package/src/ui-contract.ts +168 -0
- package/src/utils/interaction-inputs.test.ts +109 -0
- package/src/utils/interaction-inputs.ts +331 -0
- package/src/utils/interaction-labels.ts +23 -0
- 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
|
+
}
|