Skip to content

Commit 9096359

Browse files
committed
Add invisible input
1 parent 22931a3 commit 9096359

File tree

3 files changed

+95
-0
lines changed

3 files changed

+95
-0
lines changed

components/ControlPanel.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
import { useRoomState } from '../utilities/useRoomState'
4242
import { Countdown } from './Countdown'
4343
import { IdSpecificThemeProvider } from './IdSpecificThemeProvider'
44+
import { InvisibleTimeInput } from './InvisibleTimeInput'
4445
import { CastButton } from './chromecast/sender/components/CastButton'
4546
import { useIsChromecastSenderAvailable } from './chromecast/sender/useIsChromecastAvailable'
4647

@@ -449,6 +450,14 @@ export const ControlPanel: React.FunctionComponent<ControlPanelProps> = ({
449450
Set
450451
</Button>
451452
</Grid>
453+
<InvisibleTimeInput
454+
onInput={(seconds) => {
455+
setCountdown(roomDocumentReference, seconds)
456+
}}
457+
onTogglePause={() => {
458+
togglePaused(roomState, roomDocumentReference)
459+
}}
460+
/>
452461
</>
453462
)}
454463
</Grid>

components/InvisibleTimeInput.tsx

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { useEffect, useState, type FunctionComponent } from 'react'
2+
3+
export type InvisibleTimeInputProps = {
4+
onInput: (seconds: number) => void
5+
onTogglePause: () => void
6+
}
7+
8+
export const InvisibleTimeInput: FunctionComponent<InvisibleTimeInputProps> = ({
9+
onInput,
10+
onTogglePause,
11+
}) => {
12+
const [input, setInput] = useState('')
13+
14+
useEffect(() => {
15+
const timer = setTimeout(() => {
16+
setInput('')
17+
}, 5000)
18+
19+
return () => {
20+
clearTimeout(timer)
21+
}
22+
}, [input])
23+
24+
useEffect(() => {
25+
const callback = (event: KeyboardEvent) => {
26+
const isInteractive =
27+
event.target instanceof HTMLInputElement ||
28+
event.target instanceof HTMLTextAreaElement ||
29+
event.target instanceof HTMLButtonElement ||
30+
event.target instanceof HTMLAnchorElement ||
31+
event.target instanceof HTMLSelectElement
32+
if (isInteractive) {
33+
return
34+
}
35+
if (event.key === 'Enter') {
36+
if (input.length === 0) {
37+
onTogglePause()
38+
} else {
39+
const [seconds = 0, minutes = 0, hours = 0] = input
40+
.split(':')
41+
.map((part) => parseInt(part, 10) || 0)
42+
.reverse()
43+
const totalSeconds = seconds + minutes * 60 + hours * 60 * 60
44+
onInput(totalSeconds)
45+
setInput('')
46+
}
47+
} else if (event.code === 'Delete') {
48+
setInput('')
49+
} else if (event.code === 'Backspace') {
50+
setInput((input) => input.slice(0, -1))
51+
} else {
52+
const number = (() => {
53+
if (event.code.startsWith('Digit') && event.code.length === 6) {
54+
return event.code.substring(5)
55+
}
56+
if (event.code.startsWith('Numpad') && event.code.length === 7) {
57+
return event.code.substring(6)
58+
}
59+
return null
60+
})()
61+
setInput((input) => {
62+
if (number !== null) {
63+
return input + number
64+
}
65+
if (input.length > 0 && input.split(':').length <= 2) {
66+
return input + ':'
67+
}
68+
return input
69+
})
70+
}
71+
}
72+
window.addEventListener('keydown', callback)
73+
return () => {
74+
window.removeEventListener('keydown', callback)
75+
}
76+
}, [input, input.length, onInput, onTogglePause])
77+
78+
return <div className="invisibleInput">{input}</div>
79+
}

styles/globals.css

+7
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,10 @@ body:has(.fullScreenCountdown.is-iframe) {
129129
transition-duration: 0.3s;
130130
}
131131
}
132+
133+
.invisibleInput {
134+
position: fixed;
135+
inset: 0 auto auto 0;
136+
z-index: 10;
137+
padding: 0.125rem;
138+
}

0 commit comments

Comments
 (0)