1
- import React , { useEffect , useMemo , useState } from 'react'
1
+ import React , { useEffect , useMemo , useState , useCallback } from 'react'
2
2
import { DeviceOptions , DeviceFramesetProps } from './DeviceFrameset'
3
3
import { DeviceName , DeviceNames } from './DeviceOptions'
4
4
5
5
export type DeviceSelectorProps = React . HTMLAttributes < HTMLDivElement > & {
6
6
banDevices ?: DeviceName [ ] ,
7
7
children : ( props : DeviceFramesetProps ) => React . ReactNode ,
8
+ value ?: DeviceName ,
9
+ onChange ?: ( deviceName : DeviceName ) => void ,
8
10
}
9
11
10
- export const DeviceSelector = React . memo < DeviceSelectorProps > ( function DeviceSelector ( { children, banDevices = [ ] , ...divProps } ) {
12
+ export const DeviceSelector = React . memo < DeviceSelectorProps > ( function DeviceSelector ( { children, value , onChange , banDevices = [ ] , ...divProps } ) {
11
13
const deviceNames = useMemo ( ( ) => DeviceNames . filter ( devName => ! banDevices . includes ( devName ) ) as Array < keyof typeof DeviceOptions > , [ ] )
12
- const [ selectedDeviceIndex , setSelectedDeviceIndex ] = useState ( 0 )
14
+ const [ deviceName , setDeviceName ] = useState < DeviceName > ( deviceNames [ 0 ] ?? '' )
15
+ const selectedDeviceName = useMemo ( ( ) => value ?? deviceName , [ value , deviceName ] )
16
+
17
+ const handleSelectChange = useCallback (
18
+ ( event : React . MouseEvent < HTMLElement > ) => {
19
+
20
+ const newDeviceName = event . currentTarget . dataset [ 'deviceName' ] as DeviceName
21
+ if ( ! deviceNames . includes ( newDeviceName ) ) throw new Error ( `Invalid device name for ${ newDeviceName } ` )
22
+
23
+ onChange ?.( newDeviceName )
24
+ setDeviceName ( newDeviceName )
25
+ } ,
26
+ [ deviceNames , onChange ] ,
27
+ )
28
+
13
29
const [ showMenu , setShowMenu ] = useState ( true )
14
30
15
- const deviceName = useMemo ( ( ) => deviceNames [ selectedDeviceIndex ] , [ selectedDeviceIndex ] )
31
+ const { colors , hasLandscape , width , height } = useMemo ( ( ) => DeviceOptions [ selectedDeviceName ] , [ selectedDeviceName ] )
16
32
17
- const { colors , hasLandscape , width , height } = useMemo ( ( ) => DeviceOptions [ deviceName ] , [ deviceName ] )
33
+ const firstColor = useMemo ( ( ) => colors [ 0 ] ! , [ colors ] )
18
34
19
- const [ selectedColorIndex , setSelectedColorIndex ] = useState ( 0 )
20
- const [ isLandscape , setIsLandscape ] = useState < boolean | undefined > ( undefined )
35
+ const [ selectedColor , setSelectedColor ] = useState < typeof colors [ number ] > ( firstColor )
21
36
22
- useEffect (
23
- ( ) => { setSelectedColorIndex ( 0 ) } ,
24
- [ colors ] ,
25
- )
26
- useEffect (
27
- ( ) => { setIsLandscape ( hasLandscape ? false : undefined ) } ,
28
- [ hasLandscape ] ,
37
+ const handleColorChange = useCallback (
38
+ ( event : React . MouseEvent < HTMLLIElement > ) => {
39
+
40
+ const newDeviceColor = event . currentTarget . dataset [ 'deviceColor' ] as typeof colors [ number ]
41
+
42
+ setSelectedColor ( newDeviceColor )
43
+ } ,
44
+ [ ] ,
29
45
)
30
46
31
- const selectedColor = useMemo ( ( ) => colors [ selectedColorIndex ] , [ colors , selectedColorIndex ] )
47
+ useEffect ( ( ) => { setSelectedColor ( firstColor ) } , [ firstColor ] )
48
+
49
+ const [ isLandscape , setIsLandscape ] = useState < boolean | undefined > ( undefined )
50
+
51
+ const isLandscapeChecked = useMemo ( ( ) => hasLandscape ? isLandscape : undefined , [ hasLandscape , isLandscape ] )
52
+
53
+ const handleIsLandscapeChange = useCallback (
54
+ ( ) => {
55
+ if ( ! hasLandscape ) return
56
+
57
+ setIsLandscape ( is => ! is )
58
+ } ,
59
+ [ hasLandscape ]
60
+ )
32
61
33
62
const deviceFramesetProps = useMemo (
34
63
( ) => ( {
35
- device : deviceName ,
64
+ device : selectedDeviceName ,
36
65
color : selectedColor ,
37
- landscape : isLandscape ,
66
+ landscape : isLandscapeChecked ,
38
67
width,
39
68
height,
40
69
} ) as DeviceFramesetProps ,
41
- [ deviceName , selectedColor , isLandscape , width , height ] ,
70
+ [ selectedDeviceName , selectedColor , isLandscapeChecked , width , height ] ,
42
71
)
43
72
44
73
return (
45
74
< div className = "device-selector" { ...divProps } >
46
75
< dl >
47
76
< dt >
48
- < p > The Chosen: { deviceName } </ p >
77
+ < p > The Chosen: { selectedDeviceName } </ p >
49
78
< span
50
79
className = { ( showMenu ? 'active' : '' ) }
51
80
onClick = { ( ) => setShowMenu ( is => ! is ) }
52
81
>
53
82
show all devices
54
83
</ span >
55
84
</ dt >
56
- { showMenu && deviceNames . map ( ( devName , index ) => (
85
+ { showMenu && deviceNames . map ( ( devName ) => (
57
86
< dd
58
87
key = { devName }
59
- onClick = { ( ) => setSelectedDeviceIndex ( index ) }
60
- className = { devName === deviceName ? 'active' : '' }
88
+ data-device-name = { devName }
89
+ onClick = { handleSelectChange }
90
+ className = { devName === selectedDeviceName ? 'active' : '' }
61
91
>
62
92
< input type = "radio" id = { devName } />
63
93
< label htmlFor = { devName } >
@@ -66,19 +96,20 @@ export const DeviceSelector = React.memo<DeviceSelectorProps>(function DeviceSel
66
96
{ DeviceOptions [ devName ] . hasLandscape && (
67
97
< span
68
98
className = { ( devName === deviceName && isLandscape ) ? 'active' : '' }
69
- onClick = { ( ) => setIsLandscape ( is => ! is ) }
99
+ onClick = { handleIsLandscapeChange }
70
100
>
71
101
landscape
72
102
</ span >
73
103
) }
74
104
</ div >
75
105
< ul >
76
106
{ DeviceOptions [ devName ] . colors . map (
77
- ( color : string , index : number ) => (
107
+ ( color : string ) => (
78
108
< li
79
109
key = { color }
80
110
title = { color }
81
- onClick = { ( ) => setSelectedColorIndex ( index ) }
111
+ data-device-color = { color }
112
+ onClick = { handleColorChange }
82
113
className = { [ ( ( devName === deviceName && color === selectedColor ) ? 'active' : '' ) , color ] . join ( ' ' ) }
83
114
/>
84
115
)
0 commit comments