On touch the 44 px key cap and Go filled a 44 px pill edge to edge and the placeholder was cut. The key cap is left out on touch (⌘K still works), the pill grows to 52 px so a ring shows round Go, the box's floor counts the controls beside the text, and Go drops to its icon by the column's width, not the screen's.
71 lines
3.0 KiB
TypeScript
71 lines
3.0 KiB
TypeScript
import { useTranslation } from "react-i18next";
|
|
import { Command } from "lucide-react";
|
|
|
|
import { SHORTCUTS, formatChord, openPalette } from "../lib/keymap";
|
|
import { Pressable } from "./ui/Pressable";
|
|
import clsx from "clsx";
|
|
import { fieldClass } from "./ui/fieldStyles";
|
|
|
|
// The visible way into the command palette (and, from there, the shortcut sheet). Ctrl/Cmd+K and
|
|
// `h` had no entry point anywhere but a palette row — the one in-app pointer to the cheat sheet sat
|
|
// behind the other undiscoverable chord. The owner's pick (2026-09-19): a key cap where you already
|
|
// type, the header's search field; a page with no search field gets the same key cap on a small
|
|
// "Jump to…" pill in its place, so the entry is on every page.
|
|
|
|
/** The chord the palette row actually binds, read from the table rather than restated. */
|
|
const paletteChord = SHORTCUTS.find((s) => s.id === "palette")?.keys[0] ?? "Mod+k";
|
|
|
|
function useCap(): string {
|
|
const { t } = useTranslation();
|
|
return formatChord(paletteChord, {
|
|
Space: t("keys.cap.space"),
|
|
ContextMenu: t("keys.cap.menu"),
|
|
});
|
|
}
|
|
|
|
// Each accessible name CONTAINS what the control shows (WCAG 2.5.3, Label in Name): a voice-control
|
|
// user says what they see — "click Jump to", "click Ctrl K" — and a name that replaced the visible
|
|
// words matched nothing.
|
|
|
|
/** The key cap at the end of a search field. A button, so the pointer and a screen reader reach it
|
|
* too; left out of a phone's narrow header and of every touch screen, where there is (usually) no
|
|
* keyboard to press it on — on a tablet it was a 44 px box crowding the field. ⌘K still works. */
|
|
export function PaletteKeycap() {
|
|
const { t } = useTranslation();
|
|
const cap = useCap();
|
|
return (
|
|
<Pressable
|
|
onClick={openPalette}
|
|
aria-label={`${t("palette.open")} (${cap})`}
|
|
title={`${t("palette.open")} (${cap})`}
|
|
className="inline-flex h-6 shrink-0 items-center justify-center rounded-md border border-border px-1.5 font-mono text-3xs touch-target text-muted transition hover:border-accent/50 hover:text-fg pointer-coarse:hidden @max-[640px]/appmain:hidden"
|
|
>
|
|
{cap}
|
|
</Pressable>
|
|
);
|
|
}
|
|
|
|
/** The same entry for a page whose header has no search field. */
|
|
export function PaletteLauncher() {
|
|
const { t } = useTranslation();
|
|
const cap = useCap();
|
|
return (
|
|
<Pressable
|
|
onClick={openPalette}
|
|
title={t("palette.open")}
|
|
// The search box's own frame (a 40 glass pill with the field edge): on a page without a search
|
|
// this is where the search would be, and it opens the same place.
|
|
className={clsx(
|
|
fieldClass({ size: "lg", pill: true, floating: true }),
|
|
"pointer-events-auto flex items-center gap-2 text-muted hover:text-fg",
|
|
)}
|
|
>
|
|
<Command className="h-4 w-4 shrink-0" aria-hidden="true" />
|
|
<span>{t("palette.title")}</span>
|
|
<kbd className="rounded-md border border-border px-1.5 py-0.5 font-mono text-3xs @max-[640px]/appmain:hidden">
|
|
{cap}
|
|
</kbd>
|
|
</Pressable>
|
|
);
|
|
}
|