File
Blob: worker/src/client/exhibit/controls/led-controls.tsx
| 1 | import type { CSSProperties } from 'react'; |
| 2 | import type { RGB } from '@/shared/contracts/robot.ts'; |
| 3 | |
| 4 | const colors: { name: string; rgb: RGB; display: string }[] = [ |
| 5 | { name: 'coral', rgb: [32, 3, 0], display: '#db704f' }, |
| 6 | { name: 'amber', rgb: [28, 18, 0], display: '#c89535' }, |
| 7 | { name: 'green', rgb: [0, 24, 5], display: '#4b9471' }, |
| 8 | { name: 'turquoise', rgb: [0, 24, 24], display: 'oklch(0.63 0.085 195)' }, |
| 9 | { name: 'blue', rgb: [0, 10, 32], display: '#548abb' }, |
| 10 | { name: 'violet', rgb: [18, 0, 32], display: 'oklch(0.61 0.105 305)' }, |
| 11 | { name: 'rose', rgb: [32, 0, 12], display: 'oklch(0.65 0.105 350)' }, |
| 12 | { name: 'off', rgb: [0, 0, 0], display: '#e5e5dd' }, |
| 13 | ]; |
| 14 | |
| 15 | export function LedControls({ |
| 16 | led, |
| 17 | disabled, |
| 18 | pending, |
| 19 | onChange, |
| 20 | }: { |
| 21 | led?: RGB; |
| 22 | disabled: boolean; |
| 23 | pending: boolean; |
| 24 | onChange: (rgb: RGB) => void; |
| 25 | }) { |
| 26 | return ( |
| 27 | <fieldset |
| 28 | className="mt-3 mb-1 min-w-0 border-0 p-0 [&_legend]:font-mono [&_legend]:text-[9px] [&_legend]:tracking-wide [&_legend]:text-muted" |
| 29 | disabled={disabled} |
| 30 | > |
| 31 | <legend>ONBOARD LED · GPIO38</legend> |
| 32 | <div className="mt-2 grid w-fit grid-cols-4 gap-1 [&_button]:flex [&_button]:size-11 [&_button]:items-center [&_button]:justify-center [&_button]:rounded-md [&_button]:border [&_button]:border-transparent [&_button]:p-1 [&_button]:text-xl [&_button[aria-pressed=true]]:border-ink [&_button>span]:size-6 [&_button>span]:rounded-full [&_button>span]:bg-(--swatch) [&_button>span]:ring-1 [&_button>span]:ring-inset [&_button>span]:ring-ink/10"> |
| 33 | {colors.map((color) => ( |
| 34 | <button |
| 35 | type="button" |
| 36 | key={color.name} |
| 37 | aria-label={color.name === 'off' ? 'Turn LED off' : `Set LED to ${color.name}`} |
| 38 | aria-disabled={disabled || pending} |
| 39 | aria-pressed={led?.join(',') === color.rgb.join(',')} |
| 40 | data-rgb={color.rgb.join(',')} |
| 41 | style={{ '--swatch': color.display } as CSSProperties} |
| 42 | onClick={() => { |
| 43 | if (!disabled && !pending) onChange(color.rgb); |
| 44 | }} |
| 45 | > |
| 46 | {color.name === 'off' ? '○' : <span aria-hidden="true" />} |
| 47 | </button> |
| 48 | ))} |
| 49 | </div> |
| 50 | </fieldset> |
| 51 | ); |
| 52 | } |