Move page titles into the top header bar to reclaim vertical space
PageHeader now sets title via context instead of rendering inline. TopBar displays the current page title and subtitle in the left area that was previously an empty spacer. Content area gains ~40px of vertical space on every page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { DataProvider } from '@/lib/data-context';
|
import { DataProvider } from '@/lib/data-context';
|
||||||
|
import { PageTitleProvider } from '@/lib/page-title-context';
|
||||||
import { Sidebar } from './Sidebar';
|
import { Sidebar } from './Sidebar';
|
||||||
import { TopBar } from './TopBar';
|
import { TopBar } from './TopBar';
|
||||||
import { MobileActivityFAB } from '@/components/ui/MobileActivityFAB';
|
import { MobileActivityFAB } from '@/components/ui/MobileActivityFAB';
|
||||||
@@ -10,6 +11,7 @@ import { DashboardData, DashboardConfig } from '@/types/data';
|
|||||||
export function DashboardShell({ children, initialData, config }: { children: ReactNode; initialData: DashboardData; config: DashboardConfig }) {
|
export function DashboardShell({ children, initialData, config }: { children: ReactNode; initialData: DashboardData; config: DashboardConfig }) {
|
||||||
return (
|
return (
|
||||||
<DataProvider initialData={initialData} config={config}>
|
<DataProvider initialData={initialData} config={config}>
|
||||||
|
<PageTitleProvider>
|
||||||
<div className="flex h-screen overflow-hidden">
|
<div className="flex h-screen overflow-hidden">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<div className="flex-1 flex flex-col min-w-0">
|
<div className="flex-1 flex flex-col min-w-0">
|
||||||
@@ -20,6 +22,7 @@ export function DashboardShell({ children, initialData, config }: { children: Re
|
|||||||
</div>
|
</div>
|
||||||
<MobileActivityFAB />
|
<MobileActivityFAB />
|
||||||
</div>
|
</div>
|
||||||
|
</PageTitleProvider>
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useData, DatePreset } from '@/lib/data-context';
|
import { useData, DatePreset } from '@/lib/data-context';
|
||||||
|
import { usePageTitle } from '@/lib/page-title-context';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { useState, useRef, useEffect } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import { DISTRICTS } from '@/types/data';
|
import { DISTRICTS } from '@/types/data';
|
||||||
@@ -15,6 +16,7 @@ const DATE_PRESETS: { value: DatePreset; label: string }[] = [
|
|||||||
|
|
||||||
export function TopBar() {
|
export function TopBar() {
|
||||||
const { filters, setDistricts, setDatePreset, refresh, isLoading, lastRefreshed, clearAllFilters, clearCrossFilter } = useData();
|
const { filters, setDistricts, setDatePreset, refresh, isLoading, lastRefreshed, clearAllFilters, clearCrossFilter } = useData();
|
||||||
|
const { title, subtitle } = usePageTitle();
|
||||||
const [showDistrictDropdown, setShowDistrictDropdown] = useState(false);
|
const [showDistrictDropdown, setShowDistrictDropdown] = useState(false);
|
||||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -51,8 +53,15 @@ export function TopBar() {
|
|||||||
<span className="font-bold text-sm text-brand-dark-blue">AgentMinder</span>
|
<span className="font-bold text-sm text-brand-dark-blue">AgentMinder</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Desktop spacer */}
|
{/* Page title */}
|
||||||
<div className="hidden lg:block flex-1" />
|
<div className="hidden lg:flex flex-1 items-center gap-2 min-w-0">
|
||||||
|
{title && (
|
||||||
|
<>
|
||||||
|
<h1 className="text-sm font-bold text-foreground truncate">{title}</h1>
|
||||||
|
{subtitle && <span className="text-xs text-muted truncate hidden xl:inline">— {subtitle}</span>}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Active filters indicator */}
|
{/* Active filters indicator */}
|
||||||
{filters.crossFilter && (
|
{filters.crossFilter && (
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { usePageTitle } from '@/lib/page-title-context';
|
||||||
|
|
||||||
interface PageHeaderProps {
|
interface PageHeaderProps {
|
||||||
title: string;
|
title: string;
|
||||||
subtitle: string;
|
subtitle: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PageHeader({ title, subtitle }: PageHeaderProps) {
|
export function PageHeader({ title, subtitle }: PageHeaderProps) {
|
||||||
return (
|
const { setPageTitle } = usePageTitle();
|
||||||
<div className="mb-6">
|
|
||||||
<h1 className="text-xl font-bold text-foreground">{title}</h1>
|
useEffect(() => {
|
||||||
<p className="text-sm text-muted mt-0.5">{subtitle}</p>
|
setPageTitle(title, subtitle);
|
||||||
</div>
|
return () => setPageTitle('', '');
|
||||||
);
|
}, [title, subtitle, setPageTitle]);
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
39
src/lib/page-title-context.tsx
Normal file
39
src/lib/page-title-context.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { createContext, useContext, useState, useCallback, ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface PageTitleState {
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageTitleContextValue extends PageTitleState {
|
||||||
|
setPageTitle: (title: string, subtitle: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PageTitleContext = createContext<PageTitleContextValue>({
|
||||||
|
title: '',
|
||||||
|
subtitle: '',
|
||||||
|
setPageTitle: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function PageTitleProvider({ children }: { children: ReactNode }) {
|
||||||
|
const [state, setState] = useState<PageTitleState>({ title: '', subtitle: '' });
|
||||||
|
|
||||||
|
const setPageTitle = useCallback((title: string, subtitle: string) => {
|
||||||
|
setState(prev => {
|
||||||
|
if (prev.title === title && prev.subtitle === subtitle) return prev;
|
||||||
|
return { title, subtitle };
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageTitleContext.Provider value={{ ...state, setPageTitle }}>
|
||||||
|
{children}
|
||||||
|
</PageTitleContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePageTitle() {
|
||||||
|
return useContext(PageTitleContext);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user