refactor: migrate to monorepo structure with Docker, PocketBase, and e2e tests
- Reorganize into apps/, packages/, docs/, e2e/, pocketbase/ directories - Add Dockerfiles for web, worker, and PocketBase services - Add docker-compose.yml for local orchestration - Add turbo.json for monorepo task management - Add Playwright e2e test infrastructure - Add PocketBase backend with migrations - Remove Vite/Next.js/ESLint/PostCSS config files - Update package.json with workspace dependencies - Add .env.example and .dockerignore
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { useKeyboardShortcutsStore } from '@/lib/stores/use-keyboard-shortcuts-store';
|
||||
|
||||
export function ShortcutsHelp() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { shortcuts } = useKeyboardShortcutsStore();
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === '?' && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
||||
const target = e.target as HTMLElement;
|
||||
if (
|
||||
target.tagName === 'INPUT' ||
|
||||
target.tagName === 'TEXTAREA' ||
|
||||
target.isContentEditable
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setOpen((prev) => !prev);
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Keyboard shortcuts</DialogTitle>
|
||||
<DialogDescription>
|
||||
Press <kbd className="rounded border bg-muted px-1">?</kbd> to toggle this help
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-2 gap-4 max-h-[60vh] overflow-y-auto">
|
||||
<div>
|
||||
<h2 className="mb-2 text-sm font-semibold">Navigation</h2>
|
||||
<div className="space-y-1">
|
||||
{shortcuts
|
||||
.filter((s) => s.action.startsWith('navigate_'))
|
||||
.map((shortcut) => (
|
||||
<div key={shortcut.key} className="flex items-center justify-between text-sm">
|
||||
<span className="text-muted-foreground">{shortcut.description}</span>
|
||||
<kbd className="rounded border bg-muted px-2 py-0.5 text-xs font-mono">
|
||||
{shortcut.key}
|
||||
</kbd>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="mb-2 text-sm font-semibold">Actions</h2>
|
||||
<div className="space-y-1">
|
||||
{shortcuts
|
||||
.filter((s) => !s.action.startsWith('navigate_'))
|
||||
.map((shortcut) => (
|
||||
<div key={shortcut.key} className="flex items-center justify-between text-sm">
|
||||
<span className="text-muted-foreground">{shortcut.description}</span>
|
||||
<kbd className="rounded border bg-muted px-2 py-0.5 text-xs font-mono">
|
||||
{shortcut.key}
|
||||
</kbd>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user