// Help Content View
// Purpose: Defines the Help Content View module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useMemo } from 'react';
import { formatKeyLabel } from '../../controls/keymapUtils.js';
import { getHelpContent } from '../../help/content.js';
function KeyPill({ actionId, keymap }) {
const value = keymap?.[actionId]?.[0] ?? '';
return (
{formatKeyLabel(value)}
);
}
function renderSegments(segments, keymap) {
return segments.map((segment, idx) => {
if (typeof segment === 'string') {
return {segment};
}
if (segment?.action) {
return ;
}
if (segment?.text) {
return {segment.text};
}
return null;
});
}
function renderLine(line, keymap) {
if (typeof line === 'string') return line;
if (Array.isArray(line?.segments)) return renderSegments(line.segments, keymap);
if (line && typeof line === 'object') {
// Fallback: render any stray object as text if possible
if (typeof line.text === 'string') return line.text;
if (Array.isArray(line)) return renderSegments(line, keymap);
}
return String(line ?? '');
}
function Hero({ hero, keymap }) {
if (!hero) return null;
return (
{hero.title}
{hero.subtitle &&
{hero.subtitle}
}
{hero.chips && (
{hero.chips.map((chip) => (
{chip}
))}
)}
{hero.bullets && (
{hero.bullets.map((line, idx) => {
const key = Array.isArray(line?.segments) ? `hero-${idx}` : `hero-${idx}`;
return (
-
{renderLine(line, keymap)}
);
})}
)}
);
}
function ListBlock({ block, keymap }) {
return (
{block.title}
{block.items.map((item, idx) => {
const key = `item-${idx}`;
return (
-
{renderLine(item, keymap)}
);
})}
);
}
function CalloutBlock({ block }) {
const toneClass = block.tone === 'info' ? 'border-cyan-500/40' : 'border-slate-700';
return (
{block.title}
{block.body && (
{block.body.map((line, idx) => (
-
{renderLine(line, {})}
))}
)}
);
}
function KeyboardGroup({ group, keymap }) {
return (
{group.title}
{group.items.map((item) => (
{/* A binding label may wrap in a slim sidebar, while the key remains
a compact complete token instead of forcing horizontal overflow. */}
{item.label}
))}
);
}
function KeyboardBlock({ block, keymap }) {
if (!block) return null;
return (
{/* Heading and footnote share a row when possible and wrap independently
when the Help card is mounted in a narrow desktop column. */}
{block.title}
{block.footnote && {block.footnote}}
{/* Two keyboard groups fit comfortably once the Help surface reaches 32rem.
Using the real content threshold restores the established old-page layout
without forcing two crushed columns inside the narrow new-drive sidebar. */}
{block.groups?.map((group) => (
))}
);
}
function GamepadBlock({ block }) {
if (!block) return null;
return (
{block.title}
{block.items?.map((line, idx) => (
-
{renderLine(line, {})}
))}
);
}
function BlockRenderer({ block, keymap }) {
if (!block) return null;
switch (block.type) {
case 'list':
return ;
case 'callout':
return ;
case 'keyboard':
return ;
case 'gamepad':
return ;
default:
return null;
}
}
export function HelpContentView({ layout, keymap }) {
const content = useMemo(() => getHelpContent(layout), [layout]);
const bindings = keymap || {};
const mainBlocks = content.main || [];
const asideBlocks = content.aside || [];
return (
{/* The previous fixed 360px minimum was wider than the new-drive sidebar
and forced the entire Help card to overflow. This intrinsic minimum
uses one full-width column when 22.5rem cannot physically fit, while
preserving the multi-column presentation in genuinely wide panels. */}
{mainBlocks.map((block, idx) => (
))}
{asideBlocks.map((block, idx) => (
))}
);
}
export default HelpContentView;