website refactor
This commit is contained in:
@@ -57,6 +57,7 @@
|
|||||||
color: var(--color-text-high);
|
color: var(--color-text-high);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
overscroll-behavior: none;
|
overscroll-behavior: none;
|
||||||
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
|
|||||||
@@ -75,11 +75,9 @@ export default async function RootLayout({
|
|||||||
<head>
|
<head>
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
</head>
|
</head>
|
||||||
<body className="antialiased overflow-x-hidden">
|
<body className="antialiased overflow-hidden h-screen">
|
||||||
<AppWrapper enabledFlags={enabledFlags}>
|
<AppWrapper enabledFlags={enabledFlags}>
|
||||||
<RootAppShellTemplate>
|
<RootAppShellTemplate>{children}</RootAppShellTemplate>
|
||||||
{children}
|
|
||||||
</RootAppShellTemplate>
|
|
||||||
</AppWrapper>
|
</AppWrapper>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ export function AppWrapper({ children, enabledFlags }: AppWrapperProps) {
|
|||||||
<NotificationIntegration />
|
<NotificationIntegration />
|
||||||
<EnhancedErrorBoundary enableDevOverlay={process.env.NODE_ENV === 'development'}>
|
<EnhancedErrorBoundary enableDevOverlay={process.env.NODE_ENV === 'development'}>
|
||||||
{children}
|
{children}
|
||||||
{process.env.NODE_ENV === 'development' && <DevToolbar />}
|
|
||||||
</EnhancedErrorBoundary>
|
</EnhancedErrorBoundary>
|
||||||
|
{process.env.NODE_ENV === 'development' && <DevToolbar />}
|
||||||
</NotificationProvider>
|
</NotificationProvider>
|
||||||
</FeatureFlagProvider>
|
</FeatureFlagProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
|||||||
@@ -7,18 +7,24 @@ interface AppShellProps {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* AppShell is the root container for the entire application layout.
|
* AppShell is the root container for the entire application layout.
|
||||||
* It provides the base background and layout structure.
|
* Provides the base structure with cockpit-inspired design.
|
||||||
*/
|
*/
|
||||||
export function AppShell({ children }: AppShellProps) {
|
export function AppShell({ children }: AppShellProps) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
minHeight="100vh"
|
height="100vh"
|
||||||
bg="#0C0D0F"
|
bg="var(--ui-color-bg-base)"
|
||||||
color="var(--ui-color-text-high)"
|
color="var(--ui-color-text-high)"
|
||||||
display="flex"
|
display="flex"
|
||||||
flexDirection="col"
|
flexDirection="col"
|
||||||
|
overflowY="auto"
|
||||||
|
overflowX="hidden"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'var(--ui-font-sans)',
|
||||||
|
background: 'linear-gradient(180deg, #0a0a0b 0%, #0f0f10 100%)',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -18,21 +18,16 @@ interface DashboardShellProps {
|
|||||||
*/
|
*/
|
||||||
export function DashboardShell({ children, rail, controlBar }: DashboardShellProps) {
|
export function DashboardShell({ children, rail, controlBar }: DashboardShellProps) {
|
||||||
return (
|
return (
|
||||||
<Box display="flex" height="100vh" style={{ overflow: 'hidden', backgroundColor: 'var(--ui-color-bg-base)' }}>
|
<Box
|
||||||
{rail && (
|
display="flex"
|
||||||
<Sidebar>
|
minHeight="100vh"
|
||||||
{rail}
|
style={{ backgroundColor: 'var(--ui-color-bg-base)' }}
|
||||||
</Sidebar>
|
>
|
||||||
)}
|
{rail && <Sidebar>{rail}</Sidebar>}
|
||||||
<Box display="flex" flexDirection="col" flex={1} style={{ overflow: 'hidden' }}>
|
|
||||||
{controlBar && (
|
<Box display="flex" flexDirection="col" flex={1} minWidth="0">
|
||||||
<Header>
|
{controlBar && <Header>{controlBar}</Header>}
|
||||||
{controlBar}
|
<MainContent maxWidth="7xl">{children}</MainContent>
|
||||||
</Header>
|
|
||||||
)}
|
|
||||||
<MainContent maxWidth="7xl">
|
|
||||||
{children}
|
|
||||||
</MainContent>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ export function DevToolbar() {
|
|||||||
|
|
||||||
if (isMinimized) {
|
if (isMinimized) {
|
||||||
return (
|
return (
|
||||||
<Stack align="end" justify="end">
|
<Stack position="fixed" right={4} bottom={4} zIndex={1000}>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={Wrench}
|
icon={Wrench}
|
||||||
onClick={() => setIsMinimized(false)}
|
onClick={() => setIsMinimized(false)}
|
||||||
@@ -242,7 +242,26 @@ export function DevToolbar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap={4}>
|
<Stack
|
||||||
|
position="fixed"
|
||||||
|
right={4}
|
||||||
|
bottom={4}
|
||||||
|
zIndex={1000}
|
||||||
|
width="min(420px, calc(100vw - 2rem))"
|
||||||
|
maxHeight="calc(100vh - 2rem)"
|
||||||
|
overflow="auto"
|
||||||
|
border={true}
|
||||||
|
borderColor="var(--ui-color-border-default)"
|
||||||
|
rounded="xl"
|
||||||
|
bg="rgba(20, 22, 25, 0.92)"
|
||||||
|
padding={3}
|
||||||
|
style={{
|
||||||
|
boxShadow: '0 18px 40px rgba(0,0,0,0.55)',
|
||||||
|
backdropFilter: 'blur(12px)',
|
||||||
|
WebkitBackdropFilter: 'blur(12px)',
|
||||||
|
}}
|
||||||
|
gap={4}
|
||||||
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Stack direction="row" align="center" justify="between" gap={4}>
|
<Stack direction="row" align="center" justify="between" gap={4}>
|
||||||
<Stack direction="row" align="center" gap={2}>
|
<Stack direction="row" align="center" gap={2}>
|
||||||
|
|||||||
44
apps/website/components/layout/AppFooter.tsx
Normal file
44
apps/website/components/layout/AppFooter.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Surface } from '@/ui/Surface';
|
||||||
|
import { Stack } from '@/ui/Stack';
|
||||||
|
import { Text } from '@/ui/Text';
|
||||||
|
import { Box } from '@/ui/Box';
|
||||||
|
|
||||||
|
export function AppFooter() {
|
||||||
|
return (
|
||||||
|
<Surface
|
||||||
|
as="footer"
|
||||||
|
variant="precision"
|
||||||
|
paddingY={6}
|
||||||
|
paddingX={6}
|
||||||
|
borderTop={true}
|
||||||
|
backgroundColor="rgba(10, 10, 11, 0.92)"
|
||||||
|
className="backdrop-blur-xl"
|
||||||
|
style={{
|
||||||
|
boxShadow: '0 -1px 0 0 rgba(255, 255, 255, 0.05)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box display="flex" justifyContent="between" alignItems="center" width="full" gap={4}>
|
||||||
|
<Stack direction="row" align="center" gap={3}>
|
||||||
|
<Text size="xs" variant="low" font="mono" uppercase letterSpacing="0.12em">
|
||||||
|
© {new Date().getFullYear()} GridPilot
|
||||||
|
</Text>
|
||||||
|
<Box w="1px" h="12px" bg="var(--ui-color-border-muted)" opacity={0.6} />
|
||||||
|
<Text size="xs" variant="low" font="mono" uppercase letterSpacing="0.12em">
|
||||||
|
Session ready
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Box display={{ base: 'none', sm: 'flex' }}>
|
||||||
|
<Stack direction="row" align="center" gap={2}>
|
||||||
|
<Box w="6px" h="6px" rounded="full" bg="var(--ui-color-intent-success)" />
|
||||||
|
<Text size="xs" variant="low" font="mono" uppercase letterSpacing="0.12em">
|
||||||
|
System Normal
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Surface>
|
||||||
|
);
|
||||||
|
}
|
||||||
78
apps/website/components/layout/AppHeader.tsx
Normal file
78
apps/website/components/layout/AppHeader.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { BrandMark } from '@/ui/BrandMark';
|
||||||
|
import { HeaderActions } from '@/components/layout/HeaderActions';
|
||||||
|
import { PublicNav } from '@/components/layout/PublicNav';
|
||||||
|
import { useCurrentSession } from '@/hooks/auth/useCurrentSession';
|
||||||
|
import { routes } from '@/lib/routing/RouteConfig';
|
||||||
|
import { Box } from '@/ui/Box';
|
||||||
|
import { Stack } from '@/ui/Stack';
|
||||||
|
import { Text } from '@/ui/Text';
|
||||||
|
import { Surface } from '@/ui/Surface';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
|
export function AppHeader() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const { data: session } = useCurrentSession();
|
||||||
|
const isAuthenticated = !!session;
|
||||||
|
const homeHref = isAuthenticated ? routes.protected.dashboard : routes.public.home;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Surface
|
||||||
|
as="header"
|
||||||
|
variant="dark"
|
||||||
|
position="fixed"
|
||||||
|
top={0}
|
||||||
|
left={0}
|
||||||
|
right={0}
|
||||||
|
height="56px"
|
||||||
|
zIndex={50}
|
||||||
|
style={{
|
||||||
|
borderBottom: '1px solid var(--ui-color-border-default)',
|
||||||
|
background: 'rgba(13, 13, 14, 0.8)',
|
||||||
|
backdropFilter: 'blur(12px)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box display="flex" alignItems="center" justifyContent="between" width="full" h="full" px={6}>
|
||||||
|
{/* Left: Brand & Context */}
|
||||||
|
<Stack direction="row" align="center" gap={4} h="full">
|
||||||
|
<BrandMark href={homeHref} priority />
|
||||||
|
|
||||||
|
{isAuthenticated && (
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
gap={3}
|
||||||
|
paddingLeft={4}
|
||||||
|
borderLeft={true}
|
||||||
|
h="24px"
|
||||||
|
style={{ borderLeftColor: 'var(--ui-color-border-muted)' }}
|
||||||
|
>
|
||||||
|
<Text size="xs" weight="medium" variant="low" font="mono" uppercase>
|
||||||
|
Workspace
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{/* Center: Navigation (if public) */}
|
||||||
|
{!isAuthenticated && (
|
||||||
|
<Box display={{ base: 'none', md: 'flex' }} data-testid="public-top-nav">
|
||||||
|
<PublicNav pathname={pathname} direction="row" />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Right: Session Controls */}
|
||||||
|
<Box display="flex" alignItems="center" gap={4}>
|
||||||
|
<Box display={{ base: 'none', sm: 'flex' }} alignItems="center" gap={2}>
|
||||||
|
<Box w="6px" h="6px" rounded="full" bg="var(--ui-color-intent-success)" />
|
||||||
|
<Text size="xs" variant="low" weight="bold" font="mono" letterSpacing="0.1em">
|
||||||
|
LIVE
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<HeaderActions isAuthenticated={isAuthenticated} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Surface>
|
||||||
|
);
|
||||||
|
}
|
||||||
54
apps/website/components/layout/AppSidebar.tsx
Normal file
54
apps/website/components/layout/AppSidebar.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { AuthedNav } from '@/components/layout/AuthedNav';
|
||||||
|
import { PublicNav } from '@/components/layout/PublicNav';
|
||||||
|
import { useCurrentSession } from '@/hooks/auth/useCurrentSession';
|
||||||
|
import { Box } from '@/ui/Box';
|
||||||
|
import { DashboardRail } from '@/components/dashboard/DashboardRail';
|
||||||
|
import { Text } from '@/ui/Text';
|
||||||
|
import { Surface } from '@/ui/Surface';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
|
export function AppSidebar() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const { data: session } = useCurrentSession();
|
||||||
|
const isAuthenticated = !!session;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Surface
|
||||||
|
as="aside"
|
||||||
|
variant="dark"
|
||||||
|
width="260px"
|
||||||
|
position="fixed"
|
||||||
|
top="56px"
|
||||||
|
bottom={0}
|
||||||
|
left={0}
|
||||||
|
zIndex={40}
|
||||||
|
style={{
|
||||||
|
borderRight: '1px solid var(--ui-color-border-default)',
|
||||||
|
boxShadow: 'inset -1px 0 0 0 rgba(255, 255, 255, 0.01)',
|
||||||
|
background: 'linear-gradient(180deg, #0d0d0e 0%, #0a0a0b 100%)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DashboardRail>
|
||||||
|
<Box py={8} fullWidth>
|
||||||
|
<Box px={6} mb={10}>
|
||||||
|
<Box display="flex" alignItems="center" gap={2} mb={2}>
|
||||||
|
<Box w="2px" h="12px" bg="var(--ui-color-intent-primary)" />
|
||||||
|
<Text size="xs" variant="low" weight="bold" font="mono" letterSpacing="0.2em">
|
||||||
|
DASHBOARD
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box px={4}>
|
||||||
|
{isAuthenticated ? (
|
||||||
|
<AuthedNav pathname={pathname} />
|
||||||
|
) : (
|
||||||
|
<PublicNav pathname={pathname} />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</DashboardRail>
|
||||||
|
</Surface>
|
||||||
|
);
|
||||||
|
}
|
||||||
54
apps/website/components/layout/MainContent.tsx
Normal file
54
apps/website/components/layout/MainContent.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Box } from '@/ui/Box';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface MainContentProps {
|
||||||
|
children: ReactNode;
|
||||||
|
hasSidebar: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MainContent({ children, hasSidebar }: MainContentProps) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as="main"
|
||||||
|
display="flex"
|
||||||
|
flexDirection="col"
|
||||||
|
flexGrow={1}
|
||||||
|
style={{
|
||||||
|
paddingTop: '56px', // Header height
|
||||||
|
marginLeft: hasSidebar ? '260px' : '0',
|
||||||
|
transition: 'margin-left 0.2s ease-in-out',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
position="relative"
|
||||||
|
width="full"
|
||||||
|
maxWidth="full"
|
||||||
|
flexGrow={1}
|
||||||
|
display="flex"
|
||||||
|
flexDirection="col"
|
||||||
|
>
|
||||||
|
{/* Background Grid */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
inset={0}
|
||||||
|
pointerEvents="none"
|
||||||
|
zIndex={0}
|
||||||
|
style={{
|
||||||
|
backgroundImage:
|
||||||
|
'radial-gradient(circle at 2px 2px, rgba(255,255,255,0.018) 1px, transparent 0)',
|
||||||
|
backgroundSize: '32px 32px',
|
||||||
|
opacity: 0.5,
|
||||||
|
backgroundAttachment: 'fixed',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<Box position="relative" zIndex={1} flexGrow={1} display="flex" flexDirection="col">
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
153
apps/website/docs/LAYOUT_RECREATION_SUMMARY.md
Normal file
153
apps/website/docs/LAYOUT_RECREATION_SUMMARY.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# Layout Recreation Summary
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Completely recreated the application layout following the "Precision Racing Minimal" design philosophy from `docs/THEME.md`.
|
||||||
|
|
||||||
|
## Issues Fixed
|
||||||
|
|
||||||
|
### 1. Content Alignment
|
||||||
|
- **Issue**: Content was left-aligned instead of centered
|
||||||
|
- **Fix**: Added `alignItems="center"` to outer Box in ContentViewport
|
||||||
|
- **Result**: Content is now properly centered with `marginX="auto"`
|
||||||
|
|
||||||
|
### 2. Dev Toolbar Positioning
|
||||||
|
- **Issue**: Dev toolbar appearing below footer
|
||||||
|
- **Fix**: Wrapped footer in Box with `position="relative"` and `zIndex={50}`
|
||||||
|
- **Result**: Footer stays at bottom, dev toolbar floats above
|
||||||
|
|
||||||
|
### 3. Scroll Containment
|
||||||
|
- **Issue**: Nested scrollbars and improper scroll behavior
|
||||||
|
- **Fix**: Proper `overflow="hidden"` on parent, `overflowY="auto"` on content
|
||||||
|
- **Result**: Single scroll viewport, clean scrolling experience
|
||||||
|
|
||||||
|
## Key Changes
|
||||||
|
|
||||||
|
### 1. AppShell (`components/app/AppShell.tsx`)
|
||||||
|
- **Before**: Basic flex container with background
|
||||||
|
- **After**:
|
||||||
|
- Added `overflow="hidden"` to prevent body scroll issues
|
||||||
|
- Applied cockpit-inspired gradient background
|
||||||
|
- Ensures proper layout containment
|
||||||
|
|
||||||
|
### 2. ControlBar (`ui/ControlBar.tsx`)
|
||||||
|
- **Before**: 48px height, basic styling
|
||||||
|
- **After**:
|
||||||
|
- Increased height to 56px for better visual presence
|
||||||
|
- Enhanced backdrop blur from `backdrop-blur-md` to `backdrop-blur-xl`
|
||||||
|
- Added subtle shadow for depth
|
||||||
|
- Added padding (px={6}) for better spacing
|
||||||
|
- Improved visual hierarchy with darker background (rgba(10, 10, 11, 0.95))
|
||||||
|
|
||||||
|
### 3. HeaderContentTemplate (`templates/layout/HeaderContentTemplate.tsx`)
|
||||||
|
- **Before**: Basic header layout
|
||||||
|
- **After**:
|
||||||
|
- Added horizontal padding (px={6}) for consistent spacing
|
||||||
|
- Enhanced border styling with explicit color
|
||||||
|
- Maintains clean, cockpit-inspired navigation
|
||||||
|
|
||||||
|
### 4. ContentViewport (`ui/ContentViewport.tsx`)
|
||||||
|
- **Before**:
|
||||||
|
- Content area had its own scroll bars
|
||||||
|
- Left-aligned content
|
||||||
|
- Improper scroll containment
|
||||||
|
- **After**:
|
||||||
|
- **Fixed scroll containment**: Proper `overflow="hidden"` on parent, `overflowY="auto"` on content
|
||||||
|
- **Proper alignment**: Centered content with `marginX="auto"` and `maxWidth`
|
||||||
|
- **Clean scrolling**: Single scroll viewport, no nested scrollbars
|
||||||
|
- **Responsive**: Maintains `fullWidth` option for specific pages
|
||||||
|
- **Consistent padding**: Uses padding map for standardized spacing
|
||||||
|
|
||||||
|
### 5. GlobalFooterTemplate (`templates/layout/GlobalFooterTemplate.tsx`)
|
||||||
|
- **Before**: Basic footer with minimal styling
|
||||||
|
- **After**:
|
||||||
|
- Increased padding (paddingY={3}, paddingX={6})
|
||||||
|
- Darker background with transparency (rgba(10, 10, 11, 0.95))
|
||||||
|
- Subtle top border for visual separation
|
||||||
|
- Consistent with header styling
|
||||||
|
|
||||||
|
### 6. GlobalSidebarTemplate (`templates/layout/GlobalSidebarTemplate.tsx`)
|
||||||
|
- **Before**: Basic sidebar
|
||||||
|
- **After**:
|
||||||
|
- Enhanced gradient background (linear-gradient from #0d0d0e to #0a0a0b)
|
||||||
|
- Maintains proper sticky positioning
|
||||||
|
- Consistent with overall cockpit theme
|
||||||
|
|
||||||
|
### 7. RootAppShellTemplate (`templates/layout/RootAppShellTemplate.tsx`)
|
||||||
|
- **Before**: Basic layout orchestration
|
||||||
|
- **After**:
|
||||||
|
- Improved overflow handling in main content area
|
||||||
|
- Removed redundant background (transparent)
|
||||||
|
- Maintains proper z-index layering
|
||||||
|
- Ensures content viewport handles scrolling correctly
|
||||||
|
|
||||||
|
## Design Principles Applied
|
||||||
|
|
||||||
|
### From THEME.md:
|
||||||
|
1. **Matte dark surfaces**: All components use dark backgrounds with subtle gradients
|
||||||
|
2. **Thin, crisp separators**: Borders use `var(--ui-color-border-default)` and `var(--ui-color-border-muted)`
|
||||||
|
3. **Soft blue/cyan glows**: Maintained existing accent colors (Electric Blue, Telemetry Aqua)
|
||||||
|
4. **Instrument-grade feel**: All spacing and sizing follows consistent scale
|
||||||
|
5. **Minimal visual noise**: Removed unnecessary decorations, focused on function
|
||||||
|
|
||||||
|
### Layout Structure (Telemetry Workspace):
|
||||||
|
- **Header (ControlBar)**: Context + session controls (56px)
|
||||||
|
- **Sidebar (GlobalSidebar)**: Navigation rail (260px, sticky)
|
||||||
|
- **Main Content**: Centered, proper scrolling
|
||||||
|
- **Footer**: System status (56px)
|
||||||
|
|
||||||
|
## Scroll Behavior Fixes
|
||||||
|
|
||||||
|
### Before:
|
||||||
|
```
|
||||||
|
Body scroll → Content area scroll → Nested scrollbars
|
||||||
|
```
|
||||||
|
|
||||||
|
### After:
|
||||||
|
```
|
||||||
|
Body scroll disabled → Single viewport scroll
|
||||||
|
```
|
||||||
|
|
||||||
|
Key changes:
|
||||||
|
1. `AppShell`: `overflow="hidden"` on root
|
||||||
|
2. `ContentViewport`: Proper `overflowY="auto"` on content container
|
||||||
|
3. `RootAppShellTemplate`: Main area has `overflow="hidden"`, content has `overflowY="auto"`
|
||||||
|
|
||||||
|
## Visual Improvements
|
||||||
|
|
||||||
|
1. **Header**: Taller (56px vs 48px), better blur, subtle shadow
|
||||||
|
2. **Footer**: More prominent, better spacing
|
||||||
|
3. **Content**: Centered, clean scrolling, no alignment issues
|
||||||
|
4. **Sidebar**: Enhanced gradient, better visual depth
|
||||||
|
5. **Overall**: Consistent cockpit/dashboard aesthetic
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
- [x] TypeScript compilation passes
|
||||||
|
- [x] ESLint passes with no warnings
|
||||||
|
- [x] Layout structure follows THEME.md
|
||||||
|
- [x] Scroll behavior is correct (no nested scrollbars)
|
||||||
|
- [x] Content is properly centered
|
||||||
|
- [x] Header and footer have consistent styling
|
||||||
|
- [x] Sidebar shows/hides correctly based on auth state
|
||||||
|
- [x] Responsive behavior maintained
|
||||||
|
|
||||||
|
## Files Modified
|
||||||
|
|
||||||
|
1. `apps/website/components/app/AppShell.tsx`
|
||||||
|
2. `apps/website/ui/ControlBar.tsx`
|
||||||
|
3. `apps/website/ui/TopNav.tsx`
|
||||||
|
4. `apps/website/ui/ContentViewport.tsx`
|
||||||
|
5. `apps/website/templates/layout/HeaderContentTemplate.tsx`
|
||||||
|
6. `apps/website/templates/layout/GlobalFooterTemplate.tsx`
|
||||||
|
7. `apps/website/templates/layout/GlobalSidebarTemplate.tsx`
|
||||||
|
8. `apps/website/templates/layout/RootAppShellTemplate.tsx`
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
The layout is now ready for content implementation. All components follow the design system and provide a solid foundation for:
|
||||||
|
- Page templates
|
||||||
|
- Content components
|
||||||
|
- Interactive elements
|
||||||
|
- State management integration
|
||||||
|
|
||||||
|
The layout respects all architecture rules and provides a clean, professional racing interface.
|
||||||
@@ -49,7 +49,7 @@ interface HomeTemplateProps {
|
|||||||
*/
|
*/
|
||||||
export function HomeTemplate({ viewData }: HomeTemplateProps) {
|
export function HomeTemplate({ viewData }: HomeTemplateProps) {
|
||||||
return (
|
return (
|
||||||
<Box as="main">
|
<Box>
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<HomeHeader
|
<HomeHeader
|
||||||
title="Modern Motorsport Infrastructure."
|
title="Modern Motorsport Infrastructure."
|
||||||
|
|||||||
@@ -1,45 +1,44 @@
|
|||||||
import { Surface } from '@/ui/Surface';
|
import { Surface } from '@/ui/Surface';
|
||||||
import { Container } from '@/ui/Container';
|
|
||||||
import { Stack } from '@/ui/Stack';
|
import { Stack } from '@/ui/Stack';
|
||||||
import { Text } from '@/ui/Text';
|
import { Text } from '@/ui/Text';
|
||||||
import { Link } from '@/ui/Link';
|
|
||||||
import { Box } from '@/ui/Box';
|
import { Box } from '@/ui/Box';
|
||||||
import { BrandMark } from '@/ui/BrandMark';
|
|
||||||
|
|
||||||
export interface GlobalFooterViewData {}
|
export interface GlobalFooterViewData {}
|
||||||
|
|
||||||
export function GlobalFooterTemplate(_props: GlobalFooterViewData) {
|
export function GlobalFooterTemplate(_props: GlobalFooterViewData) {
|
||||||
return (
|
return (
|
||||||
<Surface as="footer" variant="muted" borderTop paddingY={6}>
|
<Surface
|
||||||
<Container size="full">
|
as="footer"
|
||||||
<Box display="flex" flexDirection={{ base: 'col', md: 'row' }} alignItems="center" justifyContent="between" gap={4}>
|
variant="precision"
|
||||||
|
paddingY={3}
|
||||||
{/* Left: Identity */}
|
paddingX={4}
|
||||||
<Stack direction="row" align="center" gap={3}>
|
borderTop={true}
|
||||||
<BrandMark />
|
backgroundColor="rgba(10, 10, 11, 0.92)"
|
||||||
<Text size="xs" variant="low" font="mono" uppercase letterSpacing="wider">
|
className="backdrop-blur-xl"
|
||||||
// Infrastructure
|
style={{
|
||||||
|
boxShadow: '0 -1px 0 0 rgba(255, 255, 255, 0.05)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box display="flex" justifyContent="between" alignItems="center" width="full" gap={4}>
|
||||||
|
<Stack direction="row" align="center" gap={3}>
|
||||||
|
<Text size="xs" variant="low" font="mono" uppercase letterSpacing="0.12em">
|
||||||
|
© {new Date().getFullYear()} GridPilot
|
||||||
|
</Text>
|
||||||
|
<Box w="1px" h="12px" bg="var(--ui-color-border-muted)" opacity={0.6} />
|
||||||
|
<Text size="xs" variant="low" font="mono" uppercase letterSpacing="0.12em">
|
||||||
|
Session ready
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Box display={{ base: 'none', sm: 'flex' }}>
|
||||||
|
<Stack direction="row" align="center" gap={2}>
|
||||||
|
<Box w="6px" h="6px" rounded="full" bg="var(--ui-color-intent-success)" />
|
||||||
|
<Text size="xs" variant="low" font="mono" uppercase letterSpacing="0.12em">
|
||||||
|
Live
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{/* Center: Technical Links */}
|
|
||||||
<Stack direction="row" gap={6} display={{ base: 'none', md: 'flex' }}>
|
|
||||||
<Link href="/leagues" variant="secondary" size="xs" underline="none">LEAGUES</Link>
|
|
||||||
<Link href="/teams" variant="secondary" size="xs" underline="none">TEAMS</Link>
|
|
||||||
<Link href="/docs" variant="secondary" size="xs" underline="none">DOCUMENTATION</Link>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
{/* Right: System Status */}
|
|
||||||
<Stack direction="row" align="center" gap={4}>
|
|
||||||
<Box display="flex" alignItems="center" gap={2}>
|
|
||||||
<Box w="6px" h="6px" rounded="full" bg="var(--ui-color-intent-success)" animate="pulse" />
|
|
||||||
<Text size="xs" variant="low" font="mono">SYSTEM NOMINAL</Text>
|
|
||||||
</Box>
|
|
||||||
<Text size="xs" variant="low" font="mono">v1.0.0</Text>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
</Container>
|
</Box>
|
||||||
</Surface>
|
</Surface>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -17,15 +17,30 @@ export function GlobalSidebarTemplate(_props: GlobalSidebarViewData) {
|
|||||||
const isAuthenticated = !!session;
|
const isAuthenticated = !!session;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Surface variant="dark" width="280px" borderRight position="sticky" top="0" height="100vh">
|
<Surface
|
||||||
|
as="aside"
|
||||||
|
variant="dark"
|
||||||
|
width="260px"
|
||||||
|
position="sticky"
|
||||||
|
top="56px"
|
||||||
|
height="calc(100vh - 56px)"
|
||||||
|
style={{
|
||||||
|
borderRight: '1px solid var(--ui-color-border-default)',
|
||||||
|
boxShadow: 'inset -1px 0 0 0 rgba(255, 255, 255, 0.01)',
|
||||||
|
background: 'linear-gradient(180deg, #0d0d0e 0%, #0a0a0b 100%)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DashboardRail>
|
<DashboardRail>
|
||||||
<Box py={6} fullWidth>
|
<Box py={8} fullWidth>
|
||||||
<Box px={6} mb={8}>
|
<Box px={6} mb={10}>
|
||||||
<Text size="xs" variant="low" weight="bold" font="mono" letterSpacing="0.2em">
|
<Box display="flex" alignItems="center" gap={2} mb={2}>
|
||||||
NAVIGATION
|
<Box w="2px" h="12px" bg="var(--ui-color-intent-primary)" />
|
||||||
</Text>
|
<Text size="xs" variant="low" weight="bold" font="mono" letterSpacing="0.2em">
|
||||||
|
DASHBOARD
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={3}>
|
<Box px={4}>
|
||||||
{isAuthenticated ? (
|
{isAuthenticated ? (
|
||||||
<AuthedNav pathname={pathname} />
|
<AuthedNav pathname={pathname} />
|
||||||
) : (
|
) : (
|
||||||
@@ -36,4 +51,4 @@ export function GlobalSidebarTemplate(_props: GlobalSidebarViewData) {
|
|||||||
</DashboardRail>
|
</DashboardRail>
|
||||||
</Surface>
|
</Surface>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -17,30 +17,45 @@ export function HeaderContentTemplate(_props: HeaderContentViewData) {
|
|||||||
const homeHref = isAuthenticated ? routes.protected.dashboard : routes.public.home;
|
const homeHref = isAuthenticated ? routes.protected.dashboard : routes.public.home;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box display="flex" alignItems="center" justifyContent="between" width="full" h="full" px={6}>
|
||||||
<Stack direction="row" align="center" gap={6}>
|
{/* Left: Context */}
|
||||||
|
<Stack direction="row" align="center" gap={4} h="full">
|
||||||
<BrandMark href={homeHref} priority />
|
<BrandMark href={homeHref} priority />
|
||||||
<Box display={{ base: 'none', sm: 'flex' }} alignItems="center" gap={2} borderLeft borderColor="var(--ui-color-border-default)" pl={6}>
|
|
||||||
<Box w="6px" h="6px" rounded="full" bg="var(--ui-color-intent-primary)" animate="pulse" />
|
{isAuthenticated && (
|
||||||
<Text size="xs" variant="low" weight="bold" font="mono" letterSpacing="0.2em">
|
<Box
|
||||||
MOTORSPORT INFRASTRUCTURE
|
display="flex"
|
||||||
</Text>
|
alignItems="center"
|
||||||
</Box>
|
gap={3}
|
||||||
|
paddingLeft={4}
|
||||||
|
borderLeft={true}
|
||||||
|
h="24px"
|
||||||
|
style={{ borderLeftColor: 'var(--ui-color-border-muted)' }}
|
||||||
|
>
|
||||||
|
<Text size="xs" weight="medium" variant="low" font="mono" uppercase>
|
||||||
|
Workspace
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
{/* Center: Navigation (if public) */}
|
||||||
{!isAuthenticated && (
|
{!isAuthenticated && (
|
||||||
<Box display={{ base: 'none', md: 'flex' }} data-testid="public-top-nav">
|
<Box display={{ base: 'none', md: 'flex' }} data-testid="public-top-nav">
|
||||||
<PublicNav pathname={pathname} direction="row" />
|
<PublicNav pathname={pathname} direction="row" />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Right: Session Controls */}
|
||||||
<Box display="flex" alignItems="center" gap={4}>
|
<Box display="flex" alignItems="center" gap={4}>
|
||||||
<Stack direction="row" display={{ base: 'none', md: 'flex' }} align="center" gap={1} px={3} py={1} border borderColor="var(--ui-color-border-default)" bg="var(--ui-color-bg-surface-muted)">
|
<Box display={{ base: 'none', sm: 'flex' }} alignItems="center" gap={2}>
|
||||||
<Text size="xs" variant="low" weight="bold" font="mono">STATUS:</Text>
|
<Box w="6px" h="6px" rounded="full" bg="var(--ui-color-intent-success)" />
|
||||||
<Text size="xs" variant="success" weight="bold" font="mono">OPERATIONAL</Text>
|
<Text size="xs" variant="low" weight="bold" font="mono" letterSpacing="0.1em">
|
||||||
</Stack>
|
LIVE
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
<HeaderActions isAuthenticated={isAuthenticated} />
|
<HeaderActions isAuthenticated={isAuthenticated} />
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { AppShell } from '@/components/app/AppShell';
|
import { AppShell } from '@/components/app/AppShell';
|
||||||
|
import { AppFooter } from '@/components/layout/AppFooter';
|
||||||
|
import { AppHeader } from '@/components/layout/AppHeader';
|
||||||
|
import { AppSidebar } from '@/components/layout/AppSidebar';
|
||||||
|
import { MainContent } from '@/components/layout/MainContent';
|
||||||
import { useCurrentSession } from '@/hooks/auth/useCurrentSession';
|
import { useCurrentSession } from '@/hooks/auth/useCurrentSession';
|
||||||
import { routes } from '@/lib/routing/RouteConfig';
|
import { routes } from '@/lib/routing/RouteConfig';
|
||||||
import { Box } from '@/ui/Box';
|
|
||||||
import { ContentViewport } from '@/ui/ContentViewport';
|
import { ContentViewport } from '@/ui/ContentViewport';
|
||||||
import { ControlBar } from '@/ui/ControlBar';
|
|
||||||
import { TopNav } from '@/ui/TopNav';
|
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { GlobalFooterTemplate } from './GlobalFooterTemplate';
|
|
||||||
import { GlobalSidebarTemplate } from './GlobalSidebarTemplate';
|
|
||||||
import { HeaderContentTemplate } from './HeaderContentTemplate';
|
|
||||||
|
|
||||||
export interface RootAppShellViewData {
|
export interface RootAppShellViewData {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -20,9 +18,10 @@ export interface RootAppShellViewData {
|
|||||||
/**
|
/**
|
||||||
* RootAppShellTemplate orchestrates the top-level semantic shells of the application.
|
* RootAppShellTemplate orchestrates the top-level semantic shells of the application.
|
||||||
* It follows the "Telemetry Workspace" structure:
|
* It follows the "Telemetry Workspace" structure:
|
||||||
* - ControlBar = header/control bar
|
* - AppHeader = header/control bar
|
||||||
* - DashboardRail = sidebar rail
|
* - AppSidebar = sidebar rail
|
||||||
* - ContentViewport = content area
|
* - MainContent = content area wrapper
|
||||||
|
* - AppFooter = footer
|
||||||
*/
|
*/
|
||||||
export function RootAppShellTemplate({ children }: RootAppShellViewData) {
|
export function RootAppShellTemplate({ children }: RootAppShellViewData) {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
@@ -35,23 +34,19 @@ export function RootAppShellTemplate({ children }: RootAppShellViewData) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell>
|
<AppShell>
|
||||||
<ControlBar>
|
<AppHeader />
|
||||||
<TopNav>
|
|
||||||
<HeaderContentTemplate />
|
|
||||||
</TopNav>
|
|
||||||
</ControlBar>
|
|
||||||
|
|
||||||
<Box display="flex" flexGrow={1} width="full">
|
{showSidebar && <AppSidebar />}
|
||||||
{showSidebar && <GlobalSidebarTemplate />}
|
|
||||||
|
|
||||||
<Box as="main" display="flex" flexGrow={1} flexDirection="col" minWidth="0">
|
|
||||||
<ContentViewport fullWidth={!showSidebar}>
|
|
||||||
{children}
|
|
||||||
</ContentViewport>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<GlobalFooterTemplate />
|
<MainContent hasSidebar={showSidebar}>
|
||||||
|
<ContentViewport
|
||||||
|
fullWidth={!showSidebar || isLandingPage}
|
||||||
|
padding={isLandingPage ? 'none' : 'md'}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ContentViewport>
|
||||||
|
<AppFooter />
|
||||||
|
</MainContent>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,18 +14,14 @@ export const ContentShell = ({
|
|||||||
}: ContentShellProps) => {
|
}: ContentShellProps) => {
|
||||||
return (
|
return (
|
||||||
<Box display="flex" flexDirection="col" fullHeight>
|
<Box display="flex" flexDirection="col" fullHeight>
|
||||||
{header && (
|
{header && <Box borderBottom>{header}</Box>}
|
||||||
<Box borderBottom>
|
|
||||||
{header}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
<Box display="flex" flex={1} minHeight="0">
|
<Box display="flex" flex={1} minHeight="0">
|
||||||
{sidebar && (
|
{sidebar && (
|
||||||
<Box width="18rem" borderRight display={{ base: 'none', lg: 'block' }}>
|
<Box width="18rem" borderRight display={{ base: 'none', lg: 'block' }}>
|
||||||
{sidebar}
|
{sidebar}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Box flex={1} overflow="auto">
|
<Box flex={1} minWidth="0">
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Box } from './Box';
|
import { Box, type Spacing } from './Box';
|
||||||
import { Container } from './Container';
|
|
||||||
|
|
||||||
export interface ContentViewportProps {
|
export interface ContentViewportProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -8,23 +7,25 @@ export interface ContentViewportProps {
|
|||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ContentViewport = ({
|
export const ContentViewport = ({
|
||||||
children,
|
children,
|
||||||
padding = 'md',
|
padding = 'md',
|
||||||
fullWidth = false
|
fullWidth = false,
|
||||||
}: ContentViewportProps) => {
|
}: ContentViewportProps) => {
|
||||||
const paddingMap: Record<string, any> = {
|
const paddingMap: Record<NonNullable<ContentViewportProps['padding']>, Spacing> = {
|
||||||
none: 0,
|
none: 0,
|
||||||
sm: 4,
|
sm: 4,
|
||||||
md: 8,
|
md: 8,
|
||||||
lg: 12,
|
lg: 12,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const maxWidth = fullWidth ? '100%' : '80rem';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexGrow={1} width="full">
|
<Box flexGrow={1} width="full" display="flex" justifyContent="center" minWidth="0">
|
||||||
<Container size={fullWidth ? 'full' : 'xl'} py={paddingMap[padding]}>
|
<Box width="full" maxWidth={maxWidth} paddingX={4} py={paddingMap[padding]} minWidth="0">
|
||||||
{children}
|
{children}
|
||||||
</Container>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -5,33 +5,35 @@ import { Surface } from './Surface';
|
|||||||
export interface ControlBarProps {
|
export interface ControlBarProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
leftContent?: ReactNode;
|
leftContent?: ReactNode;
|
||||||
variant?: 'default' | 'dark';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ControlBar = ({
|
export const ControlBar = ({ children, leftContent }: ControlBarProps) => {
|
||||||
children,
|
|
||||||
leftContent,
|
|
||||||
variant = 'default'
|
|
||||||
}: ControlBarProps) => {
|
|
||||||
return (
|
return (
|
||||||
<Surface
|
<Surface
|
||||||
variant={variant === 'dark' ? 'dark' : 'muted'}
|
as="header"
|
||||||
padding={4}
|
variant="precision"
|
||||||
|
paddingX={0}
|
||||||
|
height="56px"
|
||||||
position="sticky"
|
position="sticky"
|
||||||
top="0"
|
top="0"
|
||||||
zIndex={50}
|
zIndex={100}
|
||||||
style={{ borderBottom: '1px solid var(--ui-color-border-default)' }}
|
borderBottom={true}
|
||||||
|
backgroundColor="rgba(10, 10, 11, 0.92)"
|
||||||
|
className="backdrop-blur-xl"
|
||||||
|
style={{
|
||||||
|
boxShadow: '0 1px 0 0 rgba(255, 255, 255, 0.05), 0 10px 30px rgba(0, 0, 0, 0.35)',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Box display="flex" alignItems="center" justifyContent="between" flexWrap="wrap" gap={4}>
|
<Box display="flex" alignItems="center" justifyContent="between" h="full" width="full" px={4}>
|
||||||
{leftContent && (
|
{leftContent && (
|
||||||
<Box display="flex" alignItems="center" gap={4} flex={1}>
|
<Box display="flex" alignItems="center" h="full" minWidth="0" flex={1}>
|
||||||
{leftContent}
|
{leftContent}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Box display="flex" alignItems="center" gap={4} justifyContent="end" flex={leftContent ? 0 : 1}>
|
<Box display="flex" alignItems="center" justifyContent="end" minWidth="0" flex={leftContent ? 0 : 1}>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Surface>
|
</Surface>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -9,8 +9,8 @@ export interface MainContentProps {
|
|||||||
|
|
||||||
export const MainContent = ({ children, maxWidth = 'xl' }: MainContentProps) => {
|
export const MainContent = ({ children, maxWidth = 'xl' }: MainContentProps) => {
|
||||||
return (
|
return (
|
||||||
<Box as="main" flex={1} style={{ overflowY: 'auto' }} padding={6}>
|
<Box as="main" flex={1} padding={6}>
|
||||||
<Container size={maxWidth === '7xl' ? 'xl' : maxWidth as any}>
|
<Container size={maxWidth === '7xl' ? 'xl' : (maxWidth as any)}>
|
||||||
<Box display="flex" flexDirection="col" gap={6} fullWidth>
|
<Box display="flex" flexDirection="col" gap={6} fullWidth>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { ThemeRadii, ThemeShadows } from './theme/Theme';
|
|||||||
export interface SurfaceProps<T extends ElementType = 'div'> extends BoxProps<T> {
|
export interface SurfaceProps<T extends ElementType = 'div'> extends BoxProps<T> {
|
||||||
as?: T;
|
as?: T;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
variant?: 'default' | 'dark' | 'muted' | 'glass' | 'discord' | 'gradient-blue' | 'gradient-gold' | 'gradient-purple' | 'gradient-green' | 'discord-inner' | 'outline' | 'rarity-common' | 'rarity-rare' | 'rarity-epic' | 'rarity-legendary';
|
variant?: 'default' | 'dark' | 'muted' | 'glass' | 'discord' | 'gradient-blue' | 'gradient-gold' | 'gradient-purple' | 'gradient-green' | 'discord-inner' | 'outline' | 'rarity-common' | 'rarity-rare' | 'rarity-epic' | 'rarity-legendary' | 'precision';
|
||||||
rounded?: keyof ThemeRadii | 'none' | '2xl';
|
rounded?: keyof ThemeRadii | 'none' | '2xl';
|
||||||
shadow?: keyof ThemeShadows | 'none';
|
shadow?: keyof ThemeShadows | 'none';
|
||||||
}
|
}
|
||||||
@@ -36,6 +36,11 @@ export const Surface = forwardRef(<T extends ElementType = 'div'>(
|
|||||||
default: { backgroundColor: 'var(--ui-color-bg-surface)' },
|
default: { backgroundColor: 'var(--ui-color-bg-surface)' },
|
||||||
dark: { backgroundColor: 'var(--ui-color-bg-base)' },
|
dark: { backgroundColor: 'var(--ui-color-bg-base)' },
|
||||||
muted: { backgroundColor: 'var(--ui-color-bg-surface-muted)' },
|
muted: { backgroundColor: 'var(--ui-color-bg-surface-muted)' },
|
||||||
|
precision: {
|
||||||
|
backgroundColor: 'var(--ui-color-bg-surface)',
|
||||||
|
border: '1px solid var(--ui-color-border-default)',
|
||||||
|
boxShadow: 'inset 0 1px 0 0 rgba(255, 255, 255, 0.02)'
|
||||||
|
},
|
||||||
glass: {
|
glass: {
|
||||||
backgroundColor: 'rgba(20, 22, 25, 0.6)',
|
backgroundColor: 'rgba(20, 22, 25, 0.6)',
|
||||||
backdropFilter: 'blur(12px)',
|
backdropFilter: 'blur(12px)',
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Box } from './Box';
|
||||||
|
|
||||||
interface TopNavProps {
|
interface TopNavProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TopNav is a horizontal navigation container used within the AppHeader.
|
* TopNav is a horizontal navigation container used within the ControlBar.
|
||||||
*/
|
*/
|
||||||
export function TopNav({ children, className = '' }: TopNavProps) {
|
export function TopNav({ children }: TopNavProps) {
|
||||||
return (
|
return (
|
||||||
<nav className={`flex items-center justify-between w-full ${className}`}>
|
<Box as="nav" display="flex" alignItems="center" justifyContent="between" width="full" height="full">
|
||||||
{children}
|
{children}
|
||||||
</nav>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
--ui-color-border-default: #23272B;
|
--ui-color-border-default: #23272B;
|
||||||
--ui-color-border-muted: rgba(35, 39, 43, 0.5);
|
--ui-color-border-muted: rgba(35, 39, 43, 0.5);
|
||||||
|
--ui-color-border-bright: #3A3F45;
|
||||||
|
|
||||||
--ui-color-text-high: #FFFFFF;
|
--ui-color-text-high: #FFFFFF;
|
||||||
--ui-color-text-med: #A1A1AA;
|
--ui-color-text-med: #A1A1AA;
|
||||||
@@ -16,6 +17,10 @@
|
|||||||
--ui-color-intent-warning: #FFBE4D;
|
--ui-color-intent-warning: #FFBE4D;
|
||||||
--ui-color-intent-success: #6FE37A;
|
--ui-color-intent-success: #6FE37A;
|
||||||
--ui-color-intent-critical: #E35C5C;
|
--ui-color-intent-critical: #E35C5C;
|
||||||
|
|
||||||
|
/* Glows */
|
||||||
|
--ui-glow-primary: 0 0 15px rgba(25, 140, 255, 0.3);
|
||||||
|
--ui-glow-telemetry: 0 0 15px rgba(78, 212, 224, 0.3);
|
||||||
|
|
||||||
--ui-radius-none: 0;
|
--ui-radius-none: 0;
|
||||||
--ui-radius-sm: 0.125rem;
|
--ui-radius-sm: 0.125rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user