feat(ui): add momentary keyboard control surface

This commit is contained in:
Codex
2026-09-25 16:35:14 +03:00
parent 8dd9190573
commit a5a73c8753
7 changed files with 74 additions and 0 deletions
+4
View File
@@ -12,6 +12,7 @@ import {
ApplicationShell,
ApplicationSidePanel,
Button,
KeyButton,
Checker,
ColorField,
ConfirmationModal,
@@ -1410,6 +1411,9 @@ export function CatalogApp() {
<Button variant="ghost" icon={<Icon name="external" />}>Открыть</Button>
<Button variant="danger" icon={<Icon name="trash" />}>Удалить</Button>
<IconButton label="Добавить"><Icon name="plus" /></IconButton>
<KeyButton label="Клавиша W">W</KeyButton>
<KeyButton label="Клавиша A нажата" pressed>A</KeyButton>
<KeyButton label="Клавиша S недоступна" disabled>S</KeyButton>
</div>
</Preview>
<Preview title="Активность" note="default / compact" className="catalog-preview--compact">
+4
View File
@@ -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).
+15
View File
@@ -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).
+10
View File
@@ -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; }
+17
View File
@@ -0,0 +1,17 @@
import {forwardRef, type ButtonHTMLAttributes} from 'react';
import {cn} from './cn.js';
export interface KeyButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
/** 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<HTMLButtonElement, KeyButtonProps>(function KeyButton(
{pressed=false,label,children,className,type='button',disabled,...props},ref,
) {
return <button {...props} ref={ref} type={type} disabled={disabled}
aria-label={label} title={label} aria-pressed={pressed && !disabled}
className={cn('nodedc-key-button',className)}>{children}</button>;
});
+1
View File
@@ -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';
+23
View File
@@ -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."
]
}
]
}