feat(frontend): add Tabs primitive + useHashTab hook
- useHashTab: pure-TS hook reading window.location.hash, falls back to defaultId, hashchange listener cleaned up on unmount - Tabs: <Tabs items activeId onChange> with tab-underline / tab-underline-active recipes and count-pill (rounded-pill exception per DESIGN.md) - index.css: tab-underline*, tab-count-pill*, alert-*, table-compact recipes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
25
frontend/src/hooks/useHashTab.ts
Normal file
25
frontend/src/hooks/useHashTab.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
function readHash(defaultId: string): string {
|
||||
const hash = window.location.hash.slice(1); // strip leading '#'
|
||||
return hash || defaultId;
|
||||
}
|
||||
|
||||
export function useHashTab(defaultId: string): [string, (id: string) => void] {
|
||||
const [activeId, setActiveId] = useState<string>(() => readHash(defaultId));
|
||||
|
||||
useEffect(() => {
|
||||
function onHashChange() {
|
||||
setActiveId(readHash(defaultId));
|
||||
}
|
||||
window.addEventListener('hashchange', onHashChange);
|
||||
return () => window.removeEventListener('hashchange', onHashChange);
|
||||
}, [defaultId]);
|
||||
|
||||
const navigate = useCallback((id: string) => {
|
||||
window.location.hash = id;
|
||||
setActiveId(id);
|
||||
}, []);
|
||||
|
||||
return [activeId, navigate];
|
||||
}
|
||||
Reference in New Issue
Block a user