Shortcuts Dialog
blocklayoutLinear-style keyboard reference sheet: a '?' dialog listing shortcut groups (label, description, Kbd combo) in a responsive two-column grid. Ships a built-in trigger button, controlled/uncontrolled open, custom groups via input, and an Esc footer hint. Display only — wire real handlers in your app.
Installation
$pnpm dlx uipkge-ng@latest add shortcuts-dialog$npx uipkge-ng@latest add shortcuts-dialog$yarn dlx uipkge-ng@latest add shortcuts-dialog$bunx uipkge-ng@latest add shortcuts-dialogManifest:
https://uipkge.dev/r/angular/shortcuts-dialog.jsonInstalls to:src/app/components/blocks/shortcuts-dialog/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
Shortcutinterface Shortcut {
keys: string[]
description: string
}ShortcutGroupinterface ShortcutGroup {
label: string
shortcuts: Shortcut[]
}npm dependencies
Files installed (2)
src/app/components/blocks/shortcuts-dialog/shortcuts-dialog.component.ts6 kB
// Linear-style keyboard reference sheet: a '?' dialog listing shortcut groups (label, // description, Kbd combo) in a responsive two-column grid. Ships a built-in trigger button, // controlled/uncontrolled open, custom groups via input, and an Esc footer hint. // Display only -- wire real handlers in your app. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, booleanAttribute, signal, } from '@angular/core' import { Keyboard, LucideAngularModule } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiButtonComponent } from '@/ui/button/button.component' import { UiDialogComponent, UiDialogContentComponent, UiDialogDescriptionComponent, UiDialogFooterComponent, UiDialogHeaderComponent, UiDialogTitleComponent, UiDialogTriggerComponent, } from '@/ui/dialog/dialog.component' import { UiKbdComponent } from '@/ui/kbd/kbd.component' export interface Shortcut { keys: string[] description: string } export interface ShortcutGroup { label: string shortcuts: Shortcut[] } const defaultGroups: ShortcutGroup[] = [ { label: 'Navigation', shortcuts: [ { keys: ['G', 'I'], description: 'Go to inbox' }, { keys: ['G', 'D'], description: 'Go to drafts' }, { keys: ['⌘', 'K'], description: 'Open command menu' }, { keys: ['⌥', '↑', '↓'], description: 'Move between items' }, ], }, { label: 'Actions', shortcuts: [ { keys: ['C'], description: 'Compose new' }, { keys: ['/'], description: 'Focus search' }, { keys: ['⌘', 'S'], description: 'Save changes' }, { keys: ['E'], description: 'Archive selection' }, ], }, { label: 'General', shortcuts: [ { keys: ['?'], description: 'Show this dialog' }, { keys: ['⇧', '?'], description: 'Show cheat sheet' }, { keys: ['Esc'], description: 'Close dialogs' }, ], }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-shortcuts-dialog, [ui-shortcuts-dialog]', standalone: true, // React renders the Dialog root itself (no element): the host stays out of layout. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiButtonComponent, UiDialogComponent, UiDialogContentComponent, UiDialogDescriptionComponent, UiDialogFooterComponent, UiDialogHeaderComponent, UiDialogTitleComponent, UiDialogTriggerComponent, UiKbdComponent, ], template: ` <ui-dialog [open]="dialogOpen" (openChange)="onOpenChange($event)"> <div data-slot="shortcuts-dialog" class="contents"> @if (trigger) { <button type="button" ui-button ui-dialog-trigger variant="outline" size="sm"> <lucide-icon [img]="Keyboard" aria-hidden="true" /> Shortcuts <span ui-kbd>?</span> </button> } @else { <!-- Custom trigger: projected content cannot inject the dialog (Angular resolves projected DI at the declaration site), so clicks are wired here instead. Project any button -- no ui-dialog-trigger needed. --> <span class="contents" (click)="openDialog()"> <ng-content /> </span> } <ui-dialog-content [class]="contentClass"> <ui-dialog-header> <h2 ui-dialog-title>Keyboard shortcuts</h2> <p ui-dialog-description>Move faster without leaving the keyboard.</p> </ui-dialog-header> <div class="grid max-h-[60vh] gap-x-8 gap-y-6 overflow-y-auto pr-1 sm:grid-cols-2"> @for (group of groups; track group.label) { <div class="space-y-2"> <p class="text-muted-foreground text-xs font-medium tracking-widest uppercase">{{ group.label }}</p> <ul class="divide-y"> @for (shortcut of group.shortcuts; track shortcut.description) { <li class="flex items-center justify-between gap-4 py-1.5"> <span class="text-sm">{{ shortcut.description }}</span> <span class="flex shrink-0 items-center gap-1"> @for (key of shortcut.keys; track key) { <span ui-kbd>{{ key }}</span> } </span> </li> } </ul> </div> } </div> @if (footer) { <ui-dialog-footer class="justify-start"> <p class="text-muted-foreground flex items-center gap-1.5 text-xs"> Press <span ui-kbd>Esc</span> to close </p> </ui-dialog-footer> } </ui-dialog-content> </div> </ui-dialog> `, }) export class UiShortcutsDialogComponent { /** Shortcut sections to render. Defaults to the Navigation / Actions / General set. */ @Input() groups: ShortcutGroup[] = defaultGroups /** Render the built-in trigger button. Set false to project your own trigger (clicks open the dialog). */ @Input({ transform: booleanAttribute }) trigger = true /** Show the 'Press Esc to close' footer hint. */ @Input({ transform: booleanAttribute }) footer = true /** Controlled open state (pair with openChange); omit for uncontrolled. */ @Input() set open(v: boolean | undefined) { this.controlledOpen.set(v) } @Output() openChange = new EventEmitter<boolean>() @Input('class') className?: string private readonly controlledOpen = signal<boolean | undefined>(undefined) private readonly internalOpen = signal(false) protected readonly Keyboard = Keyboard get dialogOpen(): boolean { return this.controlledOpen() ?? this.internalOpen() } get contentClass(): string { return cn('max-w-lg', this.className) } onOpenChange(value: boolean): void { this.internalOpen.set(value) this.openChange.emit(value) } /** Custom-trigger path: the projected button has no trigger directive, so open directly. */ openDialog(): void { this.onOpenChange(true) } }src/app/components/blocks/shortcuts-dialog/index.ts0.1 kB
export { UiShortcutsDialogComponent, type Shortcut, type ShortcutGroup } from './shortcuts-dialog.component'
Raw manifest:https://uipkge.dev/r/angular/shortcuts-dialog.json