website refactor
This commit is contained in:
@@ -7,29 +7,20 @@ interface BrandMarkProps {
|
||||
priority?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* BrandMark provides the consistent logo/wordmark for the application.
|
||||
* Aligned with "Precision Racing Minimal" theme.
|
||||
*/
|
||||
export function BrandMark({ href = '/' }: BrandMarkProps) {
|
||||
return (
|
||||
<Link href={href} variant="inherit" underline="none">
|
||||
<Box position="relative" display="inline-flex" alignItems="center">
|
||||
<Box height={{ base: '1.5rem', md: '1.75rem' }} style={{ transition: 'opacity 0.2s' }}>
|
||||
<Image
|
||||
src="/images/logos/wordmark-rectangle-dark.svg"
|
||||
alt="GridPilot"
|
||||
style={{ height: '100%', width: 'auto', display: 'block' }}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
position="absolute"
|
||||
bottom="-4px"
|
||||
left="0"
|
||||
width="0"
|
||||
height="2px"
|
||||
bg="var(--ui-color-intent-primary)"
|
||||
style={{ transition: 'width 0.2s' }}
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Image
|
||||
src="/images/logos/square-logo-dark.svg"
|
||||
alt=""
|
||||
style={{ height: '1.5rem', width: 'auto' }}
|
||||
/>
|
||||
|
||||
<Image
|
||||
src="/images/logos/wordmark-rectangle-dark.svg"
|
||||
alt="GridPilot"
|
||||
style={{ height: '1.125rem', width: 'auto' }}
|
||||
/>
|
||||
</Box>
|
||||
</Link>
|
||||
|
||||
@@ -6,78 +6,71 @@ export interface LayoutProps {
|
||||
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;
|
||||
fixedFooter?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout is the canonical app frame component.
|
||||
* It orchestrates the high-level structure: Header, Sidebar, Main Content, and Footer.
|
||||
* Redesigned for "Cockpit" layout: Sidebar is primary (full height), Header and Content sit to the right.
|
||||
*/
|
||||
export const Layout = ({
|
||||
children,
|
||||
header,
|
||||
footer,
|
||||
sidebar,
|
||||
fixedSidebar = false,
|
||||
fixedHeader = false
|
||||
fixedSidebar = true,
|
||||
fixedHeader = true,
|
||||
fixedFooter = true // Default to true for AppShellBar
|
||||
}: LayoutProps) => {
|
||||
return (
|
||||
<Box display="flex" flexDirection="col" minHeight="100vh">
|
||||
{header && (
|
||||
<Box display="flex" minHeight="100vh" className="bg-base-black text-text-high">
|
||||
{/* Sidebar - Primary Vertical Axis - Solid Background */}
|
||||
{sidebar && (
|
||||
<Box
|
||||
as="header"
|
||||
position={fixedHeader ? "fixed" : "relative"}
|
||||
top={fixedHeader ? 0 : undefined}
|
||||
left={fixedHeader ? 0 : undefined}
|
||||
right={fixedHeader ? 0 : undefined}
|
||||
as="aside"
|
||||
width="64" // 16rem / 256px
|
||||
display={{ base: 'none', lg: 'block' }}
|
||||
position={fixedSidebar ? "fixed" : "relative"}
|
||||
top={0}
|
||||
bottom={0}
|
||||
left={0}
|
||||
zIndex={50}
|
||||
className="bg-base-black border-r border-outline-steel"
|
||||
>
|
||||
{header}
|
||||
{sidebar}
|
||||
</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>
|
||||
)}
|
||||
{/* Main Content Area - Right of Sidebar */}
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="col"
|
||||
flex={1}
|
||||
marginLeft={fixedSidebar && sidebar ? { lg: 64 } : undefined}
|
||||
minWidth="0" // Prevent flex child overflow
|
||||
>
|
||||
{/* Header - Rendered directly as it contains AppShellBar (fixed) */}
|
||||
{header}
|
||||
|
||||
{/* Main Scrollable Content */}
|
||||
<Box
|
||||
as="main"
|
||||
flex={1}
|
||||
display="flex"
|
||||
flexDirection="col"
|
||||
marginLeft={fixedSidebar ? { lg: 64 } : undefined}
|
||||
position="relative"
|
||||
marginTop={fixedHeader ? 14 : 0} // Offset for fixed header (h-14)
|
||||
paddingBottom={fixedFooter ? 14 : 0} // Offset for fixed footer (h-14)
|
||||
>
|
||||
<Box flex={1}>
|
||||
<Box flex={1} p={0}>
|
||||
{children}
|
||||
</Box>
|
||||
|
||||
{footer && (
|
||||
<Box as="footer">
|
||||
{footer}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Footer - Rendered directly as it contains AppShellBar (fixed) */}
|
||||
{footer}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -12,25 +12,56 @@ interface NavLinkProps {
|
||||
variant?: 'sidebar' | 'top';
|
||||
}
|
||||
|
||||
/**
|
||||
* NavLink provides a consistent link component for navigation.
|
||||
* Supports both sidebar and top navigation variants.
|
||||
*/
|
||||
export function NavLink({ href, label, icon, isActive, variant = 'sidebar' }: NavLinkProps) {
|
||||
const isTop = variant === 'top';
|
||||
|
||||
// Dieter Rams style: Unobtrusive, Honest, Thorough.
|
||||
// No glows. No shadows. Just clear contrast and alignment.
|
||||
|
||||
const content = (
|
||||
<Box display="flex" alignItems="center" gap={variant === 'top' ? 2 : 3} paddingX={3} paddingY={2} rounded={variant === 'sidebar' ? 'md' : undefined} style={{ transition: 'all 0.2s' }}>
|
||||
{icon && <Icon icon={icon} size={variant === 'top' ? 4 : 5} intent={isActive ? 'primary' : 'low'} />}
|
||||
<Text size="sm" weight={isActive ? 'bold' : 'medium'} variant={isActive ? 'primary' : 'med'}>
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={3}
|
||||
paddingX={isTop ? 4 : 4}
|
||||
paddingY={isTop ? 2 : 3}
|
||||
className={`
|
||||
relative group transition-all duration-200 ease-out
|
||||
${isActive
|
||||
? 'text-text-high bg-white/5'
|
||||
: 'text-text-med hover:text-text-high hover:bg-white/5'
|
||||
}
|
||||
${!isTop && 'rounded-md mx-2'}
|
||||
`}
|
||||
>
|
||||
{icon && (
|
||||
<Icon
|
||||
icon={icon}
|
||||
size={4}
|
||||
className={`transition-colors duration-200 ${isActive ? 'text-primary-accent' : 'text-text-low group-hover:text-text-med'}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Text
|
||||
size="sm"
|
||||
weight={isActive ? 'medium' : 'normal'}
|
||||
variant="inherit"
|
||||
className="tracking-wide"
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
{variant === 'sidebar' && isActive && (
|
||||
<Box marginLeft="auto" width="4px" height="1rem" bg="var(--ui-color-intent-primary)" rounded="full" />
|
||||
|
||||
{/* Minimal Active Indicator */}
|
||||
{!isTop && isActive && (
|
||||
<Box
|
||||
className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-4 bg-primary-accent rounded-r-full"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Link href={href} variant="inherit" underline="none" block={variant === 'sidebar'}>
|
||||
<Link href={href} variant="inherit" underline="none" block={!isTop}>
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user