@dreamboard-games/ui-sdk 0.0.43 → 0.0.45
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/dist/components/ActionButton.d.ts.map +1 -1
- package/dist/components/ActionButton.js +2 -1
- package/dist/components/Card.d.ts +1 -1
- package/dist/components/Card.d.ts.map +1 -1
- package/dist/components/DiceRoller.d.ts +3 -2
- package/dist/components/DiceRoller.d.ts.map +1 -1
- package/dist/components/DiceRoller.js +4 -13
- package/dist/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/components/ErrorBoundary.js +94 -2
- package/dist/components/InteractionForm.d.ts +1 -1
- package/dist/components/InteractionForm.d.ts.map +1 -1
- package/dist/components/InteractionForm.js +29 -15
- package/dist/components/PrimaryActionButton.d.ts.map +1 -1
- package/dist/components/PrimaryActionButton.js +7 -6
- package/dist/components/ResourceCounter.d.ts +59 -25
- package/dist/components/ResourceCounter.d.ts.map +1 -1
- package/dist/components/ResourceCounter.js +106 -115
- package/dist/components/Toast.d.ts +13 -6
- package/dist/components/Toast.d.ts.map +1 -1
- package/dist/components/Toast.js +10 -5
- package/dist/components/board/HexGrid.js +6 -6
- package/dist/components/board/target-layer.d.ts +18 -2
- package/dist/components/board/target-layer.d.ts.map +1 -1
- package/dist/components/board/target-layer.js +20 -3
- package/dist/components/index.d.ts +3 -4
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +3 -4
- package/dist/components/surfaces/InboxSurface.d.ts.map +1 -1
- package/dist/components/surfaces/InboxSurface.js +2 -6
- package/dist/components/surfaces/PlayerCardsSurface.js +2 -2
- package/dist/components/surfaces/internal/CardZoneRoutedForm.d.ts +7 -0
- package/dist/components/surfaces/internal/CardZoneRoutedForm.d.ts.map +1 -0
- package/dist/components/surfaces/internal/CardZoneRoutedForm.js +9 -0
- package/dist/components/surfaces/internal/DefaultInteractionButton.d.ts.map +1 -1
- package/dist/components/surfaces/internal/DefaultInteractionButton.js +5 -8
- package/dist/components/surfaces/internal/useCardZoneInteractions.d.ts +2 -2
- package/dist/components/surfaces/internal/useCardZoneInteractions.d.ts.map +1 -1
- package/dist/components/surfaces/internal/useCardZoneInteractions.js +19 -43
- package/dist/context/InteractionDraftContext.d.ts +11 -2
- package/dist/context/InteractionDraftContext.d.ts.map +1 -1
- package/dist/context/InteractionDraftContext.js +41 -4
- package/dist/defaults/components.d.ts +0 -5
- package/dist/defaults/components.d.ts.map +1 -1
- package/dist/defaults/components.js +7 -11
- package/dist/hooks/useBoardInteractions.d.ts +35 -12
- package/dist/hooks/useBoardInteractions.d.ts.map +1 -1
- package/dist/hooks/useBoardInteractions.js +186 -82
- package/dist/hooks/useInteractionHandle.d.ts +1 -1
- package/dist/hooks/useInteractionHandle.d.ts.map +1 -1
- package/dist/hooks/useInteractionHandle.js +12 -27
- package/dist/index.d.ts +11 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -14
- package/dist/primitives/board.d.ts +53 -3
- package/dist/primitives/board.d.ts.map +1 -1
- package/dist/primitives/board.js +65 -41
- package/dist/primitives/dialog-lifecycle.d.ts +17 -0
- package/dist/primitives/dialog-lifecycle.d.ts.map +1 -0
- package/dist/primitives/dialog-lifecycle.js +24 -0
- package/dist/primitives/dice.d.ts +31 -0
- package/dist/primitives/dice.d.ts.map +1 -0
- package/dist/primitives/dice.js +33 -0
- package/dist/primitives/game.d.ts +55 -0
- package/dist/primitives/game.d.ts.map +1 -0
- package/dist/primitives/game.js +101 -0
- package/dist/primitives/index.d.ts +7 -4
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +7 -4
- package/dist/primitives/interaction-form-binding.d.ts +12 -0
- package/dist/primitives/interaction-form-binding.d.ts.map +1 -0
- package/dist/primitives/interaction-form-binding.js +14 -0
- package/dist/primitives/interaction-submit.d.ts +23 -0
- package/dist/primitives/interaction-submit.d.ts.map +1 -0
- package/dist/primitives/interaction-submit.js +41 -0
- package/dist/primitives/interaction.d.ts +76 -6
- package/dist/primitives/interaction.d.ts.map +1 -1
- package/dist/primitives/interaction.js +210 -26
- package/dist/primitives/player-roster.d.ts +2 -1
- package/dist/primitives/player-roster.d.ts.map +1 -1
- package/dist/primitives/prompt.d.ts +36 -11
- package/dist/primitives/prompt.d.ts.map +1 -1
- package/dist/primitives/prompt.js +29 -17
- package/dist/primitives/ui.d.ts +9 -0
- package/dist/primitives/ui.d.ts.map +1 -0
- package/dist/primitives/ui.js +7 -0
- package/dist/primitives/zone.d.ts +111 -5
- package/dist/primitives/zone.d.ts.map +1 -1
- package/dist/primitives/zone.js +349 -9
- package/dist/reducer.d.ts +2 -14
- package/dist/reducer.d.ts.map +1 -1
- package/dist/reducer.js +1 -14
- package/dist/runtime/createPluginRuntimeAPI.js +1 -1
- package/dist/types/hex-color.d.ts +7 -0
- package/dist/types/hex-color.d.ts.map +1 -0
- package/dist/types/hex-color.js +13 -0
- package/dist/types/player-state.d.ts +28 -14
- package/dist/types/player-state.d.ts.map +1 -1
- package/dist/types/plugin-state.d.ts +9 -3
- package/dist/types/plugin-state.d.ts.map +1 -1
- package/dist/ui-contract.d.ts +119 -14
- package/dist/ui-contract.d.ts.map +1 -1
- package/dist/ui-contract.js +4 -3
- package/dist/ui-sdk.d.ts +1637 -1245
- package/dist/utils/interaction-inputs.d.ts +8 -5
- package/dist/utils/interaction-inputs.d.ts.map +1 -1
- package/dist/utils/interaction-inputs.js +82 -14
- package/dist/utils/interaction-router.d.ts +31 -0
- package/dist/utils/interaction-router.d.ts.map +1 -0
- package/dist/utils/interaction-router.js +114 -0
- package/package.json +1 -1
- package/src/components/ActionButton.tsx +2 -1
- package/src/components/Card.tsx +1 -1
- package/src/components/DiceRoller.tsx +13 -22
- package/src/components/ErrorBoundary.test.tsx +19 -0
- package/src/components/ErrorBoundary.tsx +113 -24
- package/src/components/InteractionForm.test.tsx +24 -0
- package/src/components/InteractionForm.tsx +48 -23
- package/src/components/PrimaryActionButton.tsx +19 -5
- package/src/components/ResourceCounter.test.tsx +13 -13
- package/src/components/ResourceCounter.tsx +238 -244
- package/src/components/Toast.tsx +23 -10
- package/src/components/__fixtures__/ResourceCounter.fixture.tsx +70 -169
- package/src/components/board/HexGrid.tsx +6 -6
- package/src/components/board/target-layer.ts +44 -5
- package/src/components/index.ts +17 -10
- package/src/components/surfaces/InboxSurface.tsx +7 -5
- package/src/components/surfaces/PlayerCardsSurface.tsx +6 -6
- package/src/components/surfaces/internal/CardZoneRoutedForm.tsx +35 -0
- package/src/components/surfaces/internal/DefaultInteractionButton.tsx +17 -7
- package/src/components/surfaces/internal/useCardZoneInteractions.ts +25 -67
- package/src/context/InteractionDraftContext.tsx +51 -5
- package/src/defaults/components.tsx +12 -50
- package/src/defaults/defaults.test.tsx +1 -50
- package/src/hooks/useBoardInteractions.test.tsx +240 -17
- package/src/hooks/useBoardInteractions.ts +330 -105
- package/src/hooks/useInteractionHandle.ts +23 -28
- package/src/index.test.ts +60 -40
- package/src/index.ts +30 -36
- package/src/primitives/board.test.tsx +73 -0
- package/src/primitives/board.tsx +191 -40
- package/src/primitives/dialog-lifecycle.ts +58 -0
- package/src/primitives/dice.test.tsx +47 -0
- package/src/primitives/dice.tsx +79 -0
- package/src/primitives/game.test.tsx +98 -0
- package/src/primitives/game.tsx +213 -0
- package/src/primitives/index.ts +84 -0
- package/src/primitives/interaction-form-binding.tsx +56 -0
- package/src/primitives/interaction-submit.ts +90 -0
- package/src/primitives/interaction.test.tsx +396 -0
- package/src/primitives/interaction.tsx +451 -31
- package/src/primitives/player-roster.tsx +2 -1
- package/src/primitives/prompt.test.tsx +94 -3
- package/src/primitives/prompt.tsx +87 -48
- package/src/primitives/ui.test.tsx +131 -0
- package/src/primitives/ui.tsx +13 -0
- package/src/primitives/zone.test.tsx +305 -0
- package/src/primitives/zone.tsx +660 -12
- package/src/reducer.ts +7 -20
- package/src/runtime/createPluginRuntimeAPI.ts +1 -1
- package/src/types/hex-color.ts +20 -0
- package/src/types/player-state.ts +36 -18
- package/src/types/plugin-state.ts +10 -3
- package/src/ui-contract.ts +253 -21
- package/src/utils/interaction-inputs.test.ts +400 -0
- package/src/utils/interaction-inputs.ts +113 -11
- package/src/utils/interaction-router.ts +200 -0
|
@@ -1,275 +1,269 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { motion, AnimatePresence } from "framer-motion";
|
|
14
|
-
import { clsx } from "clsx";
|
|
15
|
-
import { createElement, type ComponentType, type ReactNode } from "react";
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
createElement,
|
|
4
|
+
useContext,
|
|
5
|
+
useMemo,
|
|
6
|
+
type ComponentType,
|
|
7
|
+
type HTMLAttributes,
|
|
8
|
+
type ReactElement,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
} from "react";
|
|
16
11
|
import type { ResourceId } from "@dreamboard/manifest-contract";
|
|
17
12
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} from "../internal/ui/tooltip.js";
|
|
23
|
-
import { useTheme } from "../theme/ThemeProvider.js";
|
|
24
|
-
|
|
25
|
-
type CssVariableStyle = React.CSSProperties & {
|
|
26
|
-
[K in `--${string}`]?: string | number;
|
|
27
|
-
};
|
|
13
|
+
composeEventHandlers,
|
|
14
|
+
renderPrimitive,
|
|
15
|
+
type PrimitiveCommonProps,
|
|
16
|
+
} from "../primitives/index.js";
|
|
28
17
|
|
|
29
|
-
export interface ResourceDisplayConfig {
|
|
30
|
-
type:
|
|
18
|
+
export interface ResourceDisplayConfig<Resource extends string = ResourceId> {
|
|
19
|
+
type: Resource;
|
|
31
20
|
label: string;
|
|
32
21
|
icon:
|
|
33
22
|
| ReactNode
|
|
34
23
|
| ComponentType<{
|
|
35
24
|
className?: string;
|
|
36
25
|
strokeWidth?: number;
|
|
37
|
-
"aria-hidden"?:
|
|
26
|
+
"aria-hidden"?: boolean | "true" | "false";
|
|
38
27
|
}>;
|
|
39
28
|
iconColor?: string;
|
|
40
29
|
bgColor?: string;
|
|
41
30
|
textColor?: string;
|
|
42
31
|
}
|
|
43
32
|
|
|
44
|
-
export interface
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
33
|
+
export interface ResourceCounterItemState<
|
|
34
|
+
Resource extends string = ResourceId,
|
|
35
|
+
> {
|
|
36
|
+
type: Resource;
|
|
37
|
+
label: string;
|
|
38
|
+
icon: ResourceDisplayConfig<Resource>["icon"];
|
|
39
|
+
iconColor?: string;
|
|
40
|
+
bgColor?: string;
|
|
41
|
+
textColor?: string;
|
|
42
|
+
count: number;
|
|
43
|
+
isZero: boolean;
|
|
44
|
+
interactive: boolean;
|
|
45
|
+
select: () => void;
|
|
46
|
+
renderIcon: (props?: ResourceIconProps) => ReactNode;
|
|
47
|
+
dataAttributes: {
|
|
48
|
+
"data-resource-id": Resource;
|
|
49
|
+
"data-resource-count": number;
|
|
50
|
+
"data-resource-zero": boolean | undefined;
|
|
51
|
+
"data-interactive": boolean | undefined;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ResourceIconProps {
|
|
53
56
|
className?: string;
|
|
57
|
+
strokeWidth?: number;
|
|
58
|
+
"aria-hidden"?: boolean | "true" | "false";
|
|
54
59
|
}
|
|
55
60
|
|
|
56
|
-
export
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}: ResourceCounterProps) {
|
|
66
|
-
const theme = useTheme();
|
|
67
|
-
const reducedMotion = theme.motion.reducedMotion === "true";
|
|
61
|
+
export type ResourceCounterRootProps<Resource extends string = ResourceId> =
|
|
62
|
+
Omit<PrimitiveCommonProps, "children"> &
|
|
63
|
+
Omit<HTMLAttributes<HTMLElement>, "children"> & {
|
|
64
|
+
resources: ReadonlyArray<ResourceDisplayConfig<Resource>>;
|
|
65
|
+
counts: Partial<Record<Resource, number>>;
|
|
66
|
+
zero?: "show" | "hide";
|
|
67
|
+
onResourceClick?: (resourceType: Resource) => void;
|
|
68
|
+
children: ReactNode;
|
|
69
|
+
};
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
fontSize: theme.typography.fontSize.sm,
|
|
73
|
-
paddingBlock: theme.space[1],
|
|
74
|
-
paddingInline: theme.space[2],
|
|
75
|
-
gap: theme.space[1],
|
|
76
|
-
},
|
|
77
|
-
md: {
|
|
78
|
-
icon: "w-5 h-5",
|
|
79
|
-
fontSize: theme.typography.fontSize.md,
|
|
80
|
-
paddingBlock: theme.space[1.5],
|
|
81
|
-
paddingInline: theme.space[3],
|
|
82
|
-
gap: theme.space[1.5],
|
|
83
|
-
},
|
|
84
|
-
lg: {
|
|
85
|
-
icon: "w-6 h-6",
|
|
86
|
-
fontSize: theme.typography.fontSize.lg,
|
|
87
|
-
paddingBlock: theme.space[2],
|
|
88
|
-
paddingInline: theme.space[4],
|
|
89
|
-
gap: theme.space[2],
|
|
90
|
-
},
|
|
91
|
-
} as const;
|
|
71
|
+
export type BoundResourceCounterRootProps<
|
|
72
|
+
Resource extends string = ResourceId,
|
|
73
|
+
> = Omit<ResourceCounterRootProps<Resource>, "resources">;
|
|
92
74
|
|
|
93
|
-
|
|
75
|
+
export type ResourceCounterProps<Resource extends string = ResourceId> =
|
|
76
|
+
ResourceCounterRootProps<Resource>;
|
|
94
77
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
78
|
+
export type ResourceCounterPartProps<Resource extends string = ResourceId> =
|
|
79
|
+
Omit<PrimitiveCommonProps, "children"> &
|
|
80
|
+
Omit<HTMLAttributes<HTMLElement>, "children"> & {
|
|
81
|
+
children?:
|
|
82
|
+
| ReactNode
|
|
83
|
+
| ((resource: ResourceCounterItemState<Resource>) => ReactNode);
|
|
84
|
+
};
|
|
98
85
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
paddingInline: styles.paddingInline,
|
|
112
|
-
borderRadius: theme.radius.md,
|
|
113
|
-
border: `2px solid ${theme.semantic.border.default}`,
|
|
114
|
-
background: bgColorClass ? undefined : theme.semantic.surface.card,
|
|
115
|
-
boxShadow: theme.elevation.rest,
|
|
116
|
-
fontFamily: theme.typography.fontFamily.body,
|
|
117
|
-
transition: `transform ${theme.motion.duration.fast} ${theme.motion.easing.out}, box-shadow ${theme.motion.duration.normal} ${theme.motion.easing.out}`,
|
|
118
|
-
});
|
|
86
|
+
const ResourceCounterItemContext =
|
|
87
|
+
createContext<ResourceCounterItemState<string> | null>(null);
|
|
88
|
+
|
|
89
|
+
function useResourceCounterItemContext<Resource extends string>() {
|
|
90
|
+
const value = useContext(ResourceCounterItemContext);
|
|
91
|
+
if (!value) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
"ResourceCounter item primitives must be rendered inside <ResourceCounter.Item>.",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return value as ResourceCounterItemState<Resource>;
|
|
97
|
+
}
|
|
119
98
|
|
|
99
|
+
function renderResourceIcon(
|
|
100
|
+
icon: ResourceDisplayConfig<string>["icon"],
|
|
101
|
+
props: ResourceIconProps = {},
|
|
102
|
+
) {
|
|
103
|
+
if (typeof icon === "function") {
|
|
104
|
+
return createElement(icon, {
|
|
105
|
+
"aria-hidden": true,
|
|
106
|
+
strokeWidth: 2.5,
|
|
107
|
+
...props,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const {
|
|
111
|
+
strokeWidth: _strokeWidth,
|
|
112
|
+
"aria-hidden": ariaHidden,
|
|
113
|
+
...spanProps
|
|
114
|
+
} = props;
|
|
120
115
|
return (
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
layout === "row" && "flex flex-wrap",
|
|
125
|
-
layout === "compact" && "flex flex-wrap",
|
|
126
|
-
layout === "row" && "gap-3 sm:gap-4",
|
|
127
|
-
layout === "compact" && "gap-2",
|
|
128
|
-
layout === "grid" && "gap-3 sm:gap-4",
|
|
129
|
-
className,
|
|
130
|
-
)}
|
|
131
|
-
style={
|
|
132
|
-
layout === "grid"
|
|
133
|
-
? {
|
|
134
|
-
gridTemplateColumns: `repeat(${columns}, 1fr)`,
|
|
135
|
-
fontFamily: theme.typography.fontFamily.body,
|
|
136
|
-
}
|
|
137
|
-
: { fontFamily: theme.typography.fontFamily.body }
|
|
138
|
-
}
|
|
139
|
-
role="list"
|
|
140
|
-
aria-label="Resource counts"
|
|
116
|
+
<span
|
|
117
|
+
aria-hidden={ariaHidden === undefined ? true : ariaHidden !== "false"}
|
|
118
|
+
{...spanProps}
|
|
141
119
|
>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const count = counts[type] ?? 0;
|
|
147
|
-
const iconNode =
|
|
148
|
-
typeof icon === "function" ? (
|
|
149
|
-
createElement(icon, {
|
|
150
|
-
className: clsx(styles.icon, iconColor),
|
|
151
|
-
strokeWidth: 2.5,
|
|
152
|
-
"aria-hidden": "true",
|
|
153
|
-
})
|
|
154
|
-
) : (
|
|
155
|
-
<span
|
|
156
|
-
className={clsx(styles.icon, iconColor)}
|
|
157
|
-
aria-hidden="true"
|
|
158
|
-
style={{
|
|
159
|
-
display: "inline-flex",
|
|
160
|
-
alignItems: "center",
|
|
161
|
-
justifyContent: "center",
|
|
162
|
-
fontSize: "1.1em",
|
|
163
|
-
}}
|
|
164
|
-
>
|
|
165
|
-
{icon}
|
|
166
|
-
</span>
|
|
167
|
-
);
|
|
168
|
-
const content = (
|
|
169
|
-
<>
|
|
170
|
-
{iconNode}
|
|
171
|
-
<motion.span
|
|
172
|
-
key={count}
|
|
173
|
-
initial={reducedMotion ? { scale: 1 } : { scale: 1.5 }}
|
|
174
|
-
animate={{ scale: 1 }}
|
|
175
|
-
className={clsx("font-bold", textColor)}
|
|
176
|
-
style={{
|
|
177
|
-
fontSize: styles.fontSize,
|
|
178
|
-
fontFamily: theme.typography.fontFamily.tabular,
|
|
179
|
-
fontWeight: theme.typography.fontWeight.bold,
|
|
180
|
-
color: textColor
|
|
181
|
-
? undefined
|
|
182
|
-
: theme.semantic.text.primary,
|
|
183
|
-
}}
|
|
184
|
-
>
|
|
185
|
-
{count}
|
|
186
|
-
</motion.span>
|
|
187
|
-
</>
|
|
188
|
-
);
|
|
120
|
+
{icon}
|
|
121
|
+
</span>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
189
124
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
);
|
|
125
|
+
function resolveResourceChildren<Resource extends string>(
|
|
126
|
+
children: ResourceCounterPartProps<Resource>["children"],
|
|
127
|
+
resource: ResourceCounterItemState<Resource>,
|
|
128
|
+
) {
|
|
129
|
+
return typeof children === "function" ? children(resource) : children;
|
|
130
|
+
}
|
|
197
131
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
132
|
+
export function ResourceCounterRoot<Resource extends string = ResourceId>({
|
|
133
|
+
resources,
|
|
134
|
+
counts,
|
|
135
|
+
zero = "show",
|
|
136
|
+
onResourceClick,
|
|
137
|
+
children,
|
|
138
|
+
"aria-label": ariaLabel,
|
|
139
|
+
...props
|
|
140
|
+
}: ResourceCounterRootProps<Resource>) {
|
|
141
|
+
const items = useMemo(
|
|
142
|
+
() =>
|
|
143
|
+
resources
|
|
144
|
+
.map((resource) => {
|
|
145
|
+
const count = counts[resource.type] ?? 0;
|
|
146
|
+
return {
|
|
147
|
+
...resource,
|
|
148
|
+
count,
|
|
149
|
+
isZero: count === 0,
|
|
150
|
+
interactive: Boolean(onResourceClick),
|
|
151
|
+
select: () => onResourceClick?.(resource.type),
|
|
152
|
+
renderIcon: (iconProps) =>
|
|
153
|
+
renderResourceIcon(resource.icon, iconProps),
|
|
154
|
+
dataAttributes: {
|
|
155
|
+
"data-resource-id": resource.type,
|
|
156
|
+
"data-resource-count": count,
|
|
157
|
+
"data-resource-zero": count === 0 || undefined,
|
|
158
|
+
"data-interactive": onResourceClick ? true : undefined,
|
|
159
|
+
},
|
|
160
|
+
} satisfies ResourceCounterItemState<Resource>;
|
|
161
|
+
})
|
|
162
|
+
.filter((resource) => zero === "show" || !resource.isZero),
|
|
163
|
+
[counts, onResourceClick, resources, zero],
|
|
164
|
+
);
|
|
202
165
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
166
|
+
return renderPrimitive("div", {
|
|
167
|
+
role: "list",
|
|
168
|
+
"aria-label": ariaLabel ?? "Resource counts",
|
|
169
|
+
"data-dreamboard-resource-counter": "",
|
|
170
|
+
...props,
|
|
171
|
+
children: items.map((resource) => (
|
|
172
|
+
<ResourceCounterItemContext.Provider key={resource.type} value={resource}>
|
|
173
|
+
{children}
|
|
174
|
+
</ResourceCounterItemContext.Provider>
|
|
175
|
+
)),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
214
178
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
179
|
+
export function ResourceCounterItem<Resource extends string = ResourceId>({
|
|
180
|
+
children,
|
|
181
|
+
onClick,
|
|
182
|
+
"aria-label": ariaLabel,
|
|
183
|
+
...props
|
|
184
|
+
}: ResourceCounterPartProps<Resource>) {
|
|
185
|
+
const resource = useResourceCounterItemContext<Resource>();
|
|
186
|
+
return renderPrimitive("span", {
|
|
187
|
+
role: "listitem",
|
|
188
|
+
"aria-label": ariaLabel ?? `${resource.label}: ${resource.count}`,
|
|
189
|
+
...resource.dataAttributes,
|
|
190
|
+
...props,
|
|
191
|
+
onClick: composeEventHandlers(
|
|
192
|
+
onClick,
|
|
193
|
+
resource.interactive ? resource.select : undefined,
|
|
194
|
+
),
|
|
195
|
+
children: resolveResourceChildren(children, resource),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
234
198
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
199
|
+
export function ResourceCounterIcon<Resource extends string = ResourceId>({
|
|
200
|
+
className,
|
|
201
|
+
strokeWidth,
|
|
202
|
+
"aria-hidden": ariaHidden,
|
|
203
|
+
}: ResourceIconProps): ReactNode {
|
|
204
|
+
const resource = useResourceCounterItemContext<Resource>();
|
|
205
|
+
return resource.renderIcon({
|
|
206
|
+
className,
|
|
207
|
+
strokeWidth,
|
|
208
|
+
"aria-hidden": ariaHidden,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function ResourceCounterCount<Resource extends string = ResourceId>({
|
|
213
|
+
children,
|
|
214
|
+
...props
|
|
215
|
+
}: ResourceCounterPartProps<Resource>) {
|
|
216
|
+
const resource = useResourceCounterItemContext<Resource>();
|
|
217
|
+
return renderPrimitive("span", {
|
|
218
|
+
...props,
|
|
219
|
+
"data-dreamboard-resource-count": "",
|
|
220
|
+
children: resolveResourceChildren(children ?? resource.count, resource),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function ResourceCounterLabel<Resource extends string = ResourceId>({
|
|
225
|
+
children,
|
|
226
|
+
...props
|
|
227
|
+
}: ResourceCounterPartProps<Resource>) {
|
|
228
|
+
const resource = useResourceCounterItemContext<Resource>();
|
|
229
|
+
return renderPrimitive("span", {
|
|
230
|
+
...props,
|
|
231
|
+
"data-dreamboard-resource-label": "",
|
|
232
|
+
children: resolveResourceChildren(children ?? resource.label, resource),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export interface ResourceCounterComponents<
|
|
237
|
+
Resource extends string = ResourceId,
|
|
238
|
+
> {
|
|
239
|
+
Root(props: BoundResourceCounterRootProps<Resource>): ReactElement;
|
|
240
|
+
Item(props: ResourceCounterPartProps<Resource>): ReactElement;
|
|
241
|
+
Icon(props: ResourceIconProps): ReactNode;
|
|
242
|
+
Count(props: ResourceCounterPartProps<Resource>): ReactElement;
|
|
243
|
+
Label(props: ResourceCounterPartProps<Resource>): ReactElement;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function createResourceCounter<Resource extends string>(
|
|
247
|
+
resources: ReadonlyArray<ResourceDisplayConfig<Resource>>,
|
|
248
|
+
): ResourceCounterComponents<Resource> {
|
|
249
|
+
return {
|
|
250
|
+
Root(props) {
|
|
251
|
+
return createElement(ResourceCounterRoot<Resource>, {
|
|
252
|
+
...props,
|
|
253
|
+
resources,
|
|
254
|
+
});
|
|
255
|
+
},
|
|
256
|
+
Item: ResourceCounterItem,
|
|
257
|
+
Icon: ResourceCounterIcon,
|
|
258
|
+
Count: ResourceCounterCount,
|
|
259
|
+
Label: ResourceCounterLabel,
|
|
260
|
+
} satisfies ResourceCounterComponents<Resource>;
|
|
275
261
|
}
|
|
262
|
+
|
|
263
|
+
export const ResourceCounter = {
|
|
264
|
+
Root: ResourceCounterRoot,
|
|
265
|
+
Item: ResourceCounterItem,
|
|
266
|
+
Icon: ResourceCounterIcon,
|
|
267
|
+
Count: ResourceCounterCount,
|
|
268
|
+
Label: ResourceCounterLabel,
|
|
269
|
+
};
|
package/src/components/Toast.tsx
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plugin-internal toast notification system.
|
|
3
3
|
*
|
|
4
|
-
* `<ToastProvider>` exposes
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* is intentionally NOT wired to the host notification stream:
|
|
4
|
+
* `<ToastProvider>` exposes `<Toast.Actions>` for game-specific feedback
|
|
5
|
+
* ("Resource gained", "Card discarded", "Tip: rotate the board with R", …).
|
|
6
|
+
* It is intentionally NOT wired to the host notification stream:
|
|
8
7
|
* `YOUR_TURN`, `PROMPT_OPENED` and `ACTION_REJECTED` events are owned
|
|
9
8
|
* by `@dreamboard/ui-host-runtime`'s `<HostFeedbackToaster>` and must
|
|
10
9
|
* not be mirrored from inside the plugin tree.
|
|
@@ -27,7 +26,7 @@ import { ThemedButton } from "./ThemedButton.js";
|
|
|
27
26
|
|
|
28
27
|
export type ToastType = "success" | "error" | "info" | "warning";
|
|
29
28
|
|
|
30
|
-
export interface
|
|
29
|
+
export interface ToastNotification {
|
|
31
30
|
id: string;
|
|
32
31
|
type: ToastType;
|
|
33
32
|
message: string;
|
|
@@ -35,7 +34,7 @@ export interface Toast {
|
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
interface ToastContextValue {
|
|
38
|
-
toasts:
|
|
37
|
+
toasts: ToastNotification[];
|
|
39
38
|
show: (message: string, type?: ToastType, duration?: number) => void;
|
|
40
39
|
dismiss: (id: string) => void;
|
|
41
40
|
success: (message: string, duration?: number) => void;
|
|
@@ -46,17 +45,19 @@ interface ToastContextValue {
|
|
|
46
45
|
|
|
47
46
|
const ToastContext = createContext<ToastContextValue | null>(null);
|
|
48
47
|
|
|
48
|
+
export type ToastActionsValue = ToastContextValue;
|
|
49
|
+
|
|
49
50
|
export interface ToastProviderProps {
|
|
50
51
|
children: ReactNode;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
export function ToastProvider({ children }: ToastProviderProps) {
|
|
54
|
-
const [toasts, setToasts] = useState<
|
|
55
|
+
const [toasts, setToasts] = useState<ToastNotification[]>([]);
|
|
55
56
|
|
|
56
57
|
const show = useCallback(
|
|
57
58
|
(message: string, type: ToastType = "info", duration = 3000) => {
|
|
58
59
|
const id = `toast-${Date.now()}-${Math.random()}`;
|
|
59
|
-
const toast:
|
|
60
|
+
const toast: ToastNotification = { id, type, message, duration };
|
|
60
61
|
|
|
61
62
|
setToasts((prev) => {
|
|
62
63
|
// Dedup by `(type, message)` so a fast burst of identical
|
|
@@ -120,11 +121,23 @@ export function useToast() {
|
|
|
120
121
|
return context;
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
export interface ToastActionsProps {
|
|
125
|
+
children: (actions: ToastActionsValue) => ReactNode;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function ToastActions({ children }: ToastActionsProps) {
|
|
129
|
+
return <>{children(useToast())}</>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const Toast = {
|
|
133
|
+
Actions: ToastActions,
|
|
134
|
+
} as const;
|
|
135
|
+
|
|
123
136
|
function ToastContainer({
|
|
124
137
|
toasts,
|
|
125
138
|
onDismiss,
|
|
126
139
|
}: {
|
|
127
|
-
toasts:
|
|
140
|
+
toasts: ToastNotification[];
|
|
128
141
|
onDismiss: (id: string) => void;
|
|
129
142
|
}) {
|
|
130
143
|
return (
|
|
@@ -184,7 +197,7 @@ function ToastItem({
|
|
|
184
197
|
toast,
|
|
185
198
|
onDismiss,
|
|
186
199
|
}: {
|
|
187
|
-
toast:
|
|
200
|
+
toast: ToastNotification;
|
|
188
201
|
onDismiss: (id: string) => void;
|
|
189
202
|
}) {
|
|
190
203
|
const theme = useTheme();
|