Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion app/playground/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TerminalApp } from '@/components/terminal-app'
import { Terminal, TerminalCommand, TerminalDiff, TerminalOutput, TerminalSpinner, TerminalBadge, ThemeSwitcher } from '@/components/terminal'
import { Terminal, TerminalCommand, TerminalDiff, TerminalOutput, TerminalSpinner, TerminalBadge, ThemeSwitcher, TerminalKeybinding } from '@/components/terminal'
import { TerminalProgress } from '@/components/terminal-progress'
import { LogDemo } from './log-demo'
import { PromptDemo } from './prompt-demo'
Expand Down Expand Up @@ -175,6 +175,30 @@ export default function PlaygroundPage() {
</TerminalOutput>
</Terminal>
</section>

<section className="flex flex-col gap-2">
<h2 className="text-lg font-semibold font-mono text-[var(--term-fg)]">
TerminalKeybinding
</h2>
<p className="text-sm text-[var(--term-fg-dim)] font-mono">
Display keyboard shortcuts in terminal-style format.
</p>
<Terminal title="keybindings.sh">
<TerminalCommand>show-shortcuts</TerminalCommand>
<TerminalOutput type="info">
<div className="flex flex-col gap-2">
<TerminalKeybinding keys="Ctrl+C" description="Interrupt current process" />
<TerminalKeybinding keys="Ctrl+D" description="Exit shell" />
<TerminalKeybinding keys="↑" description="Previous command" />
<TerminalKeybinding keys="↓" description="Next command" />
<TerminalKeybinding keys="Tab" description="Autocomplete" variant="info" />
<TerminalKeybinding keys="Cmd+K" description="Clear screen" variant="warning" />
<TerminalKeybinding keys="Alt+." description="Insert last argument" />
<TerminalKeybinding keys="Ctrl+Shift+P" description="Command palette" variant="success" />
</div>
</TerminalOutput>
</Terminal>
</section>
</main>
)
}
114 changes: 114 additions & 0 deletions components/terminal-keybinding.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
'use client'

import { ReactNode } from 'react'

export interface TerminalKeybindingProps {
/** Keyboard shortcut (e.g., 'Ctrl+C', 'Alt+Tab', '↑') */
keys: string
/** Optional description of the action */
description?: string
/** Variant color (default: 'neutral') */
variant?: 'neutral' | 'success' | 'error' | 'warning' | 'info'
/** Additional CSS classes */
className?: string
}

const variantClasses: Record<NonNullable<TerminalKeybindingProps['variant']>, string> = {
neutral: 'border-[var(--glass-border)] text-[var(--term-fg)]',
success: 'border-[var(--term-green)]/40 text-[var(--term-green)]',
error: 'border-[var(--term-red)]/40 text-[var(--term-red)]',
warning: 'border-[var(--term-yellow)]/40 text-[var(--term-yellow)]',
info: 'border-[var(--term-blue)]/40 text-[var(--term-blue)]',
}

// Special key mappings for consistent display
const KEY_DISPLAY_MAP: Record<string, string> = {
// Modifiers
ctrl: 'Ctrl',
control: 'Ctrl',
cmd: 'Cmd',
command: 'Cmd',
meta: 'Meta',
alt: 'Alt',
option: 'Alt',
shift: 'Shift',
// Special keys
enter: 'Enter',
return: 'Enter',
esc: 'Esc',
escape: 'Esc',
tab: 'Tab',
space: 'Space',
backspace: 'Backspace',
delete: 'Del',
// Arrow keys (support both symbols and names)
up: '↑',
down: '↓',
left: '←',
right: '→',
arrowup: '↑',
arrowdown: '↓',
arrowleft: '←',
arrowright: '→',
}

/**
* Displays keyboard shortcuts in terminal-style format.
* Parses key combinations and renders them as styled badges.
*
* @param keys - Keyboard shortcut string (e.g., 'Ctrl+C', 'Alt+Tab', '↑')
* @param description - Optional description of what the shortcut does
* @param variant - Visual variant for semantic color
* @param className - Additional classes applied to the root element
*
* @example
* ```tsx
* <TerminalKeybinding keys="Ctrl+C" description="Copy" />
* <TerminalKeybinding keys="Alt+Tab" variant="info" />
* <TerminalKeybinding keys="↑" description="Previous command" />
* <TerminalKeybinding keys="Cmd+Shift+P" description="Command palette" />
* ```
*/
export function TerminalKeybinding({
keys,
description,
variant = 'neutral',
className = '',
}: TerminalKeybindingProps) {
// Parse the keys string (split by + or -)
const keyParts = keys
.split(/[\+\-]/)
.map((k) => k.trim())
.filter(Boolean)
.map((k) => {
const lower = k.toLowerCase()
return KEY_DISPLAY_MAP[lower] || k
})

const variantClass = variantClasses[variant]

return (
<div className={`inline-flex items-center gap-2 ${className}`.trim()}>
{/* Key badges */}
<span className="inline-flex items-center gap-1">
{keyParts.map((key, i) => (
<span key={i} className="inline-flex items-center">
{i > 0 && (
<span className="text-[var(--term-fg-dim)] mx-0.5 text-xs">+</span>
)}
<kbd
className={`inline-flex items-center justify-center rounded border px-2 py-0.5 font-mono text-xs leading-none min-w-[1.5rem] bg-[var(--glass-bg)] ${variantClass}`.trim()}
>
{key}
</kbd>
</span>
))}
</span>

{/* Optional description */}
{description && (
<span className="text-sm text-[var(--term-fg-dim)]">{description}</span>
)}
</div>
)
}
1 change: 1 addition & 0 deletions components/terminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,4 @@ export { TerminalAutocomplete, useAutocomplete, COMMON_COMMANDS, COMMON_FLAGS, f
export { TerminalGhosttyTheme, GhosttyThemePicker } from './terminal-ghostty'
export { ThemeSwitcher } from './theme-switcher'
export { TerminalBadge } from './terminal-badge'
export { TerminalKeybinding } from './terminal-keybinding'