Controls

The shell and site controls live: segmented switches, toggles, sliders, steppers, the install field and copy buttons, with their states and keys.

Updated

The shell's controls come from one small set in src/components/viewer: the tool button, the segmented switch built from tool buttons, the count that steps through items, the theme button and the install field. tokens.css owns their states, so a hover, a focus ring or a pressed frame looks the same in the toolbar, the index panel and a page head. The plates mount the real components. The toolbar, the install field and the panels read the shell's state through usePtShell, which throws outside ViewerShell, so those plates mount them inside a small provider in src/components/docs/demos/interactables/demo-shell.tsx that publishes the same state.

The toolbar

The plate mounts Toolbar over a stage of the brand deck's first twelve slides, with the index panel, the help card, the progress line and the toast that the shell draws around it. Every button acts: the count steps, the seg changes the view, Index slides the panel in, and Theme flips this page.

The bar measures its two groups against its own box and collapses labels in tiers until they fit. Tier one takes the words off Copy link and Fullscreen, tier two off Previous and Next, tier three off Theme, and tier four off Index, Help and Present while the Search field narrows to 120px. Route views shows the bar adapting to the views a route offers: with two views Present leaves, and with one view the seg leaves too. At 900px and below the bar is one 52px row of icon squares, and at 600px and below Previous, Next and Copy link leave.

The count is the stepper. Click it and type a number, and Enter goes there. Escape or a click away closes the field, and a number past the end says No slide 40 in the toast. Previous and Next clamp at the first and last item, and the slider under the plate drives the same active slide.

Import and props

import { Toolbar } from '@/components/viewer/Toolbar';

<Toolbar title='Brand deck' mark='gt' />;
PropTypeWhat it does
titlestringThe route's name, shown beside the mark while the list column is closed.
mark'gt' or 'pt'The mark beside the title.
slotnodeThe route's own controls, first in the right group.
modeLabelspartial record of view to wordThe route's own words for the seg, such as { slide: 'Live' }.

Everything else comes from the shell's state: the views, the key table, the item noun, the active item and the open panels. Progress takes an optional value from 0 to 1; on a paged route it fills to the active item's place as a scaleX transform over 160ms.

Keys

The plate binds these keys while focus is inside it. In the shell they act anywhere on the page, and Cmd K or Ctrl K opens the search; this page keeps Cmd K for its own search, so the plate opens it with the slash.

KeyAction
Right arrow, SpaceNext slide
Left arrow, Shift SpacePrevious slide
Home, EndFirst and last slide
G, BGrid view, book view
/Open the search
ROpen or close the index panel
?Open or close the keyboard shortcuts
DFlip the theme
EscBack one layer: the shortcuts, the index, then the first view

Reduced motion and themes

Under reduced motion every --pt-dur- token is 0ms, so the seg's indicator, the panel's slide, the progress fill and the toast land at once. Every color is a --pt- token, so the bar follows the theme.

The segmented switch

Seg is a ruled group of tool buttons with one active option, shown here in the three forms the shell uses: the toolbar's view switch, the index panel's set switch and the sidebar's icon-only density toggle. Slow stretches the indicator's move to 900ms so it can be watched.

One ink pill slides under the options. It sits 2px inside the active option with the chip corner, and its place and width move together over 180ms. The group measures the pill with a ResizeObserver, so the first measurement forces no layout; until then the active option fills itself, and a render without scripts still shows the choice. A click on the active option returns to the first option when the active one is not the first, as the deck viewer does. After a pick the option lets go of focus.

Import and props

import { Seg } from '@/components/viewer/Seg';

<Seg label='View' options={options} value={mode} onChange={setMode} />;
PropTypeWhat it does
options{ value, label, icon?, title }[]The options in order. title is the tooltip and names the key, as in Everything as a grid (G).
valuestringThe active option's value.
onChange(value) => voidCalled with the picked value, or with the first value on a click of the active option.
labelstringThe group's accessible name.
iconOnlybooleanIcon squares with the label as each option's accessible name.
classNamestringExtra classes; is-small is the sidebar's 28px form.

Keys

KeyAction
TabMoves to the next option; every option is in the tab order
Enter, SpacePicks the focused option

States

StateWhat draws it
RestThe option's text in --pt-ink-2
HoverA plate pill 2px inside the option
FocusA 1px ink ring on the option's pill; paper, 3px inside, on the active option
ActiveThe shared ink pill under paper text

Tool buttons

ToolButton is every button in the shell: a 32px button whose title names its key. With a label it is a text button; without one it is a 32px icon square, and its accessible name is the title without the key. Point at a button in the row or focus it, and the readout prints its tooltip and the name a screen reader hears. The specimen under it draws each state at rest from the shell's own hover and focus rules.

The tooltip is the native title attribute. A toggle carries aria-pressed; quiet keeps the attribute and drops the ink frame for a toggle whose state shows elsewhere, as the list toggle does while the list is open. solid is the one filled button, Present, which keeps the old nav's 8px corner and draws its play glyph after the word. A tool button has no disabled state: the shell leaves out a control that cannot act, and Previous and Next clamp at the ends.

The theme button is a tool button with a text glyph: ◐ in the light theme and ◑ in the dark. Its accessible name says where it goes next (Switch to light). The choice lives on html[data-theme], persists under gt-theme and reaches every same-origin frame on the page.

Import and props

import { ThemeButton } from '@/components/viewer/ThemeButton';
import { ToolButton } from '@/components/viewer/ToolButton';

<ToolButton icon='index' label='Index' title='Show or hide the index (R)' pressed={open} onClick={toggle} />;
<ThemeButton />;
PropTypeWhat it does
titlestringThe tooltip; names the key in parentheses, as in Dark or light (D).
onClick() => voidThe action.
iconicon nameA 16px Heroicons solid glyph from icons.tsx.
labelstringThe word; absent makes the button an icon square.
pressedbooleanSets aria-pressed and the ink frame; leave it out on a button that does not toggle.
quietbooleanaria-pressed without the ink frame.
solidbooleanThe filled call to action.
hideSmbooleanHidden at 900px and below.
ariaLabelstringThe accessible name, when the title without its key is not it.
childrennodeReplaces the icon, as the theme button's glyph does.

ThemeButton takes label (the word Theme, and the D key in the title) and className. The label is on unless a className is passed, so a className alone gives the direction navs' icon square.

Keys

KeyAction
TabMoves to the next button
Enter, SpacePresses the focused button

States

StateWhat draws it
Rest--pt-ink-2 text, no frame
HoverInk text and a 1px --pt-hair frame
FocusA 1px ink outline on the button's edge
PressedInk text and a 1px ink frame
SolidAn ink ground under paper text; hover drops the ground for ink text in the ink frame

Text color, ground and frame move over 120ms with ease-out, and nothing else on the button animates.

The install field

InstallField is the site's one copy control: the command in the mono face in a field at the control corner, with the copy button as the field's last segment. A command breaks only between its groups, and a flag keeps its value, so --project <dir> never splits. Width sets the field at the book head panel's 304px, a wider column or a 220px box.

A click on the button writes the command to the clipboard, says so in the shell's toast and shows the check glyph for 1.6 seconds. Without a clipboard the toast shows the command itself. One click on the command selects all of it for a manual copy. At 900px and below the copy segment is a 44px target.

Import and props

import { InstallField } from '@/components/viewer/InstallField';

<InstallField command='pnpm skills:install gt-voice --project <dir>' />;
PropTypeWhat it does
commandstringThe command as a reader would type it.

The field calls the shell's say(), so it mounts under ViewerShell or a provider that publishes the same state. commandGroups(command) returns the groups a line may break between.

Keys

KeyAction
TabMoves to the copy button; the field's frame turns ink
Enter, SpaceCopies the command

States

StateWhat draws it
RestA 1px --pt-hair frame
HoverThe frame in --pt-ink-2
FocusThe frame in ink while the copy button has focus
CopiedThe check glyph in ink for 1.6 seconds

On this page