diff --git a/apps/website/components/dev/DevToolbar.tsx b/apps/website/components/dev/DevToolbar.tsx
index 41d5f89f4..4ff258439 100644
--- a/apps/website/components/dev/DevToolbar.tsx
+++ b/apps/website/components/dev/DevToolbar.tsx
@@ -17,6 +17,7 @@ import { Icon } from '@/ui/Icon';
import { IconButton } from '@/ui/IconButton';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
+import { Toolbar } from '@/ui/Toolbar';
import { APIStatusSection } from './sections/APIStatusSection';
import { NotificationSendSection } from './sections/NotificationSendSection';
import { NotificationTypeSection } from './sections/NotificationTypeSection';
@@ -229,9 +230,7 @@ export function DevToolbar() {
if (isMinimized) {
return (
-
+
setIsMinimized(false)}
@@ -239,31 +238,12 @@ export function DevToolbar() {
title="Open Dev Toolbar"
size="lg"
/>
-
+
);
}
return (
-
+
{/* Header */}
@@ -386,6 +366,6 @@ export function DevToolbar() {
Click ↑ to expand dev tools
)}
-
+
);
}
diff --git a/apps/website/components/layout/AppFooter.tsx b/apps/website/components/layout/AppFooter.tsx
index 66ff9d77f..86bd758ba 100644
--- a/apps/website/components/layout/AppFooter.tsx
+++ b/apps/website/components/layout/AppFooter.tsx
@@ -1,13 +1,14 @@
'use client';
+import { useSidebar } from '@/components/layout/SidebarContext';
import { Box } from '@/ui/Box';
import { Link } from '@/ui/Link';
+import { ShellFooter } from '@/ui/shell/Shell';
import { Text } from '@/ui/Text';
import { useEffect, useState } from 'react';
-import { AppShellBar } from './AppShellBar';
export function AppFooter() {
- const currentYear = new Date().getFullYear();
+ const { isCollapsed } = useSidebar();
// Clock
const [time, setTime] = useState('');
@@ -22,12 +23,12 @@ export function AppFooter() {
}, []);
return (
-
+
{/* Left: System Info */}
-
+
GRIDPILOT
@@ -35,7 +36,7 @@ export function AppFooter() {
{/* Center: Time */}
-
+
{time} UTC
@@ -46,7 +47,7 @@ export function AppFooter() {
Privacy
Status
-
+
);
}
diff --git a/apps/website/components/layout/AppHeader.tsx b/apps/website/components/layout/AppHeader.tsx
index 136e6203a..67169a27e 100644
--- a/apps/website/components/layout/AppHeader.tsx
+++ b/apps/website/components/layout/AppHeader.tsx
@@ -1,20 +1,24 @@
'use client';
-import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
-import { Search, Bell, Command } from 'lucide-react';
+import { Bell, Command } from 'lucide-react';
import { usePathname } from 'next/navigation';
import { useCurrentSession } from '@/hooks/auth/useCurrentSession';
import { useState, useEffect } from 'react';
-import { AppShellBar } from './AppShellBar';
+import { ShellHeader } from '@/ui/shell/Shell';
import { CommandModal } from './CommandModal';
import { UserPill } from '@/components/profile/UserPill';
+import { Input } from '@/ui/Input';
+import { Box } from '@/ui/Box';
+import { IconButton } from '@/ui/IconButton';
+import { useSidebar } from '@/components/layout/SidebarContext';
export function AppHeader() {
const pathname = usePathname();
const { data: session } = useCurrentSession();
const isAuthenticated = !!session;
const [isCommandOpen, setIsCommandOpen] = useState(false);
+ const { isCollapsed } = useSidebar();
// Simple breadcrumb logic
const pathSegments = pathname.split('/').filter(Boolean);
@@ -36,48 +40,50 @@ export function AppHeader() {
return (
<>
-
+
{/* Left: Context & Search */}
-
+
{breadcrumbs}
{/* Command Search Trigger */}
-
+
+ setIsCommandOpen(true)}
+ placeholder="Search or type a command..."
+ variant="search"
+ width="24rem"
+ rightElement={
+
+
+ K
+
+ }
+ className="cursor-pointer"
+ />
+
{/* Right: User & Notifications */}
{/* Notifications - Only when authed */}
{isAuthenticated && (
-
-
-
+
+
+
)}
{/* User Pill (Handles Auth & Menu) */}
-
+
setIsCommandOpen(false)} />
>
diff --git a/apps/website/components/layout/AppSidebar.tsx b/apps/website/components/layout/AppSidebar.tsx
index 2e080e03b..141735c2a 100644
--- a/apps/website/components/layout/AppSidebar.tsx
+++ b/apps/website/components/layout/AppSidebar.tsx
@@ -3,7 +3,6 @@
import { BrandMark } from '@/ui/BrandMark';
import { NavLink } from '@/ui/NavLink';
import { routes } from '@/lib/routing/RouteConfig';
-import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import {
@@ -18,6 +17,8 @@ import {
} from 'lucide-react';
import { usePathname } from 'next/navigation';
import { useSidebar } from '@/components/layout/SidebarContext';
+import { ShellSidebar } from '@/ui/shell/Shell';
+import { Button } from '@/ui/Button';
export function AppSidebar() {
const pathname = usePathname();
@@ -36,76 +37,64 @@ export function AppSidebar() {
];
return (
-
- {/* Brand Header */}
-
-
-
-
- {/* Navigation */}
-
-
-
- {!isCollapsed && (
-
- Platform
-
- )}
-
- {mainItems.map((item) => (
-
- ))}
-
-
-
-
- {!isCollapsed && (
-
- Competition
-
- )}
-
- {competitionItems.map((item) => (
-
- ))}
-
-
-
-
-
- {/* Bottom Actions */}
-
- }
+ footer={
+ : }
onClick={toggleCollapse}
- className={`flex items-center ${isCollapsed ? 'justify-center' : 'justify-start'} gap-2 text-text-low hover:text-text-high transition-colors px-2 py-2 rounded-md hover:bg-white/5 w-full group`}
- title={isCollapsed ? "Expand Sidebar" : "Collapse Sidebar"}
+ variant="ghost"
+ fullWidth
+ justifyContent={isCollapsed ? 'center' : 'start'}
>
- {isCollapsed ? (
-
- ) : (
- <>
-
- Collapse
- >
+ {!isCollapsed && "Collapse"}
+
+ }
+ >
+
+
+ {!isCollapsed && (
+
+ Platform
+
)}
-
-
-
+
+ {mainItems.map((item) => (
+
+ ))}
+
+
+
+
+ {!isCollapsed && (
+
+ Competition
+
+ )}
+
+ {competitionItems.map((item) => (
+
+ ))}
+
+
+
+
);
}
diff --git a/apps/website/ui/Button.tsx b/apps/website/ui/Button.tsx
index 4d7c49c08..00ccb189d 100644
--- a/apps/website/ui/Button.tsx
+++ b/apps/website/ui/Button.tsx
@@ -45,6 +45,7 @@ export interface ButtonProps {
shadow?: string;
display?: string;
center?: boolean;
+ justifyContent?: string;
}
export const Button = forwardRef(({
@@ -89,6 +90,7 @@ export const Button = forwardRef {
const baseClasses = 'inline-flex items-center justify-center focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 active:opacity-80 uppercase tracking-widest font-bold';
const transitionClasses = transition !== false ? 'transition-all duration-150 ease-in-out' : '';
@@ -125,6 +127,7 @@ export const Button = forwardRef, 'size'> {
+ icon?: LucideIcon | React.ReactNode;
+ rightElement?: React.ReactNode;
+ variant?: 'default' | 'ghost' | 'search' | 'error';
+ fullWidth?: boolean;
label?: string;
error?: string;
- errorMessage?: string;
+ errorMessage?: string; // Alias for error
hint?: string;
- fullWidth?: boolean;
- size?: 'sm' | 'md' | 'lg';
- icon?: React.ReactNode;
- variant?: 'default' | 'error';
+ size?: string | number; // Allow size prop
}
export const Input = forwardRef(({
- label,
- error,
- errorMessage,
- hint,
+ icon,
+ rightElement,
+ variant = 'default',
fullWidth = false,
- size = 'md',
- icon,
- variant = 'default',
+ className,
+ label,
+ error,
+ errorMessage,
+ hint,
+ id,
+ size,
...props
}, ref) => {
- const finalError = error || errorMessage;
- const sizeClasses = {
- sm: 'px-3 py-1.5 text-xs',
- md: 'px-4 py-2 text-sm',
- lg: 'px-4 py-3 text-base'
+ const variantClasses = {
+ default: 'bg-surface-charcoal border border-outline-steel focus:border-primary-accent',
+ ghost: 'bg-transparent border-none',
+ search: 'bg-surface-charcoal/50 border border-outline-steel focus:border-primary-accent hover:border-text-low/50',
+ error: 'bg-surface-charcoal border border-critical-red focus:border-critical-red',
};
- const baseClasses = 'bg-[var(--ui-color-bg-surface)] border border-[var(--ui-color-border-default)] text-[var(--ui-color-text-high)] placeholder-[var(--ui-color-text-low)] focus:outline-none focus:border-[var(--ui-color-intent-primary)] transition-colors';
- const errorClasses = (finalError || variant === 'error') ? 'border-[var(--ui-color-intent-critical)]' : '';
- const widthClasses = fullWidth ? 'w-full' : '';
-
- const classes = [
- baseClasses,
- sizeClasses[size],
- errorClasses,
- widthClasses,
- icon ? 'pl-10' : '',
- ].filter(Boolean).join(' ');
+ const inputId = id || (label ? `input-${label.toLowerCase().replace(/\s+/g, '-')}` : undefined);
+ const displayError = error || errorMessage;
return (
-
+
{label && (
-
+
{label}
)}
-
+
+
{icon && (
-
- {icon}
-
+ React.isValidElement(icon) ? icon : (
+
+ )
)}
+
+
+ {rightElement}
- {finalError && (
+
+ {displayError && (
- {finalError}
+ {displayError}
)}
- {hint && !finalError && (
+
+ {hint && !displayError && (
{hint}
diff --git a/apps/website/ui/Toolbar.tsx b/apps/website/ui/Toolbar.tsx
new file mode 100644
index 000000000..e55159a67
--- /dev/null
+++ b/apps/website/ui/Toolbar.tsx
@@ -0,0 +1,48 @@
+import { ReactNode } from 'react';
+import { Stack } from './Stack';
+
+interface ToolbarProps {
+ children: ReactNode;
+ minimized?: boolean;
+ bottom?: number | string;
+}
+
+export function Toolbar({ children, minimized = false, bottom = '2rem' }: ToolbarProps) {
+ if (minimized) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/apps/website/ui/shell/Shell.tsx b/apps/website/ui/shell/Shell.tsx
new file mode 100644
index 000000000..868e32d1c
--- /dev/null
+++ b/apps/website/ui/shell/Shell.tsx
@@ -0,0 +1,101 @@
+import { ReactNode } from 'react';
+import { Box } from '../Box';
+
+// --- Shell Sidebar ---
+
+interface ShellSidebarProps {
+ children: ReactNode;
+ header?: ReactNode;
+ footer?: ReactNode;
+ collapsed?: boolean;
+ width?: string | number;
+ collapsedWidth?: string | number;
+}
+
+export function ShellSidebar({
+ children,
+ header,
+ footer,
+ collapsed = false,
+ width = '16rem',
+ collapsedWidth = '5rem'
+}: ShellSidebarProps) {
+ return (
+
+ {header && (
+
+ {header}
+
+ )}
+
+
+ {children}
+
+
+ {footer && (
+
+ {footer}
+
+ )}
+
+ );
+}
+
+// --- Shell Header ---
+
+interface ShellHeaderProps {
+ children: ReactNode;
+ collapsed?: boolean;
+}
+
+export function ShellHeader({ children, collapsed = false }: ShellHeaderProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+// --- Shell Footer ---
+
+interface ShellFooterProps {
+ children: ReactNode;
+ collapsed?: boolean;
+}
+
+export function ShellFooter({ children, collapsed = false }: ShellFooterProps) {
+ return (
+
+ {children}
+
+ );
+}