feat: US-004 — App shell layout and sidebar navigation
- Add electron-store@8 for sidebar collapse state persistence via settings tRPC router - Add @fontsource/geist for self-hosted Geist font (remove Google Fonts CDN) - Add right-edge vertical 'keep scrolling for AI' label with chevron-down in all views - Wire AppShell collapse toggle to settings.setSidebarCollapsed tRPC mutation - Fix ESLint config with eslint-import-resolver-typescript to resolve @/* path aliases Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,8 +6,10 @@ import {
|
||||
ClipboardCheck,
|
||||
FolderKanban,
|
||||
PanelLeft,
|
||||
ChevronDown,
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { trpc } from '@/lib/trpc';
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ to: '/', icon: House, label: 'Home' },
|
||||
@@ -21,10 +23,25 @@ interface AppShellProps {
|
||||
}
|
||||
|
||||
export function AppShell({ children }: AppShellProps) {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const collapsedQuery = trpc.settings.getSidebarCollapsed.useQuery(undefined, {
|
||||
staleTime: Infinity,
|
||||
});
|
||||
const setSidebarCollapsedMutation = trpc.settings.setSidebarCollapsed.useMutation();
|
||||
|
||||
// localCollapsed tracks user toggles after load; null means "use server value"
|
||||
const [localCollapsed, setLocalCollapsed] = useState<boolean | null>(null);
|
||||
|
||||
const routerState = useRouterState();
|
||||
const currentPath = routerState.location.pathname;
|
||||
|
||||
const collapsed = localCollapsed !== null ? localCollapsed : (collapsedQuery.data ?? false);
|
||||
|
||||
const handleToggle = () => {
|
||||
const next = !collapsed;
|
||||
setLocalCollapsed(next);
|
||||
setSidebarCollapsedMutation.mutate({ collapsed: next });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen w-screen overflow-hidden bg-background">
|
||||
{/* Sidebar */}
|
||||
@@ -90,7 +107,7 @@ export function AppShell({ children }: AppShellProps) {
|
||||
{/* Collapse toggle */}
|
||||
<div className="px-2 pb-3 shrink-0">
|
||||
<button
|
||||
onClick={() => setCollapsed((c) => !c)}
|
||||
onClick={handleToggle}
|
||||
className={cn(
|
||||
'flex items-center gap-2 h-8 px-3 rounded-md text-sm text-sidebar-foreground w-full',
|
||||
'hover:bg-sidebar-accent transition-colors',
|
||||
@@ -107,6 +124,19 @@ export function AppShell({ children }: AppShellProps) {
|
||||
{/* Main content */}
|
||||
<main className="flex-1 min-w-0 overflow-hidden relative">
|
||||
{children}
|
||||
|
||||
{/* Right-edge vertical 'keep scrolling for AI' affordance (non-interactive) */}
|
||||
<div className="absolute right-0 top-0 bottom-0 flex items-end justify-center pb-8 pointer-events-none select-none">
|
||||
<div className="flex flex-col items-center gap-1.5 pr-2">
|
||||
<span
|
||||
className="text-[9px] text-muted-foreground/30 tracking-widest uppercase font-medium"
|
||||
style={{ writingMode: 'vertical-rl', transform: 'rotate(180deg)' }}
|
||||
>
|
||||
keep scrolling for AI
|
||||
</span>
|
||||
<ChevronDown size={10} className="text-muted-foreground/30" />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user