Files
familysync/apps/pwa/src/components/LiveSyncIndicator.tsx
Lucas Berger 982438dc10 style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125
insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc
(singleQuote:true, semi:true, tabWidth:2, trailingComma:all,
printWidth:100). Isolated per D-13-08 for reviewability.
2026-06-11 20:35:18 -04:00

120 lines
3.2 KiB
TypeScript

/**
* LiveSyncIndicator — visual SSE connection status indicator (LIST-04, D-11).
*
* States per UI-SPEC §"LiveSyncIndicator":
* connected: 8px filled circle in --color-member-1 (#50C878), no label
* reconnecting: 8px pulsing circle in --color-text-muted + "Reconnecting…" label
* disconnected: 8px filled circle in --color-destructive + "Updates paused" label
*
* Accessibility:
* - role="status" for connected/reconnecting (polite announcements)
* - role="alert" for disconnected state (assertive announcement)
* - aria-label per UI-SPEC copywriting contract
*
* Positioned at the right end of the ListDetail header row.
* Visible only inside ListDetail (not on ListsIndex).
*/
import type { SyncState } from '../hooks/useListSSE.js';
interface LiveSyncIndicatorProps {
state: SyncState;
}
export function LiveSyncIndicator({ state }: LiveSyncIndicatorProps) {
if (state === 'connected') {
return (
<div
role="status"
aria-label="Live sync connected"
style={{
display: 'flex',
alignItems: 'center',
gap: 'var(--space-1)',
}}
>
{/* 8px filled green dot — --color-member-1 (#50C878) per UI-SPEC */}
<div
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
backgroundColor: 'var(--color-member-1, #50C878)',
flexShrink: 0,
}}
/>
</div>
);
}
if (state === 'reconnecting') {
return (
<div
role="status"
aria-label="Reconnecting"
style={{
display: 'flex',
alignItems: 'center',
gap: 'var(--space-1)',
}}
>
{/* 8px pulsing muted dot — animation via keyframes in CSS */}
<div
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
backgroundColor: 'var(--color-text-muted, #9CA3AF)',
flexShrink: 0,
animation: 'pulse 1.4s ease-in-out infinite',
}}
/>
<span
style={{
fontSize: 'var(--text-label-size, 13px)',
color: 'var(--color-text-muted, #9CA3AF)',
fontFamily: 'var(--font-family-base)',
whiteSpace: 'nowrap',
}}
>
Reconnecting
</span>
</div>
);
}
// disconnected — role="alert" for assertive announcement
return (
<div
role="alert"
aria-label="Updates paused — tap to retry"
style={{
display: 'flex',
alignItems: 'center',
gap: 'var(--space-1)',
}}
>
{/* 8px filled red dot — --color-destructive (#DC2626) */}
<div
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
backgroundColor: 'var(--color-destructive, #DC2626)',
flexShrink: 0,
}}
/>
<span
style={{
fontSize: 'var(--text-label-size, 13px)',
color: 'var(--color-destructive, #DC2626)',
fontFamily: 'var(--font-family-base)',
whiteSpace: 'nowrap',
}}
>
Updates paused
</span>
</div>
);
}