/** @jsxImportSource preact */
import {formatKey} from '../keyboard-shortcuts/keyboard-shortcuts';
import type {KeyboardShortcut} from '../keyboard-shortcuts/keyboard-shortcuts';
import type {WidgetPanel, WidgetPanelTheme} from './widget-containers';
import type {JSX} from 'preact';
export type KeyboardShortcutsPanelProps = {
/** Optional list of keyboard shortcuts to render in the panel. */
keyboardShortcuts?: KeyboardShortcut[];
/** Optional theme override applied to this panel subtree. */
theme?: WidgetPanelTheme;
};
/**
* A panel definition representing keyboard shortcut details for a modal/tab container.
*/
export class KeyboardShortcutsPanel implements WidgetPanel {
id = 'keyboard-shortcuts';
title = 'Keyboard Shortcuts';
theme?: WidgetPanelTheme;
content: JSX.Element;
constructor({keyboardShortcuts = [], theme = 'inherit'}: KeyboardShortcutsPanelProps = {}) {
this.theme = theme;
this.content = ;
}
}
const KEY_STYLE: JSX.CSSProperties = {
borderRadius: '6px',
border: '1px solid rgba(148, 163, 184, 0.85)',
backgroundColor: 'rgba(248, 250, 252, 1)',
padding: '2px 8px',
fontSize: '11px',
color: 'var(--button-text, #0f172a)',
whiteSpace: 'nowrap'
};
const SHORTCUT_BADGE_STYLE: JSX.CSSProperties = {
display: 'inline-flex',
alignItems: 'center',
borderRadius: '999px',
border: '1px solid rgba(148, 163, 184, 0.75)',
backgroundColor: 'rgba(248, 250, 252, 1)',
padding: '1px 6px',
fontSize: '10px',
lineHeight: '12px',
color: 'rgb(71, 85, 105)',
whiteSpace: 'nowrap'
};
const SHORTCUT_ROW_STYLE: JSX.CSSProperties = {
display: 'grid',
gridTemplateColumns: 'minmax(104px, 148px) minmax(0, 1fr) auto',
gap: '10px 14px',
alignItems: 'start',
padding: '10px 0',
borderBottom: '1px solid rgba(226, 232, 240, 0.8)'
};
const SHORTCUT_KEYS_STYLE: JSX.CSSProperties = {
display: 'flex',
alignItems: 'flex-start',
flexWrap: 'wrap',
gap: '10px',
minWidth: 0
};
const SHORTCUT_DESCRIPTION_STYLE: JSX.CSSProperties = {
fontSize: '13px',
lineHeight: '18px',
fontWeight: 500,
color: 'rgb(71, 85, 105)',
textAlign: 'left',
minWidth: 0,
width: '100%'
};
const SHORTCUT_BADGES_STYLE: JSX.CSSProperties = {
display: 'flex',
alignItems: 'flex-start',
gap: '6px',
flexWrap: 'wrap',
justifySelf: 'end'
};
const SHORTCUT_CHORD_STYLE: JSX.CSSProperties = {
display: 'inline-flex',
alignItems: 'center',
gap: '2px',
color: 'rgb(51, 65, 85)',
fontSize: '11px',
whiteSpace: 'nowrap'
};
function KeyboardSettingsPanelContent({
keyboardShortcuts
}: {
keyboardShortcuts: KeyboardShortcut[];
}) {
const shortcutRows = buildKeyboardShortcutRows(keyboardShortcuts);
return (
{shortcutRows.map((row) => (
{row.shortcuts.map((shortcut, index) => (
))}
{row.description}
{row.badges.length > 0 ? (
{row.badges.map((badge) => (
{badge}
))}
) : null}
))}
);
}
function ShortcutKey({shortcut}: {shortcut: KeyboardShortcut}) {
const parts: JSX.Element[] = [];
if (shortcut.commandKey) {
parts.push(
⌘
);
}
if (shortcut.ctrlKey) {
parts.push(
^
);
}
if (shortcut.shiftKey) {
parts.push(
Shift
);
}
if (shortcut.key) {
parts.push(
{formatKey(shortcut.key)}
);
}
if (shortcut.dragMouse) {
parts.push(
drag mouse
);
}
return (
{parts.map((part, index) => (
{part}
))}
);
}
type KeyboardShortcutRow = {
badges: string[];
description: string;
key: string;
shortcuts: KeyboardShortcut[];
};
function buildKeyboardShortcutRows(shortcuts: KeyboardShortcut[]): KeyboardShortcutRow[] {
const rows: KeyboardShortcutRow[] = [];
for (let index = 0; index < shortcuts.length; index += 1) {
const shortcut = shortcuts[index];
const nextShortcut = shortcuts[index + 1];
const shortcutDisplayPair = shortcut?.displayPair;
if (
shortcut &&
shortcutDisplayPair &&
nextShortcut &&
canPairShortcuts(shortcut, nextShortcut)
) {
rows.push({
badges: mergeShortcutBadges(shortcut, nextShortcut),
description: shortcutDisplayPair.description,
key: `${shortcutDisplayPair.id}-${index}`,
shortcuts: [shortcut, nextShortcut]
});
index += 1;
} else if (shortcut) {
rows.push({
badges: [...(shortcut.badges ?? [])],
description: shortcut.description,
key: `${shortcut.name}-${shortcut.key}-${index}`,
shortcuts: [shortcut]
});
}
}
return rows;
}
function canPairShortcuts(
shortcut: KeyboardShortcut,
nextShortcut: KeyboardShortcut | undefined
): nextShortcut is KeyboardShortcut {
if (!nextShortcut) {
return false;
}
const shortcutPair = shortcut.displayPair;
const nextShortcutPair = nextShortcut.displayPair;
if (!shortcutPair || !nextShortcutPair) {
return false;
}
return (
shortcutPair.position === 'primary' &&
nextShortcutPair.position === 'secondary' &&
shortcutPair.id === nextShortcutPair.id &&
shortcutPair.description === nextShortcutPair.description
);
}
function mergeShortcutBadges(...shortcuts: KeyboardShortcut[]): string[] {
const seenBadges = new Set();
const mergedBadges: string[] = [];
for (const shortcut of shortcuts) {
for (const badge of shortcut.badges ?? []) {
if (!seenBadges.has(badge)) {
seenBadges.add(badge);
mergedBadges.push(badge);
}
}
}
return mergedBadges;
}