Skip to content

Commit 9ba68bb

Browse files
Copilotdnafication
andauthored
Add keyboard shortcuts for timer control, visual paused indicator, and help dialog (#15)
* Initial plan * Add keyboard shortcuts for P, Space (toggle timer) and R (reset) Co-authored-by: dnafication <6381587+dnafication@users.noreply.github.com> * Update README with new keyboard shortcuts documentation Co-authored-by: dnafication <6381587+dnafication@users.noreply.github.com> * Add preventDefault to all keyboard shortcuts for consistency Co-authored-by: dnafication <6381587+dnafication@users.noreply.github.com> * Fix document title not resetting when R key is pressed Co-authored-by: dnafication <6381587+dnafication@users.noreply.github.com> * Add visual PAUSED indicator next to timer and in document title Co-authored-by: dnafication <6381587+dnafication@users.noreply.github.com> * Add keyboard shortcuts help dialog (? key) Co-authored-by: dnafication <6381587+dnafication@users.noreply.github.com> * chore: update Next.js version to ^14.2.33 in package.json and package-lock.json * feat: integrate keyboard shortcuts dialog into footer and talk timer components - Added KeyboardShortcutsDialog to the Footer component for improved accessibility. - Updated FooterProps to include showShortcutsDialog and setShowShortcutsDialog. - Modified TalkTimer to pass showShortcutsDialog and setShowShortcutsDialog to Footer. * fix: remove unnecessary size prop from settings button in footer --------- Co-authored-by: dnafication <6381587+dnafication@users.noreply.github.com> Co-authored-by: Dina <dnafication@users.noreply.github.com>
1 parent ceccb9c commit 9ba68bb

7 files changed

Lines changed: 305 additions & 180 deletions

File tree

README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-
3131

3232
## Usage
3333

34-
1. **Starting the Timer**: Click the Play button (▶️) in the footer controls
35-
2. **Pausing the Timer**: Click the Pause button (⏸) while the timer is running
36-
3. **Resetting the Timer**: Click the Reset button (↻) to set the timer back to 00:00
34+
1. **Starting the Timer**: Click the Play button (▶️) in the footer controls or press **P** or **Space**
35+
2. **Pausing the Timer**: Click the Pause button (⏸) while the timer is running or press **P** or **Space**
36+
3. **Resetting the Timer**: Click the Reset button (↻) to set the timer back to 00:00 or press **R**
3737
4. **Configuring Settings**: Click the Settings button (⚙️) to:
3838
- Change the talk title
3939
- Set the yellow threshold (warning time in seconds, default: 90 seconds)
@@ -44,7 +44,12 @@ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-
4444

4545
### Keyboard Shortcuts
4646

47-
- **F**: Toggle fullscreen mode (disabled when typing in input fields)
47+
- **P** or **Space**: Toggle start/pause timer
48+
- **R**: Reset timer to 00:00
49+
- **F**: Toggle fullscreen mode
50+
- **?**: Show keyboard shortcuts help dialog
51+
52+
*Note: All keyboard shortcuts are disabled when typing in input fields*
4853

4954
## Getting Started
5055

components/footer.tsx

Lines changed: 120 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import {
1515
Play,
1616
Settings,
1717
TimerReset,
18+
Info,
1819
} from 'lucide-react'
20+
import { KeyboardShortcutsDialog } from './keyboard-shortcuts-dialog'
1921

2022
type FooterProps = {
2123
active: boolean
@@ -24,12 +26,14 @@ type FooterProps = {
2426
yellowThreshold: number
2527
redThreshold: number
2628
isFullscreen: boolean
29+
showShortcutsDialog: boolean
2730
toggleTimer: () => void
2831
resetTimer: () => void
2932
toggleFullscreen: () => void
3033
setTalkTitle: (title: string) => void
3134
setYellowThreshold: (threshold: number) => void
3235
setRedThreshold: (threshold: number) => void
36+
setShowShortcutsDialog: (open: boolean) => void
3337
}
3438

3539
const Footer = ({
@@ -39,12 +43,14 @@ const Footer = ({
3943
yellowThreshold,
4044
redThreshold,
4145
isFullscreen,
46+
showShortcutsDialog,
4247
toggleTimer,
4348
resetTimer,
4449
toggleFullscreen,
4550
setTalkTitle,
4651
setYellowThreshold,
4752
setRedThreshold,
53+
setShowShortcutsDialog,
4854
}: FooterProps) => {
4955
return (
5056
<footer
@@ -84,116 +90,128 @@ const Footer = ({
8490
{talkTitle}
8591
</h2>
8692
</div>
87-
<Dialog>
88-
<DialogTrigger asChild>
89-
<Button variant="outline" size="icon" title="Settings">
90-
<Settings className="h-4 w-4" strokeWidth={3} />
91-
</Button>
92-
</DialogTrigger>
93-
<DialogContent className="">
94-
<DialogHeader>
95-
<DialogTitle>Timer Settings</DialogTitle>
96-
</DialogHeader>
97-
<div className="grid gap-4 py-4">
98-
<div className="grid grid-cols-4 items-center gap-4">
99-
<Label htmlFor="talkTitle" className="text-right">
100-
Title
101-
</Label>
102-
<Input
103-
id="talkTitle"
104-
value={talkTitle}
105-
onChange={(e) => setTalkTitle(e.target.value)}
106-
className="col-span-3"
107-
/>
108-
</div>
109-
<div className="grid grid-cols-4 items-center gap-4">
110-
<Label
111-
htmlFor="yellowThreshold"
112-
className="text-right text-yellow-600"
113-
>
114-
Yellow at (seconds)
115-
</Label>
116-
<Input
117-
id="yellowThreshold"
118-
type="number"
119-
value={yellowThreshold}
120-
onChange={(e) => setYellowThreshold(Number(e.target.value))}
121-
className="col-span-3"
122-
/>
123-
</div>
124-
<div className="grid grid-cols-4 items-center gap-4">
125-
<Label htmlFor="redThreshold" className="text-right text-red-500">
126-
Red at (seconds)
127-
</Label>
128-
<Input
129-
id="redThreshold"
130-
type="number"
131-
value={redThreshold}
132-
onChange={(e) => setRedThreshold(Number(e.target.value))}
133-
className="col-span-3"
134-
/>
135-
</div>
136-
<div className="text-xs text-center">
137-
<p>
138-
Built with{' '}
139-
<a
140-
href="https://nextjs.org"
141-
target="_blank"
142-
rel="noopener"
143-
className="underline hover:text-blue-300"
144-
>
145-
Next.js
146-
</a>
147-
,{' '}
148-
<a
149-
href="https://react.dev/"
150-
target="_blank"
151-
rel="noopener"
152-
className="underline hover:text-blue-300"
93+
<div className="flex items-center space-x-4">
94+
<Button variant="outline" title="Shortcuts">
95+
<Info className="h-4 w-4" strokeWidth={3} />
96+
</Button>
97+
<Dialog>
98+
<DialogTrigger asChild>
99+
<Button variant="outline" title="Settings">
100+
<Settings className="h-4 w-4" strokeWidth={3} />
101+
</Button>
102+
</DialogTrigger>
103+
<DialogContent className="">
104+
<DialogHeader>
105+
<DialogTitle>Timer Settings</DialogTitle>
106+
</DialogHeader>
107+
<div className="grid gap-4 py-4">
108+
<div className="grid grid-cols-4 items-center gap-4">
109+
<Label htmlFor="talkTitle" className="text-right">
110+
Title
111+
</Label>
112+
<Input
113+
id="talkTitle"
114+
value={talkTitle}
115+
onChange={(e) => setTalkTitle(e.target.value)}
116+
className="col-span-3"
117+
/>
118+
</div>
119+
<div className="grid grid-cols-4 items-center gap-4">
120+
<Label
121+
htmlFor="yellowThreshold"
122+
className="text-right text-yellow-600"
153123
>
154-
React
155-
</a>
156-
,{' '}
157-
<a
158-
href="https://tailwindcss.com"
159-
target="_blank"
160-
rel="noopener"
161-
className="underline hover:text-blue-300"
124+
Yellow at (seconds)
125+
</Label>
126+
<Input
127+
id="yellowThreshold"
128+
type="number"
129+
value={yellowThreshold}
130+
onChange={(e) => setYellowThreshold(Number(e.target.value))}
131+
className="col-span-3"
132+
/>
133+
</div>
134+
<div className="grid grid-cols-4 items-center gap-4">
135+
<Label
136+
htmlFor="redThreshold"
137+
className="text-right text-red-500"
162138
>
163-
Tailwind
164-
</a>
165-
, and{' '}
139+
Red at (seconds)
140+
</Label>
141+
<Input
142+
id="redThreshold"
143+
type="number"
144+
value={redThreshold}
145+
onChange={(e) => setRedThreshold(Number(e.target.value))}
146+
className="col-span-3"
147+
/>
148+
</div>
149+
<div className="text-xs text-center">
150+
<p>
151+
Built with{' '}
152+
<a
153+
href="https://nextjs.org"
154+
target="_blank"
155+
rel="noopener"
156+
className="underline hover:text-blue-300"
157+
>
158+
Next.js
159+
</a>
160+
,{' '}
161+
<a
162+
href="https://react.dev/"
163+
target="_blank"
164+
rel="noopener"
165+
className="underline hover:text-blue-300"
166+
>
167+
React
168+
</a>
169+
,{' '}
170+
<a
171+
href="https://tailwindcss.com"
172+
target="_blank"
173+
rel="noopener"
174+
className="underline hover:text-blue-300"
175+
>
176+
Tailwind
177+
</a>
178+
, and{' '}
179+
<a
180+
href="https://ui.shadcn.com"
181+
target="_blank"
182+
rel="noopener"
183+
className="underline hover:text-blue-300"
184+
>
185+
shadcn/ui
186+
</a>
187+
</p>
166188
<a
167-
href="https://ui.shadcn.com"
189+
href="https://github.com/dnafication/talk-timer"
168190
target="_blank"
169191
rel="noopener"
170-
className="underline hover:text-blue-300"
192+
className="inline-flex items-center space-x-1 mt-2 text-blue-700 hover:text-blue-400"
171193
>
172-
shadcn/ui
194+
<svg
195+
className="h-4 w-4"
196+
fill="currentColor"
197+
role="img"
198+
viewBox="0 0 24 24"
199+
xmlns="http://www.w3.org/2000/svg"
200+
>
201+
<title>GitHub</title>
202+
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
203+
</svg>
204+
<span>View on GitHub</span>
173205
</a>
174-
</p>
175-
<a
176-
href="https://github.com/dnafication/talk-timer"
177-
target="_blank"
178-
rel="noopener"
179-
className="inline-flex items-center space-x-1 mt-2 text-blue-700 hover:text-blue-400"
180-
>
181-
<svg
182-
className="h-4 w-4"
183-
fill="currentColor"
184-
role="img"
185-
viewBox="0 0 24 24"
186-
xmlns="http://www.w3.org/2000/svg"
187-
>
188-
<title>GitHub</title>
189-
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
190-
</svg>
191-
<span>View on GitHub</span>
192-
</a>
206+
</div>
193207
</div>
194-
</div>
195-
</DialogContent>
196-
</Dialog>
208+
</DialogContent>
209+
</Dialog>
210+
</div>
211+
<KeyboardShortcutsDialog
212+
open={showShortcutsDialog}
213+
onOpenChange={setShowShortcutsDialog}
214+
/>
197215
</footer>
198216
)
199217
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use client'
2+
3+
import {
4+
Dialog,
5+
DialogContent,
6+
DialogHeader,
7+
DialogTitle,
8+
} from '@/components/ui/dialog'
9+
10+
type KeyboardShortcutsDialogProps = {
11+
open: boolean
12+
onOpenChange: (open: boolean) => void
13+
}
14+
15+
const shortcuts = [
16+
{ key: 'P or Space', description: 'Toggle start/pause timer' },
17+
{ key: 'R', description: 'Reset timer to 00:00' },
18+
{ key: 'F', description: 'Toggle fullscreen mode' },
19+
{ key: '?', description: 'Show keyboard shortcuts' },
20+
]
21+
22+
export function KeyboardShortcutsDialog({
23+
open,
24+
onOpenChange,
25+
}: KeyboardShortcutsDialogProps) {
26+
return (
27+
<Dialog open={open} onOpenChange={onOpenChange}>
28+
<DialogContent className="sm:max-w-md">
29+
<DialogHeader>
30+
<DialogTitle>Keyboard Shortcuts</DialogTitle>
31+
</DialogHeader>
32+
<div className="grid gap-3 py-4">
33+
{shortcuts.map((shortcut, index) => (
34+
<div
35+
key={index}
36+
className="flex items-center justify-between border-b pb-2 last:border-b-0"
37+
>
38+
<kbd className="px-3 py-1.5 text-sm font-semibold bg-gray-100 border border-gray-300 rounded-md shadow-sm dark:bg-gray-800 dark:border-gray-600">
39+
{shortcut.key}
40+
</kbd>
41+
<span className="text-sm text-gray-600 dark:text-gray-400 ml-4 flex-1 text-right">
42+
{shortcut.description}
43+
</span>
44+
</div>
45+
))}
46+
</div>
47+
</DialogContent>
48+
</Dialog>
49+
)
50+
}

0 commit comments

Comments
 (0)