1- import React , { useEffect , useMemo , useState } from 'react'
1+ import React , { useEffect , useMemo , useState , useCallback } from 'react'
22import { DeviceOptions , DeviceFramesetProps } from './DeviceFrameset'
33import { DeviceName , DeviceNames } from './DeviceOptions'
44
55export type DeviceSelectorProps = React . HTMLAttributes < HTMLDivElement > & {
66 banDevices ?: DeviceName [ ] ,
77 children : ( props : DeviceFramesetProps ) => React . ReactNode ,
8+ value ?: DeviceName ,
9+ onChange ?: ( deviceName : DeviceName ) => void ,
810}
911
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 } ) {
1113 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+
1329 const [ showMenu , setShowMenu ] = useState ( true )
1430
15- const deviceName = useMemo ( ( ) => deviceNames [ selectedDeviceIndex ] , [ selectedDeviceIndex ] )
31+ const { colors , hasLandscape , width , height } = useMemo ( ( ) => DeviceOptions [ selectedDeviceName ] , [ selectedDeviceName ] )
1632
17- const { colors , hasLandscape , width , height } = useMemo ( ( ) => DeviceOptions [ deviceName ] , [ deviceName ] )
33+ const firstColor = useMemo ( ( ) => colors [ 0 ] ! , [ colors ] )
1834
19- const [ selectedColorIndex , setSelectedColorIndex ] = useState ( 0 )
20- const [ isLandscape , setIsLandscape ] = useState < boolean | undefined > ( undefined )
35+ const [ selectedColor , setSelectedColor ] = useState < typeof colors [ number ] > ( firstColor )
2136
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+ [ ] ,
2945 )
3046
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+ )
3261
3362 const deviceFramesetProps = useMemo (
3463 ( ) => ( {
35- device : deviceName ,
64+ device : selectedDeviceName ,
3665 color : selectedColor ,
37- landscape : isLandscape ,
66+ landscape : isLandscapeChecked ,
3867 width,
3968 height,
4069 } ) as DeviceFramesetProps ,
41- [ deviceName , selectedColor , isLandscape , width , height ] ,
70+ [ selectedDeviceName , selectedColor , isLandscapeChecked , width , height ] ,
4271 )
4372
4473 return (
4574 < div className = "device-selector" { ...divProps } >
4675 < dl >
4776 < dt >
48- < p > The Chosen: { deviceName } </ p >
77+ < p > The Chosen: { selectedDeviceName } </ p >
4978 < span
5079 className = { ( showMenu ? 'active' : '' ) }
5180 onClick = { ( ) => setShowMenu ( is => ! is ) }
5281 >
5382 show all devices
5483 </ span >
5584 </ dt >
56- { showMenu && deviceNames . map ( ( devName , index ) => (
85+ { showMenu && deviceNames . map ( ( devName ) => (
5786 < dd
5887 key = { devName }
59- onClick = { ( ) => setSelectedDeviceIndex ( index ) }
60- className = { devName === deviceName ? 'active' : '' }
88+ data-device-name = { devName }
89+ onClick = { handleSelectChange }
90+ className = { devName === selectedDeviceName ? 'active' : '' }
6191 >
6292 < input type = "radio" id = { devName } />
6393 < label htmlFor = { devName } >
@@ -66,19 +96,20 @@ export const DeviceSelector = React.memo<DeviceSelectorProps>(function DeviceSel
6696 { DeviceOptions [ devName ] . hasLandscape && (
6797 < span
6898 className = { ( devName === deviceName && isLandscape ) ? 'active' : '' }
69- onClick = { ( ) => setIsLandscape ( is => ! is ) }
99+ onClick = { handleIsLandscapeChange }
70100 >
71101 landscape
72102 </ span >
73103 ) }
74104 </ div >
75105 < ul >
76106 { DeviceOptions [ devName ] . colors . map (
77- ( color : string , index : number ) => (
107+ ( color : string ) => (
78108 < li
79109 key = { color }
80110 title = { color }
81- onClick = { ( ) => setSelectedColorIndex ( index ) }
111+ data-device-color = { color }
112+ onClick = { handleColorChange }
82113 className = { [ ( ( devName === deviceName && color === selectedColor ) ? 'active' : '' ) , color ] . join ( ' ' ) }
83114 />
84115 )
0 commit comments