feat(ui): add momentary keyboard control surface
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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).
|
||||
@@ -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; }
|
||||
|
||||
@@ -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>;
|
||||
});
|
||||
@@ -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';
|
||||
|
||||
@@ -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."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user