Initial implementation: Bordanlage boat onboard system
Complete multiroom audio + navigation dashboard: - Docker stack: SignalK, Snapcast (4 zones), librespot, shairport-sync, Mopidy, Jellyfin, Portainer - React 18 + Vite dashboard with nautical dark theme - Full mock system (SignalK NMEA simulation, Snapcast zones, Mopidy player) - Real API clients for all services with reconnect logic - SVG instruments: Compass, WindRose, Gauge, DepthSounder, SpeedLog - Pages: Overview, Navigation, Audio (zones/radio/library), Systems - Dev mode runs fully without hardware (make dev) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
53
bordanlage/dashboard/src/components/audio/ZoneCard.jsx
Normal file
53
bordanlage/dashboard/src/components/audio/ZoneCard.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
export default function ZoneCard({ zone, onVolume, onMute, onSource }) {
|
||||
const { id, name, active, volume, muted, source } = zone
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
...styles.card,
|
||||
opacity: active ? 1 : 0.45,
|
||||
borderColor: active && !muted ? 'var(--accent)' : 'var(--border)',
|
||||
}}>
|
||||
<div style={styles.header}>
|
||||
<span style={styles.name}>{name}</span>
|
||||
<div style={styles.badges}>
|
||||
<span style={{ ...styles.badge, background: active ? '#34d39922' : 'var(--border)', color: active ? 'var(--success)' : 'var(--muted)' }}>
|
||||
{active ? 'ON' : 'OFF'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={styles.source}>{source}</div>
|
||||
|
||||
<div style={styles.volumeRow}>
|
||||
<button style={styles.muteBtn} onClick={() => onMute(id, !muted)}>
|
||||
{muted ? '🔇' : '🔊'}
|
||||
</button>
|
||||
<input
|
||||
type="range" min={0} max={100} value={muted ? 0 : volume}
|
||||
onChange={e => onVolume(id, Number(e.target.value))}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={styles.volVal}>{muted ? '–' : volume}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = {
|
||||
card: {
|
||||
padding: 14,
|
||||
background: 'var(--surface)',
|
||||
borderRadius: 'var(--radius)',
|
||||
border: '1px solid var(--border)',
|
||||
display: 'flex', flexDirection: 'column', gap: 10,
|
||||
transition: 'border-color 0.2s, opacity 0.2s',
|
||||
},
|
||||
header: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' },
|
||||
name: { fontWeight: 600, fontSize: 14 },
|
||||
badges: { display: 'flex', gap: 4 },
|
||||
badge: { fontSize: 10, padding: '2px 6px', borderRadius: 4, fontWeight: 700 },
|
||||
source: { fontSize: 11, color: 'var(--muted)' },
|
||||
volumeRow: { display: 'flex', alignItems: 'center', gap: 10 },
|
||||
muteBtn: { fontSize: 18, minWidth: 44, minHeight: 44 },
|
||||
volVal: { fontFamily: 'var(--font-mono)', fontSize: 13, minWidth: 26, textAlign: 'right', color: 'var(--muted)' },
|
||||
}
|
||||
Reference in New Issue
Block a user