Some checks failed
Build & Publish / publish (push) Failing after 5s
Add autoRefresh/setAutoRefresh to GlobalFilterContext, persisted in localStorage. TopBar shows a LIVE/PAUSED toggle button with pulsing dot indicator. Consumers can use useGlobalFilters().autoRefresh to conditionally enable/disable polling intervals. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
128 lines
4.5 KiB
TypeScript
128 lines
4.5 KiB
TypeScript
import styles from './TopBar.module.css'
|
|
import { Breadcrumb } from '../../composites/Breadcrumb/Breadcrumb'
|
|
import { Dropdown } from '../../composites/Dropdown/Dropdown'
|
|
import { Avatar } from '../../primitives/Avatar/Avatar'
|
|
import { ButtonGroup } from '../../primitives/ButtonGroup/ButtonGroup'
|
|
import type { ButtonGroupItem } from '../../primitives/ButtonGroup/ButtonGroup'
|
|
import { TimeRangeDropdown } from '../../primitives/TimeRangeDropdown/TimeRangeDropdown'
|
|
import { useGlobalFilters } from '../../providers/GlobalFilterProvider'
|
|
import { useCommandPalette } from '../../providers/CommandPaletteProvider'
|
|
import { useTheme } from '../../providers/ThemeProvider'
|
|
|
|
interface BreadcrumbItem {
|
|
label: string
|
|
href?: string
|
|
}
|
|
|
|
interface TopBarProps {
|
|
breadcrumb: BreadcrumbItem[]
|
|
environment?: string
|
|
user?: { name: string }
|
|
onLogout?: () => void
|
|
className?: string
|
|
}
|
|
|
|
const STATUS_ITEMS: ButtonGroupItem[] = [
|
|
{ value: 'completed', label: 'OK', color: 'var(--success)' },
|
|
{ value: 'warning', label: 'Warn', color: 'var(--warning)' },
|
|
{ value: 'failed', label: 'Error', color: 'var(--error)' },
|
|
{ value: 'running', label: 'Running', color: 'var(--running)' },
|
|
]
|
|
|
|
export function TopBar({
|
|
breadcrumb,
|
|
environment,
|
|
user,
|
|
onLogout,
|
|
className,
|
|
}: TopBarProps) {
|
|
const globalFilters = useGlobalFilters()
|
|
const commandPalette = useCommandPalette()
|
|
const { theme, toggleTheme } = useTheme()
|
|
|
|
return (
|
|
<header className={`${styles.topbar} ${className ?? ''}`}>
|
|
{/* Left: Breadcrumb */}
|
|
<Breadcrumb items={breadcrumb} className={styles.breadcrumb} />
|
|
|
|
{/* Search trigger */}
|
|
<button
|
|
className={styles.search}
|
|
onClick={() => commandPalette.setOpen(true)}
|
|
type="button"
|
|
aria-label="Open search"
|
|
>
|
|
<span className={styles.searchIcon} aria-hidden="true">
|
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<circle cx="11" cy="11" r="8" />
|
|
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
|
</svg>
|
|
</span>
|
|
<span className={styles.searchPlaceholder}>Search... ⌘K</span>
|
|
<span className={styles.kbd}>Ctrl+K</span>
|
|
</button>
|
|
|
|
{/* Status filter group */}
|
|
<ButtonGroup
|
|
items={STATUS_ITEMS}
|
|
value={globalFilters.statusFilters}
|
|
onChange={(selected) => {
|
|
// Sync with global filter by toggling the diff
|
|
const current = globalFilters.statusFilters
|
|
for (const v of selected) {
|
|
if (!current.has(v)) globalFilters.toggleStatus(v as 'completed' | 'warning' | 'failed' | 'running')
|
|
}
|
|
for (const v of current) {
|
|
if (!selected.has(v)) globalFilters.toggleStatus(v as 'completed' | 'warning' | 'failed' | 'running')
|
|
}
|
|
}}
|
|
/>
|
|
|
|
{/* Time range pills */}
|
|
<TimeRangeDropdown
|
|
value={globalFilters.timeRange}
|
|
onChange={globalFilters.setTimeRange}
|
|
/>
|
|
|
|
{/* Right: auto-refresh toggle, theme toggle, env badge, user */}
|
|
<div className={styles.right}>
|
|
<button
|
|
className={`${styles.liveToggle} ${globalFilters.autoRefresh ? styles.liveToggleActive : ''}`}
|
|
onClick={() => globalFilters.setAutoRefresh(!globalFilters.autoRefresh)}
|
|
type="button"
|
|
aria-label={globalFilters.autoRefresh ? 'Disable auto-refresh' : 'Enable auto-refresh'}
|
|
title={globalFilters.autoRefresh ? 'Auto-refresh is on — click to pause' : 'Auto-refresh is paused — click to resume'}
|
|
>
|
|
<span className={styles.liveDot} />
|
|
{globalFilters.autoRefresh ? 'LIVE' : 'PAUSED'}
|
|
</button>
|
|
<button
|
|
className={styles.themeToggle}
|
|
onClick={toggleTheme}
|
|
type="button"
|
|
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
|
|
title={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
|
|
>
|
|
{theme === 'light' ? '\u263E' : '\u2600'}
|
|
</button>
|
|
{environment && (
|
|
<span className={styles.env}>{environment}</span>
|
|
)}
|
|
{user && (
|
|
<Dropdown
|
|
trigger={
|
|
<div className={styles.user}>
|
|
<span className={styles.userName}>{user.name}</span>
|
|
<Avatar name={user.name} size="md" />
|
|
</div>
|
|
}
|
|
items={[
|
|
{ label: 'Logout', icon: '\u23FB', onClick: onLogout },
|
|
]}
|
|
/>
|
|
)}
|
|
</div>
|
|
</header>
|
|
)
|
|
}
|