Pular para o conteúdo

Code Block

components specs/components/code-block.kmd

Code-block widget — the multi-framework code-tabs surface that hosts per-format code samples behind a shared tab strip (HTML / Flutter / Compose / SwiftUI / Web Components), with a per-tab copy button and a transient "Copied" toast. The framework choice persists across the session so picking "Flutter" once shows Flutter in every code block.

Quando esta spec se aplica

Triggers primários

Todos os triggers

Corpo da especificação

Spec — Code Block

Facet Visual do Koder Design. The KDS realization of material.io's multi-framework code panels — the highest-value "designer crosses to developer" surface. Implemented in design-gen (internal/kinds CodeTabs + assets/js tab switcher; shipped in tools/design-gen#044, copy button #028).

Anatomy

┌───────────────────────────────────────────────┐
│ [HTML] Flutter  Compose  SwiftUI  Web   [Copy] │ ← tab strip + copy
├───────────────────────────────────────────────┤
│  <button class="kd-btn filled">Save</button>   │
│  …only the selected framework's panel shows…   │ ← <pre><code>
└───────────────────────────────────────────────┘
  • Tab strip: one tab per framework supplied; the active tab is marked aria-selected="true". Frameworks with no sample for a given block are omitted (no empty tabs).
  • Panels: one <pre><code> per framework; only the selected one is visible (others hidden). Mono type role (--kds-font-mono).
  • Copy button: top-right; copies the visible panel's text.

R1 — Single logical widget, N syntaxes

A code-block tags N samples that express the same widget in different frameworks. Switching tabs swaps syntax only — never the widget being demonstrated. A block MAY carry a single framework (then the tab strip collapses to a label, not a control).

R2 — Shared, persisted framework selection

All code-blocks on a page (and across the session) share one framework choice, persisted in localStorage["kds.framework"]. Picking a framework in any block updates every other block on the page immediately and restores on the next visit. Default when unset: the first framework declared on the block (HTML where present).

R3 — Syntax-agnostic rendering

Panels render pre-escaped, server-highlighted code. No client-side syntax-highlight dependency on the render path (policies/headless-first.kmd — the headless build emits the same HTML the user sees). Long lines scroll horizontally within the panel; they do not wrap or expand the page.

R4 — Copy affordance + transient confirmation

Each block exposes a copy button that writes the visible panel's raw text to the clipboard and shows a transient "Copied" toast for ~1.5 s, then reverts. The button keeps its label/icon between states (no layout shift). On clipboard failure the toast reads the canonical error per specs/errors/user-facing-messages.kmd — never a silent no-op. Reuses the SDK copy primitive (policies/reuse-first.kmd).

R5 — Accessibility

  • Tab strip is a role="tablist"; tabs are role="tab" with aria-controls pointing at their panel; panels are role="tabpanel".
  • Arrow keys move between tabs; Enter/Space activates; the copy button is reachable by Tab and announces its state change via aria-live.
  • Color contrast of tab/active states meets the themes/color-roles.kmd AA floor in both light and dark.

R6 — Degradation (no JS)

With JavaScript disabled, every framework panel is visible (stacked, each under its framework label) and the copy button is hidden — the content is never gated behind the switcher.

Out of scope

  • Editable / runnable code (playground) — see the component prop playground surface, not this block.
  • Diff rendering — a separate future surface.

Requirements (testable)

Requirement: Shared, persisted framework selection {#req-code-block-shared-framework}

All code-blocks on a page and across the session SHALL share one framework choice, persisted in localStorage["kds.framework"]; picking a framework in any block SHALL update every other block on the page immediately and SHALL restore the same choice on the next visit, defaulting to the first framework declared on the block when unset.

Scenario: Picking a framework updates every block on the page

  • GIVEN a page with two code-blocks each exposing HTML and Flutter tabs, both showing HTML
  • WHEN the user activates the Flutter tab in the first block
  • THEN the first block's visible panel is the Flutter panel
  • AND the second block's visible panel switches to its Flutter panel
  • AND localStorage["kds.framework"] equals "Flutter"

Scenario: Choice restores on next visit

  • GIVEN localStorage["kds.framework"] is set to "Flutter" before load
  • WHEN the page loads a code-block that declares HTML and Flutter
  • THEN the Flutter tab is marked aria-selected="true"
  • AND the Flutter panel is the visible panel

Scenario: Default when unset is the first declared framework

  • GIVEN no kds.framework key exists in localStorage
  • WHEN a code-block declaring HTML then Flutter loads
  • THEN the HTML tab is the active tab
  • AND the HTML panel is the only visible panel

Requirement: Copy affordance with transient confirmation {#req-code-block-copy-toast}

Each code-block SHALL expose a copy button that writes the visible panel's raw text to the clipboard and shows a transient "Copied" toast for about 1.5 s before reverting, SHALL keep its label and icon between states with no layout shift, and on clipboard failure SHALL surface the canonical user-facing error rather than a silent no-op.

Scenario: Copy writes the visible panel and confirms

  • GIVEN a code-block whose visible Flutter panel contains the text Widget build()
  • WHEN the user clicks the copy button
  • THEN the clipboard contents equal Widget build()
  • AND a "Copied" toast appears
  • AND the toast disappears after about 1.5 s while the button label and icon are unchanged

Scenario: Clipboard failure surfaces the canonical error

  • GIVEN the clipboard write is denied by the environment
  • WHEN the user clicks the copy button
  • THEN the toast shows the canonical error message per specs/errors/user-facing-messages.kmd
  • AND the action does not fail silently

Requirement: Syntax-agnostic horizontal scrolling {#req-code-block-no-wrap-scroll}

Panels SHALL render pre-escaped, server-highlighted code with no client-side syntax-highlight dependency on the render path, and long lines SHALL scroll horizontally within the panel without wrapping or expanding the page width.

Scenario: A long line scrolls within the panel

  • GIVEN a code-block panel containing a line wider than the panel
  • WHEN the panel is rendered
  • THEN the line does not wrap onto a second visual row
  • AND the panel provides horizontal scroll for the overflow
  • AND the page body width is not expanded by the panel

Requirement: Accessible tablist and keyboard operation {#req-code-block-a11y-keyboard}

The tab strip SHALL be a role="tablist" whose tabs are role="tab" with aria-controls referencing their role="tabpanel", arrow keys SHALL move between tabs with Enter or Space activating the focused tab, the copy button SHALL be reachable by Tab and announce its state change via aria-live, and tab/active-state contrast SHALL meet the AA floor in both light and dark themes.

Scenario: Arrow keys move focus and Enter activates a tab

  • GIVEN a code-block with the HTML tab focused and active
  • WHEN the user presses ArrowRight and then Enter on the Flutter tab
  • THEN the Flutter tab has aria-selected="true"
  • AND the panel referenced by that tab's aria-controls becomes visible
  • AND its role is tabpanel

Scenario: Copy button is keyboard reachable and announces state

  • GIVEN a code-block rendered with keyboard focus at the start of its controls
  • WHEN the user tabs to the copy button and activates it
  • THEN the button receives focus via Tab
  • AND the state change is announced through an aria-live region

Requirement: No-JS degradation shows all panels {#req-code-block-nojs-degradation}

With JavaScript disabled, every framework panel SHALL be visible, stacked and each under its framework label, and the copy button SHALL be hidden, so the code content is never gated behind the switcher.

Scenario: All panels are shown when JS is disabled

  • GIVEN a code-block declaring HTML and Flutter rendered with JavaScript disabled
  • WHEN the page is displayed
  • THEN both the HTML and Flutter panels are visible and not hidden
  • AND each panel is preceded by its framework label
  • AND the copy button is not shown

Referências