diff --git a/apps/catalog/src/CatalogApp.tsx b/apps/catalog/src/CatalogApp.tsx index ca05cdf..8c45003 100644 --- a/apps/catalog/src/CatalogApp.tsx +++ b/apps/catalog/src/CatalogApp.tsx @@ -12,6 +12,7 @@ import { ApplicationShell, ApplicationSidePanel, Button, + KeyButton, Checker, ColorField, ConfirmationModal, @@ -1410,6 +1411,9 @@ export function CatalogApp() { + W + A + S diff --git a/docs/COMPONENTS.md b/docs/COMPONENTS.md index 970d91e..70562b5 100644 --- a/docs/COMPONENTS.md +++ b/docs/COMPONENTS.md @@ -380,3 +380,7 @@ StatusBadge `variant="indicator"` отображает только одну л Общая главная: надзаголовок, заголовок, описание, быстрые действия, фон и необязательные status/footer slots. Изображения меняются по таймеру, видео — после завершения; ошибочные источники пропускаются. Пустой фон однотонный, без декоративного круга. Состояние страниц и хранение принадлежат приложению. Для primary Button `tone="neutral"` фиксирует белый enabled и серый disabled, независимо от темы и акцента; keyboard focus остаётся видимым. MediaSourceField принимает `disabled` и блокирует все операции выбора источника, пока родитель сохраняет документ или загружает файл. + +## KeyButton + +Контурная клавиша удержания с контролируемым pressed-состоянием. [Контракт](KEY_BUTTON.md). diff --git a/docs/KEY_BUTTON.md b/docs/KEY_BUTTON.md new file mode 100644 index 0000000..85097f8 --- /dev/null +++ b/docs/KEY_BUTTON.md @@ -0,0 +1,15 @@ +# KeyButton + +Owner-approved momentary keyboard/pointer visualization (Mission Core, 2026-09-25). +Outlined square key surface; pressed is white with black text in every theme. +This explicit outline identifies a key, not a decorative panel border. + +`label` is the complete accessible action; children are the short key legend. +`pressed` is controlled and never toggled by the component. Native button keyboard +semantics, disabled behavior and focus indicator are retained. Consumers own +pointer capture and release, keyboard scope, blur/cancel/unmount handling and +any domain command lease. A displayed pressed key is not an acknowledgement +from equipment. Disabled always renders released. No global listeners exist. + +Catalog includes idle, pressed, disabled and focusable surfaces. Use only for +momentary key input, not ordinary actions (Button) or mode choice (SegmentedControl). diff --git a/packages/ui-core/styles.css b/packages/ui-core/styles.css index dda0312..7bb8b2a 100644 --- a/packages/ui-core/styles.css +++ b/packages/ui-core/styles.css @@ -4302,3 +4302,13 @@ textarea.nodedc-field__control { } } .nodedc-landing-stage__eyebrow { color: var(--nodedc-text-muted); font-size: var(--nodedc-font-size-xs); font-weight: var(--nodedc-font-weight-strong); letter-spacing: .12em; } + +/* Explicit key outline: approved momentary-input affordance, not a card rim. */ +.nodedc-key-button { display:inline-grid; place-items:center; width:46px; height:46px; + border:1px solid var(--nodedc-text-secondary); border-radius:var(--nodedc-radius-sm, 8px); + background:transparent; color:var(--nodedc-text-primary); font:inherit; font-weight:600; + cursor:pointer; user-select:none; touch-action:none; } +.nodedc-key-button:hover:not(:disabled) { background:var(--nodedc-glass-control-hover); } +.nodedc-key-button[aria-pressed="true"]:not(:disabled) { background:#fff; color:#000; border-color:#fff; } +.nodedc-key-button:disabled { opacity:.4; cursor:not-allowed; } +.nodedc-key-button:focus-visible { outline:2px solid rgb(var(--nodedc-accent-rgb)); outline-offset:3px; } diff --git a/packages/ui-react/src/KeyButton.tsx b/packages/ui-react/src/KeyButton.tsx new file mode 100644 index 0000000..c62ef03 --- /dev/null +++ b/packages/ui-react/src/KeyButton.tsx @@ -0,0 +1,17 @@ +import {forwardRef, type ButtonHTMLAttributes} from 'react'; +import {cn} from './cn.js'; + +export interface KeyButtonProps extends ButtonHTMLAttributes { + /** Controlled physical/pointer input indication; this is never a toggle. */ + pressed?: boolean; + label: string; +} + +/** A momentary key surface. The consumer owns input, focus scope and release policy. */ +export const KeyButton = forwardRef(function KeyButton( + {pressed=false,label,children,className,type='button',disabled,...props},ref, +) { + return ; +}); diff --git a/packages/ui-react/src/index.ts b/packages/ui-react/src/index.ts index bf3df57..9477158 100644 --- a/packages/ui-react/src/index.ts +++ b/packages/ui-react/src/index.ts @@ -35,3 +35,4 @@ export * from "./EnvironmentSettingsWindow.js"; export * from "./EnvironmentMediaPlaylistEditor.js"; export * from "./EnvironmentBackgroundMedia.js"; export * from "./LandingStage.js"; +export {KeyButton, type KeyButtonProps} from './KeyButton.js'; diff --git a/registry/components.json b/registry/components.json index c120c08..dd8c648 100644 --- a/registry/components.json +++ b/registry/components.json @@ -625,6 +625,29 @@ "Applications supply content and actions, not duplicate stage markup.", "Background playback and scene navigation never own device acquisition." ] + }, + { + "id": "key-button", + "status": "baseline", + "package": "@nodedc/ui-react", + "exports": [ + "KeyButton", + "KeyButtonProps" + ], + "domContract": [ + "nodedc-key-button" + ], + "summary": "Controlled outlined momentary input key; white pressed surface with black legend.", + "states": [ + "idle", + "pressed", + "disabled", + "focus-visible" + ], + "rules": [ + "Consumer owns command lifecycle and blur/cancel release.", + "No internal toggle or global keyboard handler." + ] } ] }