@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.
- package/LICENSE.md +96 -0
- package/README.md +12 -0
- package/dist/HandView-ncJIVLhN.d.ts +193 -0
- package/dist/ResourceCounter-CTREyF73.d.ts +102 -0
- package/dist/ThemeProvider-fy0_QzgO.d.ts +99 -0
- package/dist/bundle-TIZcw8LB.d.ts +281 -0
- package/dist/cards-Sl3b40Mv.d.ts +13 -0
- package/dist/chunk-7YAHLYBR.js +481 -0
- package/dist/chunk-7YAHLYBR.js.map +1 -0
- package/dist/chunk-FDNZTDD6.js +8085 -0
- package/dist/chunk-FDNZTDD6.js.map +1 -0
- package/dist/chunk-GKKBPPSW.js +598 -0
- package/dist/chunk-GKKBPPSW.js.map +1 -0
- package/dist/chunk-I46YJSOD.js +1 -0
- package/dist/chunk-I46YJSOD.js.map +1 -0
- package/dist/chunk-KAELH4KC.js +104 -0
- package/dist/chunk-KAELH4KC.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-T3ZKNUZ7.js +1 -0
- package/dist/chunk-T3ZKNUZ7.js.map +1 -0
- package/dist/chunk-T52J5RMF.js +1 -0
- package/dist/chunk-T52J5RMF.js.map +1 -0
- package/dist/chunk-TDSWKVZ4.js +5401 -0
- package/dist/chunk-TDSWKVZ4.js.map +1 -0
- package/dist/chunk-U5C6BONG.js +34 -0
- package/dist/chunk-U5C6BONG.js.map +1 -0
- package/dist/chunk-VDXOF4FW.js +69 -0
- package/dist/chunk-VDXOF4FW.js.map +1 -0
- package/dist/chunk-VFTAA4WO.js +115 -0
- package/dist/chunk-VFTAA4WO.js.map +1 -0
- package/dist/chunk-WN74KVNY.js +17 -0
- package/dist/chunk-WN74KVNY.js.map +1 -0
- package/dist/chunk-WYPQ3GG5.js +10990 -0
- package/dist/chunk-WYPQ3GG5.js.map +1 -0
- package/dist/components-D5ZRE2Hl.d.ts +1451 -0
- package/dist/generated/runtime/primitives.d.ts +12 -0
- package/dist/generated/runtime/primitives.js +180 -0
- package/dist/generated/runtime/primitives.js.map +1 -0
- package/dist/generated/runtime-api.d.ts +3 -0
- package/dist/generated/runtime-api.js +2 -0
- package/dist/generated/runtime-api.js.map +1 -0
- package/dist/generated/runtime.d.ts +14 -0
- package/dist/generated/runtime.js +18 -0
- package/dist/generated/runtime.js.map +1 -0
- package/dist/generated/workspace-contract.d.ts +14 -0
- package/dist/generated/workspace-contract.js +14 -0
- package/dist/generated/workspace-contract.js.map +1 -0
- package/dist/hex-board-view-D_07hO6O.d.ts +933 -0
- package/dist/hex-color-MhOyuY-o.d.ts +8 -0
- package/dist/index-BwqPQtBu.d.ts +1433 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/infrastructure/reducer-bundle-abi.d.ts +1083 -0
- package/dist/infrastructure/reducer-bundle-abi.js +14 -0
- package/dist/infrastructure/reducer-bundle-abi.js.map +1 -0
- package/dist/infrastructure/workspace-codegen.d.ts +53 -0
- package/dist/infrastructure/workspace-codegen.js +44 -0
- package/dist/infrastructure/workspace-codegen.js.map +1 -0
- package/dist/manifest-contract-BNHVGFtU.d.ts +9 -0
- package/dist/package-set.d.ts +13 -0
- package/dist/package-set.js +12 -0
- package/dist/package-set.js.map +1 -0
- package/dist/primitive-props-DpKs-GCr.d.ts +11 -0
- package/dist/reducer.d.ts +3786 -0
- package/dist/reducer.js +8131 -0
- package/dist/reducer.js.map +1 -0
- package/dist/runtime/primitives.d.ts +226 -0
- package/dist/runtime/primitives.js +180 -0
- package/dist/runtime/primitives.js.map +1 -0
- package/dist/runtime/types/runtime-api.d.ts +1 -0
- package/dist/runtime/types/runtime-api.js +2 -0
- package/dist/runtime/types/runtime-api.js.map +1 -0
- package/dist/runtime/workspace-contract.d.ts +172 -0
- package/dist/runtime/workspace-contract.js +14 -0
- package/dist/runtime/workspace-contract.js.map +1 -0
- package/dist/runtime-api-3dshj6kK.d.ts +101 -0
- package/dist/runtime-api-DWxvTr-O.d.ts +379 -0
- package/dist/runtime.d.ts +58 -0
- package/dist/runtime.js +13 -0
- package/dist/runtime.js.map +1 -0
- package/dist/slots-1GPGihk8.d.ts +8 -0
- package/dist/testing.d.ts +149 -0
- package/dist/testing.js +513 -0
- package/dist/testing.js.map +1 -0
- package/dist/types.d.ts +496 -0
- package/dist/types.js +28 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/components.d.ts +16 -0
- package/dist/ui/components.js +192 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/defaults.d.ts +19 -0
- package/dist/ui/defaults.js +104 -0
- package/dist/ui/defaults.js.map +1 -0
- package/dist/ui/plugin-styles.css +250 -0
- package/dist/ui/types/player-state.d.ts +365 -0
- package/dist/ui/types/player-state.js +1 -0
- package/dist/ui/types/player-state.js.map +1 -0
- package/dist/ui-contract-iQfTtUSL.d.ts +1161 -0
- package/dist/ui.d.ts +320 -0
- package/dist/ui.js +253 -0
- package/dist/ui.js.map +1 -0
- package/package.json +199 -0
- package/src/generated/reducer-contract/builders.ts +90 -0
- package/src/generated/reducer-contract/version.ts +9 -0
- package/src/generated/reducer-contract/wire.ts +100 -0
- package/src/generated/reducer-contract/zod.ts +101 -0
- package/src/generated/runtime/primitives.ts +2 -0
- package/src/generated/runtime-api.ts +5 -0
- package/src/generated/runtime.ts +35 -0
- package/src/generated/workspace-contract.ts +2 -0
- package/src/index.ts +7 -0
- package/src/infrastructure/reducer-bundle-abi.ts +8 -0
- package/src/infrastructure/reducer-contract/bundle.ts +37 -0
- package/src/infrastructure/workspace-codegen/hex-geometry.ts +69 -0
- package/src/infrastructure/workspace-codegen/index.ts +64 -0
- package/src/infrastructure/workspace-codegen/manifest-contract.ts +6632 -0
- package/src/infrastructure/workspace-codegen/manifest-validation.ts +795 -0
- package/src/infrastructure/workspace-codegen/ownership.ts +131 -0
- package/src/infrastructure/workspace-codegen/preset-card-sets.ts +169 -0
- package/src/infrastructure/workspace-codegen/seeds.ts +1705 -0
- package/src/infrastructure/workspace-codegen.ts +1 -0
- package/src/package-set.ts +19 -0
- package/src/reducer/authoring/contract.ts +157 -0
- package/src/reducer/authoring/effect.ts +224 -0
- package/src/reducer/authoring/game.ts +23 -0
- package/src/reducer/authoring/interaction.ts +98 -0
- package/src/reducer/authoring/phase.ts +300 -0
- package/src/reducer/authoring/types.ts +70 -0
- package/src/reducer/authoring/validation.ts +382 -0
- package/src/reducer/authoring/view-stage.ts +68 -0
- package/src/reducer/authoring.ts +29 -0
- package/src/reducer/bundle/ingress-bundle.ts +491 -0
- package/src/reducer/bundle/trusted/engine-instruction-resolver.ts +254 -0
- package/src/reducer/bundle/trusted/flow-instruction-resolver.ts +73 -0
- package/src/reducer/bundle/trusted/instruction-runner.ts +414 -0
- package/src/reducer/bundle/trusted/interaction-authorization.ts +137 -0
- package/src/reducer/bundle/trusted/interaction-collectors.ts +859 -0
- package/src/reducer/bundle/trusted/interaction-decision.ts +747 -0
- package/src/reducer/bundle/trusted/interaction-resolver.ts +95 -0
- package/src/reducer/bundle/trusted/interaction-types.ts +171 -0
- package/src/reducer/bundle/trusted/lifecycle-runner.ts +427 -0
- package/src/reducer/bundle/trusted/projection-builder.ts +356 -0
- package/src/reducer/bundle/trusted/projection-context.ts +39 -0
- package/src/reducer/bundle/trusted/rng-sampler.ts +150 -0
- package/src/reducer/bundle/trusted/runtime-registry.ts +120 -0
- package/src/reducer/bundle/trusted/runtime-scope.ts +336 -0
- package/src/reducer/bundle/trusted/simultaneous-player.ts +97 -0
- package/src/reducer/bundle/trusted/stage-resolver.ts +87 -0
- package/src/reducer/bundle/trusted/static-projection.ts +116 -0
- package/src/reducer/bundle/trusted/trusted-runtime-args.ts +97 -0
- package/src/reducer/bundle/trusted/trusted-runtime-result.ts +39 -0
- package/src/reducer/bundle/trusted/trusted-setup-profiles.ts +43 -0
- package/src/reducer/bundle/trusted/trusted-state-codec.ts +48 -0
- package/src/reducer/bundle/trusted-bundle.ts +97 -0
- package/src/reducer/bundle/types.ts +171 -0
- package/src/reducer/bundle.ts +2 -0
- package/src/reducer/client-param-schemas.ts +57 -0
- package/src/reducer/compose.ts +34 -0
- package/src/reducer/core/runtime-input.ts +30 -0
- package/src/reducer/core/runtime-instruction.ts +59 -0
- package/src/reducer/core/types.ts +62 -0
- package/src/reducer/definition-index.ts +277 -0
- package/src/reducer/derived.ts +106 -0
- package/src/reducer/effects.ts +92 -0
- package/src/reducer/engine/runtime-instruction-engine.ts +155 -0
- package/src/reducer/ingress/decode-runtime-input.ts +7 -0
- package/src/reducer/ingress/decode-session-state.ts +9 -0
- package/src/reducer/ingress/encode-session-state.ts +6 -0
- package/src/reducer/ingress/input-codec.ts +18 -0
- package/src/reducer/ingress/phase-schemas.ts +62 -0
- package/src/reducer/ingress/raw-types.ts +107 -0
- package/src/reducer/ingress/runtime-codec.ts +14 -0
- package/src/reducer/ingress/runtime-payload.ts +13 -0
- package/src/reducer/ingress/session-codec.ts +392 -0
- package/src/reducer/ingress/types.ts +6 -0
- package/src/reducer/inputs/boardInput.ts +217 -0
- package/src/reducer/inputs/boardTarget.ts +190 -0
- package/src/reducer/inputs/cardInput.ts +86 -0
- package/src/reducer/inputs/cardTarget.ts +101 -0
- package/src/reducer/inputs/choiceTarget.ts +104 -0
- package/src/reducer/inputs/defineInputs.ts +71 -0
- package/src/reducer/inputs/formInput.ts +809 -0
- package/src/reducer/inputs/many.ts +120 -0
- package/src/reducer/inputs/promptInput.ts +87 -0
- package/src/reducer/inputs/rngInput.ts +58 -0
- package/src/reducer/inputs/targetRule.ts +123 -0
- package/src/reducer/inputs.ts +41 -0
- package/src/reducer/model/definition.ts +1072 -0
- package/src/reducer/model/extract.ts +745 -0
- package/src/reducer/model/manifest.ts +570 -0
- package/src/reducer/model/queries.ts +641 -0
- package/src/reducer/model/runtime.ts +264 -0
- package/src/reducer/model/spec.ts +1386 -0
- package/src/reducer/model/table.ts +260 -0
- package/src/reducer/model.ts +7 -0
- package/src/reducer/ops.ts +1034 -0
- package/src/reducer/parse-utils.ts +28 -0
- package/src/reducer/per-player.ts +422 -0
- package/src/reducer/rng.ts +69 -0
- package/src/reducer/schema-helpers.ts +185 -0
- package/src/reducer/setup-bootstrap-helpers.ts +171 -0
- package/src/reducer/setup-bootstrap.ts +481 -0
- package/src/reducer/table-ops.ts +2671 -0
- package/src/reducer/table-queries.ts +372 -0
- package/src/reducer/transaction.ts +120 -0
- package/src/reducer.ts +314 -0
- package/src/runtime/primitives.ts +1 -0
- package/src/runtime/types/runtime-api.ts +1 -0
- package/src/runtime/workspace-contract.ts +32 -0
- package/src/runtime-internal/components/InteractionForm.tsx +1309 -0
- package/src/runtime-internal/components/PluginRuntime.tsx +103 -0
- package/src/runtime-internal/components/board/target-layer.ts +70 -0
- package/src/runtime-internal/context/ClientParamSchemaContext.tsx +44 -0
- package/src/runtime-internal/context/InteractionDraftContext.tsx +279 -0
- package/src/runtime-internal/context/PluginSessionContext.tsx +47 -0
- package/src/runtime-internal/context/PluginStateContext.tsx +262 -0
- package/src/runtime-internal/context/RuntimeContext.tsx +96 -0
- package/src/runtime-internal/defaults/components.tsx +409 -0
- package/src/runtime-internal/defaults/index.ts +11 -0
- package/src/runtime-internal/errors/ValidationError.ts +29 -0
- package/src/runtime-internal/hooks/useActivePlayers.ts +33 -0
- package/src/runtime-internal/hooks/useBoardInteractions.ts +665 -0
- package/src/runtime-internal/hooks/useGameSelector.ts +105 -0
- package/src/runtime-internal/hooks/useGameView.ts +9 -0
- package/src/runtime-internal/hooks/useInteractionByKey.ts +354 -0
- package/src/runtime-internal/hooks/useInteractionHandle.ts +438 -0
- package/src/runtime-internal/hooks/useIsMyTurn.ts +20 -0
- package/src/runtime-internal/hooks/useLobby.ts +76 -0
- package/src/runtime-internal/hooks/useMe.ts +48 -0
- package/src/runtime-internal/hooks/usePlayerInfo.ts +28 -0
- package/src/runtime-internal/hooks/usePlayerTurnOrder.ts +23 -0
- package/src/runtime-internal/hooks/usePluginRuntime.ts +147 -0
- package/src/runtime-internal/hooks/useSeatInbox.ts +61 -0
- package/src/runtime-internal/hooks/useSimultaneousPhase.ts +10 -0
- package/src/runtime-internal/index.ts +42 -0
- package/src/runtime-internal/internal.ts +43 -0
- package/src/runtime-internal/plugin-styles.css +250 -0
- package/src/runtime-internal/primitives/board.tsx +459 -0
- package/src/runtime-internal/primitives/dialog-lifecycle.ts +58 -0
- package/src/runtime-internal/primitives/dice.tsx +79 -0
- package/src/runtime-internal/primitives/game-ui-provider.tsx +35 -0
- package/src/runtime-internal/primitives/game.tsx +387 -0
- package/src/runtime-internal/primitives/hand-intent-adapter.ts +147 -0
- package/src/runtime-internal/primitives/hand-surface.tsx +594 -0
- package/src/runtime-internal/primitives/index.ts +196 -0
- package/src/runtime-internal/primitives/interaction-form-binding.tsx +56 -0
- package/src/runtime-internal/primitives/interaction-submit.ts +90 -0
- package/src/runtime-internal/primitives/interaction.tsx +987 -0
- package/src/runtime-internal/primitives/phase.tsx +43 -0
- package/src/runtime-internal/primitives/player-roster.tsx +302 -0
- package/src/runtime-internal/primitives/primitive-props.tsx +101 -0
- package/src/runtime-internal/primitives/prompt.tsx +255 -0
- package/src/runtime-internal/primitives/ui.tsx +60 -0
- package/src/runtime-internal/primitives/zone.tsx +791 -0
- package/src/runtime-internal/reducer.ts +30 -0
- package/src/runtime-internal/runtime/createPluginRuntimeAPI.ts +605 -0
- package/src/runtime-internal/types/plugin-state.ts +508 -0
- package/src/runtime-internal/types/reducer-state.ts +24 -0
- package/src/runtime-internal/types/runtime-api.ts +114 -0
- package/src/runtime-internal/ui-contract.ts +519 -0
- package/src/runtime-internal/utils/card-intent-adapter.ts +546 -0
- package/src/runtime-internal/utils/interaction-inputs.ts +492 -0
- package/src/runtime-internal/utils/interaction-labels.ts +23 -0
- package/src/runtime-internal/utils/interaction-router.ts +273 -0
- package/src/runtime-internal/utils/interaction-status.ts +74 -0
- package/src/runtime-internal/workspace-contract.ts +1170 -0
- package/src/runtime.ts +34 -0
- package/src/testing/create-expect-api.ts +352 -0
- package/src/testing/create-test-runtime.ts +381 -0
- package/src/testing/definitions.ts +127 -0
- package/src/testing/index.ts +3 -0
- package/src/testing.ts +1 -0
- package/src/type-stubs/manifest-contract.d.ts +42 -0
- package/src/type-stubs/manifest-contract.js +72 -0
- package/src/type-stubs/ui-contract.d.ts +5 -0
- package/src/type-stubs/ui-contract.js +1 -0
- package/src/types/authoring-card-properties.type-test.ts +266 -0
- package/src/types/authoring.ts +1282 -0
- package/src/types/cards.ts +19 -0
- package/src/types/contracts.ts +1550 -0
- package/src/types/generated-helpers.ts +35 -0
- package/src/types/index.ts +147 -0
- package/src/types/slots.ts +11 -0
- package/src/types.ts +1 -0
- package/src/ui/components/ActionButton.tsx +97 -0
- package/src/ui/components/ActionPanel.tsx +315 -0
- package/src/ui/components/Card.tsx +378 -0
- package/src/ui/components/CardDragSurface.tsx +1076 -0
- package/src/ui/components/ChromeSuppressionContext.tsx +70 -0
- package/src/ui/components/CostDisplay.tsx +145 -0
- package/src/ui/components/DiceRoller.tsx +581 -0
- package/src/ui/components/Drawer.tsx +180 -0
- package/src/ui/components/ErrorBoundary.tsx +275 -0
- package/src/ui/components/GameEndDisplay.tsx +398 -0
- package/src/ui/components/GameSkeleton.tsx +260 -0
- package/src/ui/components/Hand.tsx +468 -0
- package/src/ui/components/HandDock.tsx +299 -0
- package/src/ui/components/HandView.tsx +441 -0
- package/src/ui/components/MobileHandTray.tsx +381 -0
- package/src/ui/components/MoreActions.tsx +143 -0
- package/src/ui/components/PhaseIndicator.tsx +341 -0
- package/src/ui/components/PlayArea.tsx +146 -0
- package/src/ui/components/PrimaryActionButton.tsx +336 -0
- package/src/ui/components/PrimaryButton.tsx +45 -0
- package/src/ui/components/ResourceCounter.tsx +270 -0
- package/src/ui/components/StagingZone.tsx +134 -0
- package/src/ui/components/ThemedButton.tsx +113 -0
- package/src/ui/components/Toast.tsx +264 -0
- package/src/ui/components/board/HexGrid.tsx +1294 -0
- package/src/ui/components/board/NetworkGraph.tsx +476 -0
- package/src/ui/components/board/SlotSystem.tsx +388 -0
- package/src/ui/components/board/SquareGrid.tsx +1165 -0
- package/src/ui/components/board/TrackBoard.tsx +496 -0
- package/src/ui/components/board/ZoneMap.tsx +448 -0
- package/src/ui/components/board/hex-board-view.ts +123 -0
- package/src/ui/components/board/index.ts +142 -0
- package/src/ui/components/board/interaction-accessibility.ts +21 -0
- package/src/ui/components/board/target-layer.ts +66 -0
- package/src/ui/components/card-render-content.type-test.ts +27 -0
- package/src/ui/components/hand-layout-math.ts +163 -0
- package/src/ui/components/hand-pointer-engine.ts +413 -0
- package/src/ui/components/index.ts +245 -0
- package/src/ui/components.ts +1 -0
- package/src/ui/defaults/components.tsx +106 -0
- package/src/ui/defaults/index.ts +8 -0
- package/src/ui/defaults.ts +1 -0
- package/src/ui/errors/ValidationError.ts +29 -0
- package/src/ui/helpers/cards.ts +19 -0
- package/src/ui/helpers/track-board.ts +211 -0
- package/src/ui/hooks/useBoardTopology.ts +316 -0
- package/src/ui/hooks/useCards.ts +10 -0
- package/src/ui/hooks/useHandCardPointer.ts +381 -0
- package/src/ui/hooks/useHandLayout.ts +378 -0
- package/src/ui/hooks/useHandPresentation.ts +121 -0
- package/src/ui/hooks/useHexBoard.ts +74 -0
- package/src/ui/hooks/useHexGrid.ts +185 -0
- package/src/ui/hooks/useIsMobile.ts +35 -0
- package/src/ui/hooks/usePanZoom.ts +278 -0
- package/src/ui/hooks/useSquareBoard.ts +124 -0
- package/src/ui/hooks/useSquareGrid.ts +328 -0
- package/src/ui/index.ts +98 -0
- package/src/ui/internal/ui/alert.tsx +51 -0
- package/src/ui/internal/ui/button.tsx +58 -0
- package/src/ui/internal/ui/dialog.tsx +134 -0
- package/src/ui/internal/ui/input.tsx +21 -0
- package/src/ui/internal/ui/label.tsx +21 -0
- package/src/ui/internal/ui/select.tsx +129 -0
- package/src/ui/internal/ui/tooltip.tsx +54 -0
- package/src/ui/internal/ui/utils.ts +5 -0
- package/src/ui/plugin-styles.css +250 -0
- package/src/ui/primitives/dialog-lifecycle.ts +58 -0
- package/src/ui/primitives/dice.tsx +79 -0
- package/src/ui/primitives/primitive-props.tsx +101 -0
- package/src/ui/theme/ThemeProvider.tsx +252 -0
- package/src/ui/theme/board.ts +61 -0
- package/src/ui/theme/css-vars.ts +105 -0
- package/src/ui/theme/derive.ts +240 -0
- package/src/ui/theme/index.ts +61 -0
- package/src/ui/theme/presets/arcade.ts +261 -0
- package/src/ui/theme/presets/studio.ts +261 -0
- package/src/ui/theme/presets/tabletop.ts +266 -0
- package/src/ui/theme/tokens.ts +392 -0
- package/src/ui/types/hex-color.ts +20 -0
- package/src/ui/types/player-state.ts +463 -0
- package/src/ui/types/tiled-board.ts +785 -0
- package/src/ui/types/visual-state.ts +137 -0
- package/src/ui.ts +1 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
} from "react";
|
|
11
|
+
import { useDrag } from "@use-gesture/react";
|
|
12
|
+
import { clsx } from "clsx";
|
|
13
|
+
import { useIsMobile } from "../hooks/useIsMobile.js";
|
|
14
|
+
import { useThemeCssVars } from "../theme/ThemeProvider.js";
|
|
15
|
+
|
|
16
|
+
export type HandRole = "primary" | "auxiliary" | "task";
|
|
17
|
+
|
|
18
|
+
export interface MobileHandRegistration {
|
|
19
|
+
id: string;
|
|
20
|
+
zone: string;
|
|
21
|
+
label: string;
|
|
22
|
+
role: HandRole;
|
|
23
|
+
order?: number;
|
|
24
|
+
version: string;
|
|
25
|
+
count: number;
|
|
26
|
+
active: boolean;
|
|
27
|
+
autoOpen: boolean;
|
|
28
|
+
content: ReactNode;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface MobileHandTrayContextValue {
|
|
32
|
+
registerHand: (hand: MobileHandRegistration) => () => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const MobileHandTrayContext = createContext<MobileHandTrayContextValue | null>(
|
|
36
|
+
null,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Kept separate from the registration context on purpose: this value changes
|
|
40
|
+
// whenever the tray opens/closes or hands (de)register, whereas `registerHand`
|
|
41
|
+
// must stay referentially stable so `useRegisterMobileHand`'s effect does not
|
|
42
|
+
// re-run and thrash registrations.
|
|
43
|
+
const MobileHandTrayStateContext = createContext<{ active: boolean }>({
|
|
44
|
+
active: false,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const ROLE_PRIORITY: Record<HandRole, number> = {
|
|
48
|
+
task: 0,
|
|
49
|
+
primary: 1,
|
|
50
|
+
auxiliary: 2,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const TRAY_CONTENT_ID = "dreamboard-mobile-hand-tray";
|
|
54
|
+
|
|
55
|
+
/** Dock snap heights, ordered collapsed → tall. */
|
|
56
|
+
const DOCK_SNAPS = ["peek", "raised", "expanded"] as const;
|
|
57
|
+
type DockSnap = (typeof DOCK_SNAPS)[number];
|
|
58
|
+
const DOCK_SNAP_MAX_HEIGHT: Record<DockSnap, number | string> = {
|
|
59
|
+
peek: 0,
|
|
60
|
+
raised: "min(52vh, 440px)",
|
|
61
|
+
expanded: "82vh",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function MobileHandTrayProvider({ children }: { children: ReactNode }) {
|
|
65
|
+
const [handsById, setHandsById] = useState<
|
|
66
|
+
ReadonlyMap<string, MobileHandRegistration>
|
|
67
|
+
>(() => new Map());
|
|
68
|
+
const isMobile = useIsMobile();
|
|
69
|
+
const registerHand = useCallback((hand: MobileHandRegistration) => {
|
|
70
|
+
setHandsById((current) => {
|
|
71
|
+
const previous = current.get(hand.id);
|
|
72
|
+
if (
|
|
73
|
+
previous &&
|
|
74
|
+
previous.zone === hand.zone &&
|
|
75
|
+
previous.label === hand.label &&
|
|
76
|
+
previous.role === hand.role &&
|
|
77
|
+
previous.order === hand.order &&
|
|
78
|
+
previous.version === hand.version &&
|
|
79
|
+
previous.count === hand.count &&
|
|
80
|
+
previous.active === hand.active &&
|
|
81
|
+
previous.autoOpen === hand.autoOpen
|
|
82
|
+
) {
|
|
83
|
+
return current;
|
|
84
|
+
}
|
|
85
|
+
const next = new Map(current);
|
|
86
|
+
next.set(hand.id, hand);
|
|
87
|
+
return next;
|
|
88
|
+
});
|
|
89
|
+
return () => {
|
|
90
|
+
setHandsById((current) => {
|
|
91
|
+
if (!current.has(hand.id)) return current;
|
|
92
|
+
const next = new Map(current);
|
|
93
|
+
next.delete(hand.id);
|
|
94
|
+
return next;
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
}, []);
|
|
98
|
+
const value = useMemo<MobileHandTrayContextValue>(
|
|
99
|
+
() => ({ registerHand }),
|
|
100
|
+
[registerHand],
|
|
101
|
+
);
|
|
102
|
+
const hands = useMemo(
|
|
103
|
+
() =>
|
|
104
|
+
[...handsById.values()].sort(
|
|
105
|
+
(a, b) =>
|
|
106
|
+
(a.order ?? ROLE_PRIORITY[a.role]) -
|
|
107
|
+
(b.order ?? ROLE_PRIORITY[b.role]) ||
|
|
108
|
+
ROLE_PRIORITY[a.role] - ROLE_PRIORITY[b.role] ||
|
|
109
|
+
a.label.localeCompare(b.label),
|
|
110
|
+
),
|
|
111
|
+
[handsById],
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const trayActive = isMobile && hands.length > 0;
|
|
115
|
+
const stateValue = useMemo(() => ({ active: trayActive }), [trayActive]);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<MobileHandTrayContext.Provider value={value}>
|
|
119
|
+
<MobileHandTrayStateContext.Provider value={stateValue}>
|
|
120
|
+
<div
|
|
121
|
+
data-dreamboard-mobile-hand-shell=""
|
|
122
|
+
data-mobile-hand-count={hands.length}
|
|
123
|
+
data-mobile-hand-tray-active={trayActive ? "true" : undefined}
|
|
124
|
+
style={{
|
|
125
|
+
minHeight: "100%",
|
|
126
|
+
paddingBottom: trayActive
|
|
127
|
+
? "calc(92px + env(safe-area-inset-bottom, 0px))"
|
|
128
|
+
: undefined,
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
{children}
|
|
132
|
+
</div>
|
|
133
|
+
{trayActive ? <MobileHandTray hands={hands} /> : null}
|
|
134
|
+
</MobileHandTrayStateContext.Provider>
|
|
135
|
+
</MobileHandTrayContext.Provider>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function useRegisterMobileHand(hand: MobileHandRegistration): void {
|
|
140
|
+
const context = useContext(MobileHandTrayContext);
|
|
141
|
+
if (!context) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
"Generated hand surfaces must be rendered inside <UI.Root>; mobile hand tray registration is unavailable.",
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
useEffect(() => context.registerHand(hand), [context, hand]);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Whether the mobile hand tray is currently presenting hands — i.e. the
|
|
151
|
+
* viewport is below the mobile breakpoint and at least one primary/auxiliary
|
|
152
|
+
* hand has registered. Authors can use this to drop redundant inline hand
|
|
153
|
+
* chrome (labels, framing) that the tray already provides, instead of guessing
|
|
154
|
+
* the breakpoint with a CSS media query. Returns `false` outside `<UI.Root>`.
|
|
155
|
+
*/
|
|
156
|
+
export function useMobileHandTrayActive(): boolean {
|
|
157
|
+
return useContext(MobileHandTrayStateContext).active;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function MobileHandTray({
|
|
161
|
+
hands,
|
|
162
|
+
}: {
|
|
163
|
+
hands: readonly MobileHandRegistration[];
|
|
164
|
+
}) {
|
|
165
|
+
// Snap height of the dock. `peek` is the collapsed handle bar; `raised` shows
|
|
166
|
+
// the hand at an actionable height; `expanded` opens it tall for a long hand.
|
|
167
|
+
// The dock is always mounted and never modal — there is no scrim, and only
|
|
168
|
+
// the dock surface captures pointer events, so the board behind it stays
|
|
169
|
+
// visible and interactive.
|
|
170
|
+
const [snap, setSnap] = useState<DockSnap>("peek");
|
|
171
|
+
const open = snap !== "peek";
|
|
172
|
+
const advanceSnap = useCallback((direction: 1 | -1) => {
|
|
173
|
+
setSnap((current) => {
|
|
174
|
+
const index = DOCK_SNAPS.indexOf(current);
|
|
175
|
+
const next = Math.min(
|
|
176
|
+
DOCK_SNAPS.length - 1,
|
|
177
|
+
Math.max(0, index + direction),
|
|
178
|
+
);
|
|
179
|
+
return DOCK_SNAPS[next] ?? current;
|
|
180
|
+
});
|
|
181
|
+
}, []);
|
|
182
|
+
const [activeId, setActiveId] = useState<string | null>(null);
|
|
183
|
+
const themeCssVars = useThemeCssVars();
|
|
184
|
+
|
|
185
|
+
const preferredHand =
|
|
186
|
+
hands.find((hand) => hand.id === activeId) ??
|
|
187
|
+
hands.find((hand) => hand.active) ??
|
|
188
|
+
hands.find((hand) => hand.role === "primary") ??
|
|
189
|
+
hands[0] ??
|
|
190
|
+
null;
|
|
191
|
+
const selectedHand =
|
|
192
|
+
hands.find((hand) => hand.id === activeId) ?? preferredHand;
|
|
193
|
+
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
if (!preferredHand) {
|
|
196
|
+
setActiveId(null);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
setActiveId((current) =>
|
|
200
|
+
current && hands.some((hand) => hand.id === current)
|
|
201
|
+
? current
|
|
202
|
+
: preferredHand.id,
|
|
203
|
+
);
|
|
204
|
+
}, [hands, preferredHand]);
|
|
205
|
+
|
|
206
|
+
// Auto-raise to the actionable height when it becomes this seat's turn to
|
|
207
|
+
// act, and settle back to the peek when the turn passes. We only toggle on
|
|
208
|
+
// the active transition, so a manual expand/collapse sticks until the turn
|
|
209
|
+
// changes.
|
|
210
|
+
const active = selectedHand?.active ?? false;
|
|
211
|
+
const prevActiveRef = useRef(false);
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
if (active !== prevActiveRef.current) {
|
|
214
|
+
prevActiveRef.current = active;
|
|
215
|
+
setSnap(active ? "raised" : "peek");
|
|
216
|
+
}
|
|
217
|
+
}, [active]);
|
|
218
|
+
|
|
219
|
+
// Swipe the handle up to expand a step (peek → raised → expanded), down to
|
|
220
|
+
// collapse a step. Taps fall through to the handle's onClick toggle
|
|
221
|
+
// (filterTaps), and velocity lets a quick flick advance even on a short drag.
|
|
222
|
+
const bindDrag = useDrag(
|
|
223
|
+
({ movement: [, my], velocity: [, vy], last }) => {
|
|
224
|
+
if (!last) return;
|
|
225
|
+
if (my < -24 || (my < 0 && vy > 0.4)) advanceSnap(1);
|
|
226
|
+
else if (my > 24 || (my > 0 && vy > 0.4)) advanceSnap(-1);
|
|
227
|
+
},
|
|
228
|
+
{ axis: "y", filterTaps: true },
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
if (!selectedHand) return null;
|
|
232
|
+
|
|
233
|
+
const activeBadges = hands.filter(
|
|
234
|
+
(hand) => hand.id !== selectedHand.id && hand.active,
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
return (
|
|
238
|
+
<div
|
|
239
|
+
data-dreamboard-mobile-hand-tray=""
|
|
240
|
+
data-state={snap}
|
|
241
|
+
data-active-hand={selectedHand.id}
|
|
242
|
+
style={{
|
|
243
|
+
...themeCssVars,
|
|
244
|
+
position: "fixed",
|
|
245
|
+
left: "env(safe-area-inset-left, 0px)",
|
|
246
|
+
right: "env(safe-area-inset-right, 0px)",
|
|
247
|
+
bottom: 0,
|
|
248
|
+
zIndex: 900,
|
|
249
|
+
display: "flex",
|
|
250
|
+
justifyContent: "center",
|
|
251
|
+
// Non-modal: the wrapper ignores pointers so taps land on the board;
|
|
252
|
+
// only the dock panel below opts back in.
|
|
253
|
+
pointerEvents: "none",
|
|
254
|
+
}}
|
|
255
|
+
>
|
|
256
|
+
<div
|
|
257
|
+
className="flex w-full flex-col overflow-hidden rounded-t-2xl"
|
|
258
|
+
style={{
|
|
259
|
+
pointerEvents: "auto",
|
|
260
|
+
maxWidth: "min(40rem, 100%)",
|
|
261
|
+
background: "var(--background, #fdfbf7)",
|
|
262
|
+
color: "var(--foreground, #2d2d2d)",
|
|
263
|
+
borderTop: "1px solid var(--border, rgba(45,45,45,0.18))",
|
|
264
|
+
borderLeft: "1px solid var(--border, rgba(45,45,45,0.10))",
|
|
265
|
+
borderRight: "1px solid var(--border, rgba(45,45,45,0.10))",
|
|
266
|
+
boxShadow:
|
|
267
|
+
"0 -18px 48px -22px rgba(45,45,45,0.30), 0 -6px 18px -16px rgba(45,45,45,0.20)",
|
|
268
|
+
}}
|
|
269
|
+
>
|
|
270
|
+
{/* Handle + summary bar — drag or tap to toggle peek/open. */}
|
|
271
|
+
<button
|
|
272
|
+
type="button"
|
|
273
|
+
{...bindDrag()}
|
|
274
|
+
onClick={() =>
|
|
275
|
+
setSnap((value) => (value === "peek" ? "raised" : "peek"))
|
|
276
|
+
}
|
|
277
|
+
aria-controls={TRAY_CONTENT_ID}
|
|
278
|
+
aria-expanded={open}
|
|
279
|
+
data-dreamboard-mobile-hand-trigger=""
|
|
280
|
+
data-hand-role={selectedHand.role}
|
|
281
|
+
data-active-badges={activeBadges.length || undefined}
|
|
282
|
+
className="flex w-full flex-col items-stretch gap-1.5 px-4 pb-2 pt-2 text-left"
|
|
283
|
+
style={{
|
|
284
|
+
touchAction: "none",
|
|
285
|
+
background: "transparent",
|
|
286
|
+
border: 0,
|
|
287
|
+
cursor: "pointer",
|
|
288
|
+
}}
|
|
289
|
+
>
|
|
290
|
+
<span
|
|
291
|
+
aria-hidden
|
|
292
|
+
className="mx-auto h-1.5 w-10 rounded-full"
|
|
293
|
+
style={{ background: "rgba(45,45,45,0.28)" }}
|
|
294
|
+
/>
|
|
295
|
+
<span className="flex items-center justify-between gap-2">
|
|
296
|
+
<span className="flex min-w-0 items-center gap-2 text-sm font-semibold">
|
|
297
|
+
<span className="truncate">{selectedHand.label}</span>
|
|
298
|
+
<span
|
|
299
|
+
className="rounded-full px-2 py-0.5 text-xs font-bold"
|
|
300
|
+
style={{ background: "rgba(45,45,45,0.08)" }}
|
|
301
|
+
>
|
|
302
|
+
{selectedHand.count}
|
|
303
|
+
</span>
|
|
304
|
+
{active ? (
|
|
305
|
+
<span className="rounded-full bg-amber-400 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-amber-950">
|
|
306
|
+
your turn
|
|
307
|
+
</span>
|
|
308
|
+
) : null}
|
|
309
|
+
</span>
|
|
310
|
+
{activeBadges.length > 0 ? (
|
|
311
|
+
<span
|
|
312
|
+
aria-label={`${activeBadges.length} other active hand sections`}
|
|
313
|
+
className="inline-flex min-w-5 items-center justify-center rounded-full bg-red-500 px-1.5 text-xs font-bold text-white"
|
|
314
|
+
>
|
|
315
|
+
{activeBadges.length}
|
|
316
|
+
</span>
|
|
317
|
+
) : null}
|
|
318
|
+
</span>
|
|
319
|
+
</button>
|
|
320
|
+
|
|
321
|
+
{/* Collapsible body — kept mounted at the peek (clipped to zero height
|
|
322
|
+
and made inert) so raising the dock never remounts the hand or
|
|
323
|
+
loses scroll position, and so the auto-raise can animate. The body
|
|
324
|
+
is the scroll container, so the action slot's sticky footer pins to
|
|
325
|
+
its bottom edge. */}
|
|
326
|
+
<div
|
|
327
|
+
id={TRAY_CONTENT_ID}
|
|
328
|
+
role="region"
|
|
329
|
+
aria-label={selectedHand.label}
|
|
330
|
+
data-state={snap}
|
|
331
|
+
inert={open ? undefined : true}
|
|
332
|
+
className="overscroll-contain"
|
|
333
|
+
style={{
|
|
334
|
+
maxHeight: DOCK_SNAP_MAX_HEIGHT[snap],
|
|
335
|
+
overflowY: open ? "auto" : "hidden",
|
|
336
|
+
transition: "max-height 240ms ease",
|
|
337
|
+
}}
|
|
338
|
+
>
|
|
339
|
+
{hands.length > 1 ? (
|
|
340
|
+
<div
|
|
341
|
+
role="tablist"
|
|
342
|
+
aria-label="Hand sections"
|
|
343
|
+
className="flex gap-2 overflow-x-auto px-4 pb-2 pt-1 [scrollbar-width:none]"
|
|
344
|
+
>
|
|
345
|
+
{hands.map((hand) => (
|
|
346
|
+
<button
|
|
347
|
+
key={hand.id}
|
|
348
|
+
type="button"
|
|
349
|
+
role="tab"
|
|
350
|
+
aria-selected={hand.id === selectedHand.id}
|
|
351
|
+
data-active={hand.active || undefined}
|
|
352
|
+
data-hand-role={hand.role}
|
|
353
|
+
onClick={() => setActiveId(hand.id)}
|
|
354
|
+
className={clsx(
|
|
355
|
+
"shrink-0 rounded-full border px-3 py-1 text-sm font-semibold",
|
|
356
|
+
hand.id === selectedHand.id
|
|
357
|
+
? "border-slate-900 bg-white text-slate-950"
|
|
358
|
+
: "border-slate-300 bg-white/60 text-slate-600",
|
|
359
|
+
hand.active && hand.id !== selectedHand.id
|
|
360
|
+
? "ring-2 ring-red-400"
|
|
361
|
+
: null,
|
|
362
|
+
)}
|
|
363
|
+
>
|
|
364
|
+
{hand.label} ({hand.count})
|
|
365
|
+
</button>
|
|
366
|
+
))}
|
|
367
|
+
</div>
|
|
368
|
+
) : null}
|
|
369
|
+
<div
|
|
370
|
+
className="px-3 pt-2 sm:px-4"
|
|
371
|
+
style={{
|
|
372
|
+
paddingBottom: "calc(20px + env(safe-area-inset-bottom, 0px))",
|
|
373
|
+
}}
|
|
374
|
+
>
|
|
375
|
+
{selectedHand.content}
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
@@ -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
|
+
}
|