@jgengine/react 0.12.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 +26 -0
- package/dist/characterSheet.d.ts +120 -0
- package/dist/characterSheet.js +134 -0
- package/dist/hooks.d.ts +11 -0
- package/dist/hooks.js +15 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/inventoryGrid.d.ts +20 -0
- package/dist/inventoryGrid.js +135 -0
- package/dist/panels.d.ts +107 -0
- package/dist/panels.js +215 -0
- package/dist/slots.d.ts +7 -4
- package/package.json +2 -2
- package/skills/jgengine/SKILL.md +1 -0
- package/skills/jgengine/api.md +13 -5
- package/skills/jgengine-ui/SKILL.md +6 -4
- package/skills/jgengine-ui/api.md +116 -6
- package/skills/jgengine-ui/capabilities.md +64 -0
- package/skills/jgengine-ui/reference.md +40 -3
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,32 @@ Agents building on the published SDK can also read this programmatically:
|
|
|
12
12
|
same data as typed values, so an updater can diff its installed version against
|
|
13
13
|
the latest and surface the migration steps.
|
|
14
14
|
|
|
15
|
+
## 0.13.0
|
|
16
|
+
|
|
17
|
+
### Migrate
|
|
18
|
+
|
|
19
|
+
- **Nothing required** — this release is additive fixes, defaults, and new drop-in primitives. Bump `@jgengine/*` pins to `^0.13.0` (CLI `jgengine` is 0.10.0; `@jgengine/github` unchanged at 0.1.0) and rebuild.
|
|
20
|
+
- Two defaults changed in the player's favor, both overridable: **offline games now persist to localStorage automatically** (#1207) and **rigged catalog assets animate by default** via clip metadata + semantic clip roles (#1209). If a game relied on rigged models staying frozen or on saves being memory-only, opt out explicitly; otherwise you get better behavior for free.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **Drop-in inventory grid, window manager, and character sheet** (#1220) — `@jgengine/react` ships `InventoryGrid` (real drag/stack/split), a toggleable-window system (`panels`: bag, character sheet, spellbook-style windows with `@jgengine/core/ui/panelModel` state), and a `CharacterSheet` paperdoll, all HudTheme-skinnable — wire in a line or two instead of re-deriving from raw divs.
|
|
25
|
+
- **Serializable runtime snapshots** (#1179) — capture/restore of live runtime state, including magazine state (`@jgengine/core/combat/magazine`) and stat modifiers, for saves and replication.
|
|
26
|
+
- **Portable weapon plumbing** (#1211) — `@jgengine/core/combat/weaponFire` composes trigger → magazine → spread → projectile/hitscan as data, no game-local glue.
|
|
27
|
+
- **Portable leveling** (#1206) and **portable damage-pool access** (#1208) — progression and damage pools exposed as genre-agnostic seams on the game context.
|
|
28
|
+
- **Named combat VFX presets** (#1204) — `@jgengine/core/combat/vfxPresets`: a visible attack is one line.
|
|
29
|
+
- **Runtime-measured render-bounds hitboxes** (#1227) — colliders wrap what the shell actually mounts, not the raw asset bounds.
|
|
30
|
+
- **Character motion feel** — procedural part motion for rig-less part-composed characters (#1222) plus squash & stretch and death splat (#1229).
|
|
31
|
+
- **City fabric** (#1191) — `streetGenerator` + `cityGenerator`, editor bake verb, street-aware lots, road junctions, grounding, and a web playground.
|
|
32
|
+
- **`jgengine recipe` CLI** (#1214, #1231) — vetted wired compositions, compile-pinned against the SDK, listed and copied from the CLI.
|
|
33
|
+
- **Editor/debug modes mirror to the URL** with a proper editor exit (#1203); game template ships a dependency-free WebGL screenshot script (#1202).
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- Offline games persist to localStorage by default (#1207); rigged assets animate by default (#1209).
|
|
38
|
+
- RTS camera pan inversion fixed (#1228); sprite raycast warning silenced and duplicate nameplate health bars removed (#1194); scaffolded F2+E editor Tailwind `@source` renders its theme (#1195).
|
|
39
|
+
- Outside-game DX: stat-pool no-op stability, camera-transparent decor, cleaner install/version output, headless UI-intent seam (#1223); new-game briefs direct engine bugs/gaps upstream (#1200).
|
|
40
|
+
|
|
15
41
|
## 0.12.0
|
|
16
42
|
|
|
17
43
|
### Migrate
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
import { type SlotItem } from "./slots.js";
|
|
3
|
+
import { type HudFrameVariation } from "./hudFrame.js";
|
|
4
|
+
/**
|
|
5
|
+
* Character-sheet / paperdoll composition (#1033 follow-up): a drop-in `<CharacterSheet>` that
|
|
6
|
+
* arranges caller-supplied equip slots as a paperdoll around a portrait and lists derived stats,
|
|
7
|
+
* all inside a {@link HudFrame}. It reuses the atomic slot rendering ({@link EquipmentSlots}) and the
|
|
8
|
+
* shared `--jg-slot-*` / `--jg-accent` HudTheme tokens (#1034) — the game owns which slots and which
|
|
9
|
+
* stats matter (both are props), the skin, and the window that toggles it open/closed. No genre list
|
|
10
|
+
* is baked in; {@link defaultEquipLayout} is a convenience arrangement the game can override wholesale.
|
|
11
|
+
*/
|
|
12
|
+
/** Which flank of the paperdoll a slot sits on — `left`/`right` columns, or the `bottom` row under the portrait. */
|
|
13
|
+
export type EquipSlotPosition = "left" | "right" | "bottom";
|
|
14
|
+
/** One named equip slot: a stable `id`, a display `label`, its worn `item` (or `null` = empty), and where it sits. */
|
|
15
|
+
export interface EquipSlot {
|
|
16
|
+
/** Stable slot id passed to `onSlotActivate` (e.g. `head`, `mainHand`, `ring1`). */
|
|
17
|
+
id: string;
|
|
18
|
+
/** Human label shown under/beside the slot and used as its accessible name. */
|
|
19
|
+
label: string;
|
|
20
|
+
/** The worn item, or `null` for an empty slot (renders the themed placeholder). */
|
|
21
|
+
item: SlotItem | null;
|
|
22
|
+
/** Which flank the slot sits on. Default `left`. */
|
|
23
|
+
position?: EquipSlotPosition;
|
|
24
|
+
}
|
|
25
|
+
/** An ordered set of equip slots that make up a paperdoll. Caller-supplied; see {@link defaultEquipLayout}. */
|
|
26
|
+
export type EquipSlotLayout = readonly EquipSlot[];
|
|
27
|
+
/** Slot ids used by {@link defaultEquipLayout}'s convenience arrangement. */
|
|
28
|
+
export type DefaultEquipSlotId = "head" | "chest" | "hands" | "legs" | "feet" | "mainHand" | "offHand" | "ring1" | "ring2" | "trinket";
|
|
29
|
+
/**
|
|
30
|
+
* A sensible default paperdoll arrangement — worn armor down the left flank, held gear and jewelry
|
|
31
|
+
* down the right — that the game fills by passing worn items keyed by slot id. Every id is optional;
|
|
32
|
+
* unfilled slots render empty. Games that need a different slot set pass their own {@link EquipSlotLayout}.
|
|
33
|
+
*/
|
|
34
|
+
export declare function defaultEquipLayout(items?: Partial<Record<DefaultEquipSlotId, SlotItem | null>>): EquipSlot[];
|
|
35
|
+
/** Props for {@link Paperdoll}. */
|
|
36
|
+
export interface PaperdollProps {
|
|
37
|
+
/** The equip slots to arrange. Order within a flank is preserved. */
|
|
38
|
+
slots: EquipSlotLayout;
|
|
39
|
+
/** Center node — a portrait, 3D preview, or custom art. A silhouette placeholder is drawn when omitted. */
|
|
40
|
+
portrait?: ReactNode;
|
|
41
|
+
/** Slot tile size in px. Default 44. */
|
|
42
|
+
slotSize?: number;
|
|
43
|
+
/** Fired with the slot `id` on click / Enter / Space — wire drag or an equip picker here. */
|
|
44
|
+
onSlotActivate?: (id: string) => void;
|
|
45
|
+
className?: string;
|
|
46
|
+
style?: CSSProperties;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Arranges caller-supplied equip slots as a paperdoll — worn gear in two flanking columns around a
|
|
50
|
+
* center `portrait` (or a silhouette placeholder), with any `bottom` slots in a row beneath it. Each
|
|
51
|
+
* slot reuses the atomic {@link EquipmentSlots} tile (filled icon or themed empty placeholder), carries
|
|
52
|
+
* a label, and is a focusable `role="group"` with an `aria-label`; `onSlotActivate` fires on
|
|
53
|
+
* click/Enter/Space so games can wire drag or an equip picker. Composition and skin stay the game's.
|
|
54
|
+
*
|
|
55
|
+
* @capability character-paperdoll equip-slot paperdoll — flanking columns around a portrait, activatable slots
|
|
56
|
+
*/
|
|
57
|
+
export declare function Paperdoll({ slots, portrait, slotSize, onSlotActivate, className, style }: PaperdollProps): ReactNode;
|
|
58
|
+
/** One derived-stat row. `group` optionally buckets rows under a small header. */
|
|
59
|
+
export interface StatRow {
|
|
60
|
+
/** Stable row id. */
|
|
61
|
+
id: string;
|
|
62
|
+
/** Small-caps row label (e.g. `Armor`, `Crit`). */
|
|
63
|
+
label: string;
|
|
64
|
+
/** The value to show — the game formats it (a number, `"12%"`, `"1.2/s"`). */
|
|
65
|
+
value: string | number;
|
|
66
|
+
/** Optional secondary hint shown dimmed after the value (e.g. a delta or source). */
|
|
67
|
+
hint?: string;
|
|
68
|
+
/** Optional group header this row falls under; consecutive rows sharing a group render one header. */
|
|
69
|
+
group?: string;
|
|
70
|
+
}
|
|
71
|
+
/** Props for {@link StatList}. */
|
|
72
|
+
export interface StatListProps {
|
|
73
|
+
/** Derived stat rows, in display order. The game owns which stats matter. */
|
|
74
|
+
stats: readonly StatRow[];
|
|
75
|
+
className?: string;
|
|
76
|
+
style?: CSSProperties;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Renders derived character stats as clean small-caps label→value rows, token-themed. Rows carrying a
|
|
80
|
+
* `group` field are bucketed under a small uppercase header (consecutive same-group rows share one).
|
|
81
|
+
* Pure and prop-driven — the game supplies the rows (from `useEntityStat`/derived values); the engine
|
|
82
|
+
* ships no stat list of its own.
|
|
83
|
+
*
|
|
84
|
+
* @capability character-statlist derived-stat readout — grouped small-caps label→value rows, token-themed
|
|
85
|
+
*/
|
|
86
|
+
export declare function StatList({ stats, className, style }: StatListProps): ReactNode;
|
|
87
|
+
/** Props for {@link CharacterSheet}. */
|
|
88
|
+
export interface CharacterSheetProps {
|
|
89
|
+
/** Equip slots to arrange as the paperdoll (see {@link defaultEquipLayout}). */
|
|
90
|
+
slots: EquipSlotLayout;
|
|
91
|
+
/** Derived stat rows for the {@link StatList}. */
|
|
92
|
+
stats: readonly StatRow[];
|
|
93
|
+
/** Character name shown in the header. */
|
|
94
|
+
name?: ReactNode;
|
|
95
|
+
/** Secondary header line — level, class, faction, etc. */
|
|
96
|
+
subtitle?: ReactNode;
|
|
97
|
+
/** Portrait node passed through to the {@link Paperdoll} center. */
|
|
98
|
+
portrait?: ReactNode;
|
|
99
|
+
/** Replace the whole header block (name/subtitle) with custom content. */
|
|
100
|
+
header?: ReactNode;
|
|
101
|
+
/** Frame title label. Default `Character`. */
|
|
102
|
+
title?: ReactNode;
|
|
103
|
+
/** {@link HudFrame} skin. Default `themed`. */
|
|
104
|
+
variation?: HudFrameVariation;
|
|
105
|
+
/** Slot tile size in px, forwarded to {@link Paperdoll}. */
|
|
106
|
+
slotSize?: number;
|
|
107
|
+
/** Fired with the equip slot `id` on activate. */
|
|
108
|
+
onSlotActivate?: (id: string) => void;
|
|
109
|
+
className?: string;
|
|
110
|
+
style?: CSSProperties;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Drop-in character sheet: a {@link HudFrame} wrapping an optional name/subtitle/portrait header, the
|
|
114
|
+
* equip-slot {@link Paperdoll}, and the derived-stat {@link StatList}. The game supplies slots, stats,
|
|
115
|
+
* header content, and the activation callback, and owns the window that toggles it (this component does
|
|
116
|
+
* not manage its own open/close). Pure composition over existing primitives and HudTheme tokens.
|
|
117
|
+
*
|
|
118
|
+
* @capability character-sheet drop-in paperdoll + derived-stat sheet in a HudFrame — game supplies slots/stats
|
|
119
|
+
*/
|
|
120
|
+
export declare function CharacterSheet({ slots, stats, name, subtitle, portrait, header, title, variation, slotSize, onSlotActivate, className, style, }: CharacterSheetProps): ReactNode;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import {} from "react";
|
|
3
|
+
import { EquipmentSlots } from "./slots.js";
|
|
4
|
+
import { HudFrame } from "./hudFrame.js";
|
|
5
|
+
const DEFAULT_ARRANGEMENT = [
|
|
6
|
+
{ id: "head", label: "Head", position: "left" },
|
|
7
|
+
{ id: "chest", label: "Chest", position: "left" },
|
|
8
|
+
{ id: "hands", label: "Hands", position: "left" },
|
|
9
|
+
{ id: "legs", label: "Legs", position: "left" },
|
|
10
|
+
{ id: "feet", label: "Feet", position: "left" },
|
|
11
|
+
{ id: "mainHand", label: "Main Hand", position: "right" },
|
|
12
|
+
{ id: "offHand", label: "Off Hand", position: "right" },
|
|
13
|
+
{ id: "ring1", label: "Ring", position: "right" },
|
|
14
|
+
{ id: "ring2", label: "Ring", position: "right" },
|
|
15
|
+
{ id: "trinket", label: "Trinket", position: "right" },
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* A sensible default paperdoll arrangement — worn armor down the left flank, held gear and jewelry
|
|
19
|
+
* down the right — that the game fills by passing worn items keyed by slot id. Every id is optional;
|
|
20
|
+
* unfilled slots render empty. Games that need a different slot set pass their own {@link EquipSlotLayout}.
|
|
21
|
+
*/
|
|
22
|
+
export function defaultEquipLayout(items = {}) {
|
|
23
|
+
return DEFAULT_ARRANGEMENT.map((slot) => ({
|
|
24
|
+
id: slot.id,
|
|
25
|
+
label: slot.label,
|
|
26
|
+
item: items[slot.id] ?? null,
|
|
27
|
+
position: slot.position,
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
function SlotLabel({ children }) {
|
|
31
|
+
return (_jsx("span", { style: {
|
|
32
|
+
fontSize: 9,
|
|
33
|
+
letterSpacing: 0.6,
|
|
34
|
+
fontVariant: "small-caps",
|
|
35
|
+
textTransform: "lowercase",
|
|
36
|
+
color: "var(--jg-bar-text, #f4f6fb)",
|
|
37
|
+
opacity: 0.62,
|
|
38
|
+
whiteSpace: "nowrap",
|
|
39
|
+
}, children: children }));
|
|
40
|
+
}
|
|
41
|
+
function EquipSlotCell({ slot, slotSize, align, onSlotActivate, }) {
|
|
42
|
+
const interactive = onSlotActivate !== undefined;
|
|
43
|
+
const activate = interactive ? () => onSlotActivate(slot.id) : undefined;
|
|
44
|
+
return (_jsxs("div", { "data-equip-slot": slot.id, role: "group", "aria-label": slot.label, tabIndex: interactive ? 0 : undefined, ...(activate === undefined ? {} : { onClick: activate }), ...(activate === undefined
|
|
45
|
+
? {}
|
|
46
|
+
: {
|
|
47
|
+
onKeyDown: (event) => {
|
|
48
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
activate();
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
}), style: {
|
|
54
|
+
display: "flex",
|
|
55
|
+
flexDirection: "column",
|
|
56
|
+
alignItems: align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start",
|
|
57
|
+
gap: 3,
|
|
58
|
+
cursor: interactive ? "pointer" : undefined,
|
|
59
|
+
}, children: [_jsx(EquipmentSlots, { items: [slot.item], size: slotSize }), _jsx(SlotLabel, { children: slot.label })] }));
|
|
60
|
+
}
|
|
61
|
+
function Silhouette({ size }) {
|
|
62
|
+
return (_jsx("div", { "data-paperdoll-silhouette": "", "aria-hidden": true, style: {
|
|
63
|
+
width: size,
|
|
64
|
+
height: Math.round(size * 2.6),
|
|
65
|
+
borderRadius: "var(--jg-slot-radius, 9px)",
|
|
66
|
+
background: "var(--jg-slot-bg, rgba(12,14,20,0.5))",
|
|
67
|
+
border: "var(--jg-slot-border, 1px solid rgba(255,255,255,0.1))",
|
|
68
|
+
boxShadow: "inset 0 2px 10px rgba(0,0,0,0.5)",
|
|
69
|
+
display: "flex",
|
|
70
|
+
alignItems: "center",
|
|
71
|
+
justifyContent: "center",
|
|
72
|
+
color: "var(--jg-bar-text, #f4f6fb)",
|
|
73
|
+
opacity: 0.28,
|
|
74
|
+
fontSize: Math.round(size * 0.9),
|
|
75
|
+
boxSizing: "border-box",
|
|
76
|
+
}, children: _jsx("span", { "aria-hidden": true, children: "\u265F" }) }));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Arranges caller-supplied equip slots as a paperdoll — worn gear in two flanking columns around a
|
|
80
|
+
* center `portrait` (or a silhouette placeholder), with any `bottom` slots in a row beneath it. Each
|
|
81
|
+
* slot reuses the atomic {@link EquipmentSlots} tile (filled icon or themed empty placeholder), carries
|
|
82
|
+
* a label, and is a focusable `role="group"` with an `aria-label`; `onSlotActivate` fires on
|
|
83
|
+
* click/Enter/Space so games can wire drag or an equip picker. Composition and skin stay the game's.
|
|
84
|
+
*
|
|
85
|
+
* @capability character-paperdoll equip-slot paperdoll — flanking columns around a portrait, activatable slots
|
|
86
|
+
*/
|
|
87
|
+
export function Paperdoll({ slots, portrait, slotSize = 44, onSlotActivate, className, style }) {
|
|
88
|
+
const left = slots.filter((s) => (s.position ?? "left") === "left");
|
|
89
|
+
const right = slots.filter((s) => s.position === "right");
|
|
90
|
+
const bottom = slots.filter((s) => s.position === "bottom");
|
|
91
|
+
const column = (cells, align) => (_jsx("div", { style: { display: "flex", flexDirection: "column", gap: 10, alignItems: align === "right" ? "flex-end" : "flex-start" }, children: cells.map((slot) => (_jsx(EquipSlotCell, { slot: slot, slotSize: slotSize, align: align, ...(onSlotActivate === undefined ? {} : { onSlotActivate }) }, slot.id))) }));
|
|
92
|
+
return (_jsxs("div", { "data-paperdoll": "", className: className, style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 12, ...style }, children: [_jsxs("div", { style: { display: "flex", gap: 16, alignItems: "flex-start", justifyContent: "center" }, children: [column(left, "right"), _jsx("div", { "data-paperdoll-portrait": "", style: { display: "flex", alignItems: "center", justifyContent: "center", alignSelf: "stretch" }, children: portrait ?? _jsx(Silhouette, { size: slotSize * 1.4 }) }), column(right, "left")] }), bottom.length > 0 ? (_jsx("div", { style: { display: "flex", gap: 12, justifyContent: "center" }, children: bottom.map((slot) => (_jsx(EquipSlotCell, { slot: slot, slotSize: slotSize, align: "center", ...(onSlotActivate === undefined ? {} : { onSlotActivate }) }, slot.id))) })) : null] }));
|
|
93
|
+
}
|
|
94
|
+
function StatListRow({ row }) {
|
|
95
|
+
return (_jsxs("div", { "data-stat-row": row.id, style: { display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 12, padding: "2px 0" }, children: [_jsx("span", { style: { fontSize: 11, fontVariant: "small-caps", letterSpacing: 0.5, color: "var(--jg-bar-text, #f4f6fb)", opacity: 0.72 }, children: row.label }), _jsxs("span", { style: { fontSize: 12, fontWeight: 700, fontVariantNumeric: "tabular-nums", color: "var(--jg-accent, #38bdf8)" }, children: [row.value, row.hint !== undefined ? (_jsx("span", { style: { marginLeft: 5, fontWeight: 500, color: "var(--jg-bar-text, #f4f6fb)", opacity: 0.5 }, children: row.hint })) : null] })] }));
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Renders derived character stats as clean small-caps label→value rows, token-themed. Rows carrying a
|
|
99
|
+
* `group` field are bucketed under a small uppercase header (consecutive same-group rows share one).
|
|
100
|
+
* Pure and prop-driven — the game supplies the rows (from `useEntityStat`/derived values); the engine
|
|
101
|
+
* ships no stat list of its own.
|
|
102
|
+
*
|
|
103
|
+
* @capability character-statlist derived-stat readout — grouped small-caps label→value rows, token-themed
|
|
104
|
+
*/
|
|
105
|
+
export function StatList({ stats, className, style }) {
|
|
106
|
+
const groups = [];
|
|
107
|
+
for (const row of stats) {
|
|
108
|
+
const last = groups[groups.length - 1];
|
|
109
|
+
if (last !== undefined && last.name === row.group)
|
|
110
|
+
last.rows.push(row);
|
|
111
|
+
else
|
|
112
|
+
groups.push({ name: row.group, rows: [row] });
|
|
113
|
+
}
|
|
114
|
+
return (_jsx("div", { "data-stat-list": "", className: className, style: { display: "flex", flexDirection: "column", gap: 4, ...style }, children: groups.map((group, index) => (_jsxs("div", { "data-stat-group": group.name ?? "", children: [group.name !== undefined ? (_jsx("div", { style: {
|
|
115
|
+
fontSize: 9,
|
|
116
|
+
letterSpacing: 1.4,
|
|
117
|
+
textTransform: "uppercase",
|
|
118
|
+
opacity: 0.55,
|
|
119
|
+
margin: "6px 0 2px",
|
|
120
|
+
color: "var(--jg-bar-text, #f4f6fb)",
|
|
121
|
+
}, children: group.name })) : null, group.rows.map((row) => (_jsx(StatListRow, { row: row }, row.id)))] }, index))) }));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Drop-in character sheet: a {@link HudFrame} wrapping an optional name/subtitle/portrait header, the
|
|
125
|
+
* equip-slot {@link Paperdoll}, and the derived-stat {@link StatList}. The game supplies slots, stats,
|
|
126
|
+
* header content, and the activation callback, and owns the window that toggles it (this component does
|
|
127
|
+
* not manage its own open/close). Pure composition over existing primitives and HudTheme tokens.
|
|
128
|
+
*
|
|
129
|
+
* @capability character-sheet drop-in paperdoll + derived-stat sheet in a HudFrame — game supplies slots/stats
|
|
130
|
+
*/
|
|
131
|
+
export function CharacterSheet({ slots, stats, name, subtitle, portrait, header, title = "Character", variation = "themed", slotSize, onSlotActivate, className, style, }) {
|
|
132
|
+
const hasHeader = header !== undefined || name !== undefined || subtitle !== undefined;
|
|
133
|
+
return (_jsx(HudFrame, { variation: variation, title: title, interactive: true, padding: 14, ...(className === undefined ? {} : { className }), ...(style === undefined ? {} : { style }), children: _jsxs("div", { "data-character-sheet": "", style: { display: "flex", flexDirection: "column", gap: 12, minWidth: 260 }, children: [hasHeader ? (_jsx("div", { "data-character-header": "", style: { display: "flex", flexDirection: "column", gap: 1 }, children: header ?? (_jsxs(_Fragment, { children: [name !== undefined ? _jsx("div", { style: { fontSize: 16, fontWeight: 800, letterSpacing: 0.2 }, children: name }) : null, subtitle !== undefined ? (_jsx("div", { style: { fontSize: 11, opacity: 0.65, fontVariant: "small-caps", letterSpacing: 0.4 }, children: subtitle })) : null] })) })) : null, _jsxs("div", { style: { display: "flex", gap: 18, alignItems: "flex-start", flexWrap: "wrap" }, children: [_jsx(Paperdoll, { slots: slots, ...(portrait === undefined ? {} : { portrait }), ...(slotSize === undefined ? {} : { slotSize }), ...(onSlotActivate === undefined ? {} : { onSlotActivate }) }), _jsx(StatList, { stats: stats, style: { flex: "1 1 140px", minWidth: 140 } })] })] }) }));
|
|
134
|
+
}
|
package/dist/hooks.d.ts
CHANGED
|
@@ -55,6 +55,17 @@ export declare function useNearestWorldItem(radius: number): WorldItemRecord | n
|
|
|
55
55
|
export declare function useEntityStat(instanceId: string, statId: string): StatValue | null;
|
|
56
56
|
export declare function useTarget(fromInstanceId: string): string | null;
|
|
57
57
|
export declare function useInventory(inventoryId: string): readonly InventorySlot[];
|
|
58
|
+
/** Live slots plus `move`/`split` actions bound to `inventoryId`, routed through the notifying `inventory.move`/`inventory.split` commands so React re-renders. */
|
|
59
|
+
export interface InventoryGridBinding {
|
|
60
|
+
slots: readonly InventorySlot[];
|
|
61
|
+
move(from: number, to: number): void;
|
|
62
|
+
split(slot: number, amount: number, toSlot?: number): void;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Binds a live inventory to a HUD grid: `slots` from {@link useInventory}, and `move`/`split` that run
|
|
66
|
+
* the built-in `inventory.move`/`inventory.split` commands (which notify, so the grid re-renders).
|
|
67
|
+
*/
|
|
68
|
+
export declare function useInventoryGrid(inventoryId: string): InventoryGridBinding;
|
|
58
69
|
export declare function useCurrency(currencyId: string): number;
|
|
59
70
|
export declare function useFeed({ action, limit }: {
|
|
60
71
|
action: string;
|
package/dist/hooks.js
CHANGED
|
@@ -87,6 +87,21 @@ export function useTarget(fromInstanceId) {
|
|
|
87
87
|
export function useInventory(inventoryId) {
|
|
88
88
|
return useGameStore((ctx) => ctx.player.inventory.state(inventoryId).slots);
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Binds a live inventory to a HUD grid: `slots` from {@link useInventory}, and `move`/`split` that run
|
|
92
|
+
* the built-in `inventory.move`/`inventory.split` commands (which notify, so the grid re-renders).
|
|
93
|
+
*/
|
|
94
|
+
export function useInventoryGrid(inventoryId) {
|
|
95
|
+
const slots = useInventory(inventoryId);
|
|
96
|
+
const { commands } = useGame();
|
|
97
|
+
const move = useCallback((from, to) => {
|
|
98
|
+
commands.run("inventory.move", { inventoryId, from, to });
|
|
99
|
+
}, [commands, inventoryId]);
|
|
100
|
+
const split = useCallback((slot, amount, toSlot) => {
|
|
101
|
+
commands.run("inventory.split", toSlot === undefined ? { inventoryId, slot, amount } : { inventoryId, slot, amount, toSlot });
|
|
102
|
+
}, [commands, inventoryId]);
|
|
103
|
+
return useMemo(() => ({ slots, move, split }), [slots, move, split]);
|
|
104
|
+
}
|
|
90
105
|
export function useCurrency(currencyId) {
|
|
91
106
|
return useGameStore((ctx) => ctx.game.economy.balance(ctx.player.userId, currencyId));
|
|
92
107
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,9 @@ export * from "./barsPreview.js";
|
|
|
15
15
|
export * from "./iconTreatment.js";
|
|
16
16
|
export * from "./iconsPreview.js";
|
|
17
17
|
export * from "./slots.js";
|
|
18
|
+
export * from "./inventoryGrid.js";
|
|
19
|
+
export * from "./panels.js";
|
|
20
|
+
export * from "./characterSheet.js";
|
|
18
21
|
export * from "./hudTheme.js";
|
|
19
22
|
export * from "./hudThemePreview.js";
|
|
20
23
|
export * from "./hudFrame.js";
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,9 @@ export * from "./barsPreview.js";
|
|
|
15
15
|
export * from "./iconTreatment.js";
|
|
16
16
|
export * from "./iconsPreview.js";
|
|
17
17
|
export * from "./slots.js";
|
|
18
|
+
export * from "./inventoryGrid.js";
|
|
19
|
+
export * from "./panels.js";
|
|
20
|
+
export * from "./characterSheet.js";
|
|
18
21
|
export * from "./hudTheme.js";
|
|
19
22
|
export * from "./hudThemePreview.js";
|
|
20
23
|
export * from "./hudFrame.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;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
3
|
+
import { moveGridFocus } from "@jgengine/core/ui/actionModel";
|
|
4
|
+
import { DragGhost, DraggableCard, DropZone, useDragLayer } from "./dragLayer.js";
|
|
5
|
+
import { iconForItemId } from "./gameIcons.js";
|
|
6
|
+
import { IconTreatment, schoolForItem } from "./iconTreatment.js";
|
|
7
|
+
import { useInventoryGrid } from "./hooks.js";
|
|
8
|
+
/**
|
|
9
|
+
* A drop-in NxN inventory grid bound to a live inventory: `<InventoryGrid inventoryId="bag" columns={4} />`
|
|
10
|
+
* renders every slot (painted {@link IconTreatment} tile + count badge for filled, a themed `--jg-slot-*`
|
|
11
|
+
* placeholder for empty), and wires the real mechanics on top of the engine's inventory model —
|
|
12
|
+
* drag-to-move, swap, stack-merge with remainder (all via the `inventory.move` command), and stack-split
|
|
13
|
+
* (shift/right-click a stack, via `inventory.split`). Fully keyboard-accessible: `role="grid"`, arrow-key
|
|
14
|
+
* roving focus, Enter/Space to pick up and drop. Games own placement, skin, and terminology; the engine
|
|
15
|
+
* ships the working grid.
|
|
16
|
+
*
|
|
17
|
+
* @capability inventory-grid-hud drop-in NxN inventory grid with drag/swap/stack/split bound to a live inventory
|
|
18
|
+
*/
|
|
19
|
+
const ARROW_DIRECTIONS = {
|
|
20
|
+
ArrowRight: "right",
|
|
21
|
+
ArrowLeft: "left",
|
|
22
|
+
ArrowUp: "up",
|
|
23
|
+
ArrowDown: "down",
|
|
24
|
+
Home: "first",
|
|
25
|
+
End: "last",
|
|
26
|
+
};
|
|
27
|
+
function EmptySlot({ size, picked }) {
|
|
28
|
+
return (_jsx("div", { "data-slot-empty": "", style: {
|
|
29
|
+
width: size,
|
|
30
|
+
height: size,
|
|
31
|
+
borderRadius: "var(--jg-slot-radius, 9px)",
|
|
32
|
+
background: "var(--jg-slot-bg, rgba(12,14,20,0.5))",
|
|
33
|
+
border: picked
|
|
34
|
+
? "1px solid var(--jg-accent, #38bdf8)"
|
|
35
|
+
: "var(--jg-slot-border, 1px solid rgba(255,255,255,0.1))",
|
|
36
|
+
boxShadow: "inset 0 2px 6px rgba(0,0,0,0.45)",
|
|
37
|
+
boxSizing: "border-box",
|
|
38
|
+
} }));
|
|
39
|
+
}
|
|
40
|
+
/** A drop-in inventory grid with drag/swap/stack-merge/split and full keyboard control, bound to `inventoryId`. */
|
|
41
|
+
export function InventoryGrid({ inventoryId, columns, size = 44, gap = 6, itemIcon, onActivate, className, style, }) {
|
|
42
|
+
const { slots, move, split } = useInventoryGrid(inventoryId);
|
|
43
|
+
const cols = columns ?? Math.max(1, Math.ceil(Math.sqrt(slots.length)));
|
|
44
|
+
const [focus, setFocus] = useState(0);
|
|
45
|
+
const [picked, setPicked] = useState(null);
|
|
46
|
+
const cellRefs = useRef(new Map());
|
|
47
|
+
const layer = useDragLayer({
|
|
48
|
+
onDrop: (info) => {
|
|
49
|
+
const target = info.target === null ? null : Number(info.target);
|
|
50
|
+
if (target !== null && Number.isInteger(target) && target !== info.payload.value) {
|
|
51
|
+
move(info.payload.value, target);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (focus < slots.length)
|
|
57
|
+
cellRefs.current.get(focus)?.focus();
|
|
58
|
+
}, [focus, slots.length]);
|
|
59
|
+
const splitHalf = useCallback((index) => {
|
|
60
|
+
const slot = slots[index];
|
|
61
|
+
if (slot !== null && slot !== undefined && slot.count > 1)
|
|
62
|
+
split(index, Math.floor(slot.count / 2));
|
|
63
|
+
}, [slots, split]);
|
|
64
|
+
const onKeyDown = useCallback((event) => {
|
|
65
|
+
const dir = ARROW_DIRECTIONS[event.key];
|
|
66
|
+
if (dir !== undefined) {
|
|
67
|
+
const next = moveGridFocus(focus, slots.length, cols, dir);
|
|
68
|
+
if (next >= 0) {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
setFocus(next);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") {
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
if (picked === null) {
|
|
77
|
+
if (slots[focus] !== null && slots[focus] !== undefined)
|
|
78
|
+
setPicked(focus);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
if (picked !== focus)
|
|
82
|
+
move(picked, focus);
|
|
83
|
+
setPicked(null);
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (event.key === "Escape" && picked !== null) {
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
setPicked(null);
|
|
90
|
+
}
|
|
91
|
+
}, [focus, slots, cols, picked, move]);
|
|
92
|
+
const rows = useMemo(() => {
|
|
93
|
+
const out = [];
|
|
94
|
+
for (let i = 0; i < slots.length; i += cols) {
|
|
95
|
+
out.push(Array.from({ length: Math.min(cols, slots.length - i) }, (_, k) => i + k));
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}, [slots.length, cols]);
|
|
99
|
+
const renderTile = (index) => {
|
|
100
|
+
const slot = slots[index] ?? null;
|
|
101
|
+
const isPicked = picked === index;
|
|
102
|
+
const label = slot === null
|
|
103
|
+
? `Empty slot ${index + 1}`
|
|
104
|
+
: `${slot.itemId}${slot.count > 1 ? ` (${slot.count})` : ""}, slot ${index + 1}`;
|
|
105
|
+
const custom = slot !== null ? itemIcon?.(slot.itemId) : undefined;
|
|
106
|
+
const named = slot !== null ? iconForItemId(slot.itemId) : null;
|
|
107
|
+
return (_jsx(DropZone, { id: String(index), layer: layer, children: _jsx("div", { role: "gridcell", "aria-label": label, "aria-selected": isPicked, tabIndex: focus === index ? 0 : -1, "data-slot": index, ref: (node) => {
|
|
108
|
+
if (node === null)
|
|
109
|
+
cellRefs.current.delete(index);
|
|
110
|
+
else
|
|
111
|
+
cellRefs.current.set(index, node);
|
|
112
|
+
}, onFocus: () => setFocus(index), onKeyDown: onKeyDown, onDoubleClick: () => {
|
|
113
|
+
if (slot !== null)
|
|
114
|
+
onActivate?.(index);
|
|
115
|
+
}, onClick: (event) => {
|
|
116
|
+
if (event.shiftKey) {
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
splitHalf(index);
|
|
119
|
+
}
|
|
120
|
+
}, onContextMenu: (event) => {
|
|
121
|
+
if (slot !== null && slot.count > 1) {
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
splitHalf(index);
|
|
124
|
+
}
|
|
125
|
+
}, style: { width: size, height: size, position: "relative", cursor: slot !== null ? "grab" : "default" }, children: slot !== null ? (_jsx(DraggableCard, { id: String(index), value: index, layer: layer, onRotate: false, children: custom !== undefined && custom !== null ? (_jsx(IconTreatment, { glyph: custom, size: size, count: slot.count, active: isPicked })) : (_jsx(IconTreatment, { ...(named === null ? {} : { icon: named }), school: schoolForItem(slot.itemId), size: size, count: slot.count, active: isPicked })) })) : (_jsx(EmptySlot, { size: size, picked: isPicked })) }) }, index));
|
|
126
|
+
};
|
|
127
|
+
return (_jsxs("div", { role: "grid", "aria-label": `${inventoryId} inventory`, "aria-colcount": cols, className: className, "data-inventory-grid": inventoryId, style: { display: "grid", gap, ...style }, children: [rows.map((row, r) => (_jsx("div", { role: "row", style: { display: "grid", gridTemplateColumns: `repeat(${cols}, ${size}px)`, gap }, children: row.map(renderTile) }, r))), _jsx(DragGhost, { layer: layer, children: (payload) => {
|
|
128
|
+
const slot = slots[payload.value] ?? null;
|
|
129
|
+
if (slot === null)
|
|
130
|
+
return null;
|
|
131
|
+
const named = iconForItemId(slot.itemId);
|
|
132
|
+
const custom = itemIcon?.(slot.itemId);
|
|
133
|
+
return custom !== undefined && custom !== null ? (_jsx(IconTreatment, { glyph: custom, size: size, count: slot.count })) : (_jsx(IconTreatment, { ...(named === null ? {} : { icon: named }), school: schoolForItem(slot.itemId), size: size, count: slot.count }));
|
|
134
|
+
} })] }));
|
|
135
|
+
}
|
package/dist/panels.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
import { type PanelDef, type PanelPosition, type PanelState } from "@jgengine/core/ui/panelModel";
|
|
3
|
+
import { type HudFrameShape, type HudFrameVariation } from "./hudFrame.js";
|
|
4
|
+
/**
|
|
5
|
+
* React chrome over the headless panel/window model (`@jgengine/core/ui/panelModel`) — the toggleable,
|
|
6
|
+
* draggable, z-stacked windows a WoW-style UI is made of (B for bag, C for character, ESC to close).
|
|
7
|
+
* `usePanels` is the DATA/HOOK layer (open/close/toggle/focus + a keybind listener), `PanelHost` renders
|
|
8
|
+
* the open windows in z-order, and `Window` is a standalone one-off window. All unskinned and
|
|
9
|
+
* token-driven: games reskin via `HudTheme` tokens and by swapping the `HudFrame` variation.
|
|
10
|
+
*/
|
|
11
|
+
/** Intent a key press resolves to over a panel set: toggle a panel, close the top window, or ignore. */
|
|
12
|
+
export interface PanelKeyResult {
|
|
13
|
+
handled: boolean;
|
|
14
|
+
type?: "toggle" | "closeTop";
|
|
15
|
+
id?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Pure keybind resolver for a panel set: ESC resolves to `closeTop` when a closable window is open, and
|
|
19
|
+
* any other key routes through {@link panelByHotkey} (trying `code` then `key`) to a `toggle`. Returns
|
|
20
|
+
* the intent without touching the DOM, so it unit-tests headless and `usePanels` is a thin shell over it.
|
|
21
|
+
*/
|
|
22
|
+
export declare function panelKeyAction(defs: readonly PanelDef[], state: PanelState, event: {
|
|
23
|
+
code?: string;
|
|
24
|
+
key?: string;
|
|
25
|
+
}): PanelKeyResult;
|
|
26
|
+
/** Options for {@link usePanels}. */
|
|
27
|
+
export interface UsePanelsOptions {
|
|
28
|
+
/** Fired when a panel transitions from closed to open (not on refocus of an already-open panel). */
|
|
29
|
+
onOpen?: (id: string) => void;
|
|
30
|
+
/** Fired when an open panel is closed. */
|
|
31
|
+
onClose?: (id: string) => void;
|
|
32
|
+
/** Set false to skip installing the window keybind/ESC listener (default true). */
|
|
33
|
+
hotkeys?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** The live panel manager returned by {@link usePanels} and consumed by {@link PanelHost}. */
|
|
36
|
+
export interface PanelsManager {
|
|
37
|
+
state: PanelState;
|
|
38
|
+
defs: readonly PanelDef[];
|
|
39
|
+
isOpen: (id: string) => boolean;
|
|
40
|
+
open: (id: string) => void;
|
|
41
|
+
close: (id: string) => void;
|
|
42
|
+
toggle: (id: string) => void;
|
|
43
|
+
focus: (id: string) => void;
|
|
44
|
+
move: (id: string, pos: PanelPosition) => void;
|
|
45
|
+
byHotkey: (code: string) => string | null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The DATA/HOOK layer: wrap the core panel model in state and install a window keybind listener —
|
|
49
|
+
* per-panel hotkeys toggle their window and ESC closes the topmost closable one. Hotkeys are ignored
|
|
50
|
+
* while the player is typing in an input/textarea/contenteditable. The listener is cleaned up on
|
|
51
|
+
* unmount and SSR-safe (armed in an effect). Feed the returned manager to {@link PanelHost}, or drive a
|
|
52
|
+
* bespoke window layout from it directly.
|
|
53
|
+
*
|
|
54
|
+
* @capability use-panels headless toggleable-window manager with keybind + ESC handling over the core panel model
|
|
55
|
+
*/
|
|
56
|
+
export declare function usePanels(defs: readonly PanelDef[], options?: UsePanelsOptions): PanelsManager;
|
|
57
|
+
/**
|
|
58
|
+
* Renders the open panels of a {@link usePanels} manager as absolutely-positioned, draggable, closable
|
|
59
|
+
* windows in z-order — the WoW window layer. Each window uses {@link HudFrame} chrome, is dragged by its
|
|
60
|
+
* title bar, raises focus on pointer-down, and gets its content from the `render` prop (or a `panels`
|
|
61
|
+
* map). Accessible: every window is a `role="dialog"` with an `aria-label` and a focusable close button;
|
|
62
|
+
* ESC-close is wired by the manager. Unskinned — art-direct via `HudTheme` tokens and the `variation`.
|
|
63
|
+
*
|
|
64
|
+
* @capability panel-host render a manager's open windows as draggable, closable, z-stacked dialogs
|
|
65
|
+
*/
|
|
66
|
+
export declare function PanelHost({ manager, render, panels, variation, shape, width, className, windowClassName, windowStyle, bodyStyle, }: {
|
|
67
|
+
manager: PanelsManager;
|
|
68
|
+
/** Content for a panel by id. Takes precedence over `panels`. */
|
|
69
|
+
render?: (id: string) => ReactNode;
|
|
70
|
+
/** Static content map by panel id, used when `render` is omitted. */
|
|
71
|
+
panels?: Record<string, ReactNode>;
|
|
72
|
+
variation?: HudFrameVariation;
|
|
73
|
+
shape?: HudFrameShape;
|
|
74
|
+
/** Default window width (per-panel unless overridden by the panel content). */
|
|
75
|
+
width?: number | string;
|
|
76
|
+
className?: string;
|
|
77
|
+
windowClassName?: string;
|
|
78
|
+
windowStyle?: CSSProperties;
|
|
79
|
+
bodyStyle?: CSSProperties;
|
|
80
|
+
}): import("react").JSX.Element;
|
|
81
|
+
/**
|
|
82
|
+
* A single standalone window — title bar, close button, and title-bar drag over {@link HudFrame} chrome —
|
|
83
|
+
* usable without the {@link usePanels} manager for a one-off dialog. Position is uncontrolled (seeded by
|
|
84
|
+
* `x`/`y`) unless both `x`/`y` and `onMove` are supplied, in which case the caller owns placement.
|
|
85
|
+
* Accessible: `role="dialog"` with an `aria-label` and a focusable close button.
|
|
86
|
+
*
|
|
87
|
+
* @capability window standalone draggable, closable window primitive over HudFrame — no manager required
|
|
88
|
+
*/
|
|
89
|
+
export declare function Window({ title, ariaLabel, closable, onClose, x, y, onMove, z, width, variation, shape, className, style, bodyStyle, children, }: {
|
|
90
|
+
title: ReactNode;
|
|
91
|
+
ariaLabel?: string;
|
|
92
|
+
closable?: boolean;
|
|
93
|
+
onClose?: () => void;
|
|
94
|
+
/** Initial (uncontrolled) or current (controlled, with `onMove`) x position. Default 56. */
|
|
95
|
+
x?: number;
|
|
96
|
+
/** Initial (uncontrolled) or current (controlled, with `onMove`) y position. Default 56. */
|
|
97
|
+
y?: number;
|
|
98
|
+
onMove?: (pos: PanelPosition) => void;
|
|
99
|
+
z?: number;
|
|
100
|
+
width?: number | string;
|
|
101
|
+
variation?: HudFrameVariation;
|
|
102
|
+
shape?: HudFrameShape;
|
|
103
|
+
className?: string;
|
|
104
|
+
style?: CSSProperties;
|
|
105
|
+
bodyStyle?: CSSProperties;
|
|
106
|
+
children?: ReactNode;
|
|
107
|
+
}): import("react").JSX.Element;
|