- The main destinations are always on screen: Campaigns, Campaign and Party (for the campaign last visited, from any page) and Account. They are links along the top bar on a wide screen, and a bar along the bottom on a phone or small tablet (900px or less). - Nothing floats over the page on a phone: the dice roller is the last button of the bottom bar, and quick stats sits in the top bar. - The campaign page and the character sheet start with a row of links to their sections. - On a small screen every table is a stack of labelled cards, rows of controls wrap, fields shrink to fit, and things to tap are bigger. - The encounter list is paged, ten to a page, as is the archived list. Moving an encounter still works across pages. - Text boxes with no type given, and native checkboxes and dropdowns, now match the dark theme. Long monster notes and item descriptions wrap inside their table cell. - All packages at version 1.10.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
37 lines
1.3 KiB
TypeScript
37 lines
1.3 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
|
|
// A row of links to the sections of a long page. It finds them itself: any
|
|
// element with a `data-jump="Label"` attribute that is on the page right now
|
|
// gets a link, so sections only some people see need no special handling.
|
|
export function JumpNav() {
|
|
const [labels, setLabels] = useState<string[]>([]);
|
|
|
|
// Sections come and go as data loads, so the page is watched for them.
|
|
useEffect(() => {
|
|
const look = () => {
|
|
const found = [...document.querySelectorAll<HTMLElement>('[data-jump]')].map((el) => el.dataset.jump ?? '');
|
|
setLabels((current) => (current.join('|') === found.join('|') ? current : found));
|
|
};
|
|
const observer = new MutationObserver(look);
|
|
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-jump'] });
|
|
look();
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
if (labels.length < 3) return null;
|
|
return (
|
|
<nav className="jump-nav" aria-label="Sections of this page">
|
|
{labels.map((label) => (
|
|
<button
|
|
key={label}
|
|
type="button"
|
|
className="jump-link"
|
|
onClick={() => document.querySelector(`[data-jump="${label}"]`)?.scrollIntoView({ behavior: 'smooth', block: 'start' })}
|
|
>
|
|
{label}
|
|
</button>
|
|
))}
|
|
</nav>
|
|
);
|
|
}
|