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.
120 lines
3.2 KiB
TypeScript
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>
|
|
);
|
|
}
|