Spaces:
Running
Running
| import React, { useCallback } from 'react'; | |
| import type { BluetoothDevice, Action } from '../types.ts'; | |
| import { DeviceStatus } from '../types.ts'; | |
| import { DeviceCard } from './DeviceCard.tsx'; | |
| import { ScanIcon } from './icons.tsx'; | |
| interface DeviceScannerProps { | |
| devices: BluetoothDevice[]; | |
| onScan: () => void; | |
| dispatch: React.Dispatch<Action>; | |
| isScanning: boolean; | |
| } | |
| export const DeviceScanner: React.FC<DeviceScannerProps> = ({ devices, onScan, dispatch, isScanning }) => { | |
| const handleConnect = useCallback((id: string) => { | |
| dispatch({ type: 'UPDATE_STATUS', payload: { id, status: DeviceStatus.Connecting } }); | |
| setTimeout(() => { | |
| const randomLatency = Math.floor(Math.random() * 50) + 5; | |
| dispatch({ type: 'UPDATE_STATUS', payload: { id, status: DeviceStatus.Connected, latency: randomLatency } }); | |
| }, 1500); | |
| }, [dispatch]); | |
| return ( | |
| <div className="bg-slate-800/50 rounded-lg p-6 shadow-lg h-full flex flex-col"> | |
| <div className="flex justify-between items-center mb-4"> | |
| <h2 className="text-xl font-semibold text-white">Available Devices</h2> | |
| <button | |
| onClick={onScan} | |
| disabled={isScanning} | |
| className="flex items-center space-x-2 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:bg-slate-600 disabled:cursor-not-allowed transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-slate-900" | |
| aria-label={isScanning ? 'Scanning for devices' : 'Scan for new devices'} | |
| > | |
| {isScanning ? ( | |
| <svg className="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> | |
| <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> | |
| <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> | |
| </svg> | |
| ) : ( | |
| <ScanIcon className="h-5 w-5" /> | |
| )} | |
| <span>{isScanning ? 'Scanning...' : 'Scan'}</span> | |
| </button> | |
| </div> | |
| <div className="flex-grow overflow-y-auto pr-2 -mr-2 space-y-3"> | |
| {isScanning && devices.length === 0 && ( | |
| <div className="text-center py-10 text-slate-400"> | |
| <p>Searching for nearby devices...</p> | |
| </div> | |
| )} | |
| {!isScanning && devices.length === 0 && ( | |
| <div className="text-center py-10 text-slate-400"> | |
| <p>No devices found. Try scanning again.</p> | |
| </div> | |
| )} | |
| {devices.map(device => ( | |
| <DeviceCard key={device.id} device={device} onConnect={handleConnect} /> | |
| ))} | |
| </div> | |
| </div> | |
| ); | |
| }; |