From bebe4c1037e146febf6666f1a7dbbb250174bc63 Mon Sep 17 00:00:00 2001 From: sean-eskerium Date: Thu, 9 Oct 2025 11:49:03 -0400 Subject: [PATCH] candidate for release --- PRPs/ai_docs/UI_STANDARDS.md | 56 ++++-- archon-ui-main/src/contexts/ThemeContext.tsx | 27 ++- .../layouts/NavigationExplanation.tsx | 178 ++++++------------ archon-ui-main/src/index.css | 90 ++++----- 4 files changed, 149 insertions(+), 202 deletions(-) diff --git a/PRPs/ai_docs/UI_STANDARDS.md b/PRPs/ai_docs/UI_STANDARDS.md index fc63c135..641e0ce5 100644 --- a/PRPs/ai_docs/UI_STANDARDS.md +++ b/PRPs/ai_docs/UI_STANDARDS.md @@ -77,22 +77,27 @@ ## 2) Tailwind CSS (v4) **Rules** -- **Define tokens properly**: Variables in `:root` and `.dark`, then map to Tailwind with `@theme inline`. +- **CRITICAL: Define custom dark variant FIRST** - Required for `dark:` utilities to work in v4. +- **Define tokens properly**: Variables in `:root` and `.dark` with **bare HSL values** (NO hsl() wrapper), then map to Tailwind with `@theme inline`. ```css @import "tailwindcss"; +@custom-variant dark (&:where(.dark, .dark *)); /* REQUIRED for dark: utilities */ + :root { - --background: hsl(0 0% 98%); - --foreground: hsl(240 10% 3.9%); - --border: hsl(240 5.9% 90%); + /* Bare HSL values - Tailwind adds hsl() wrapper automatically */ + --background: 0 0% 98%; + --foreground: 240 10% 3.9%; + --border: 240 5.9% 90%; --radius: 0.5rem; color-scheme: light; } .dark { - --background: hsl(0 0% 0%); - --foreground: hsl(0 0% 100%); - --border: hsl(240 3.7% 15.9%); + /* Bare HSL values - redefine same variables */ + --background: 0 0% 0%; + --foreground: 0 0% 100%; + --border: 240 3.7% 15.9%; color-scheme: dark; } @@ -593,6 +598,7 @@ Then use generated utilities (e.g., `bg-brand-500`, `border-border`) or token re Before committing any UI component, verify ALL of these: ### Tailwind v4 Rules +- [ ] **@custom-variant dark defined** (`@custom-variant dark (&:where(.dark, .dark *));` in index.css) - [ ] **No dynamic class construction** (`bg-${color}-500`, template literals with variables) - [ ] **CSS variables allowed in arbitrary values** (static utility names: `bg-[var(--accent)]`) - [ ] **Static class lookup objects** for discrete variants (e.g., `const colorClasses = { cyan: "...", ... }`) @@ -600,7 +606,8 @@ Before committing any UI component, verify ALL of these: - [ ] **Conditional classes are complete strings** (use `cn()` with full class names) - [ ] **Arbitrary values are static** (written as complete strings in source code) - [ ] **Inline style ONLY for CSS variables** (`style={{ "--accent": token }}`), never direct visual CSS -- [ ] **Variables in `:root` and `.dark`**, mapped with `@theme inline` +- [ ] **Variables in `:root` and `.dark` with bare HSL values** (NO hsl() wrapper) +- [ ] **Variables mapped with `@theme inline`** - [ ] **`@layer` and `@apply` used properly** (base styles and component classes) ### Responsive Layout Rules @@ -667,6 +674,14 @@ Use these patterns to programmatically detect violations in `.tsx` files. ### Critical Violations (Breaking Changes) +**Missing @custom-variant dark (DARK MODE WON'T WORK)** +```bash +grep -n "@custom-variant dark" [path]/index.css +``` +**Rule**: Section 2 - @custom-variant dark REQUIRED for Tailwind v4 +**Symptom**: dark: utilities don't apply, theme toggle appears to work but nothing changes visually +**Fix**: Add `@custom-variant dark (&:where(.dark, .dark *));` immediately after `@import "tailwindcss";` + **Dynamic Tailwind Class Construction (NO CSS GENERATED)** ```bash grep -rn "className={\`.*\${.*}.*\`}" [path] --include="*.tsx" @@ -932,14 +947,17 @@ export function GlassCard({ color = "cyan", customAccent }: Props) { ### Critical Reminders -1. **NO dynamic class construction** - `bg-${color}-500` will NOT generate CSS -2. **CSS variables ARE allowed** - `bg-[var(--accent)]` works (utility name is static) -3. **Inline style ONLY for CSS variables** - `style={{ "--accent": token }}`, never direct visual CSS -4. **Proper @theme pattern** - Variables in `:root`/`.dark`, map with `@theme inline` -5. **`@layer` and `@apply` still work** - Valid in v4 for base/component styles -6. **Constrain scroll containers** - `overflow-x-auto` parent needs `w-full` -7. **Responsive grids always** - Never fixed `grid-cols-4` without breakpoints -8. **Use primitives** - Card, PillNavigation, Radix UI components -9. **Desktop-primary** - Optimize for desktop, add responsive breakpoints for smaller screens -10. **Text truncation** - Always handle long text explicitly -11. **Dark mode** - Every visible color needs `dark:` variant +1. **@custom-variant dark REQUIRED** - First line after `@import "tailwindcss";` or dark: won't work! +2. **Bare HSL values** - Variables must be `0 0% 98%` NOT `hsl(0 0% 98%)` +3. **NO dynamic class construction** - `bg-${color}-500` will NOT generate CSS +4. **CSS variables ARE allowed** - `bg-[var(--accent)]` works (utility name is static) +5. **Inline style ONLY for CSS variables** - `style={{ "--accent": token }}`, never direct visual CSS +6. **Proper @theme pattern** - Variables in `:root`/`.dark`, map with `@theme inline` +7. **`@layer` and `@apply` still work** - Valid in v4 for base/component styles +8. **Constrain scroll containers** - `overflow-x-auto` parent needs `w-full` +9. **Flex parent with scroll needs min-w-0** - Prevents page expansion +10. **Responsive grids always** - Never fixed `grid-cols-4` without breakpoints +11. **Use primitives** - Card, PillNavigation, Radix UI components +12. **Desktop-primary** - Optimize for desktop, add responsive breakpoints for smaller screens +13. **Text truncation** - Always handle long text explicitly +14. **Dark mode** - Every visible color needs `dark:` variant diff --git a/archon-ui-main/src/contexts/ThemeContext.tsx b/archon-ui-main/src/contexts/ThemeContext.tsx index 726d8d96..fc92eca1 100644 --- a/archon-ui-main/src/contexts/ThemeContext.tsx +++ b/archon-ui-main/src/contexts/ThemeContext.tsx @@ -8,25 +8,22 @@ const ThemeContext = createContext(undefined); export const ThemeProvider: React.FC<{ children: React.ReactNode; }> = ({ children }) => { - const [theme, setTheme] = useState('dark'); - useEffect(() => { - // Check if theme is stored in localStorage + // Read from localStorage immediately to avoid flash + const [theme, setTheme] = useState(() => { const savedTheme = localStorage.getItem('theme') as Theme | null; - if (savedTheme) { - setTheme(savedTheme); - } else { - // Default to dark mode - setTheme('dark'); - localStorage.setItem('theme', 'dark'); - } - }, []); + return savedTheme || 'dark'; + }); useEffect(() => { // Apply theme class to document element const root = window.document.documentElement; - // Remove both classes first - root.classList.remove('dark', 'light'); - // Add the current theme class - root.classList.add(theme); + + // Tailwind v4: Only toggle .dark class, don't add .light + if (theme === 'dark') { + root.classList.add('dark'); + } else { + root.classList.remove('dark'); + } + // Save to localStorage localStorage.setItem('theme', theme); }, [theme]); diff --git a/archon-ui-main/src/features/style-guide/layouts/NavigationExplanation.tsx b/archon-ui-main/src/features/style-guide/layouts/NavigationExplanation.tsx index 8e26e4ef..ff1cfc07 100644 --- a/archon-ui-main/src/features/style-guide/layouts/NavigationExplanation.tsx +++ b/archon-ui-main/src/features/style-guide/layouts/NavigationExplanation.tsx @@ -1,145 +1,75 @@ import { ChevronRight } from "lucide-react"; -import { Card } from "@/features/ui/primitives/card"; export const NavigationExplanation = () => { return ( -
-
-

- Navigation Patterns -

-

- Archon uses a layered navigation approach with distinct patterns for different navigation levels. -

-
- -
- {/* Main Navigation */} - -
-
- 1 -
-
-

- Main Navigation -

-

- Fixed left sidebar (72px wide) with icon-based navigation. Always visible across all pages. -

-
-
-
- -
position: fixed
-
left: 24px (left-6)
-
width: 72px
-
Icon-based with tooltips
-
-
-
- - {/* Content Area */} - -
-
- 2 -
-
-

- Content Area -

-

- All page content lives in the content area with left padding to accommodate main nav. -

-
-
-
- -
padding-left: 100px (pl-[100px])
-
Gives space for 72px nav + 28px gap
-
All layouts exist INSIDE this area
-
-
-
- - {/* Page Navigation */} - -
-
- 3 -
-
-

- Page Navigation -

-

- Top-level tabs or pills for page sections. Uses Radix Tabs primitive with glassmorphism. -

-
-
-
- -
{''} with TabsList
-
Example: Docs/Tasks tabs
-
Color variants: cyan, blue, purple, orange
-
-
-
- - {/* View Controls */} - -
-
- 4 -
-
-

- View Controls -

-

- Toggle buttons for switching between view modes (grid/table/list). -

-
-
-
- -
Icon buttons with active state
-
Grid, List, Table icons
-
Glassmorphism background
-
-
-
-
- - {/* Visual Hierarchy */} - +
+ {/* Navigation Hierarchy at Top */} +

Navigation Hierarchy

-
- +
+ Main Nav - - + + Content Area - - + + Page Tabs - - + + View Controls
- +
+ + {/* Wireframe Mockup */} +
+ {/* Main Navigation - Floating Left (Centered Vertically) */} +
+
Main Navigation
+
+ Floating +
+
+ + {/* Content Area - Full Width (with left padding for nav) */} +
+
Content Area
+
+ + {/* Page Tabs - Pill Shaped */} +
+
Page Navigation (Pill Tabs)
+
+ Docs | Tasks +
+
+ + {/* View Controls */} +
+
View Controls
+
+ Grid/List +
+
+ + {/* Content Placeholder */} +
+ Page Content +
+
+
+
{/* Key Point */} -
-

- Important:{" "} +

+

+ Important:{" "} Main navigation is OUTSIDE the content area (fixed position). All page layouts (including sidebar variants) exist INSIDE the content area and use relative positioning to avoid overlapping with the main nav. diff --git a/archon-ui-main/src/index.css b/archon-ui-main/src/index.css index be3ca9dd..803aa3f4 100644 --- a/archon-ui-main/src/index.css +++ b/archon-ui-main/src/index.css @@ -1,58 +1,60 @@ @import "tailwindcss"; +@custom-variant dark (&:where(.dark, .dark *)); + :root { - /* Light mode variables */ - --background: hsl(0 0% 98%); - --foreground: hsl(240 10% 3.9%); - --muted: hsl(240 4.8% 95.9%); - --muted-foreground: hsl(240 3.8% 46.1%); - --popover: hsl(0 0% 100%); - --popover-foreground: hsl(240 10% 3.9%); - --border: hsl(240 5.9% 90%); - --input: hsl(240 5.9% 90%); - --card: hsl(0 0% 100%); - --card-foreground: hsl(240 10% 3.9%); - --primary: hsl(271 91% 65%); - --primary-foreground: hsl(0 0% 100%); - --secondary: hsl(240 4.8% 95.9%); - --secondary-foreground: hsl(240 5.9% 10%); - --accent: hsl(271 91% 65%); - --accent-foreground: hsl(0 0% 100%); - --destructive: hsl(0 84.2% 60.2%); - --destructive-foreground: hsl(0 0% 98%); - --ring: hsl(240 5.9% 10%); + /* Light mode variables - bare HSL values (Tailwind adds hsl() wrapper) */ + --background: 0 0% 98%; + --foreground: 240 10% 3.9%; + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + --primary: 271 91% 65%; + --primary-foreground: 0 0% 100%; + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + --accent: 271 91% 65%; + --accent-foreground: 0 0% 100%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --ring: 240 5.9% 10%; --radius: 0.5rem; /* Tron accent colors */ - --purple-accent: hsl(271 91% 65%); - --green-accent: hsl(160 84% 39%); - --pink-accent: hsl(330 90% 65%); - --blue-accent: hsl(217 91% 60%); + --purple-accent: 271 91% 65%; + --green-accent: 160 84% 39%; + --pink-accent: 330 90% 65%; + --blue-accent: 217 91% 60%; color-scheme: light; } .dark { - /* Dark mode variables */ - --background: hsl(0 0% 0%); - --foreground: hsl(0 0% 100%); - --muted: hsl(240 4% 16%); - --muted-foreground: hsl(240 5% 65%); - --popover: hsl(0 0% 0%); - --popover-foreground: hsl(0 0% 100%); - --border: hsl(240 3.7% 15.9%); - --input: hsl(240 3.7% 15.9%); - --card: hsl(0 0% 0%); - --card-foreground: hsl(0 0% 100%); - --primary: hsl(271 91% 65%); - --primary-foreground: hsl(0 0% 100%); - --secondary: hsl(240 3.7% 15.9%); - --secondary-foreground: hsl(0 0% 98%); - --accent: hsl(271 91% 65%); - --accent-foreground: hsl(0 0% 100%); - --destructive: hsl(0 84.2% 60.2%); - --destructive-foreground: hsl(0 0% 98%); - --ring: hsl(240 3.7% 15.9%); + /* Dark mode variables - bare HSL values */ + --background: 0 0% 0%; + --foreground: 0 0% 100%; + --muted: 240 4% 16%; + --muted-foreground: 240 5% 65%; + --popover: 0 0% 0%; + --popover-foreground: 0 0% 100%; + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --card: 0 0% 0%; + --card-foreground: 0 0% 100%; + --primary: 271 91% 65%; + --primary-foreground: 0 0% 100%; + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + --accent: 271 91% 65%; + --accent-foreground: 0 0% 100%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --ring: 240 3.7% 15.9%; color-scheme: dark; }