docs(desktop): tip only when hover teaches something new
Drop the blanket "every icon* button needs a Tip" rule — it produced tautological kebab tips like "Actions for <row title>". Menu triggers keep aria-label; tips stay for unlabeled discovery chrome and keybind hints.
This commit is contained in:
parent
84858d76ba
commit
58714e0e2a
|
|
@ -117,20 +117,28 @@ that sit inside a heading/sentence; replaces `h-auto px-0 py-0`), `micro`
|
|||
(status-stack/table-footers), and the icon family `icon` / `icon-xs` /
|
||||
`icon-sm` / `icon-lg` / `icon-titlebar`.
|
||||
|
||||
**Icon-only buttons must have a tooltip.** Every button with an `icon*` size
|
||||
carries no visible text label, so it must be wrapped in `<Tip label={...}>`
|
||||
with a descriptive label (matching the button's `aria-label`). Never use the
|
||||
native HTML `title=` attribute — it's unstyled, delayed (~500ms OS default),
|
||||
and visually inconsistent with the instant themed `Tip`. An enforcement test
|
||||
(`src/components/ui/__tests__/no-native-title.test.ts`) fails on any `<button>`
|
||||
or `<Button>` that still carries `title=`.
|
||||
**Tooltips only when hover teaches something new.** `<Tip>` is for discovery,
|
||||
not a tax on every icon. Ask: does hover reveal something the user cannot
|
||||
already see or infer? If not, skip the tip; keep an `aria-label` for a11y.
|
||||
|
||||
**Keybind hints in tooltips.** When a button corresponds to a rebindable
|
||||
hotkey, use `<TipKeybindLabel actionId="..." />` as the `Tip` label — it
|
||||
auto-reads both the i18n label and the current keybind combo from the store,
|
||||
so the hint stays live when the user rebinds. Pass `text={...}` only when the
|
||||
tooltip is context-dependent (e.g. "Show" / "Hide" based on state). Never
|
||||
hardcode combos in components — always read from the `$bindings` store via
|
||||
Tip unlabeled chrome when the job (or a keybind / truncated path / host /
|
||||
other detail) is not already on screen — toolbar / titlebar / statusbar icons,
|
||||
`TipKeybindLabel` shortcuts, ownership chips, unlabeled icon grids.
|
||||
|
||||
Do **not** tip menu triggers (kebabs / ⋯ / `ActionsMenu` /
|
||||
`DropdownMenuTrigger`). The affordance is "open menu"; the verbs live in the
|
||||
menu. Never `"Actions for ${row title}"`, `"Project actions"`, `"Actions"`, or
|
||||
`"Credential actions"` on a ⋯ — the row already names the thing. Same for any
|
||||
control whose visible label already says what the tip would.
|
||||
|
||||
Never use native HTML `title=` on buttons — unstyled, ~500ms OS delay, clashes
|
||||
with the themed `Tip`. `src/components/ui/__tests__/no-native-title.test.ts`
|
||||
fails on any `<button>` / `<Button>` that still carries `title=`.
|
||||
|
||||
**Keybind hints in tooltips.** On a tipped button bound to a rebindable hotkey,
|
||||
use `<TipKeybindLabel actionId="..." />` — it reads the i18n label and the
|
||||
current combo from `$bindings`. Pass `text={...}` only when the label is
|
||||
context-dependent (e.g. "Show" / "Hide"). Never hardcode combos; always use
|
||||
`useKeybindHint` or `TipKeybindLabel`.
|
||||
|
||||
Notes:
|
||||
|
|
@ -299,9 +307,10 @@ The detailed state contract lives in the scoped
|
|||
- [ ] Tokens (`--ui-*`, `shadow-nous`, `--stroke-nous`) — zero raw colors /
|
||||
one-off shadows?
|
||||
- [ ] No `className` overriding a primitive's padding / size / radius / chrome?
|
||||
- [ ] Icon-only buttons wrapped in `<Tip>` with a descriptive label?
|
||||
- [ ] No native `title=` on buttons — use `<Tip>` instead?
|
||||
- [ ] Keybind hints read from the store via `useKeybindHint` / `TipKeybindLabel`?
|
||||
- [ ] Tips only where hover teaches something new (no kebab / menu-trigger
|
||||
tips; unlabeled chrome that needs discovery gets `<Tip>` + `aria-label`)?
|
||||
- [ ] No native `title=` on buttons?
|
||||
- [ ] Keybind hints on tipped buttons use `useKeybindHint` / `TipKeybindLabel`?
|
||||
- [ ] Overlay uses `shadow-nous` + `border-(--stroke-nous)`, no hard border?
|
||||
- [ ] Flat — no card-in-card, no gratuitous row dividers?
|
||||
- [ ] No automatic navigation, focus steal, or pane opening from background
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ import { describe, expect, it } from 'vitest'
|
|||
|
||||
// Static-analysis guard: no <button> or <Button> element in the desktop renderer
|
||||
// may use the native HTML `title=` attribute. Native tooltips are unstyled,
|
||||
// delayed (~500ms OS default), and visually inconsistent with the app's instant
|
||||
// themed `Tip` component. Use `<Tip label={...}>` instead.
|
||||
// delayed (~500ms OS default), and visually inconsistent with the themed `Tip`.
|
||||
// When a tip is warranted (see DESIGN.md — not every icon, never menu triggers),
|
||||
// use `<Tip label={...}>` instead of `title=`.
|
||||
//
|
||||
// This is a source-text scan, not a behavior test — it's the same category as
|
||||
// an ESLint rule, expressed as a vitest so it runs with the rest of the suite.
|
||||
// See DESIGN.md "Buttons — one component" for the rule.
|
||||
|
||||
// Recursively walk a directory and collect all .tsx file paths.
|
||||
function collectTsxFiles(dir: string): string[] {
|
||||
|
|
|
|||
Loading…
Reference in New Issue