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:
dwindown
2025-11-06 15:34:00 +07:00
parent 99748ca202
commit 2ec76c7dec
4 changed files with 98 additions and 28 deletions

View File

@@ -0,0 +1,39 @@
import React, { createContext, useContext, useState, ReactNode } from 'react';
interface PageHeaderContextType {
title: string | null;
action: ReactNode | null;
setPageHeader: (title: string | null, action?: ReactNode) => void;
clearPageHeader: () => void;
}
const PageHeaderContext = createContext<PageHeaderContextType | undefined>(undefined);
export function PageHeaderProvider({ children }: { children: ReactNode }) {
const [title, setTitle] = useState<string | null>(null);
const [action, setAction] = useState<ReactNode | null>(null);
const setPageHeader = (newTitle: string | null, newAction?: ReactNode) => {
setTitle(newTitle);
setAction(newAction || null);
};
const clearPageHeader = () => {
setTitle(null);
setAction(null);
};
return (
<PageHeaderContext.Provider value={{ title, action, setPageHeader, clearPageHeader }}>
{children}
</PageHeaderContext.Provider>
);
}
export function usePageHeader() {
const context = useContext(PageHeaderContext);
if (!context) {
throw new Error('usePageHeader must be used within PageHeaderProvider');
}
return context;
}