85 lines
2.1 KiB
TypeScript
85 lines
2.1 KiB
TypeScript
import { ReactNode } from 'react';
|
|
import { Box } from './Box';
|
|
|
|
export interface LayoutProps {
|
|
children: ReactNode;
|
|
header?: ReactNode;
|
|
footer?: ReactNode;
|
|
sidebar?: ReactNode;
|
|
/**
|
|
* Whether the sidebar should be fixed to the side.
|
|
* If true, the main content will be offset by the sidebar width.
|
|
*/
|
|
fixedSidebar?: boolean;
|
|
/**
|
|
* Whether the header should be fixed to the top.
|
|
* If true, the main content will be offset by the header height.
|
|
*/
|
|
fixedHeader?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Layout is the canonical app frame component.
|
|
* It orchestrates the high-level structure: Header, Sidebar, Main Content, and Footer.
|
|
*/
|
|
export const Layout = ({
|
|
children,
|
|
header,
|
|
footer,
|
|
sidebar,
|
|
fixedSidebar = false,
|
|
fixedHeader = false
|
|
}: LayoutProps) => {
|
|
return (
|
|
<Box display="flex" flexDirection="col" minHeight="100vh">
|
|
{header && (
|
|
<Box
|
|
as="header"
|
|
position={fixedHeader ? "fixed" : "relative"}
|
|
top={fixedHeader ? 0 : undefined}
|
|
left={fixedHeader ? 0 : undefined}
|
|
right={fixedHeader ? 0 : undefined}
|
|
zIndex={50}
|
|
>
|
|
{header}
|
|
</Box>
|
|
)}
|
|
|
|
<Box display="flex" flex={1} marginTop={fixedHeader ? 14 : undefined}>
|
|
{sidebar && (
|
|
<Box
|
|
as="aside"
|
|
width="64"
|
|
display={{ base: 'none', lg: 'block' }}
|
|
position={fixedSidebar ? "fixed" : "relative"}
|
|
top={fixedSidebar ? (fixedHeader ? 14 : 0) : undefined}
|
|
bottom={fixedSidebar ? 0 : undefined}
|
|
left={fixedSidebar ? 0 : undefined}
|
|
zIndex={40}
|
|
>
|
|
{sidebar}
|
|
</Box>
|
|
)}
|
|
|
|
<Box
|
|
as="main"
|
|
flex={1}
|
|
display="flex"
|
|
flexDirection="col"
|
|
marginLeft={fixedSidebar ? { lg: 64 } : undefined}
|
|
>
|
|
<Box flex={1}>
|
|
{children}
|
|
</Box>
|
|
|
|
{footer && (
|
|
<Box as="footer">
|
|
{footer}
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
};
|