refactor: Move page header outside content container using context
Problem: - Page header inside scrollable content container - Complex sticky positioning logic - Different behavior in different modes Better Architecture: Move page header to same level as submenu, outside scroll container Structure: <main flex flex-col> <SubmenuBar sticky> ← Sticky outside scroll <PageHeader sticky> ← Sticky outside scroll ✅ <div overflow-auto> ← Only content scrolls <AppRoutes /> Implementation: 1. PageHeaderContext - Global state for page header - title: string - action: ReactNode (e.g., Save button) - setPageHeader() / clearPageHeader() 2. PageHeader Component - Renders at app level - Positioned after submenu - Sticky top-[49px] (below submenu) - Boxed layout (max-w-5xl, centered) - Consumes context 3. SettingsLayout - Sets header via context - useEffect to set/clear header - No inline sticky header - Cleaner component Benefits: ✅ Page header outside scroll container ✅ Sticky works consistently (no mode detection) ✅ Submenu layout preserved (justify-start) ✅ Page header uses page layout (boxed, centered) ✅ Separation of concerns ✅ Reusable for any page that needs sticky header Layout Hierarchy: ┌─────────────────────────────────────┐ │ <main flex flex-col> │ │ ┌─────────────────────────────┐ │ │ │ SubmenuBar (sticky) │ │ ← justify-start │ ├─────────────────────────────┤ │ │ │ PageHeader (sticky) │ │ ← max-w-5xl centered │ ├─────────────────────────────┤ │ │ │ <div overflow-auto> │ │ │ │ Content (scrolls) │ │ │ └─────────────────────────────┘ │ └─────────────────────────────────────┘ Files Created: - PageHeaderContext.tsx: Context provider - PageHeader.tsx: Header component Files Modified: - App.tsx: Added PageHeader after submenu in all layouts - SettingsLayout.tsx: Use context instead of inline header Result: ✅ Clean architecture ✅ Consistent sticky behavior ✅ No mode-specific logic ✅ Reusable pattern
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { usePageHeader } from '@/contexts/PageHeaderContext';
|
||||
|
||||
interface SettingsLayoutProps {
|
||||
title: string;
|
||||
@@ -22,6 +23,7 @@ export function SettingsLayout({
|
||||
action,
|
||||
}: SettingsLayoutProps) {
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const { setPageHeader, clearPageHeader } = usePageHeader();
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!onSave) return;
|
||||
@@ -33,32 +35,35 @@ export function SettingsLayout({
|
||||
}
|
||||
};
|
||||
|
||||
// Set page header when component mounts
|
||||
useEffect(() => {
|
||||
if (onSave) {
|
||||
setPageHeader(
|
||||
title,
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={isSaving || isLoading}
|
||||
size="sm"
|
||||
>
|
||||
{isSaving ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Saving...
|
||||
</>
|
||||
) : (
|
||||
saveLabel
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
} else {
|
||||
clearPageHeader();
|
||||
}
|
||||
|
||||
return () => clearPageHeader();
|
||||
}, [title, onSave, isSaving, isLoading, saveLabel]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Sticky Header with Save Button - Edge to edge */}
|
||||
{onSave && (
|
||||
<div className="sticky top-0 z-10 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 -mx-4 px-4 mb-6">
|
||||
<div className="container px-0 max-w-5xl mx-auto py-3 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold">{title}</h1>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={isSaving || isLoading}
|
||||
size="sm"
|
||||
>
|
||||
{isSaving ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Saving...
|
||||
</>
|
||||
) : (
|
||||
saveLabel
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Content */}
|
||||
<div className="container px-0 max-w-5xl mx-auto">
|
||||
|
||||
Reference in New Issue
Block a user