mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-14 08:11:03 +00:00
4051af58a7
The rail gave its labels 48px and let them break anywhere, so Elements read Element and s, and nine languages broke a word at the default text size. Labels wrap only between words now, and the rail grows to its longest word from 64px.
78 lines
2.6 KiB
TypeScript
78 lines
2.6 KiB
TypeScript
import { Fragment, useRef, type KeyboardEvent } from 'react';
|
|
|
|
import '../../../styles/studio-panels.css';
|
|
import { rovingTarget } from '../studioRoving';
|
|
import { RAIL_IDS, RAIL_SECTIONS, type RailSection } from './railSections';
|
|
|
|
/**
|
|
* The rail down the side of Studio: one tab per section, each an icon over its
|
|
* name.
|
|
*
|
|
* A list of tabs to a screen reader and to the keyboard, as it is to the eye:
|
|
* Tab reaches the open section, the arrows move up and down the rail, Home and
|
|
* End jump to its ends, and Enter or Space opens a section (studioRoving.ts).
|
|
* A name longer than the rail is wide wraps onto a second line, only ever
|
|
* between words: where one word alone is wider, the rail grows to fit it
|
|
* (studio.css) instead of cutting it. The title carries the name whole.
|
|
*/
|
|
export function StudioRail({
|
|
section,
|
|
onSection,
|
|
t,
|
|
}: {
|
|
section: RailSection;
|
|
onSection: (id: RailSection) => void;
|
|
t: (k: string) => string;
|
|
}) {
|
|
const tabs = useRef<(HTMLButtonElement | null)[]>([]);
|
|
const onKeyDown = (e: KeyboardEvent<HTMLButtonElement>, index: number) => {
|
|
const next = rovingTarget(e.key, index, RAIL_SECTIONS.length, { vertical: true, rtl: false });
|
|
if (next === null) return;
|
|
e.preventDefault();
|
|
tabs.current[next]?.focus();
|
|
};
|
|
|
|
return (
|
|
<div className="st-rail">
|
|
<div
|
|
className="st-rail-tabs"
|
|
role="tablist"
|
|
aria-orientation="vertical"
|
|
aria-label={t('journey.studio.sections')}
|
|
>
|
|
{RAIL_SECTIONS.map((entry, i) => {
|
|
const on = entry.id === section;
|
|
const label = t(entry.labelKey);
|
|
const Icon = entry.icon;
|
|
return (
|
|
<Fragment key={entry.id}>
|
|
{entry.startsGroup && <span className="st-rail-sep" aria-hidden />}
|
|
<button
|
|
type="button"
|
|
role="tab"
|
|
id={RAIL_IDS.tab(entry.id)}
|
|
aria-selected={on}
|
|
aria-controls={RAIL_IDS.panel}
|
|
tabIndex={on ? 0 : -1}
|
|
className={`st-rail-btn ${on ? 'is-on' : ''}`}
|
|
ref={(el) => {
|
|
tabs.current[i] = el;
|
|
}}
|
|
onClick={() => onSection(entry.id)}
|
|
onKeyDown={(e) => onKeyDown(e, i)}
|
|
title={label}
|
|
aria-label={label}
|
|
>
|
|
<Icon size={18} strokeWidth={1.7} aria-hidden />
|
|
<span className="st-rail-label" aria-hidden>
|
|
{label}
|
|
</span>
|
|
</button>
|
|
</Fragment>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|