@jgengine/react 0.11.0 → 0.13.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 +52 -0
- package/dist/actionHud.d.ts +5 -5
- package/dist/actionHud.js +14 -12
- package/dist/bars.d.ts +103 -0
- package/dist/bars.js +180 -0
- package/dist/barsPreview.d.ts +4 -0
- package/dist/barsPreview.js +47 -0
- package/dist/characterSheet.d.ts +120 -0
- package/dist/characterSheet.js +134 -0
- package/dist/components.d.ts +8 -1
- package/dist/components.js +8 -1
- package/dist/hooks.d.ts +31 -0
- package/dist/hooks.js +53 -0
- package/dist/hud.d.ts +21 -4
- package/dist/hud.js +38 -27
- package/dist/hudFrame.d.ts +47 -0
- package/dist/hudFrame.js +80 -0
- package/dist/hudLayout.d.ts +13 -1
- package/dist/hudLayout.js +18 -12
- package/dist/hudLayoutPersist.d.ts +23 -0
- package/dist/hudLayoutPersist.js +19 -0
- package/dist/hudTheme.d.ts +76 -0
- package/dist/hudTheme.js +156 -0
- package/dist/hudThemePreview.d.ts +4 -0
- package/dist/hudThemePreview.js +37 -0
- package/dist/iconTreatment.d.ts +50 -0
- package/dist/iconTreatment.js +84 -0
- package/dist/iconsPreview.d.ts +4 -0
- package/dist/iconsPreview.js +29 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/inventoryGrid.d.ts +20 -0
- package/dist/inventoryGrid.js +135 -0
- package/dist/map.d.ts +72 -10
- package/dist/map.js +84 -15
- package/dist/panels.d.ts +107 -0
- package/dist/panels.js +215 -0
- package/dist/selectionHud.d.ts +11 -5
- package/dist/selectionHud.js +11 -5
- package/dist/slots.d.ts +47 -0
- package/dist/slots.js +37 -0
- package/package.json +2 -2
- package/skills/jgengine/SKILL.md +8 -4
- package/skills/jgengine/api.md +182 -81
- package/skills/jgengine/capabilities.md +104 -2
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-ui/SKILL.md +17 -10
- package/skills/jgengine-ui/api.md +316 -51
- package/skills/jgengine-ui/capabilities.md +135 -2
- package/skills/jgengine-ui/recipes/portable-minimap.md +150 -0
- package/skills/jgengine-ui/reference.md +59 -9
- package/skills/jgengine-ui/references/visual-scorecard.md +1 -1
- package/skills/jgengine/reference-cartridge.md +0 -72
package/dist/hudLayout.js
CHANGED
|
@@ -9,6 +9,7 @@ import { useDisplayProfile } from "./display.js";
|
|
|
9
9
|
import { useEngineState } from "./engineStore.js";
|
|
10
10
|
import { useGameLayoutMode, useRegisterLayoutRegion } from "./gameViewport.js";
|
|
11
11
|
import { useOptionalGamePhase } from "./hooks.js";
|
|
12
|
+
import { useHudLayoutPersist } from "./hudLayoutPersist.js";
|
|
12
13
|
import { useHudViewport } from "./hudViewport.js";
|
|
13
14
|
/** Whether a HUD element opted into `showDuring` is visible in the current phase; `undefined` = always visible (default). */
|
|
14
15
|
export function hudVisibleInPhase(showDuring, phase) {
|
|
@@ -21,17 +22,6 @@ const EDIT_BAR_Z = 100000;
|
|
|
21
22
|
const REGION_GAP = 10;
|
|
22
23
|
const COMPACT_HUD_SCALE = 0.85;
|
|
23
24
|
const HUD_ANCHORS = Object.keys(HUD_ANCHOR_FRACTIONS);
|
|
24
|
-
function patchDocumentUi(id, panel) {
|
|
25
|
-
if (typeof window === "undefined")
|
|
26
|
-
return;
|
|
27
|
-
const host = window.__jgengineEditorHost;
|
|
28
|
-
if (host === undefined)
|
|
29
|
-
return;
|
|
30
|
-
host.handle({
|
|
31
|
-
method: "dispatch",
|
|
32
|
-
command: { type: "setUiPanel", id, patch: panel },
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
25
|
function sizeStyle(panel) {
|
|
36
26
|
if (panel === undefined)
|
|
37
27
|
return {};
|
|
@@ -60,17 +50,29 @@ function ResizeHandles({ axes, onResizeStart, }) {
|
|
|
60
50
|
? handle("s", "ns-resize", { left: 4, right: 4, bottom: -3, height: 6 })
|
|
61
51
|
: null, axes === "both" ? handle("se", "nwse-resize", { right: -4, bottom: -4, width: 10, height: 10 }) : null] }));
|
|
62
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Layout state for `HudCanvas` — panel placements, edit-mode drag/resize, and per-game persistence.
|
|
55
|
+
*
|
|
56
|
+
* @capability hud-layout persistent, live-editable HUD panel layout state for `HudCanvas`
|
|
57
|
+
*/
|
|
63
58
|
export function useHudLayout(options) {
|
|
64
59
|
const storageKey = options?.storageKey;
|
|
65
60
|
const snap = options?.snap;
|
|
66
61
|
const locked = options?.locked;
|
|
67
62
|
const documentUi = options?.documentUi;
|
|
68
63
|
const documentPatches = options?.documentPatches !== false;
|
|
64
|
+
const onPanelCommit = useHudLayoutPersist();
|
|
65
|
+
const onPanelCommitRef = useRef(onPanelCommit);
|
|
66
|
+
onPanelCommitRef.current = onPanelCommit;
|
|
69
67
|
const layout = useMemo(() => {
|
|
70
68
|
const store = createHudLayout({
|
|
71
69
|
snap,
|
|
72
70
|
locked,
|
|
73
|
-
onDocumentPatch: documentPatches
|
|
71
|
+
onDocumentPatch: documentPatches
|
|
72
|
+
? (id, panel) => {
|
|
73
|
+
onPanelCommitRef.current?.(id, panel);
|
|
74
|
+
}
|
|
75
|
+
: undefined,
|
|
74
76
|
});
|
|
75
77
|
if (documentUi !== undefined) {
|
|
76
78
|
store.applyDocumentUi(documentUi);
|
|
@@ -187,6 +189,8 @@ function edgePad(envInset, extra, basePx, scale) {
|
|
|
187
189
|
* offsets, no manual clearance for sibling panels, the touch-control dock
|
|
188
190
|
* (`--jg-hud-dock-clearance`), or device safe areas. On compact displays the
|
|
189
191
|
* whole surface scales down and each panel applies its `compact` behavior.
|
|
192
|
+
*
|
|
193
|
+
* @capability hud-canvas the HUD root surface — design-resolution scaling plus live panel-placement editing (F2+C)
|
|
190
194
|
*/
|
|
191
195
|
export function HudCanvas({ layout, editChord, compactScale, showDuring, className, style, children, }) {
|
|
192
196
|
const canvasRef = useRef(null);
|
|
@@ -400,6 +404,8 @@ function HudChip({ label, anchor, children, }) {
|
|
|
400
404
|
* pointers panels stay draggable through the edit chord; a dragged panel
|
|
401
405
|
* leaves the flow and keeps its custom placement. On compact displays custom
|
|
402
406
|
* placements are ignored and the `compact` behavior applies.
|
|
407
|
+
*
|
|
408
|
+
* @capability hud-panel anchored HUD slot — drop any widget in a named, player-movable panel
|
|
403
409
|
*/
|
|
404
410
|
export function HudPanel({ id, anchor = "top-left", order, compact: compactMode = "keep", chip, interactive, inset, locked, showDuring, priority, mobileBehavior, allowOverlapWith, collisionGroup, region = true, width, height, type, className, style, children, }) {
|
|
405
411
|
const ctx = useContext(HudCanvasContext);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { EditorUiPanelLayout } from "@jgengine/core/ui/hudDocument";
|
|
3
|
+
/**
|
|
4
|
+
* Host-owned callback that commits one HUD panel layout into the scene document
|
|
5
|
+
* (typically an undoable `setUiPanel` dispatch). Injected by the editor so product
|
|
6
|
+
* UI never reaches for editor globals.
|
|
7
|
+
*/
|
|
8
|
+
export type HudLayoutPersist = (id: string, panel: EditorUiPanelLayout) => void;
|
|
9
|
+
/**
|
|
10
|
+
* Provides {@link HudLayoutPersist} / `onPanelCommit` to descendant `useHudLayout`
|
|
11
|
+
* calls. Mount around game UI when a host can accept document UI patches (editor).
|
|
12
|
+
* Absent provider → canvas moves/resizes are no-ops for document writes.
|
|
13
|
+
* @capability hud-layout-persist inject a host callback so canvas HUD edits commit into the scene document
|
|
14
|
+
*/
|
|
15
|
+
export declare function HudLayoutPersistProvider({ onPanelCommit, children, }: {
|
|
16
|
+
onPanelCommit: HudLayoutPersist;
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
}): import("react").JSX.Element;
|
|
19
|
+
/**
|
|
20
|
+
* Injected panel-commit port, or `null` when no host has provided one.
|
|
21
|
+
* @capability hud-layout-persist-hook read the host panel-commit port from a HUD layout widget
|
|
22
|
+
*/
|
|
23
|
+
export declare function useHudLayoutPersist(): HudLayoutPersist | null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
const HudLayoutPersistContext = createContext(null);
|
|
4
|
+
/**
|
|
5
|
+
* Provides {@link HudLayoutPersist} / `onPanelCommit` to descendant `useHudLayout`
|
|
6
|
+
* calls. Mount around game UI when a host can accept document UI patches (editor).
|
|
7
|
+
* Absent provider → canvas moves/resizes are no-ops for document writes.
|
|
8
|
+
* @capability hud-layout-persist inject a host callback so canvas HUD edits commit into the scene document
|
|
9
|
+
*/
|
|
10
|
+
export function HudLayoutPersistProvider({ onPanelCommit, children, }) {
|
|
11
|
+
return (_jsx(HudLayoutPersistContext.Provider, { value: onPanelCommit, children: children }));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Injected panel-commit port, or `null` when no host has provided one.
|
|
15
|
+
* @capability hud-layout-persist-hook read the host panel-commit port from a HUD layout widget
|
|
16
|
+
*/
|
|
17
|
+
export function useHudLayoutPersist() {
|
|
18
|
+
return useContext(HudLayoutPersistContext);
|
|
19
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type CSSProperties } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* `HudTheme` (#1034): one token object that restyles shared token-driven building blocks at once.
|
|
4
|
+
* It is the superset of the atomic-bar tokens (#1033) — `hudThemeVars(theme)` emits the same
|
|
5
|
+
* `--jg-*` custom properties the bars already read, plus frame / slot / minimap-ring tokens — so
|
|
6
|
+
* setting a **game-authored** theme on any HUD ancestor re-skins those primitives. Purely CSS-token
|
|
7
|
+
* driven (no image assets). Tokens are not a finished UI: every game still owns composition, skin,
|
|
8
|
+
* and art direction (see AGENTS.md). Built-in presets are demo/scaffold starting points only.
|
|
9
|
+
*
|
|
10
|
+
* @capability hud-theme game-authored token object for bars, frames, slots, and minimap ring
|
|
11
|
+
*/
|
|
12
|
+
/** Per-readout fill colors plus a shared accent/glow. */
|
|
13
|
+
export interface HudThemePalette {
|
|
14
|
+
health: string;
|
|
15
|
+
healthLow: string;
|
|
16
|
+
mana: string;
|
|
17
|
+
stamina: string;
|
|
18
|
+
shield: string;
|
|
19
|
+
xp: string;
|
|
20
|
+
soul: string;
|
|
21
|
+
ammo: string;
|
|
22
|
+
boss: string;
|
|
23
|
+
/** Accent used for glow/keycap/active states. */
|
|
24
|
+
accent: string;
|
|
25
|
+
}
|
|
26
|
+
/** Frame material + shape tokens (panels, `HudFrame variation="themed"`). */
|
|
27
|
+
export interface HudThemeFrame {
|
|
28
|
+
/** Panel background (gradient or solid). */
|
|
29
|
+
bg: string;
|
|
30
|
+
border: string;
|
|
31
|
+
/** Corner radius (any CSS length, e.g. `"10px"`, `"0"`). */
|
|
32
|
+
radius: string;
|
|
33
|
+
/** Outer glow / drop shadow. */
|
|
34
|
+
glow: string;
|
|
35
|
+
}
|
|
36
|
+
/** Framed-trough tokens shared by every atomic bar. */
|
|
37
|
+
export interface HudThemeBar {
|
|
38
|
+
track: string;
|
|
39
|
+
frame: string;
|
|
40
|
+
height: string;
|
|
41
|
+
radius: string;
|
|
42
|
+
bevel: string;
|
|
43
|
+
text: string;
|
|
44
|
+
}
|
|
45
|
+
/** Action/inventory slot tokens. */
|
|
46
|
+
export interface HudThemeSlot {
|
|
47
|
+
bg: string;
|
|
48
|
+
border: string;
|
|
49
|
+
radius: string;
|
|
50
|
+
}
|
|
51
|
+
/** A full HUD theme. */
|
|
52
|
+
export interface HudTheme {
|
|
53
|
+
palette: HudThemePalette;
|
|
54
|
+
frame: HudThemeFrame;
|
|
55
|
+
bar: HudThemeBar;
|
|
56
|
+
slot: HudThemeSlot;
|
|
57
|
+
/** Minimap chrome ring color. */
|
|
58
|
+
ring: string;
|
|
59
|
+
}
|
|
60
|
+
/** Emits the CSS custom properties for a theme — spread onto any HUD ancestor to re-skin the subtree. */
|
|
61
|
+
export declare function hudThemeVars(theme: HudTheme): CSSProperties;
|
|
62
|
+
/** Default theme tokens for previews/scaffold — not a finished game look; author a custom theme. */
|
|
63
|
+
export declare const defaultHudTheme: HudTheme;
|
|
64
|
+
/** Built-in theme presets for demos/scaffold only — not product identity; games author their own theme. */
|
|
65
|
+
export declare const HUD_THEME_PRESETS: {
|
|
66
|
+
"arcane-stone": HudTheme;
|
|
67
|
+
"survival-wood": HudTheme;
|
|
68
|
+
"military-flat": HudTheme;
|
|
69
|
+
"sleek-hex": HudTheme;
|
|
70
|
+
"elemental-circle": HudTheme;
|
|
71
|
+
"sandbox-slot": HudTheme;
|
|
72
|
+
};
|
|
73
|
+
/** Built-in demo/scaffold theme preset name — not a game identity. */
|
|
74
|
+
export type HudThemePreset = keyof typeof HUD_THEME_PRESETS;
|
|
75
|
+
/** Resolves a preset name (or a full theme) to a `HudTheme`; falls back to the default theme. */
|
|
76
|
+
export declare function resolveHudTheme(theme?: HudThemePreset | HudTheme): HudTheme;
|
package/dist/hudTheme.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import {} from "react";
|
|
2
|
+
/** Emits the CSS custom properties for a theme — spread onto any HUD ancestor to re-skin the subtree. */
|
|
3
|
+
export function hudThemeVars(theme) {
|
|
4
|
+
const { palette, frame, bar, slot } = theme;
|
|
5
|
+
return {
|
|
6
|
+
// atomic-bar tokens (same names the bars read — #1033)
|
|
7
|
+
"--jg-health": palette.health,
|
|
8
|
+
"--jg-health-low": palette.healthLow,
|
|
9
|
+
"--jg-mana": palette.mana,
|
|
10
|
+
"--jg-stamina": palette.stamina,
|
|
11
|
+
"--jg-shield": palette.shield,
|
|
12
|
+
"--jg-xp": palette.xp,
|
|
13
|
+
"--jg-soul": palette.soul,
|
|
14
|
+
"--jg-ammo": palette.ammo,
|
|
15
|
+
"--jg-boss": palette.boss,
|
|
16
|
+
"--jg-bar-track": bar.track,
|
|
17
|
+
"--jg-bar-frame": bar.frame,
|
|
18
|
+
"--jg-bar-height": bar.height,
|
|
19
|
+
"--jg-bar-radius": bar.radius,
|
|
20
|
+
"--jg-bar-bevel": bar.bevel,
|
|
21
|
+
"--jg-bar-text": bar.text,
|
|
22
|
+
// shared chrome tokens
|
|
23
|
+
"--jg-accent": palette.accent,
|
|
24
|
+
"--jg-frame-bg": frame.bg,
|
|
25
|
+
"--jg-frame-border": frame.border,
|
|
26
|
+
"--jg-frame-radius": frame.radius,
|
|
27
|
+
"--jg-frame-glow": frame.glow,
|
|
28
|
+
"--jg-slot-bg": slot.bg,
|
|
29
|
+
"--jg-slot-border": slot.border,
|
|
30
|
+
"--jg-slot-radius": slot.radius,
|
|
31
|
+
"--jg-ring": theme.ring,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Default theme tokens for previews/scaffold — not a finished game look; author a custom theme. */
|
|
35
|
+
export const defaultHudTheme = {
|
|
36
|
+
palette: {
|
|
37
|
+
health: "#e5484d",
|
|
38
|
+
healthLow: "#b21e23",
|
|
39
|
+
mana: "#3b82f6",
|
|
40
|
+
stamina: "#84cc16",
|
|
41
|
+
shield: "#22d3ee",
|
|
42
|
+
xp: "#a855f7",
|
|
43
|
+
soul: "#c084fc",
|
|
44
|
+
ammo: "#f8b84e",
|
|
45
|
+
boss: "#ef4444",
|
|
46
|
+
accent: "#38bdf8",
|
|
47
|
+
},
|
|
48
|
+
frame: {
|
|
49
|
+
bg: "linear-gradient(180deg, rgba(20,24,32,0.86), rgba(10,12,16,0.9))",
|
|
50
|
+
border: "1px solid rgba(255,255,255,0.12)",
|
|
51
|
+
radius: "10px",
|
|
52
|
+
glow: "0 6px 20px rgba(0,0,0,0.45)",
|
|
53
|
+
},
|
|
54
|
+
bar: {
|
|
55
|
+
track: "rgba(0,0,0,0.55)",
|
|
56
|
+
frame: "rgba(0,0,0,0.82)",
|
|
57
|
+
height: "18px",
|
|
58
|
+
radius: "4px",
|
|
59
|
+
bevel: "inset 0 2px 4px rgba(0,0,0,0.55), inset 0 -1px 2px rgba(255,255,255,0.08)",
|
|
60
|
+
text: "#f5f7fa",
|
|
61
|
+
},
|
|
62
|
+
slot: { bg: "rgba(12,14,20,0.7)", border: "1px solid rgba(255,255,255,0.12)", radius: "8px" },
|
|
63
|
+
ring: "rgba(140,170,200,0.35)",
|
|
64
|
+
};
|
|
65
|
+
function theme(overrides) {
|
|
66
|
+
return {
|
|
67
|
+
palette: { ...defaultHudTheme.palette, ...overrides.palette },
|
|
68
|
+
frame: { ...defaultHudTheme.frame, ...overrides.frame },
|
|
69
|
+
bar: { ...defaultHudTheme.bar, ...overrides.bar },
|
|
70
|
+
slot: { ...defaultHudTheme.slot, ...overrides.slot },
|
|
71
|
+
ring: overrides.ring ?? defaultHudTheme.ring,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Built-in theme presets for demos/scaffold only — not product identity; games author their own theme. */
|
|
75
|
+
export const HUD_THEME_PRESETS = {
|
|
76
|
+
"arcane-stone": theme({
|
|
77
|
+
palette: { health: "#c0392b", mana: "#7c5cff", xp: "#d4a017", accent: "#b483ff", shield: "#8e7bd6" },
|
|
78
|
+
frame: {
|
|
79
|
+
bg: "linear-gradient(180deg, #3a3550, #211d33)",
|
|
80
|
+
border: "2px solid #6b5b95",
|
|
81
|
+
radius: "6px",
|
|
82
|
+
glow: "0 0 18px rgba(124,92,255,0.35)",
|
|
83
|
+
},
|
|
84
|
+
bar: { frame: "#160f28", radius: "3px", height: "20px", track: "rgba(0,0,0,0.6)", bevel: "inset 0 2px 5px rgba(0,0,0,0.6)", text: "#efe7ff" },
|
|
85
|
+
slot: { bg: "#241f38", border: "2px solid #6b5b95", radius: "5px" },
|
|
86
|
+
ring: "rgba(124,92,255,0.5)",
|
|
87
|
+
}),
|
|
88
|
+
"survival-wood": theme({
|
|
89
|
+
palette: { health: "#c0553b", stamina: "#a3b545", xp: "#e0a94b", accent: "#d9a441", shield: "#7bb0a0" },
|
|
90
|
+
frame: {
|
|
91
|
+
bg: "linear-gradient(180deg, #4a3b28, #2e2117)",
|
|
92
|
+
border: "3px solid #6b4a2c",
|
|
93
|
+
radius: "4px",
|
|
94
|
+
glow: "0 4px 10px rgba(0,0,0,0.5)",
|
|
95
|
+
},
|
|
96
|
+
bar: { frame: "#2a1c10", radius: "2px", height: "18px", track: "rgba(30,20,10,0.7)", bevel: "inset 0 2px 4px rgba(0,0,0,0.6)", text: "#f3e6cf" },
|
|
97
|
+
slot: { bg: "#3a2a1a", border: "3px solid #6b4a2c", radius: "3px" },
|
|
98
|
+
ring: "rgba(107,74,44,0.6)",
|
|
99
|
+
}),
|
|
100
|
+
"military-flat": theme({
|
|
101
|
+
palette: { health: "#3fa34d", stamina: "#a8b545", xp: "#c9b458", accent: "#7d9b4e", shield: "#5f8fae" },
|
|
102
|
+
frame: {
|
|
103
|
+
bg: "linear-gradient(180deg, #2b3126, #1c2019)",
|
|
104
|
+
border: "1px solid #4a5340",
|
|
105
|
+
radius: "2px",
|
|
106
|
+
glow: "0 2px 6px rgba(0,0,0,0.5)",
|
|
107
|
+
},
|
|
108
|
+
bar: { frame: "#161a12", radius: "0px", height: "16px", track: "rgba(0,0,0,0.6)", bevel: "inset 0 1px 2px rgba(0,0,0,0.7)", text: "#e8efdc" },
|
|
109
|
+
slot: { bg: "#232a1e", border: "1px solid #4a5340", radius: "0px" },
|
|
110
|
+
ring: "rgba(125,155,78,0.5)",
|
|
111
|
+
}),
|
|
112
|
+
"sleek-hex": theme({
|
|
113
|
+
palette: { health: "#ff5a7a", mana: "#33c9ff", xp: "#38e8c0", accent: "#33c9ff", shield: "#33c9ff" },
|
|
114
|
+
frame: {
|
|
115
|
+
bg: "linear-gradient(180deg, rgba(18,24,34,0.9), rgba(8,12,18,0.94))",
|
|
116
|
+
border: "1px solid rgba(51,201,255,0.4)",
|
|
117
|
+
radius: "3px",
|
|
118
|
+
glow: "0 0 16px rgba(51,201,255,0.3)",
|
|
119
|
+
},
|
|
120
|
+
bar: { frame: "rgba(51,201,255,0.5)", radius: "2px", height: "14px", track: "rgba(0,0,0,0.5)", bevel: "inset 0 1px 3px rgba(0,0,0,0.5)", text: "#eafaff" },
|
|
121
|
+
slot: { bg: "rgba(12,18,26,0.8)", border: "1px solid rgba(51,201,255,0.4)", radius: "3px" },
|
|
122
|
+
ring: "rgba(51,201,255,0.55)",
|
|
123
|
+
}),
|
|
124
|
+
"elemental-circle": theme({
|
|
125
|
+
palette: { health: "#ff6b3d", mana: "#4aa8ff", stamina: "#7bd451", xp: "#ffcf40", accent: "#ffcf40", shield: "#4aa8ff" },
|
|
126
|
+
frame: {
|
|
127
|
+
bg: "radial-gradient(circle at 50% 40%, #2c2a3a, #16141f)",
|
|
128
|
+
border: "2px solid #d9a441",
|
|
129
|
+
radius: "9999px",
|
|
130
|
+
glow: "0 0 20px rgba(217,164,65,0.35)",
|
|
131
|
+
},
|
|
132
|
+
bar: { frame: "#1a1626", radius: "9999px", height: "16px", track: "rgba(0,0,0,0.55)", bevel: "inset 0 2px 4px rgba(0,0,0,0.6)", text: "#fff3d6" },
|
|
133
|
+
slot: { bg: "#221f30", border: "2px solid #d9a441", radius: "9999px" },
|
|
134
|
+
ring: "rgba(217,164,65,0.6)",
|
|
135
|
+
}),
|
|
136
|
+
"sandbox-slot": theme({
|
|
137
|
+
palette: { health: "#e0524a", stamina: "#77c043", xp: "#5ac0e0", accent: "#f0c040", shield: "#5ac0e0" },
|
|
138
|
+
frame: {
|
|
139
|
+
bg: "linear-gradient(180deg, #5a5a5a, #3a3a3a)",
|
|
140
|
+
border: "3px solid #2a2a2a",
|
|
141
|
+
radius: "2px",
|
|
142
|
+
glow: "3px 3px 0 rgba(0,0,0,0.5)",
|
|
143
|
+
},
|
|
144
|
+
bar: { frame: "#1e1e1e", radius: "0px", height: "18px", track: "rgba(0,0,0,0.6)", bevel: "inset 0 2px 3px rgba(0,0,0,0.6)", text: "#f4f4f4" },
|
|
145
|
+
slot: { bg: "#4a4a4a", border: "3px solid #2a2a2a", radius: "0px" },
|
|
146
|
+
ring: "rgba(240,192,64,0.5)",
|
|
147
|
+
}),
|
|
148
|
+
};
|
|
149
|
+
/** Resolves a preset name (or a full theme) to a `HudTheme`; falls back to the default theme. */
|
|
150
|
+
export function resolveHudTheme(theme) {
|
|
151
|
+
if (theme === undefined)
|
|
152
|
+
return defaultHudTheme;
|
|
153
|
+
if (typeof theme === "string")
|
|
154
|
+
return HUD_THEME_PRESETS[theme] ?? defaultHudTheme;
|
|
155
|
+
return theme;
|
|
156
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import {} from "react";
|
|
3
|
+
import { AmmoCounter, ExperienceBar, HealthBar, ManaBar, ShieldBar } from "./bars.js";
|
|
4
|
+
import { HudFrame } from "./hudFrame.js";
|
|
5
|
+
import { HUD_THEME_PRESETS, defaultHudTheme, hudThemeVars } from "./hudTheme.js";
|
|
6
|
+
/**
|
|
7
|
+
* A deterministic preset matrix for {@link HudTheme} (#1034): every genre preset × (bars + a themed
|
|
8
|
+
* frame + a slot row), rendered from static values. One `hudThemeVars(theme)` block per cell drives
|
|
9
|
+
* the atomic bars, the `HudFrame variation="themed"`, and the slot chrome together — proof that a
|
|
10
|
+
* single theme restyles all the shared chrome at once. Renders identically every time (screenshots).
|
|
11
|
+
*/
|
|
12
|
+
function Slot({ index }) {
|
|
13
|
+
return (_jsx("div", { style: {
|
|
14
|
+
width: 34,
|
|
15
|
+
height: 34,
|
|
16
|
+
background: "var(--jg-slot-bg, rgba(12,14,20,0.7))",
|
|
17
|
+
border: "var(--jg-slot-border, 1px solid rgba(255,255,255,0.12))",
|
|
18
|
+
borderRadius: "var(--jg-slot-radius, 8px)",
|
|
19
|
+
display: "flex",
|
|
20
|
+
alignItems: "center",
|
|
21
|
+
justifyContent: "center",
|
|
22
|
+
fontSize: 10,
|
|
23
|
+
color: "var(--jg-bar-text, #f4f6fb)",
|
|
24
|
+
opacity: index === 0 ? 1 : 0.6,
|
|
25
|
+
}, children: index + 1 }));
|
|
26
|
+
}
|
|
27
|
+
function ThemeCell({ name, theme }) {
|
|
28
|
+
return (_jsx("div", { style: { ...hudThemeVars(theme), width: 260 }, children: _jsx(HudFrame, { variation: "themed", title: name, padding: 12, children: _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 7 }, children: [_jsx(HealthBar, { value: 78, max: 100, label: "HP", width: "100%" }), _jsx(ManaBar, { value: 50, max: 80, label: "MP", width: "100%" }), _jsx(ShieldBar, { value: 30, max: 60, label: "SH", width: "100%" }), _jsx(ExperienceBar, { value: 4, max: 10, segments: 10, showValue: false, label: "XP", width: "100%" }), _jsx(AmmoCounter, { loaded: 17, reserve: 90, magazine: 30, width: "100%" }), _jsx("div", { style: { display: "flex", gap: 6, marginTop: 2 }, children: [0, 1, 2, 3].map((index) => (_jsx(Slot, { index: index }, index))) })] }) }) }));
|
|
29
|
+
}
|
|
30
|
+
/** Renders the default theme plus every genre preset as a deterministic matrix. */
|
|
31
|
+
export function HudThemePreview({ className }) {
|
|
32
|
+
const cells = [
|
|
33
|
+
["default", defaultHudTheme],
|
|
34
|
+
...Object.entries(HUD_THEME_PRESETS),
|
|
35
|
+
];
|
|
36
|
+
return (_jsx("div", { "data-hud-theme-preview": true, className: className, style: { display: "flex", gap: 18, flexWrap: "wrap", padding: 22, fontFamily: "system-ui, sans-serif", background: "#0e1216" }, children: cells.map(([name, theme]) => (_jsx(ThemeCell, { name: name, theme: theme }, name))) }));
|
|
37
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
import { type GameIconName } from "./gameIcons.js";
|
|
3
|
+
/**
|
|
4
|
+
* `IconTreatment` (#1035): a procedural painted-icon face — a glyph over a school-keyed radial
|
|
5
|
+
* gradient, with a vignette inset shadow, a top gloss highlight, and optional count/keycap badges.
|
|
6
|
+
* Turns the engine's flat white `GameIcon` glyphs (and game-icons.net SVGs) into painted ability /
|
|
7
|
+
* item icons that read as AAA slots, not debug UI. Theme-aware: the frame reads the `--jg-slot-*`
|
|
8
|
+
* and `--jg-accent` `HudTheme` tokens (#1034), so a theme change re-skins every treated icon.
|
|
9
|
+
*
|
|
10
|
+
* @capability hud-icons painted-icon treatment for hotbar/action slots — glyph + gradient + count badge
|
|
11
|
+
*/
|
|
12
|
+
/** Element/school palette key — drives the radial gradient behind the glyph. */
|
|
13
|
+
export type IconSchool = "fire" | "frost" | "arcane" | "nature" | "holy" | "tech" | "shadow" | "steel" | "neutral";
|
|
14
|
+
/** Infers an {@link IconSchool} from an item id (keyword match); `neutral` when nothing matches. */
|
|
15
|
+
export declare function schoolForItem(itemId: string): IconSchool;
|
|
16
|
+
/** Infers an {@link IconSchool} from an action id (keyword match); `neutral` when nothing matches. */
|
|
17
|
+
export declare function schoolForAction(action: string): IconSchool;
|
|
18
|
+
/** Props for {@link IconTreatment}. */
|
|
19
|
+
export interface IconTreatmentProps {
|
|
20
|
+
/** A `GameIcon` name to render as the glyph. */
|
|
21
|
+
icon?: GameIconName;
|
|
22
|
+
/** Or an explicit glyph node (a game-icons.net SVG, an `<img>`, custom art). Wins over `icon`. */
|
|
23
|
+
glyph?: ReactNode;
|
|
24
|
+
/** School/element gradient behind the glyph. Default `neutral`. */
|
|
25
|
+
school?: IconSchool;
|
|
26
|
+
/** Face size in px. Default 44. */
|
|
27
|
+
size?: number;
|
|
28
|
+
/** Stack count — a badge is drawn when > 1. */
|
|
29
|
+
count?: number;
|
|
30
|
+
/** Keycap/hotkey drawn top-left. */
|
|
31
|
+
keycap?: string;
|
|
32
|
+
/** Active/selected ring using `--jg-accent`. */
|
|
33
|
+
active?: boolean;
|
|
34
|
+
className?: string;
|
|
35
|
+
style?: CSSProperties;
|
|
36
|
+
}
|
|
37
|
+
/** A painted icon face — gradient + vignette + gloss behind a glyph, with count/keycap badges. */
|
|
38
|
+
export declare function IconTreatment({ icon, glyph, school, size, count, keycap, active, className, style, }: IconTreatmentProps): import("react").JSX.Element;
|
|
39
|
+
/** Maps an item id to a treated icon — resolves a `GameIcon` glyph and an inferred school. */
|
|
40
|
+
export declare function treatedItemIcon(itemId: string, options?: {
|
|
41
|
+
size?: number;
|
|
42
|
+
count?: number;
|
|
43
|
+
keycap?: string;
|
|
44
|
+
}): ReactNode;
|
|
45
|
+
/** A ready-made `ActionButton` `renderIcon` that paints the action's `GameIcon` with a treatment. */
|
|
46
|
+
export declare function treatedActionIcon(action: {
|
|
47
|
+
id?: string;
|
|
48
|
+
label?: string;
|
|
49
|
+
icon?: ReactNode;
|
|
50
|
+
}, size?: number): ReactNode;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import {} from "react";
|
|
3
|
+
import { GameIcon, iconForAction, iconForItemId } from "./gameIcons.js";
|
|
4
|
+
const SCHOOL_GRADIENT = {
|
|
5
|
+
fire: ["#ff8a3d", "#661805"],
|
|
6
|
+
frost: ["#8fe3ff", "#0e3357"],
|
|
7
|
+
arcane: ["#c58cff", "#341552"],
|
|
8
|
+
nature: ["#8fe36b", "#194013"],
|
|
9
|
+
holy: ["#ffe08a", "#6b4d0f"],
|
|
10
|
+
tech: ["#5fe0c8", "#0c3f3a"],
|
|
11
|
+
shadow: ["#9a7bd0", "#1b0d2b"],
|
|
12
|
+
steel: ["#c8d0da", "#333b45"],
|
|
13
|
+
neutral: ["#9aa3ad", "#242a31"],
|
|
14
|
+
};
|
|
15
|
+
const SCHOOL_RULES = [
|
|
16
|
+
[["fire", "flame", "burn", "lava", "ember", "molotov", "torch"], "fire"],
|
|
17
|
+
[["frost", "ice", "freeze", "snow", "water", "aqua", "chill"], "frost"],
|
|
18
|
+
[["arcane", "magic", "mana", "rune", "spell", "mystic", "void"], "arcane"],
|
|
19
|
+
[["nature", "leaf", "poison", "vine", "earth", "herb", "wood", "beast"], "nature"],
|
|
20
|
+
[["holy", "light", "heal", "divine", "bless", "sun", "cleric"], "holy"],
|
|
21
|
+
[["tech", "laser", "cyber", "robot", "energy", "plasma", "gadget"], "tech"],
|
|
22
|
+
[["shadow", "dark", "curse", "necro", "death", "blood", "demon"], "shadow"],
|
|
23
|
+
[["sword", "axe", "hammer", "spear", "dagger", "armor", "shield", "gun", "bow", "metal", "steel"], "steel"],
|
|
24
|
+
];
|
|
25
|
+
function schoolFrom(text) {
|
|
26
|
+
const lower = text.toLowerCase();
|
|
27
|
+
for (const [keys, school] of SCHOOL_RULES) {
|
|
28
|
+
if (keys.some((key) => lower.includes(key)))
|
|
29
|
+
return school;
|
|
30
|
+
}
|
|
31
|
+
return "neutral";
|
|
32
|
+
}
|
|
33
|
+
/** Infers an {@link IconSchool} from an item id (keyword match); `neutral` when nothing matches. */
|
|
34
|
+
export function schoolForItem(itemId) {
|
|
35
|
+
return schoolFrom(itemId);
|
|
36
|
+
}
|
|
37
|
+
/** Infers an {@link IconSchool} from an action id (keyword match); `neutral` when nothing matches. */
|
|
38
|
+
export function schoolForAction(action) {
|
|
39
|
+
return schoolFrom(action);
|
|
40
|
+
}
|
|
41
|
+
/** A painted icon face — gradient + vignette + gloss behind a glyph, with count/keycap badges. */
|
|
42
|
+
export function IconTreatment({ icon, glyph, school = "neutral", size = 44, count, keycap, active = false, className, style, }) {
|
|
43
|
+
const [g0, g1] = SCHOOL_GRADIENT[school];
|
|
44
|
+
const content = glyph ?? (icon !== undefined ? _jsx(GameIcon, { name: icon, size: Math.round(size * 0.58), color: "#fdfdff" }) : null);
|
|
45
|
+
return (_jsxs("div", { className: className, "data-icon-treatment": school, style: {
|
|
46
|
+
position: "relative",
|
|
47
|
+
width: size,
|
|
48
|
+
height: size,
|
|
49
|
+
borderRadius: "var(--jg-slot-radius, 9px)",
|
|
50
|
+
background: `radial-gradient(circle at 38% 30%, ${g0}, ${g1})`,
|
|
51
|
+
border: active ? "1px solid var(--jg-accent, #38bdf8)" : "var(--jg-slot-border, 1px solid rgba(255,255,255,0.16))",
|
|
52
|
+
boxShadow: active
|
|
53
|
+
? "0 0 0 1px var(--jg-accent, #38bdf8), 0 0 10px rgba(56,189,248,0.5), inset 0 -6px 12px rgba(0,0,0,0.5)"
|
|
54
|
+
: "inset 0 -7px 13px rgba(0,0,0,0.55), inset 0 2px 3px rgba(255,255,255,0.2)",
|
|
55
|
+
overflow: "hidden",
|
|
56
|
+
display: "flex",
|
|
57
|
+
alignItems: "center",
|
|
58
|
+
justifyContent: "center",
|
|
59
|
+
boxSizing: "border-box",
|
|
60
|
+
...style,
|
|
61
|
+
}, children: [_jsx("div", { style: { position: "absolute", inset: 0, background: "linear-gradient(180deg, rgba(255,255,255,0.3), rgba(255,255,255,0) 46%)", pointerEvents: "none" } }), _jsx("div", { style: { position: "absolute", inset: 0, borderRadius: "inherit", boxShadow: "inset 0 0 12px rgba(0,0,0,0.55)", pointerEvents: "none" } }), content, keycap !== undefined ? (_jsx("span", { style: { position: "absolute", top: 1, left: 4, fontSize: 9, fontWeight: 700, color: "#fff", opacity: 0.75, textShadow: "0 1px 2px rgba(0,0,0,0.9)" }, children: keycap })) : null, count !== undefined && count > 1 ? (_jsx("span", { "data-count": true, style: {
|
|
62
|
+
position: "absolute",
|
|
63
|
+
bottom: 1,
|
|
64
|
+
right: 3,
|
|
65
|
+
fontSize: 11,
|
|
66
|
+
fontWeight: 800,
|
|
67
|
+
color: "#fff",
|
|
68
|
+
fontVariantNumeric: "tabular-nums",
|
|
69
|
+
textShadow: "0 1px 2px #000, 0 0 3px #000",
|
|
70
|
+
}, children: count })) : null] }));
|
|
71
|
+
}
|
|
72
|
+
/** Maps an item id to a treated icon — resolves a `GameIcon` glyph and an inferred school. */
|
|
73
|
+
export function treatedItemIcon(itemId, options = {}) {
|
|
74
|
+
const icon = iconForItemId(itemId) ?? undefined;
|
|
75
|
+
return (_jsx(IconTreatment, { ...(icon === undefined ? {} : { icon }), school: schoolForItem(itemId), ...(options.size === undefined ? {} : { size: options.size }), ...(options.count === undefined ? {} : { count: options.count }), ...(options.keycap === undefined ? {} : { keycap: options.keycap }) }));
|
|
76
|
+
}
|
|
77
|
+
/** A ready-made `ActionButton` `renderIcon` that paints the action's `GameIcon` with a treatment. */
|
|
78
|
+
export function treatedActionIcon(action, size = 40) {
|
|
79
|
+
const key = action.id ?? action.label ?? "";
|
|
80
|
+
const named = iconForAction(key);
|
|
81
|
+
if (named === null)
|
|
82
|
+
return action.icon ?? null;
|
|
83
|
+
return _jsx(IconTreatment, { icon: named, school: schoolForAction(key), size: size });
|
|
84
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { IconTreatment } from "./iconTreatment.js";
|
|
3
|
+
/**
|
|
4
|
+
* A deterministic showcase of the painted {@link IconTreatment} (#1035): a school row (one gradient
|
|
5
|
+
* each) and a mock hotbar of treated item icons with count badges and an active slot. Static, so it
|
|
6
|
+
* screenshots identically every time. This is the before/after evidence vs. raw `itemId` text slots.
|
|
7
|
+
*/
|
|
8
|
+
const SCHOOLS = [
|
|
9
|
+
["fire", "bomb"],
|
|
10
|
+
["frost", "wand"],
|
|
11
|
+
["arcane", "staff"],
|
|
12
|
+
["nature", "bow"],
|
|
13
|
+
["holy", "shield"],
|
|
14
|
+
["tech", "gun"],
|
|
15
|
+
["shadow", "dagger"],
|
|
16
|
+
["steel", "sword"],
|
|
17
|
+
];
|
|
18
|
+
const HOTBAR = [
|
|
19
|
+
{ icon: "sword", school: "steel" },
|
|
20
|
+
{ icon: "bow", school: "nature" },
|
|
21
|
+
{ icon: "bomb", school: "fire", count: 12 },
|
|
22
|
+
{ icon: "shield", school: "holy" },
|
|
23
|
+
{ icon: "staff", school: "arcane" },
|
|
24
|
+
{ icon: "wand", school: "frost", count: 3 },
|
|
25
|
+
];
|
|
26
|
+
/** Renders the school-gradient row and a treated-icon hotbar — the deterministic #1035 fixture. */
|
|
27
|
+
export function IconsPreview({ className }) {
|
|
28
|
+
return (_jsxs("div", { "data-icons-preview": true, className: className, style: { display: "flex", flexDirection: "column", gap: 22, padding: 26, background: "#0e1216", fontFamily: "system-ui, sans-serif", color: "#e8edf2" }, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 11, fontWeight: 700, letterSpacing: 0.6, textTransform: "uppercase", opacity: 0.6, marginBottom: 8 }, children: "school gradients" }), _jsx("div", { style: { display: "flex", gap: 10 }, children: SCHOOLS.map(([school, icon]) => (_jsx(IconTreatment, { icon: icon, school: school, size: 52 }, school))) })] }), _jsxs("div", { children: [_jsx("div", { style: { fontSize: 11, fontWeight: 700, letterSpacing: 0.6, textTransform: "uppercase", opacity: 0.6, marginBottom: 8 }, children: "hotbar \u2014 treated icons + count badges (slot 1 active)" }), _jsx("div", { style: { display: "flex", gap: 6 }, children: HOTBAR.map((entry, index) => (_jsx(IconTreatment, { icon: entry.icon, school: entry.school, size: 50, keycap: String(index + 1), active: index === 0, ...(entry.count === undefined ? {} : { count: entry.count }) }, index))) })] })] }));
|
|
29
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,17 @@ export * from "./settings.js";
|
|
|
10
10
|
export * from "./save.js";
|
|
11
11
|
export * from "./components.js";
|
|
12
12
|
export * from "./hud.js";
|
|
13
|
+
export * from "./bars.js";
|
|
14
|
+
export * from "./barsPreview.js";
|
|
15
|
+
export * from "./iconTreatment.js";
|
|
16
|
+
export * from "./iconsPreview.js";
|
|
17
|
+
export * from "./slots.js";
|
|
18
|
+
export * from "./inventoryGrid.js";
|
|
19
|
+
export * from "./panels.js";
|
|
20
|
+
export * from "./characterSheet.js";
|
|
21
|
+
export * from "./hudTheme.js";
|
|
22
|
+
export * from "./hudThemePreview.js";
|
|
23
|
+
export * from "./hudFrame.js";
|
|
13
24
|
export * from "./keyHint.js";
|
|
14
25
|
export * from "./startScreen.js";
|
|
15
26
|
export * from "./engineStore.js";
|
|
@@ -18,6 +29,7 @@ export * from "./cards.js";
|
|
|
18
29
|
export * from "./actionHud.js";
|
|
19
30
|
export * from "./selectionHud.js";
|
|
20
31
|
export * from "./hudLayout.js";
|
|
32
|
+
export * from "./hudLayoutPersist.js";
|
|
21
33
|
export * from "./hudViewport.js";
|
|
22
34
|
export * from "./gameViewport.js";
|
|
23
35
|
export * from "./rotateDevice.js";
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,17 @@ export * from "./settings.js";
|
|
|
10
10
|
export * from "./save.js";
|
|
11
11
|
export * from "./components.js";
|
|
12
12
|
export * from "./hud.js";
|
|
13
|
+
export * from "./bars.js";
|
|
14
|
+
export * from "./barsPreview.js";
|
|
15
|
+
export * from "./iconTreatment.js";
|
|
16
|
+
export * from "./iconsPreview.js";
|
|
17
|
+
export * from "./slots.js";
|
|
18
|
+
export * from "./inventoryGrid.js";
|
|
19
|
+
export * from "./panels.js";
|
|
20
|
+
export * from "./characterSheet.js";
|
|
21
|
+
export * from "./hudTheme.js";
|
|
22
|
+
export * from "./hudThemePreview.js";
|
|
23
|
+
export * from "./hudFrame.js";
|
|
13
24
|
export * from "./keyHint.js";
|
|
14
25
|
export * from "./startScreen.js";
|
|
15
26
|
export * from "./engineStore.js";
|
|
@@ -18,6 +29,7 @@ export * from "./cards.js";
|
|
|
18
29
|
export * from "./actionHud.js";
|
|
19
30
|
export * from "./selectionHud.js";
|
|
20
31
|
export * from "./hudLayout.js";
|
|
32
|
+
export * from "./hudLayoutPersist.js";
|
|
21
33
|
export * from "./hudViewport.js";
|
|
22
34
|
export * from "./gameViewport.js";
|
|
23
35
|
export * from "./rotateDevice.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
/** Props for {@link InventoryGrid}. */
|
|
3
|
+
export interface InventoryGridProps {
|
|
4
|
+
/** The declared inventory id to bind (its slot count sets the tile count). */
|
|
5
|
+
inventoryId: string;
|
|
6
|
+
/** Grid columns; default derives a squarish grid from the slot count. */
|
|
7
|
+
columns?: number;
|
|
8
|
+
/** Tile size in px. Default 44. */
|
|
9
|
+
size?: number;
|
|
10
|
+
/** Gap between tiles in px. Default 6. */
|
|
11
|
+
gap?: number;
|
|
12
|
+
/** Caller-supplied item id → icon/glyph registry; return null/undefined to fall back to the default `GameIcon`. */
|
|
13
|
+
itemIcon?: (itemId: string) => ReactNode;
|
|
14
|
+
/** Invoked when a filled slot is activated (double-click) — e.g. use/equip the item. */
|
|
15
|
+
onActivate?: (slot: number) => void;
|
|
16
|
+
className?: string;
|
|
17
|
+
style?: CSSProperties;
|
|
18
|
+
}
|
|
19
|
+
/** A drop-in inventory grid with drag/swap/stack-merge/split and full keyboard control, bound to `inventoryId`. */
|
|
20
|
+
export declare function InventoryGrid({ inventoryId, columns, size, gap, itemIcon, onActivate, className, style, }: InventoryGridProps): import("react").JSX.Element;
|