@jgengine/react 0.14.0 → 0.15.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/CHANGELOG.md +500 -1
- package/dist/accessibility.d.ts +39 -0
- package/dist/accessibility.js +61 -0
- package/dist/achievements.d.ts +50 -0
- package/dist/achievements.js +94 -0
- package/dist/cameraShake.d.ts +52 -0
- package/dist/cameraShake.js +53 -0
- package/dist/coachMarks.d.ts +72 -0
- package/dist/coachMarks.js +136 -0
- package/dist/codex.d.ts +25 -0
- package/dist/codex.js +62 -0
- package/dist/comboMeter.d.ts +63 -0
- package/dist/comboMeter.js +93 -0
- package/dist/components.js +4 -3
- package/dist/creditsScreen.d.ts +19 -0
- package/dist/creditsScreen.js +31 -0
- package/dist/cutscene.d.ts +66 -0
- package/dist/cutscene.js +132 -0
- package/dist/damageDirection.d.ts +51 -0
- package/dist/damageDirection.js +103 -0
- package/dist/dialogueView.d.ts +69 -0
- package/dist/dialogueView.js +111 -0
- package/dist/entityFrames.d.ts +114 -0
- package/dist/entityFrames.js +55 -0
- package/dist/fastTravel.d.ts +36 -0
- package/dist/fastTravel.js +103 -0
- package/dist/floatingText.d.ts +84 -0
- package/dist/floatingText.js +61 -0
- package/dist/gameViewport.js +8 -12
- package/dist/hooks.d.ts +42 -0
- package/dist/hooks.js +85 -0
- package/dist/hudTheme.d.ts +104 -7
- package/dist/hudTheme.js +300 -2
- package/dist/i18n.d.ts +29 -0
- package/dist/i18n.js +44 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +34 -0
- package/dist/interactionPrompt.d.ts +80 -0
- package/dist/interactionPrompt.js +112 -0
- package/dist/keybindingMenu.d.ts +59 -0
- package/dist/keybindingMenu.js +146 -0
- package/dist/killFeed.d.ts +63 -0
- package/dist/killFeed.js +88 -0
- package/dist/map.d.ts +123 -2
- package/dist/map.js +239 -36
- package/dist/menuRouter.d.ts +51 -0
- package/dist/menuRouter.js +49 -0
- package/dist/modals.d.ts +131 -0
- package/dist/modals.js +168 -0
- package/dist/notifications.d.ts +39 -0
- package/dist/notifications.js +111 -0
- package/dist/objectiveBanner.d.ts +62 -0
- package/dist/objectiveBanner.js +129 -0
- package/dist/photoMode.d.ts +21 -0
- package/dist/photoMode.js +39 -0
- package/dist/previewFixtures.d.ts +30 -0
- package/dist/previewFixtures.js +46 -0
- package/dist/questTracker.d.ts +24 -0
- package/dist/questTracker.js +31 -0
- package/dist/quickMenu.d.ts +39 -0
- package/dist/quickMenu.js +79 -0
- package/dist/radialMenu.d.ts +50 -0
- package/dist/radialMenu.js +69 -0
- package/dist/saveSlots.d.ts +81 -0
- package/dist/saveSlots.js +138 -0
- package/dist/scoreboard.d.ts +67 -0
- package/dist/scoreboard.js +71 -0
- package/dist/settings.d.ts +9 -3
- package/dist/settings.js +10 -4
- package/dist/shopGrid.d.ts +70 -0
- package/dist/shopGrid.js +129 -0
- package/dist/statusEffectBar.d.ts +52 -0
- package/dist/statusEffectBar.js +57 -0
- package/dist/streetGeometryPreview.d.ts +7 -0
- package/dist/streetGeometryPreview.js +119 -0
- package/dist/talentTree.d.ts +60 -0
- package/dist/talentTree.js +217 -0
- package/dist/timerReadout.d.ts +90 -0
- package/dist/timerReadout.js +111 -0
- package/dist/useDebouncedCommit.d.ts +54 -0
- package/dist/useDebouncedCommit.js +81 -0
- package/dist/waveHud.d.ts +49 -0
- package/dist/waveHud.js +53 -0
- package/dist/waypointMarkers.d.ts +80 -0
- package/dist/waypointMarkers.js +116 -0
- package/package.json +3 -3
- package/skills/ce-handoff/SKILL.md +149 -0
- package/skills/jgengine/SKILL.md +3 -1
- package/skills/jgengine/api.md +17 -7
- package/skills/jgengine/capabilities.md +12 -0
- package/skills/jgengine/recipes/minimal-game.md +7 -4
- package/skills/jgengine-ui/SKILL.md +5 -3
- package/skills/jgengine-ui/api.md +708 -19
- package/skills/jgengine-ui/capabilities.md +362 -1
- package/skills/jgengine-ui/recipes/overhead-entity-frames.md +136 -0
- package/skills/jgengine-ui/reference.md +7 -4
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { I18n, Locale, TParams } from "@jgengine/core/i18n/i18n";
|
|
3
|
+
/** Provide an {@link I18n} instance to the tree so `useT`/`useLocale` can read it. */
|
|
4
|
+
export declare function I18nProvider({ i18n, children }: {
|
|
5
|
+
i18n: I18n;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}): ReactNode;
|
|
8
|
+
/** Read the current {@link I18n} from context; throws when no {@link I18nProvider} is above. */
|
|
9
|
+
export declare function useI18n(): I18n;
|
|
10
|
+
/** Subscribe to the active locale — re-renders on `setLocale`. */
|
|
11
|
+
export declare function useLocale(): Locale;
|
|
12
|
+
/**
|
|
13
|
+
* Return the translator bound to the current locale; the component re-renders
|
|
14
|
+
* when `setLocale` is called, so returned strings stay live.
|
|
15
|
+
*
|
|
16
|
+
* @capability use-translate live `t(key, params)` translator bound to the active locale, re-rendering on locale change
|
|
17
|
+
*/
|
|
18
|
+
export declare function useT(): (key: string, params?: TParams) => string;
|
|
19
|
+
/** Return the plural-aware translator bound to the current locale. */
|
|
20
|
+
export declare function usePlural(): (key: string, count: number, params?: TParams) => string;
|
|
21
|
+
/**
|
|
22
|
+
* Render a translated message inline: `<Trans k="hud.score" params={{ value }} />`.
|
|
23
|
+
*
|
|
24
|
+
* @capability trans inline translated-message component bound to the active locale
|
|
25
|
+
*/
|
|
26
|
+
export declare function Trans({ k, params }: {
|
|
27
|
+
k: string;
|
|
28
|
+
params?: TParams;
|
|
29
|
+
}): ReactNode;
|
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useSyncExternalStore } from "react";
|
|
3
|
+
const I18nContext = createContext(null);
|
|
4
|
+
/** Provide an {@link I18n} instance to the tree so `useT`/`useLocale` can read it. */
|
|
5
|
+
export function I18nProvider({ i18n, children }) {
|
|
6
|
+
return _jsx(I18nContext.Provider, { value: i18n, children: children });
|
|
7
|
+
}
|
|
8
|
+
/** Read the current {@link I18n} from context; throws when no {@link I18nProvider} is above. */
|
|
9
|
+
export function useI18n() {
|
|
10
|
+
const i18n = useContext(I18nContext);
|
|
11
|
+
if (i18n === null)
|
|
12
|
+
throw new Error("useI18n must be used within an <I18nProvider>");
|
|
13
|
+
return i18n;
|
|
14
|
+
}
|
|
15
|
+
/** Subscribe to the active locale — re-renders on `setLocale`. */
|
|
16
|
+
export function useLocale() {
|
|
17
|
+
const i18n = useI18n();
|
|
18
|
+
return useSyncExternalStore(i18n.subscribe, i18n.locale, i18n.locale);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Return the translator bound to the current locale; the component re-renders
|
|
22
|
+
* when `setLocale` is called, so returned strings stay live.
|
|
23
|
+
*
|
|
24
|
+
* @capability use-translate live `t(key, params)` translator bound to the active locale, re-rendering on locale change
|
|
25
|
+
*/
|
|
26
|
+
export function useT() {
|
|
27
|
+
const i18n = useI18n();
|
|
28
|
+
useLocale(); // re-render on locale change
|
|
29
|
+
return i18n.t;
|
|
30
|
+
}
|
|
31
|
+
/** Return the plural-aware translator bound to the current locale. */
|
|
32
|
+
export function usePlural() {
|
|
33
|
+
const i18n = useI18n();
|
|
34
|
+
useLocale();
|
|
35
|
+
return i18n.plural;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Render a translated message inline: `<Trans k="hud.score" params={{ value }} />`.
|
|
39
|
+
*
|
|
40
|
+
* @capability trans inline translated-message component bound to the active locale
|
|
41
|
+
*/
|
|
42
|
+
export function Trans({ k, params }) {
|
|
43
|
+
return useT()(k, params);
|
|
44
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,23 +5,53 @@ export * from "./chatBubbles.js";
|
|
|
5
5
|
export * from "./voice.js";
|
|
6
6
|
export * from "./social.js";
|
|
7
7
|
export * from "./hooks.js";
|
|
8
|
+
export * from "./useDebouncedCommit.js";
|
|
8
9
|
export * from "./store.js";
|
|
9
10
|
export * from "./settings.js";
|
|
10
11
|
export * from "./save.js";
|
|
12
|
+
export * from "./achievements.js";
|
|
13
|
+
export * from "./i18n.js";
|
|
14
|
+
export * from "./accessibility.js";
|
|
15
|
+
export * from "./photoMode.js";
|
|
16
|
+
export * from "./radialMenu.js";
|
|
17
|
+
export * from "./quickMenu.js";
|
|
18
|
+
export * from "./questTracker.js";
|
|
19
|
+
export * from "./codex.js";
|
|
20
|
+
export * from "./notifications.js";
|
|
21
|
+
export * from "./coachMarks.js";
|
|
22
|
+
export * from "./objectiveBanner.js";
|
|
23
|
+
export * from "./keybindingMenu.js";
|
|
24
|
+
export * from "./shopGrid.js";
|
|
25
|
+
export * from "./killFeed.js";
|
|
26
|
+
export * from "./waveHud.js";
|
|
27
|
+
export * from "./comboMeter.js";
|
|
28
|
+
export * from "./waypointMarkers.js";
|
|
29
|
+
export * from "./scoreboard.js";
|
|
30
|
+
export * from "./timerReadout.js";
|
|
31
|
+
export * from "./dialogueView.js";
|
|
32
|
+
export * from "./cutscene.js";
|
|
33
|
+
export * from "./damageDirection.js";
|
|
34
|
+
export * from "./modals.js";
|
|
35
|
+
export * from "./fastTravel.js";
|
|
36
|
+
export * from "./floatingText.js";
|
|
11
37
|
export * from "./components.js";
|
|
12
38
|
export * from "./hud.js";
|
|
13
39
|
export * from "./bars.js";
|
|
40
|
+
export * from "./entityFrames.js";
|
|
14
41
|
export * from "./barsPreview.js";
|
|
15
42
|
export * from "./iconTreatment.js";
|
|
43
|
+
export * from "./statusEffectBar.js";
|
|
16
44
|
export * from "./iconsPreview.js";
|
|
17
45
|
export * from "./slots.js";
|
|
18
46
|
export * from "./inventoryGrid.js";
|
|
19
47
|
export * from "./panels.js";
|
|
20
48
|
export * from "./characterSheet.js";
|
|
49
|
+
export * from "./talentTree.js";
|
|
21
50
|
export * from "./hudTheme.js";
|
|
22
51
|
export * from "./hudThemePreview.js";
|
|
23
52
|
export * from "./hudFrame.js";
|
|
24
53
|
export * from "./keyHint.js";
|
|
54
|
+
export * from "./interactionPrompt.js";
|
|
25
55
|
export * from "./startScreen.js";
|
|
26
56
|
export * from "./engineStore.js";
|
|
27
57
|
export * from "./dragLayer.js";
|
|
@@ -36,3 +66,7 @@ export * from "./rotateDevice.js";
|
|
|
36
66
|
export * from "./display.js";
|
|
37
67
|
export * from "./map.js";
|
|
38
68
|
export * from "./preview.js";
|
|
69
|
+
export * from "./previewFixtures.js";
|
|
70
|
+
export * from "./streetGeometryPreview.js";
|
|
71
|
+
export * from "./saveSlots.js";
|
|
72
|
+
export * from "./cameraShake.js";
|
package/dist/index.js
CHANGED
|
@@ -5,23 +5,53 @@ export * from "./chatBubbles.js";
|
|
|
5
5
|
export * from "./voice.js";
|
|
6
6
|
export * from "./social.js";
|
|
7
7
|
export * from "./hooks.js";
|
|
8
|
+
export * from "./useDebouncedCommit.js";
|
|
8
9
|
export * from "./store.js";
|
|
9
10
|
export * from "./settings.js";
|
|
10
11
|
export * from "./save.js";
|
|
12
|
+
export * from "./achievements.js";
|
|
13
|
+
export * from "./i18n.js";
|
|
14
|
+
export * from "./accessibility.js";
|
|
15
|
+
export * from "./photoMode.js";
|
|
16
|
+
export * from "./radialMenu.js";
|
|
17
|
+
export * from "./quickMenu.js";
|
|
18
|
+
export * from "./questTracker.js";
|
|
19
|
+
export * from "./codex.js";
|
|
20
|
+
export * from "./notifications.js";
|
|
21
|
+
export * from "./coachMarks.js";
|
|
22
|
+
export * from "./objectiveBanner.js";
|
|
23
|
+
export * from "./keybindingMenu.js";
|
|
24
|
+
export * from "./shopGrid.js";
|
|
25
|
+
export * from "./killFeed.js";
|
|
26
|
+
export * from "./waveHud.js";
|
|
27
|
+
export * from "./comboMeter.js";
|
|
28
|
+
export * from "./waypointMarkers.js";
|
|
29
|
+
export * from "./scoreboard.js";
|
|
30
|
+
export * from "./timerReadout.js";
|
|
31
|
+
export * from "./dialogueView.js";
|
|
32
|
+
export * from "./cutscene.js";
|
|
33
|
+
export * from "./damageDirection.js";
|
|
34
|
+
export * from "./modals.js";
|
|
35
|
+
export * from "./fastTravel.js";
|
|
36
|
+
export * from "./floatingText.js";
|
|
11
37
|
export * from "./components.js";
|
|
12
38
|
export * from "./hud.js";
|
|
13
39
|
export * from "./bars.js";
|
|
40
|
+
export * from "./entityFrames.js";
|
|
14
41
|
export * from "./barsPreview.js";
|
|
15
42
|
export * from "./iconTreatment.js";
|
|
43
|
+
export * from "./statusEffectBar.js";
|
|
16
44
|
export * from "./iconsPreview.js";
|
|
17
45
|
export * from "./slots.js";
|
|
18
46
|
export * from "./inventoryGrid.js";
|
|
19
47
|
export * from "./panels.js";
|
|
20
48
|
export * from "./characterSheet.js";
|
|
49
|
+
export * from "./talentTree.js";
|
|
21
50
|
export * from "./hudTheme.js";
|
|
22
51
|
export * from "./hudThemePreview.js";
|
|
23
52
|
export * from "./hudFrame.js";
|
|
24
53
|
export * from "./keyHint.js";
|
|
54
|
+
export * from "./interactionPrompt.js";
|
|
25
55
|
export * from "./startScreen.js";
|
|
26
56
|
export * from "./engineStore.js";
|
|
27
57
|
export * from "./dragLayer.js";
|
|
@@ -36,3 +66,7 @@ export * from "./rotateDevice.js";
|
|
|
36
66
|
export * from "./display.js";
|
|
37
67
|
export * from "./map.js";
|
|
38
68
|
export * from "./preview.js";
|
|
69
|
+
export * from "./previewFixtures.js";
|
|
70
|
+
export * from "./streetGeometryPreview.js";
|
|
71
|
+
export * from "./saveSlots.js";
|
|
72
|
+
export * from "./cameraShake.js";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
import type { PositionedPrompt, PromptPoint } from "@jgengine/core/interaction/proximityPrompt";
|
|
3
|
+
import type { PromptRegistry } from "@jgengine/core/interaction/promptRegistry";
|
|
4
|
+
/**
|
|
5
|
+
* Subscribe to a {@link PromptRegistry} and re-`resolve` it whenever the player
|
|
6
|
+
* moves, returning the prompt to draw right now — or `null` when the player is
|
|
7
|
+
* out of range of every interactable. The registry notifies only on active-prompt
|
|
8
|
+
* *changes*, so this re-renders on transitions rather than every frame the hero
|
|
9
|
+
* moves. Feed it the player's `{ x, z }` position each frame; identical positions
|
|
10
|
+
* do no work.
|
|
11
|
+
*
|
|
12
|
+
* @capability use-interaction-prompt React hook binding a prompt registry to a moving player — resolves the nearest interactable prompt and returns the active one
|
|
13
|
+
*/
|
|
14
|
+
export declare function useInteractionPrompt(registry: PromptRegistry, playerPosition: PromptPoint): PositionedPrompt | null;
|
|
15
|
+
/** Reskin tokens for {@link InteractionPrompt}. */
|
|
16
|
+
export interface InteractionPromptTheme {
|
|
17
|
+
/** Accent for the key cap and glow. Default reads `--jg-accent`. */
|
|
18
|
+
accent?: string;
|
|
19
|
+
/** Callout background. */
|
|
20
|
+
background?: string;
|
|
21
|
+
/** Callout border color. */
|
|
22
|
+
border?: string;
|
|
23
|
+
/** Label text color. */
|
|
24
|
+
text?: string;
|
|
25
|
+
/** Font family. */
|
|
26
|
+
fontFamily?: string;
|
|
27
|
+
}
|
|
28
|
+
/** Where the callout sits over the scene. */
|
|
29
|
+
export type InteractionPromptAnchor = "bottom-center" | "top-center" | "center";
|
|
30
|
+
/** Props for {@link InteractionPrompt}. */
|
|
31
|
+
export interface InteractionPromptProps {
|
|
32
|
+
/** The registry that owns the prompts and the active selection. */
|
|
33
|
+
registry: PromptRegistry;
|
|
34
|
+
/**
|
|
35
|
+
* The player's ground position. When supplied the host drives `resolve` on
|
|
36
|
+
* every position change; omit it if the game already calls `registry.resolve`
|
|
37
|
+
* from its own tick (the host still subscribes and renders the active prompt).
|
|
38
|
+
*/
|
|
39
|
+
playerPosition?: PromptPoint;
|
|
40
|
+
/** Base reskin tokens. */
|
|
41
|
+
theme?: InteractionPromptTheme;
|
|
42
|
+
/**
|
|
43
|
+
* Per-prompt accent chooser. The registry never interprets a prompt's meaning,
|
|
44
|
+
* so the game decides the accent from the active prompt (its id, action id, or
|
|
45
|
+
* label) — e.g. a warm hue for a chest, cool for a door. Returning `undefined`
|
|
46
|
+
* falls back to the theme accent.
|
|
47
|
+
*/
|
|
48
|
+
accentFor?: (prompt: PositionedPrompt) => string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Key glyph shown on the cap for a `keybind` prompt, chosen from the prompt's
|
|
51
|
+
* free-string `actionId`. Default `"E"`.
|
|
52
|
+
*/
|
|
53
|
+
keyFor?: (actionId: string) => string;
|
|
54
|
+
/**
|
|
55
|
+
* Hold progress `0..1` for a `gauge` prompt, by its free-string `gaugeId` —
|
|
56
|
+
* fills the ring/bar as the player holds. Default `0`.
|
|
57
|
+
*/
|
|
58
|
+
gaugeProgress?: (gaugeId: string) => number;
|
|
59
|
+
/** Where the callout sits. Default `"bottom-center"`. */
|
|
60
|
+
anchor?: InteractionPromptAnchor;
|
|
61
|
+
/** Overlay `z-index`. Default `40`. */
|
|
62
|
+
zIndex?: number;
|
|
63
|
+
className?: string;
|
|
64
|
+
style?: CSSProperties;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A screen-anchored "Press E to …" interaction callout that renders the active
|
|
68
|
+
* prompt of a {@link PromptRegistry}. It subscribes to the registry and — when
|
|
69
|
+
* given `playerPosition` — re-resolves as the hero moves, showing the nearest
|
|
70
|
+
* in-range interactable and switching to a closer/higher-priority one as they
|
|
71
|
+
* cross radii. Each display kind renders as itself: a `keybind` shows a key cap
|
|
72
|
+
* plus its label ("Press [E] Open"), a `gauge` shows a hold bar filling with
|
|
73
|
+
* `gaugeProgress`, and a `label` shows plain text. Presentation only — the
|
|
74
|
+
* registry never branches on a prompt's meaning; the game colors each via
|
|
75
|
+
* `accentFor` and maps action ids to key glyphs via `keyFor`. HudTheme-skinnable
|
|
76
|
+
* through `--jg-accent` and {@link InteractionPromptTheme}.
|
|
77
|
+
*
|
|
78
|
+
* @capability interaction-prompt-host screen-anchored "press E to…" callout rendering a prompt registry's active interactable — keybind cap+label, gauge hold bar, or plain label, theme- and per-prompt-accent skinnable
|
|
79
|
+
*/
|
|
80
|
+
export declare function InteractionPrompt({ registry, playerPosition, theme, accentFor, keyFor, gaugeProgress, anchor, zIndex, className, style, }: InteractionPromptProps): ReactNode;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useReducer } from "react";
|
|
3
|
+
import { StaminaBar } from "./bars.js";
|
|
4
|
+
import { KeyHint, Keycap } from "./keyHint.js";
|
|
5
|
+
/**
|
|
6
|
+
* Subscribe to a {@link PromptRegistry} and re-`resolve` it whenever the player
|
|
7
|
+
* moves, returning the prompt to draw right now — or `null` when the player is
|
|
8
|
+
* out of range of every interactable. The registry notifies only on active-prompt
|
|
9
|
+
* *changes*, so this re-renders on transitions rather than every frame the hero
|
|
10
|
+
* moves. Feed it the player's `{ x, z }` position each frame; identical positions
|
|
11
|
+
* do no work.
|
|
12
|
+
*
|
|
13
|
+
* @capability use-interaction-prompt React hook binding a prompt registry to a moving player — resolves the nearest interactable prompt and returns the active one
|
|
14
|
+
*/
|
|
15
|
+
export function useInteractionPrompt(registry, playerPosition) {
|
|
16
|
+
const [, bump] = useReducer((n) => n + 1, 0);
|
|
17
|
+
useEffect(() => registry.subscribe(bump), [registry]);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
registry.resolve(playerPosition);
|
|
20
|
+
}, [registry, playerPosition.x, playerPosition.z]);
|
|
21
|
+
return registry.active();
|
|
22
|
+
}
|
|
23
|
+
function resolveTheme(theme) {
|
|
24
|
+
return {
|
|
25
|
+
accent: theme?.accent ?? "var(--jg-accent, #38bdf8)",
|
|
26
|
+
background: theme?.background ?? "rgba(15,20,28,0.86)",
|
|
27
|
+
border: theme?.border ?? "rgba(148,163,184,0.28)",
|
|
28
|
+
text: theme?.text ?? "#e2e8f0",
|
|
29
|
+
fontFamily: theme?.fontFamily ?? "ui-sans-serif, system-ui, sans-serif",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const ANCHOR_STYLE = {
|
|
33
|
+
"bottom-center": { bottom: "12%", left: "50%", transform: "translateX(-50%)" },
|
|
34
|
+
"top-center": { top: "12%", left: "50%", transform: "translateX(-50%)" },
|
|
35
|
+
center: { top: "50%", left: "50%", transform: "translate(-50%, -50%)" },
|
|
36
|
+
};
|
|
37
|
+
/** Bind to the registry either through the moving player, or passively on the active id. */
|
|
38
|
+
function useActive(registry, playerPosition) {
|
|
39
|
+
const [, bump] = useReducer((n) => n + 1, 0);
|
|
40
|
+
useEffect(() => registry.subscribe(bump), [registry]);
|
|
41
|
+
const px = playerPosition?.x;
|
|
42
|
+
const pz = playerPosition?.z;
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (px !== undefined && pz !== undefined)
|
|
45
|
+
registry.resolve({ x: px, z: pz });
|
|
46
|
+
}, [registry, px, pz]);
|
|
47
|
+
return registry.active();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A screen-anchored "Press E to …" interaction callout that renders the active
|
|
51
|
+
* prompt of a {@link PromptRegistry}. It subscribes to the registry and — when
|
|
52
|
+
* given `playerPosition` — re-resolves as the hero moves, showing the nearest
|
|
53
|
+
* in-range interactable and switching to a closer/higher-priority one as they
|
|
54
|
+
* cross radii. Each display kind renders as itself: a `keybind` shows a key cap
|
|
55
|
+
* plus its label ("Press [E] Open"), a `gauge` shows a hold bar filling with
|
|
56
|
+
* `gaugeProgress`, and a `label` shows plain text. Presentation only — the
|
|
57
|
+
* registry never branches on a prompt's meaning; the game colors each via
|
|
58
|
+
* `accentFor` and maps action ids to key glyphs via `keyFor`. HudTheme-skinnable
|
|
59
|
+
* through `--jg-accent` and {@link InteractionPromptTheme}.
|
|
60
|
+
*
|
|
61
|
+
* @capability interaction-prompt-host screen-anchored "press E to…" callout rendering a prompt registry's active interactable — keybind cap+label, gauge hold bar, or plain label, theme- and per-prompt-accent skinnable
|
|
62
|
+
*/
|
|
63
|
+
export function InteractionPrompt({ registry, playerPosition, theme, accentFor, keyFor, gaugeProgress, anchor = "bottom-center", zIndex = 40, className, style, }) {
|
|
64
|
+
const prompt = useActive(registry, playerPosition);
|
|
65
|
+
if (prompt === null)
|
|
66
|
+
return null;
|
|
67
|
+
const t = resolveTheme(theme);
|
|
68
|
+
const accent = accentFor?.(prompt) ?? t.accent;
|
|
69
|
+
const display = prompt.prompt.display;
|
|
70
|
+
let body;
|
|
71
|
+
if (display.kind === "keybind") {
|
|
72
|
+
const glyph = keyFor?.(display.actionId) ?? "E";
|
|
73
|
+
body = (_jsxs(KeyHint, { style: { display: "inline-flex", alignItems: "center", gap: 10 }, children: [_jsx("span", { style: { fontSize: 13, fontWeight: 600, letterSpacing: 0.4, opacity: 0.85 }, children: "Press" }), _jsx(Keycap, { style: {
|
|
74
|
+
display: "inline-flex",
|
|
75
|
+
minWidth: 26,
|
|
76
|
+
justifyContent: "center",
|
|
77
|
+
padding: "3px 8px",
|
|
78
|
+
borderRadius: 6,
|
|
79
|
+
border: `1px solid ${accent}`,
|
|
80
|
+
background: "rgba(0,0,0,0.35)",
|
|
81
|
+
color: accent,
|
|
82
|
+
fontWeight: 800,
|
|
83
|
+
fontSize: 14,
|
|
84
|
+
boxShadow: `0 0 10px -2px ${accent}`,
|
|
85
|
+
}, children: glyph }), display.label !== undefined ? (_jsx("span", { style: { fontSize: 15, fontWeight: 700 }, children: display.label })) : null] }));
|
|
86
|
+
}
|
|
87
|
+
else if (display.kind === "gauge") {
|
|
88
|
+
const progress = Math.max(0, Math.min(1, gaugeProgress?.(display.gaugeId) ?? 0));
|
|
89
|
+
body = (_jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 10 }, children: [_jsx("span", { style: { fontSize: 13, fontWeight: 600, letterSpacing: 0.4, opacity: 0.85 }, children: "Hold" }), _jsx(StaminaBar, { value: progress * 100, showValue: false, width: 140, fill: accent })] }));
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
body = _jsx("span", { style: { fontSize: 15, fontWeight: 700 }, children: display.text });
|
|
93
|
+
}
|
|
94
|
+
return (_jsx("div", { className: className, "data-interaction-prompt": prompt.id, "data-prompt-kind": display.kind, style: {
|
|
95
|
+
position: "absolute",
|
|
96
|
+
...ANCHOR_STYLE[anchor],
|
|
97
|
+
display: "flex",
|
|
98
|
+
alignItems: "center",
|
|
99
|
+
pointerEvents: "none",
|
|
100
|
+
padding: "9px 16px",
|
|
101
|
+
borderRadius: 12,
|
|
102
|
+
border: `1px solid ${t.border}`,
|
|
103
|
+
background: t.background,
|
|
104
|
+
color: t.text,
|
|
105
|
+
fontFamily: t.fontFamily,
|
|
106
|
+
boxShadow: `0 8px 24px rgba(0,0,0,0.4), 0 0 0 1px ${accent}22`,
|
|
107
|
+
backdropFilter: "blur(4px)",
|
|
108
|
+
whiteSpace: "nowrap",
|
|
109
|
+
zIndex,
|
|
110
|
+
...style,
|
|
111
|
+
}, children: body }));
|
|
112
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
import type { RebindSession } from "@jgengine/core/input/rebindSession";
|
|
3
|
+
/**
|
|
4
|
+
* Subscribe to a rebind session and re-render on every change (capture, reset,
|
|
5
|
+
* arm/disarm, restore). Returns the session unchanged for convenient inline use.
|
|
6
|
+
*
|
|
7
|
+
* @capability use-rebind-session React hook that re-renders a component whenever a rebind session changes
|
|
8
|
+
*/
|
|
9
|
+
export declare function useRebindSession(session: RebindSession): RebindSession;
|
|
10
|
+
/** Reskin tokens for {@link KeybindingMenu}. All fall back to HudTheme `--jg-*` vars. */
|
|
11
|
+
export interface KeybindingMenuTheme {
|
|
12
|
+
/** Panel background. Default reads `--jg-frame-bg`. */
|
|
13
|
+
bg?: string;
|
|
14
|
+
/** Panel border. Default reads `--jg-frame-border`. */
|
|
15
|
+
border?: string;
|
|
16
|
+
/** Corner radius. Default reads `--jg-frame-radius`. */
|
|
17
|
+
radius?: string;
|
|
18
|
+
/** Row label text color. */
|
|
19
|
+
text?: string;
|
|
20
|
+
/** Muted text (hints, secondary). */
|
|
21
|
+
muted?: string;
|
|
22
|
+
/** Accent for the armed row + focus glow. Default reads `--jg-accent`. */
|
|
23
|
+
accent?: string;
|
|
24
|
+
/** Conflict highlight color. */
|
|
25
|
+
conflict?: string;
|
|
26
|
+
/** Font family. */
|
|
27
|
+
fontFamily?: string;
|
|
28
|
+
}
|
|
29
|
+
/** Props for {@link KeybindingMenu}. */
|
|
30
|
+
export interface KeybindingMenuProps {
|
|
31
|
+
/** The rebind session to edit. */
|
|
32
|
+
session: RebindSession;
|
|
33
|
+
/** Reskin tokens (fall back to HudTheme). */
|
|
34
|
+
theme?: KeybindingMenuTheme;
|
|
35
|
+
/** Heading shown above the list. Default `"Controls"`. */
|
|
36
|
+
title?: ReactNode;
|
|
37
|
+
/** Label for the reset-all button. Default `"Reset All"`. */
|
|
38
|
+
resetAllLabel?: string;
|
|
39
|
+
/** Prompt shown in a row that is armed for capture. Default `"Press a key…"`. */
|
|
40
|
+
capturePrompt?: string;
|
|
41
|
+
/** Called after a successful rebind or reset — e.g. persist `session.overrides()`. */
|
|
42
|
+
onChange?: (session: RebindSession) => void;
|
|
43
|
+
className?: string;
|
|
44
|
+
style?: CSSProperties;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A drop-in controls-settings surface for a {@link RebindSession}: one row per
|
|
48
|
+
* action showing its label and current key glyph. Clicking a row arms capture
|
|
49
|
+
* ("Press a key…") and a window `keydown` listener feeds the pressed
|
|
50
|
+
* `event.code` to `session.capture`; rows that collide with another action show
|
|
51
|
+
* a conflict badge; each row has a Reset button and the panel has a Reset-all
|
|
52
|
+
* button. Presentation only — all conflict/capture/reset logic lives in the core
|
|
53
|
+
* session, and action ids/labels are the game's free strings. Reskin via
|
|
54
|
+
* {@link KeybindingMenuTheme} or ambient HudTheme tokens. A game owns its overall
|
|
55
|
+
* settings layout; this is the reusable controls block it drops in.
|
|
56
|
+
*
|
|
57
|
+
* @capability keybinding-menu drop-in controls-settings surface over a rebind session — click-to-capture rows with key glyphs, conflict badges, per-row and reset-all, HudTheme-skinnable
|
|
58
|
+
*/
|
|
59
|
+
export declare function KeybindingMenu({ session, theme, title, resetAllLabel, capturePrompt, onChange, className, style, }: KeybindingMenuProps): ReactNode;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useReducer } from "react";
|
|
3
|
+
import { Keycap } from "./keyHint.js";
|
|
4
|
+
/**
|
|
5
|
+
* Subscribe to a rebind session and re-render on every change (capture, reset,
|
|
6
|
+
* arm/disarm, restore). Returns the session unchanged for convenient inline use.
|
|
7
|
+
*
|
|
8
|
+
* @capability use-rebind-session React hook that re-renders a component whenever a rebind session changes
|
|
9
|
+
*/
|
|
10
|
+
export function useRebindSession(session) {
|
|
11
|
+
const [, bump] = useReducer((n) => n + 1, 0);
|
|
12
|
+
useEffect(() => session.subscribe(bump), [session]);
|
|
13
|
+
return session;
|
|
14
|
+
}
|
|
15
|
+
function resolveTheme(theme) {
|
|
16
|
+
return {
|
|
17
|
+
bg: theme?.bg ?? "var(--jg-frame-bg, linear-gradient(180deg, rgba(20,24,32,0.92), rgba(10,12,16,0.95)))",
|
|
18
|
+
border: theme?.border ?? "var(--jg-frame-border, 1px solid rgba(255,255,255,0.12))",
|
|
19
|
+
radius: theme?.radius ?? "var(--jg-frame-radius, 12px)",
|
|
20
|
+
text: theme?.text ?? "#e8edf5",
|
|
21
|
+
muted: theme?.muted ?? "rgba(148,163,184,0.85)",
|
|
22
|
+
accent: theme?.accent ?? "var(--jg-accent, #38bdf8)",
|
|
23
|
+
conflict: theme?.conflict ?? "#f87171",
|
|
24
|
+
fontFamily: theme?.fontFamily ?? "ui-sans-serif, system-ui, sans-serif",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A drop-in controls-settings surface for a {@link RebindSession}: one row per
|
|
29
|
+
* action showing its label and current key glyph. Clicking a row arms capture
|
|
30
|
+
* ("Press a key…") and a window `keydown` listener feeds the pressed
|
|
31
|
+
* `event.code` to `session.capture`; rows that collide with another action show
|
|
32
|
+
* a conflict badge; each row has a Reset button and the panel has a Reset-all
|
|
33
|
+
* button. Presentation only — all conflict/capture/reset logic lives in the core
|
|
34
|
+
* session, and action ids/labels are the game's free strings. Reskin via
|
|
35
|
+
* {@link KeybindingMenuTheme} or ambient HudTheme tokens. A game owns its overall
|
|
36
|
+
* settings layout; this is the reusable controls block it drops in.
|
|
37
|
+
*
|
|
38
|
+
* @capability keybinding-menu drop-in controls-settings surface over a rebind session — click-to-capture rows with key glyphs, conflict badges, per-row and reset-all, HudTheme-skinnable
|
|
39
|
+
*/
|
|
40
|
+
export function KeybindingMenu({ session, theme, title = "Controls", resetAllLabel = "Reset All", capturePrompt = "Press a key…", onChange, className, style, }) {
|
|
41
|
+
useRebindSession(session);
|
|
42
|
+
const t = resolveTheme(theme);
|
|
43
|
+
const rows = session.rows();
|
|
44
|
+
const capturing = session.capturingActionId();
|
|
45
|
+
// While armed, the next keydown anywhere reassigns the row (Escape cancels).
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (capturing === null)
|
|
48
|
+
return;
|
|
49
|
+
const onKeyDown = (event) => {
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
event.stopPropagation();
|
|
52
|
+
if (event.code === "Escape") {
|
|
53
|
+
session.cancelCapture();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
session.capture(event.code);
|
|
57
|
+
onChange?.(session);
|
|
58
|
+
};
|
|
59
|
+
window.addEventListener("keydown", onKeyDown, { capture: true });
|
|
60
|
+
return () => window.removeEventListener("keydown", onKeyDown, { capture: true });
|
|
61
|
+
}, [capturing, session, onChange]);
|
|
62
|
+
const anyConflict = session.hasConflicts();
|
|
63
|
+
return (_jsxs("div", { "data-keybinding-menu": "", className: className, style: {
|
|
64
|
+
pointerEvents: "auto",
|
|
65
|
+
display: "flex",
|
|
66
|
+
flexDirection: "column",
|
|
67
|
+
gap: 10,
|
|
68
|
+
width: "min(420px, 92vw)",
|
|
69
|
+
padding: "16px 18px",
|
|
70
|
+
background: t.bg,
|
|
71
|
+
border: t.border,
|
|
72
|
+
borderRadius: t.radius,
|
|
73
|
+
color: t.text,
|
|
74
|
+
fontFamily: t.fontFamily,
|
|
75
|
+
boxShadow: "0 12px 40px rgba(0,0,0,0.5)",
|
|
76
|
+
...style,
|
|
77
|
+
}, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }, children: [_jsx("div", { style: { fontSize: 15, fontWeight: 800, letterSpacing: "0.08em", textTransform: "uppercase" }, children: title }), _jsx("button", { type: "button", "data-keybinding-reset-all": "", onClick: () => {
|
|
78
|
+
session.resetAll();
|
|
79
|
+
onChange?.(session);
|
|
80
|
+
}, style: buttonStyle(t.muted, t.border), children: resetAllLabel })] }), anyConflict ? (_jsx("div", { style: { fontSize: 12, color: t.conflict, fontWeight: 600 }, children: "\u26A0 Some keys are bound to more than one action." })) : null, _jsx("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: rows.map((row) => (_jsx(KeybindingRow, { row: row, armed: capturing === row.actionId, capturePrompt: capturePrompt, theme: t, onArm: () => session.beginCapture(row.actionId), onReset: () => {
|
|
81
|
+
session.reset(row.actionId);
|
|
82
|
+
onChange?.(session);
|
|
83
|
+
} }, row.actionId))) })] }));
|
|
84
|
+
}
|
|
85
|
+
function KeybindingRow({ row, armed, capturePrompt, theme, onArm, onReset, }) {
|
|
86
|
+
const conflicted = row.conflictWith.length > 0;
|
|
87
|
+
return (_jsxs("div", { "data-keybinding-row": row.actionId, "data-armed": armed ? "" : undefined, "data-conflict": conflicted ? "" : undefined, style: {
|
|
88
|
+
display: "flex",
|
|
89
|
+
alignItems: "center",
|
|
90
|
+
gap: 10,
|
|
91
|
+
padding: "8px 10px",
|
|
92
|
+
borderRadius: 8,
|
|
93
|
+
background: armed ? "rgba(56,189,248,0.14)" : "rgba(255,255,255,0.03)",
|
|
94
|
+
border: `1px solid ${armed ? theme.accent : conflicted ? theme.conflict : "transparent"}`,
|
|
95
|
+
boxShadow: armed ? `0 0 0 1px ${theme.accent}, 0 0 14px rgba(56,189,248,0.35)` : "none",
|
|
96
|
+
transition: "background 120ms, border-color 120ms",
|
|
97
|
+
}, children: [_jsxs("div", { style: { flex: 1, minWidth: 0, fontSize: 14, fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: [row.label, conflicted ? (_jsx("span", { "data-keybinding-conflict-badge": "", style: {
|
|
98
|
+
marginLeft: 8,
|
|
99
|
+
padding: "1px 7px",
|
|
100
|
+
borderRadius: 999,
|
|
101
|
+
fontSize: 10,
|
|
102
|
+
fontWeight: 800,
|
|
103
|
+
letterSpacing: "0.06em",
|
|
104
|
+
textTransform: "uppercase",
|
|
105
|
+
color: "#1a0808",
|
|
106
|
+
background: theme.conflict,
|
|
107
|
+
}, children: "Conflict" })) : null] }), _jsx("button", { type: "button", "data-keybinding-capture": "", onClick: onArm, style: {
|
|
108
|
+
minWidth: 96,
|
|
109
|
+
display: "flex",
|
|
110
|
+
justifyContent: "center",
|
|
111
|
+
cursor: "pointer",
|
|
112
|
+
background: "transparent",
|
|
113
|
+
border: "none",
|
|
114
|
+
padding: 0,
|
|
115
|
+
}, children: armed ? (_jsx("span", { style: { fontSize: 12, fontWeight: 700, color: theme.accent }, children: capturePrompt })) : (_jsx(Keycap, { style: {
|
|
116
|
+
minWidth: 34,
|
|
117
|
+
padding: "3px 9px",
|
|
118
|
+
borderRadius: 6,
|
|
119
|
+
textAlign: "center",
|
|
120
|
+
fontSize: 13,
|
|
121
|
+
fontWeight: 700,
|
|
122
|
+
color: conflicted ? theme.conflict : theme.text,
|
|
123
|
+
background: "rgba(0,0,0,0.4)",
|
|
124
|
+
border: `1px solid ${conflicted ? theme.conflict : "rgba(255,255,255,0.18)"}`,
|
|
125
|
+
boxShadow: "inset 0 -2px 0 rgba(0,0,0,0.4)",
|
|
126
|
+
}, children: row.bindingLabel || "—" })) }), _jsx("button", { type: "button", "data-keybinding-reset": "", onClick: onReset, disabled: row.isDefault, title: "Reset to default", style: {
|
|
127
|
+
...buttonStyle(theme.muted, theme.border),
|
|
128
|
+
opacity: row.isDefault ? 0.35 : 1,
|
|
129
|
+
cursor: row.isDefault ? "default" : "pointer",
|
|
130
|
+
padding: "4px 8px",
|
|
131
|
+
fontSize: 11,
|
|
132
|
+
}, children: "Reset" })] }));
|
|
133
|
+
}
|
|
134
|
+
function buttonStyle(color, border) {
|
|
135
|
+
return {
|
|
136
|
+
pointerEvents: "auto",
|
|
137
|
+
background: "rgba(255,255,255,0.05)",
|
|
138
|
+
border,
|
|
139
|
+
borderRadius: 8,
|
|
140
|
+
color,
|
|
141
|
+
fontSize: 12,
|
|
142
|
+
fontWeight: 600,
|
|
143
|
+
padding: "5px 10px",
|
|
144
|
+
cursor: "pointer",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
import type { EventTicker, EventTickerView } from "@jgengine/core/game/eventTicker";
|
|
3
|
+
/**
|
|
4
|
+
* Subscribe to an event ticker and re-render it on an animation frame (unless the game
|
|
5
|
+
* already ticks it), returning the live entries newest-first with `fade` applied. Driving
|
|
6
|
+
* the frame loop is what makes entries visibly fade and drop as they age out.
|
|
7
|
+
*
|
|
8
|
+
* @capability use-event-ticker React hook binding an event-ticker — re-renders per frame so entries fade and drop live, returning the current newest-first views
|
|
9
|
+
*/
|
|
10
|
+
export declare function useEventTicker(ticker: EventTicker, animate?: boolean): EventTickerView[];
|
|
11
|
+
/** Per-`kind` styling for a kill-feed row — accent color and an optional icon. */
|
|
12
|
+
export interface KillFeedKindStyle {
|
|
13
|
+
/** Accent color for the row's icon and left rule. */
|
|
14
|
+
accent?: string;
|
|
15
|
+
/** Free-string {@link GameIcon} name to show for this kind (overridden by an entry's own `icon`). */
|
|
16
|
+
icon?: string;
|
|
17
|
+
/** Text color for the row. Defaults to the theme text color. */
|
|
18
|
+
color?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Reskin tokens for {@link KillFeed}. */
|
|
21
|
+
export interface KillFeedTheme {
|
|
22
|
+
/** Row background. Default reads `--jg-frame-bg`. */
|
|
23
|
+
rowBg?: string;
|
|
24
|
+
/** Row text color. Default near-white. */
|
|
25
|
+
text?: string;
|
|
26
|
+
/** Fallback accent when a kind has none. Default reads `--jg-accent`. */
|
|
27
|
+
accent?: string;
|
|
28
|
+
/** Font family. */
|
|
29
|
+
fontFamily?: string;
|
|
30
|
+
/** Row corner radius. Default reads `--jg-frame-radius`. */
|
|
31
|
+
radius?: string;
|
|
32
|
+
}
|
|
33
|
+
/** Props for {@link KillFeed}. */
|
|
34
|
+
export interface KillFeedProps {
|
|
35
|
+
/** The ticker to render. */
|
|
36
|
+
ticker: EventTicker;
|
|
37
|
+
/**
|
|
38
|
+
* Per-`kind` accent + icon, keyed by the free-string `kind` a game pushes with ("kill",
|
|
39
|
+
* "assist", "info", …). The matching entry styles that row; the model never interprets `kind`.
|
|
40
|
+
*/
|
|
41
|
+
kindStyles?: Record<string, KillFeedKindStyle>;
|
|
42
|
+
/** Base reskin tokens. */
|
|
43
|
+
theme?: KillFeedTheme;
|
|
44
|
+
/** Icon size in px. Default `18`. */
|
|
45
|
+
iconSize?: number;
|
|
46
|
+
/** Drive re-render on an animation frame. Default `true`. Turn off if the game ticks it. */
|
|
47
|
+
animate?: boolean;
|
|
48
|
+
/** Extra `z-index` for the stack. Default `40`. */
|
|
49
|
+
zIndex?: number;
|
|
50
|
+
className?: string;
|
|
51
|
+
style?: CSSProperties;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A vertical event/kill-feed ticker: renders an {@link EventTicker}'s recent entries as a
|
|
55
|
+
* stack of rows, newest on top, each with its `fade` `0..1` mapped to opacity so entries dim
|
|
56
|
+
* and drop as they age out. Each row shows an optional per-kind (or per-entry) {@link GameIcon}
|
|
57
|
+
* and a per-kind accent color from `kindStyles`, styled with HudTheme tokens. Presentation
|
|
58
|
+
* only: all capping/timing lives in the core ticker, and the free-string `kind`/`text`/`icon`
|
|
59
|
+
* are never interpreted here — a game colors and icons each variant via `kindStyles`.
|
|
60
|
+
*
|
|
61
|
+
* @capability kill-feed vertical event/kill-feed ticker rendering a core event-ticker as a fading, newest-on-top stack of per-kind iconned, accent-colored rows over HudTheme tokens
|
|
62
|
+
*/
|
|
63
|
+
export declare function KillFeed({ ticker, kindStyles, theme, iconSize, animate, zIndex, className, style, }: KillFeedProps): ReactNode;
|