feat(help): traps/tokens/grid sections and clickable cross-links

Add instruction pages for traps, non-player tokens, and grid generator, and turn section mentions into in-modal navigation links.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-28 12:13:53 +08:00
parent f270812219
commit a2b418b78a
7 changed files with 289 additions and 16 deletions
@@ -1,9 +1,10 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { APP_DISPLAY_NAME_EN, APP_DISPLAY_NAME_RU } from '../../../shared/appBranding';
import { Button } from '../../shared/ui/controls';
import styles from '../EditorApp.module.css';
import { buildHelpLinkCatalog, splitHelpTextWithLinks } from '../help/helpLinkify';
import {
HELP_SECTION_IDS,
helpSectionBodyKey,
@@ -107,6 +108,8 @@ function InstructionsModalBody({
}) {
const { t } = useEditorI18n();
const [activeId, setActiveId] = useState<HelpSectionId>(initialSection);
const contentRef = useRef<HTMLDivElement | null>(null);
const navRef = useRef<HTMLElement | null>(null);
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
@@ -116,9 +119,24 @@ function InstructionsModalBody({
return () => window.removeEventListener('keydown', onKey);
}, [onClose]);
useEffect(() => {
contentRef.current?.scrollTo({ top: 0 });
const activeNav = navRef.current?.querySelector<HTMLElement>('[aria-current="true"]');
activeNav?.scrollIntoView({ block: 'nearest' });
}, [activeId]);
const linkCatalog = useMemo(
() => buildHelpLinkCatalog((id) => t(helpSectionTitleKey(id))),
[t],
);
const body = t(helpSectionBodyKey(activeId));
const paragraphs = body.split('\n\n').filter((p) => p.trim() !== '');
const goToSection = (id: HelpSectionId) => {
setActiveId(id);
};
return createPortal(
<>
<button
@@ -140,15 +158,28 @@ function InstructionsModalBody({
</button>
</div>
<div className={styles.instructionsLayout}>
<div className={styles.instructionsContent}>
<div ref={contentRef} className={styles.instructionsContent}>
<div className={styles.instructionsContentTitle}>{t(helpSectionTitleKey(activeId))}</div>
{paragraphs.map((p, i) => (
<p key={i} className={styles.instructionsParagraph}>
{p}
{splitHelpTextWithLinks(p, linkCatalog).map((part, j) =>
part.type === 'text' ? (
<React.Fragment key={j}>{part.value}</React.Fragment>
) : (
<button
key={j}
type="button"
className={styles.instructionsInlineLink}
onClick={() => goToSection(part.id)}
>
{part.value}
</button>
),
)}
</p>
))}
</div>
<nav className={styles.instructionsNav} aria-label={t('help.navAria')}>
<nav ref={navRef} className={styles.instructionsNav} aria-label={t('help.navAria')}>
{HELP_SECTION_IDS.map((id) => (
<button
key={id}
@@ -160,7 +191,7 @@ function InstructionsModalBody({
.filter(Boolean)
.join(' ')}
aria-current={id === activeId ? 'true' : undefined}
onClick={() => setActiveId(id)}
onClick={() => goToSection(id)}
>
{t(helpSectionTitleKey(id))}
</button>