From 17ca62ceb4def8dacde5500779129fee76f40660 Mon Sep 17 00:00:00 2001 From: sean-eskerium Date: Wed, 8 Oct 2025 23:43:43 -0400 Subject: [PATCH] refining --- .../archon/archon-ui-consistency-review.md | 78 +++- .../layouts/DocumentBrowserExample.tsx | 363 +++++++----------- .../layouts/KnowledgeLayoutExample.tsx | 136 +++---- .../layouts/ProjectsLayoutExample.tsx | 106 ++--- .../style-guide/showcases/StaticCards.tsx | 321 +++++++++++++--- .../src/features/ui/primitives/card.tsx | 66 +++- .../src/features/ui/primitives/data-card.tsx | 155 ++++++++ .../features/ui/primitives/draggable-card.tsx | 79 ++++ .../src/features/ui/primitives/index.ts | 4 + .../ui/primitives/selectable-card.tsx | 72 ++++ .../src/features/ui/primitives/styles.ts | 196 +++++++++- .../src/features/ui/primitives/tabs.tsx | 41 +- 12 files changed, 1167 insertions(+), 450 deletions(-) create mode 100644 archon-ui-main/src/features/ui/primitives/data-card.tsx create mode 100644 archon-ui-main/src/features/ui/primitives/draggable-card.tsx create mode 100644 archon-ui-main/src/features/ui/primitives/selectable-card.tsx diff --git a/.claude/commands/archon/archon-ui-consistency-review.md b/.claude/commands/archon/archon-ui-consistency-review.md index 181afde5..4a88e128 100644 --- a/.claude/commands/archon/archon-ui-consistency-review.md +++ b/.claude/commands/archon/archon-ui-consistency-review.md @@ -283,20 +283,90 @@ Based on the argument: Use grep/glob to find: ```bash -# Hardcoded edge-lit implementations +# Hardcoded edge-lit implementations (should use Card primitive) grep -r "absolute inset-x-0 top-0" [path] -# Native HTML form elements +# Native HTML form elements (should use Radix) grep -r " +``` + +**Why it fails:** +- Tailwind scans code at BUILD time to generate CSS +- Dynamic strings aren't scanned - no CSS generated +- Results in missing styles at runtime + +**Solution:** +```tsx +// ✅ CORRECT - Static class lookup +const colorClasses = { + cyan: "bg-cyan-500 text-cyan-700", + purple: "bg-purple-500 text-purple-700", +}; +className={colorClasses[color]} + +// ✅ CORRECT - Use pre-defined classes from styles.ts +const glowVariant = glassCard.variants[glowColor]; +className={cn(glowVariant.glow, glowVariant.border)} + +// ✅ CORRECT - Inline arbitrary values (scanned by Tailwind) +className="shadow-[0_0_30px_rgba(34,211,238,0.4)]" +``` + +### 🔴 **Not Using styles.ts Pre-Defined Classes** + +**Problem:** +```tsx +// ❌ WRONG - Hardcoding glassmorphism +
+ +// ❌ WRONG - Not using existing glassCard.variants +const myCustomGlow = "shadow-[0_0_40px_rgba(34,211,238,0.4)]"; +``` + +**Solution:** +```tsx +// ✅ CORRECT - Use glassCard from styles.ts +import { glassCard } from '@/features/ui/primitives/styles'; +className={cn(glassCard.base, glassCard.variants.cyan.glow)} + +// ✅ CORRECT - Use Card primitive with props + ``` --- diff --git a/archon-ui-main/src/features/style-guide/layouts/DocumentBrowserExample.tsx b/archon-ui-main/src/features/style-guide/layouts/DocumentBrowserExample.tsx index 2dc20df3..9e42df19 100644 --- a/archon-ui-main/src/features/style-guide/layouts/DocumentBrowserExample.tsx +++ b/archon-ui-main/src/features/style-guide/layouts/DocumentBrowserExample.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { Search, ChevronDown, ChevronRight, Code, FileText } from "lucide-react"; +import { Search, Code, FileText, Globe } from "lucide-react"; import { Button } from "@/features/ui/primitives/button"; import { Dialog, @@ -9,28 +9,34 @@ import { } from "@/features/ui/primitives/dialog"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/features/ui/primitives/tabs"; import { Input } from "@/features/ui/primitives/input"; +import { cn } from "@/features/ui/primitives/styles"; const MOCK_DOCUMENTS = [ { id: "1", - title: "Getting Started with React", - content: - "React is a JavaScript library for building user interfaces. It lets you compose complex UIs from small and isolated pieces of code called components. React components are JavaScript functions that return markup. Components can be as simple as a function that returns JSX, or they can have state and lifecycle methods...", - tags: ["guide", "intro", "react"], + title: "[Radix Homepage](https://www.radix-ui.com/)[Made by WorkOS](https://workos.com)", + preview: "[Radix Homepage](https://www.radix-ui.com/)[Made by WorkOS]...", + content: "[Radix Homepage](https://www.radix-ui.com/)[Made by WorkOS](https://workos.com)\n\n[ThemesThemes](https://www.radix-ui.com/)[PrimitivesPrimitives](https://www.radix-ui.com/primitives)[IconsIcons](https://www.radix-ui.com/icons)[ColorsColors](https://www.radix-ui.com/colors)\n\n[Documentation](https://www.radix-ui.com/themes/docs/overview/getting-started)[Playground](https://www.radix-ui.com/themes/playground)[Blog](https://www.radix-ui.com/blog)[](https://github.com/radix-ui/themes)", + sourceType: "Web" as const, + category: "Technical" as const, + url: "https://www.radix-ui.com/primitives/docs/guides/styling", }, { id: "2", - title: "API Reference - useState Hook", - content: - "useState is a React Hook that lets you add a state variable to your component. Call useState at the top level of your component to declare a state variable. The convention is to name state variables like [something, setSomething] using array destructuring. useState returns an array with exactly two values: the current state and the set function that lets you update it...", - tags: ["api", "hooks", "reference"], + title: "Deleted report #34", + preview: "7-4d586f394674?&w=64&h=64&dpr=2&q=70&crop=faces...", + content: "Detailed report content...", + sourceType: "Document" as const, + category: "Technical" as const, }, { id: "3", - title: "Performance Optimization Guide", - content: - "Before you start optimizing, make sure you're actually measuring performance. React DevTools Profiler can help you identify components that are re-rendering unnecessarily. Common optimization techniques include: using React.memo for expensive components, using useMemo and useCallback hooks to memoize values and functions, code splitting with React.lazy and Suspense...", - tags: ["performance", "optimization", "guide"], + title: "Latest updates", + preview: "[Radix Homepage](https://www.radix-ui.com/)[Made by WorkOS]...", + content: "Latest updates and changes...", + sourceType: "Web" as const, + category: "Technical" as const, + url: "https://www.radix-ui.com", }, ]; @@ -38,56 +44,14 @@ const MOCK_CODE = [ { id: "1", language: "typescript", - summary: "React functional component with useState", - code: `const Counter = () => { - const [count, setCount] = useState(0); - - return ( -
-

Count: {count}

- -
- ); -};`, - file_path: "src/components/Counter.tsx", + summary: "React component example", + code: `const Example = () => {\n return
Hello
;\n};`, }, { id: "2", language: "python", - summary: "FastAPI endpoint with dependency injection", - code: `@app.get("/api/items/{item_id}") -async def get_item( - item_id: str, - db: Session = Depends(get_db) -): - item = db.query(Item).filter(Item.id == item_id).first() - if not item: - raise HTTPException(status_code=404, detail="Item not found") - return item`, - file_path: "src/api/routes/items.py", - }, - { - id: "3", - language: "typescript", - summary: "Custom React hook for data fetching", - code: `const useData = (url: string) => { - const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - fetch(url) - .then(res => res.json()) - .then(setData) - .catch(setError) - .finally(() => setLoading(false)); - }, [url]); - - return { data, loading, error }; -};`, - file_path: "src/hooks/useData.ts", + summary: "FastAPI endpoint", + code: `@app.get("/api/test")\nasync def test():\n return {"status": "ok"}`, }, ]; @@ -96,17 +60,13 @@ export const DocumentBrowserExample = () => { return (
- {/* Explanation Text */}

- Use this pattern for: Displaying structured information in modals - (documents, logs, code, API responses). Tabs organize different data types, search filters - content, items expand/collapse for details. + Use this pattern for: Browsing documents and code with sidebar selection, + tabs, and search filtering.

- {/* Button to Open Modal */} - + - {/* Document Browser Modal */}
); @@ -121,173 +81,148 @@ const DocumentBrowserModal = ({ }) => { const [activeTab, setActiveTab] = useState<"documents" | "code">("documents"); const [searchQuery, setSearchQuery] = useState(""); - const [expandedItems, setExpandedItems] = useState>(new Set()); + const [selectedDoc, setSelectedDoc] = useState(MOCK_DOCUMENTS[0]); + const [selectedCode, setSelectedCode] = useState(MOCK_CODE[0]); - const toggleExpanded = (id: string) => { - setExpandedItems((prev) => { - const next = new Set(prev); - if (next.has(id)) { - next.delete(id); - } else { - next.add(id); - } - return next; - }); - }; - - // Filter based on search const filteredDocuments = MOCK_DOCUMENTS.filter((doc) => - doc.title.toLowerCase().includes(searchQuery.toLowerCase()) || - doc.content.toLowerCase().includes(searchQuery.toLowerCase()) + doc.title.toLowerCase().includes(searchQuery.toLowerCase()) ); const filteredCode = MOCK_CODE.filter((example) => - example.summary.toLowerCase().includes(searchQuery.toLowerCase()) || - example.code.toLowerCase().includes(searchQuery.toLowerCase()) + example.summary.toLowerCase().includes(searchQuery.toLowerCase()) ); return ( - - + + {/* Header outside tabs */} +
Document Browser -
-
- - setSearchQuery(e.target.value)} - className="pl-10 bg-black/30 border-white/10 focus:border-cyan-500/50" - /> -
+
+ + {/* Tabs and Content */} + setActiveTab(v as typeof activeTab)} className="flex-1 flex flex-col px-6"> +
+ + + + Documents ({filteredDocuments.length}) + + + + Code Examples ({filteredCode.length}) + +
- - setActiveTab(v as "documents" | "code")} - className="flex-1 flex flex-col" - > - - - - Documents ({filteredDocuments.length}) - - - - Code Examples ({filteredCode.length}) - - + {/* Documents Tab - Left Sidebar + Right Content */} + + {/* Left Sidebar */} +
+
+ + setSearchQuery(e.target.value)} + className="pl-10" + /> +
- -
- {filteredDocuments.length === 0 ? ( -
- {searchQuery ? "No documents match your search" : "No documents available"} +
+ {filteredDocuments.map((doc) => ( + + ))} +
+
+ + {/* Right Content */} +
+ {/* Header with badges and URL */} +
+
+ + + {selectedDoc.sourceType} + + + {selectedDoc.category} +
- ) : ( -
- {filteredDocuments.map((doc) => { - const isExpanded = expandedItems.has(doc.id); - const preview = doc.content.substring(0, 200); - const needsExpansion = doc.content.length > 200; + {selectedDoc.url && ( + + {selectedDoc.url} + + )} +
- return ( -
- {doc.title && ( -

- {needsExpansion && ( - - )} - {doc.title} -

- )} - -
- {isExpanded || !needsExpansion ? ( - doc.content - ) : ( - <> - {preview}... - - - )} -
- - {doc.tags && doc.tags.length > 0 && ( -
- {doc.tags.map((tag) => ( - - {tag} - - ))} -
- )} -
- ); - })} -
- )} +
+

{selectedDoc.content}

+
- -
- {filteredCode.length === 0 ? ( -
- {searchQuery ? "No code examples match your search" : "No code examples available"} -
- ) : ( -
- {filteredCode.map((example) => ( -
-
-
- - {example.language && ( - - {example.language} - - )} -
- {example.file_path && {example.file_path}} -
- - {example.summary && ( -
{example.summary}
- )} - -
-                        {example.code}
-                      
+ {/* Code Tab - Left Sidebar + Right Content */} + + {/* Left Sidebar */} +
+
+ {filteredCode.map((code) => ( +
- )} +

{code.summary}

+ + ))} +
+
+ + {/* Right Content */} +
+
+

{selectedCode.summary}

+ + {selectedCode.language} + +
+
+                {selectedCode.code}
+              
diff --git a/archon-ui-main/src/features/style-guide/layouts/KnowledgeLayoutExample.tsx b/archon-ui-main/src/features/style-guide/layouts/KnowledgeLayoutExample.tsx index 59d9cb13..a3be2b7d 100644 --- a/archon-ui-main/src/features/style-guide/layouts/KnowledgeLayoutExample.tsx +++ b/archon-ui-main/src/features/style-guide/layouts/KnowledgeLayoutExample.tsx @@ -1,7 +1,8 @@ import { useState } from "react"; import { Grid, List, Asterisk, Terminal, FileCode, Globe, FileText, Calendar } from "lucide-react"; import { Button } from "@/features/ui/primitives/button"; -import { Card } from "@/features/ui/primitives/card"; +import { DataCard, DataCardHeader, DataCardContent, DataCardFooter } from "@/features/ui/primitives/data-card"; +import { StatPill } from "@/features/ui/primitives/pill"; import { Input } from "@/features/ui/primitives/input"; import { ToggleGroup, ToggleGroupItem } from "@/features/ui/primitives/toggle-group"; import { cn } from "@/features/ui/primitives/styles"; @@ -158,114 +159,71 @@ export const KnowledgeLayoutExample = () => { ); }; -// Grid Card Component - matches real KnowledgeCard structure +// Grid Card Component - using DataCard primitive const KnowledgeCard = ({ item }: { item: typeof MOCK_KNOWLEDGE_ITEMS[0] }) => { const isUrl = !!item.url; const isTechnical = item.type === "technical"; - const getCardGradient = () => { - if (isTechnical) { - return isUrl - ? "from-cyan-100/50 via-cyan-50/25 to-white/60 dark:from-cyan-900/20 dark:via-cyan-900/10 dark:to-black/30" - : "from-purple-100/50 via-purple-50/25 to-white/60 dark:from-purple-900/20 dark:via-purple-900/10 dark:to-black/30"; - } - return isUrl - ? "from-blue-100/50 via-blue-50/25 to-white/60 dark:from-blue-900/20 dark:via-blue-900/10 dark:to-black/30" - : "from-pink-100/50 via-pink-50/25 to-white/60 dark:from-pink-900/20 dark:via-pink-900/10 dark:to-black/30"; + const getEdgeColor = (): "cyan" | "purple" | "blue" | "pink" => { + if (isTechnical) return isUrl ? "cyan" : "purple"; + return isUrl ? "blue" : "pink"; }; - const getBorderColor = () => { - if (isTechnical) { - return isUrl - ? "border-cyan-600/30 dark:border-cyan-500/30" - : "border-purple-600/30 dark:border-purple-500/30"; - } - return isUrl - ? "border-blue-600/30 dark:border-blue-500/30" - : "border-pink-600/30 dark:border-pink-500/30"; - }; - - const getAccent = () => { - if (isTechnical) { - return isUrl - ? { bar: "bg-cyan-500", smear: "from-cyan-500/25" } - : { bar: "bg-purple-500", smear: "from-purple-500/25" }; - } - return isUrl - ? { bar: "bg-blue-500", smear: "from-blue-500/25" } - : { bar: "bg-pink-500", smear: "from-pink-500/25" }; - }; - - const accent = getAccent(); - return ( -
- {/* Top accent glow */} -
-
-
-
- - {/* Content */} -
-
-
-
- {isUrl ? : } - {isUrl ? "Web Page" : "Document"} -
- - {item.type} - + +
+
+ {isUrl ? : } + {isUrl ? "Web Page" : "Document"}
+ + {item.type} +

{item.title}

- {item.url && ( -
{item.url}
- )} -
+ {item.url &&
{item.url}
} + - {/* Footer with stats */} -
+ + +
{item.date}
-
-
- - {item.chunks} -
-
+ } + size="sm" + onClick={() => console.log('View documents')} + className="cursor-pointer hover:scale-105 transition-transform" + />
-
-
+ + ); }; diff --git a/archon-ui-main/src/features/style-guide/layouts/ProjectsLayoutExample.tsx b/archon-ui-main/src/features/style-guide/layouts/ProjectsLayoutExample.tsx index 1d02c238..51d96506 100644 --- a/archon-ui-main/src/features/style-guide/layouts/ProjectsLayoutExample.tsx +++ b/archon-ui-main/src/features/style-guide/layouts/ProjectsLayoutExample.tsx @@ -1,8 +1,11 @@ import { useState } from "react"; import { LayoutGrid, List, ListTodo, Activity, CheckCircle2, FileText, Search, Table as TableIcon, Tag, User, Trash2, Pin, Copy } from "lucide-react"; -import { motion } from "framer-motion"; +import { DndProvider } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; import { Button } from "@/features/ui/primitives/button"; import { Card } from "@/features/ui/primitives/card"; +import { DraggableCard } from "@/features/ui/primitives/draggable-card"; +import { SelectableCard } from "@/features/ui/primitives/selectable-card"; import { Input } from "@/features/ui/primitives/input"; import { PillNavigation, type PillNavigationItem } from "../shared/PillNavigation"; import { cn } from "@/features/ui/primitives/styles"; @@ -273,7 +276,7 @@ const SidebarProjectCard = ({ ); }; -// Project Card matching REAL ProjectCard exactly +// Project Card using SelectableCard primitive const ProjectCardExample = ({ project, isSelected, @@ -283,36 +286,25 @@ const ProjectCardExample = ({ isSelected: boolean; onSelect: () => void; }) => { + // Custom gradients for pinned vs selected vs default + const getBackgroundClass = () => { + if (project.pinned) return "bg-gradient-to-b from-purple-100/80 via-purple-50/30 to-purple-100/50 dark:from-purple-900/30 dark:via-purple-900/20 dark:to-purple-900/10"; + if (isSelected) return "bg-gradient-to-b from-white/70 via-purple-50/20 to-white/50 dark:from-white/5 dark:via-purple-900/5 dark:to-black/20"; + return "bg-gradient-to-b from-white/80 to-white/60 dark:from-white/10 dark:to-black/30"; + }; + return ( - - {/* Aurora glow effect for selected card */} - {isSelected && ( -
-
-
- )} - {/* Main content */}
{/* Title */} @@ -450,11 +442,11 @@ const ProjectCardExample = ({
-
+ ); }; -// Kanban Board - NO BACKGROUNDS +// Kanban Board - NO BACKGROUNDS, wrapped in DndProvider const KanbanBoardView = () => { const columns = [ { status: "todo" as const, title: "Todo", color: "text-pink-500", glow: "bg-pink-500" }, @@ -468,30 +460,32 @@ const KanbanBoardView = () => { }; return ( -
- {columns.map(({ status, title, color, glow }) => ( -
- {/* Column Header - transparent */} -
-

{title}

-
{getTasksByStatus(status).length}
-
-
+ +
+ {columns.map(({ status, title, color, glow }) => ( +
+ {/* Column Header - transparent */} +
+

{title}

+
{getTasksByStatus(status).length}
+
+
- {/* Tasks */} -
- {getTasksByStatus(status).map((task) => ( - - ))} + {/* Tasks */} +
+ {getTasksByStatus(status).map((task, idx) => ( + + ))} +
-
- ))} -
+ ))} +
+
); }; -// Task Card matching REAL TaskCard exactly -const TaskCardExample = ({ task }: { task: typeof MOCK_TASKS[0] }) => { +// Task Card using DraggableCard primitive +const TaskCardExample = ({ task, index }: { task: typeof MOCK_TASKS[0]; index: number }) => { const getPriorityColor = (priority: string) => { if (priority === "high") return { color: "bg-red-500", glow: "shadow-[0_0_10px_rgba(239,68,68,0.3)]" }; if (priority === "medium") return { color: "bg-yellow-500", glow: "shadow-[0_0_10px_rgba(234,179,8,0.3)]" }; @@ -501,9 +495,15 @@ const TaskCardExample = ({ task }: { task: typeof MOCK_TASKS[0] }) => { const priorityStyle = getPriorityColor(task.priority); return ( -
-
- {/* Priority indicator glow on left */} +
+ + {/* Priority indicator on left edge */}
{/* Content */} @@ -538,7 +538,7 @@ const TaskCardExample = ({ task }: { task: typeof MOCK_TASKS[0] }) => {
-
+
); }; diff --git a/archon-ui-main/src/features/style-guide/showcases/StaticCards.tsx b/archon-ui-main/src/features/style-guide/showcases/StaticCards.tsx index 87729fa6..85ca8237 100644 --- a/archon-ui-main/src/features/style-guide/showcases/StaticCards.tsx +++ b/archon-ui-main/src/features/style-guide/showcases/StaticCards.tsx @@ -1,77 +1,292 @@ +import { useState } from "react"; import { Card } from "@/features/ui/primitives/card"; +import { DraggableCard } from "@/features/ui/primitives/draggable-card"; +import { SelectableCard } from "@/features/ui/primitives/selectable-card"; +import { DndProvider } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; import { cn } from "@/features/ui/primitives/styles"; -export const StaticCards = () => { +// Base Glass Card with transparency tabs +const BaseGlassCardShowcase = () => { + const [activeTab, setActiveTab] = useState<"light" | "frosted" | "solid">("light"); + return ( -
+
+

Base Glass Card

+ + {/* Tabs */} +
+ {(["light", "frosted", "solid"] as const).map((tab) => ( + + ))} +
+ + {/* Card Display */} + +
Card Title
+

+ {activeTab === "light" && "Light glass - low opacity (8%), see grid through"} + {activeTab === "frosted" && "Frosted glass - white frosted in light mode, black frosted in dark mode"} + {activeTab === "solid" && "Solid - high opacity (90%), opaque background"} +

+
+

+ {``} +

+
+ ); +}; + +// Outer Glow Card with size tabs +const OuterGlowCardShowcase = () => { + const [activeSize, setActiveSize] = useState<"sm" | "md" | "lg" | "xl">("md"); + + return ( +
+

Outer Glow Card

+ + {/* Size Tabs */} +
+ {(["sm", "md", "lg", "xl"] as const).map((size) => ( + + ))} +
+ + {/* Card Display */} + +
Active Card
+

+ Outer glow - {activeSize.toUpperCase()} (hover for brighter, same size) +

+
+

+ {``} +

+
+ ); +}; + +// Inner Glow Card with size tabs +const InnerGlowCardShowcase = () => { + const [activeSize, setActiveSize] = useState<"sm" | "md" | "lg" | "xl">("md"); + + return ( +
+

Inner Glow Card

+ + {/* Size Tabs */} +
+ {(["sm", "md", "lg", "xl"] as const).map((size) => ( + + ))} +
+ + {/* Card Display */} + +
Featured Card
+

+ Inner glow - {activeSize.toUpperCase()} (hover for brighter, same size) +

+
+

+ {``} +

+
+ ); +}; + +// Edge-Lit Card with color tabs +const EdgeLitCardShowcase = () => { + const [activeColor, setActiveColor] = useState<"cyan" | "purple" | "pink" | "blue">("cyan"); + + const colorDescriptions = { + cyan: "Technical web pages", + purple: "Uploaded documents", + pink: "Business content", + blue: "Information pages", + }; + + // Static color classes (NOT dynamic) - Tailwind requirement + const tabColorClasses = { + cyan: "bg-cyan-500/20 text-cyan-700 dark:text-cyan-300 border border-cyan-500/50", + purple: "bg-purple-500/20 text-purple-700 dark:text-purple-300 border border-purple-500/50", + pink: "bg-pink-500/20 text-pink-700 dark:text-pink-300 border border-pink-500/50", + blue: "bg-blue-500/20 text-blue-700 dark:text-blue-300 border border-blue-500/50", + }; + + return ( +
+

Top Edge Glow Card

+ + {/* Color Tabs */} +
+ {(["cyan", "purple", "pink", "blue"] as const).map((color) => ( + + ))} +
+ + {/* Card Display */} + +
+ {activeColor.charAt(0).toUpperCase() + activeColor.slice(1)} Edge Light +
+

+ {colorDescriptions[activeColor]} +

+
+

+ {``} +

+
+ ); +}; + +export const StaticCards = () => { + const [selectedCardId, setSelectedCardId] = useState("card-2"); + + return ( +

Cards

- Glass card variants used in the application + Glass card variants and advanced card components

-
- {/* Base Glass Card */} + {/* Responsive Grid */} +
+ {/* Base Glass Card - Transparency Variants */} + + + {/* Outer Glow Card - Size Variants */} + + + {/* Inner Glow Card - Size Variants */} + + + {/* Top Edge Glow Card - Color Variants */} + +
+ + {/* Advanced Card Components */} +
-

Base Glass Card

- -
Card Title
-

- Default glass card with backdrop blur and semi-transparent background. Used for general containers, settings panels, and content wrappers. -

-
-

- {""} +

Advanced Card Components

+

+ Specialized cards that extend the base Card primitive with additional behaviors

- {/* Outer Glow Card */} + {/* Selectable Cards */}
-

Outer Glow Card

-
-
- -
Active Card
-

- Card with external glow. Used for selected or active states. Hover to see enhanced glow. -

-
+

SelectableCard

+

+ Card with selection states, hover effects, and optional aurora glow. Click cards to select. +

+
+ {["card-1", "card-2", "card-3"].map((id) => ( + setSelectedCardId(id)} + size="sm" + className="min-h-[120px]" + > +
+ {id === selectedCardId ? "Selected" : "Click to Select"} +
+

+ Card {id.split("-")[1]} +

+
+ ))}
-

- shadow-[0_0_30px_rgba(6,182,212,0.3)] + hover effect +

+ {''}

- {/* Inner Glow Card */} + {/* Draggable Cards */}
-

Inner Glow Card

- -
Featured Card
-

- Card with internal glow effect. Used for special containers, featured sections, and highlighted content areas. -

-
-

- shadow-[inset_0_0_20px_rgba(59,130,246,0.2)] +

DraggableCard

+

+ Card with drag-and-drop functionality. Try dragging cards to reorder.

-
- - {/* Top Edge Glow Card */} -
-

Top Edge Glow Card

-
-
-
- -
Knowledge Item
-

- Card with colored top edge glow. Used for knowledge cards - cyan for technical web pages, purple for uploaded docs, blue for business content. -

-
-
-

- Top hairline (2px) + blur smear (8px) + +

+ {[1, 2, 3].map((num) => ( + +
Draggable {num}
+

+ Drag me to reorder +

+
+ ))} +
+ +

+ {''}

diff --git a/archon-ui-main/src/features/ui/primitives/card.tsx b/archon-ui-main/src/features/ui/primitives/card.tsx index 0144df17..91bdfdd3 100644 --- a/archon-ui-main/src/features/ui/primitives/card.tsx +++ b/archon-ui-main/src/features/ui/primitives/card.tsx @@ -7,32 +7,67 @@ interface CardProps extends React.HTMLAttributes { transparency?: 'clear' | 'light' | 'medium' | 'frosted' | 'solid'; glassTint?: 'none' | 'purple' | 'blue' | 'cyan' | 'green' | 'orange' | 'pink' | 'red'; - // Glow properties + // Glow properties (uses pre-defined static classes from styles.ts) glowColor?: 'none' | 'purple' | 'blue' | 'cyan' | 'green' | 'orange' | 'pink' | 'red'; + glowType?: 'outer' | 'inner'; + glowSize?: 'sm' | 'md' | 'lg' | 'xl'; // Edge-lit properties edgePosition?: 'none' | 'top' | 'left' | 'right' | 'bottom'; edgeColor?: 'purple' | 'blue' | 'cyan' | 'green' | 'orange' | 'pink' | 'red'; - // Size - size?: 'sm' | 'md' | 'lg' | 'xl'; + // Size (padding) + size?: 'none' | 'sm' | 'md' | 'lg' | 'xl'; } export const Card = React.forwardRef( ({ className, - blur = 'xl', + blur = 'md', transparency = 'light', glassTint = 'none', glowColor = 'none', + glowType = 'outer', + glowSize = 'md', edgePosition = 'none', edgeColor = 'cyan', size = 'md', children, ...props }, ref) => { - const glowVariant = glassCard.variants[glowColor] || glassCard.variants.none; const hasEdge = edgePosition !== 'none'; + const hasGlow = glowColor !== 'none'; + + // Use pre-defined static classes from styles.ts + const glowVariant = glassCard.variants[glowColor] || glassCard.variants.none; + + // Get glow class from static lookups in styles.ts + const getGlowClass = () => { + if (!hasGlow || hasEdge) return ''; + + if (glowType === 'inner') { + // @ts-ignore - accessing dynamic object safely + return glassCard.innerGlowSizes?.[glowColor]?.[glowSize] || ''; + } + + // Outer glow + // @ts-ignore - accessing dynamic object safely + return glassCard.outerGlowSizes?.[glowColor]?.[glowSize] || glowVariant.glow; + }; + + // Get size-matched hover glow class + const getHoverGlowClass = () => { + if (!hasGlow || hasEdge) return ''; + + if (glowType === 'inner') { + // @ts-ignore - accessing dynamic object safely + return glassCard.innerGlowHover?.[glowColor]?.[glowSize] || ''; + } + + // Outer glow hover + // @ts-ignore - accessing dynamic object safely + return glassCard.outerGlowHover?.[glowColor]?.[glowSize] || glowVariant.hover; + }; // Edge color mappings const edgeColors = { @@ -60,29 +95,33 @@ export const Card = React.forwardRef( if (hasEdge && edgePosition === 'top') { // Edge-lit card with actual div elements (not pseudo-elements) + // Extract flex/layout classes from className to apply to inner content div + const flexClasses = className?.match(/(flex|flex-col|flex-row|flex-1|items-\S+|justify-\S+|gap-\S+)/g)?.join(' ') || ''; + const otherClasses = className?.replace(/(flex|flex-col|flex-row|flex-1|items-\S+|justify-\S+|gap-\S+)/g, '').trim() || ''; + return (
- {/* Top edge light bar */} -
+ {/* Top edge light bar - thinner */} +
{/* Glow bleeding into card */}
- {/* Content with tinted background */} -
+ {/* Content with tinted background - INHERIT flex classes */} +
{children}
); } - // Standard card (no edge-lit) + // Standard card (no edge-lit) - use static classes from styles.ts return (
( ? glassCard.tints[glassTint][transparency] : glassCard.transparency[transparency], glassCard.sizes[size], + // Border and glow classes from static lookups !hasEdge && glowVariant.border, - !hasEdge && glowVariant.glow, - !hasEdge && glowVariant.hover, + !hasEdge && getGlowClass(), + !hasEdge && getHoverGlowClass(), // Size-matched hover className )} {...props} diff --git a/archon-ui-main/src/features/ui/primitives/data-card.tsx b/archon-ui-main/src/features/ui/primitives/data-card.tsx new file mode 100644 index 00000000..fbe4f464 --- /dev/null +++ b/archon-ui-main/src/features/ui/primitives/data-card.tsx @@ -0,0 +1,155 @@ +import React from "react"; +import { cn } from "./styles"; + +interface DataCardProps extends React.HTMLAttributes { + // Edge-lit properties + edgePosition?: 'none' | 'top' | 'left' | 'right' | 'bottom'; + edgeColor?: 'purple' | 'blue' | 'cyan' | 'green' | 'orange' | 'pink' | 'red'; + + // Glow properties + glowColor?: 'none' | 'purple' | 'blue' | 'cyan' | 'green' | 'orange' | 'pink' | 'red'; + + // Glass properties + blur?: 'none' | 'sm' | 'md' | 'lg' | 'xl'; + transparency?: 'clear' | 'light' | 'medium' | 'frosted' | 'solid'; +} + +interface DataCardHeaderProps extends React.HTMLAttributes {} +interface DataCardContentProps extends React.HTMLAttributes {} +interface DataCardFooterProps extends React.HTMLAttributes {} + +// Edge color mappings for edge-lit cards +const edgeColors = { + purple: { solid: 'bg-purple-500', gradient: 'from-purple-500/40', border: 'border-purple-500/30', bg: 'bg-gradient-to-br from-purple-500/15 to-purple-600/5' }, + blue: { solid: 'bg-blue-500', gradient: 'from-blue-500/40', border: 'border-blue-500/30', bg: 'bg-gradient-to-br from-blue-500/15 to-blue-600/5' }, + cyan: { solid: 'bg-cyan-500', gradient: 'from-cyan-500/40', border: 'border-cyan-500/30', bg: 'bg-gradient-to-br from-cyan-500/15 to-cyan-600/5' }, + green: { solid: 'bg-green-500', gradient: 'from-green-500/40', border: 'border-green-500/30', bg: 'bg-gradient-to-br from-green-500/15 to-green-600/5' }, + orange: { solid: 'bg-orange-500', gradient: 'from-orange-500/40', border: 'border-orange-500/30', bg: 'bg-gradient-to-br from-orange-500/15 to-orange-600/5' }, + pink: { solid: 'bg-pink-500', gradient: 'from-pink-500/40', border: 'border-pink-500/30', bg: 'bg-gradient-to-br from-pink-500/15 to-pink-600/5' }, + red: { solid: 'bg-red-500', gradient: 'from-red-500/40', border: 'border-red-500/30', bg: 'bg-gradient-to-br from-red-500/15 to-red-600/5' }, +}; + +const blurClasses = { + none: "backdrop-blur-none", + sm: "backdrop-blur-sm", + md: "backdrop-blur-md", + lg: "backdrop-blur-lg", + xl: "backdrop-blur-xl", +}; + +const transparencyClasses = { + clear: "bg-white/[0.02] dark:bg-white/[0.01]", + light: "bg-white/[0.08] dark:bg-white/[0.05]", + medium: "bg-white/[0.15] dark:bg-white/[0.08]", + frosted: "bg-white/[0.40] dark:bg-black/[0.40]", + solid: "bg-white/[0.90] dark:bg-black/[0.95]", +}; + +export const DataCard = React.forwardRef( + ({ + className, + edgePosition = 'none', + edgeColor = 'cyan', + glowColor = 'none', + blur = 'md', + transparency = 'light', + children, + ...props + }, ref) => { + const hasEdge = edgePosition !== 'none'; + const edgeStyle = hasEdge ? edgeColors[edgeColor] : null; + + if (hasEdge && edgePosition === 'top') { + return ( +
+ {/* Top edge light */} +
+ {/* Glow bleeding down */} +
+ + {/* Content wrapper with flex layout */} +
+ {children} +
+
+ ); + } + + // Standard card (no edge-lit) + return ( +
+ {children} +
+ ); + } +); + +DataCard.displayName = "DataCard"; + +// Header component +export const DataCardHeader = React.forwardRef( + ({ className, children, ...props }, ref) => { + return ( +
+ {children} +
+ ); + } +); + +DataCardHeader.displayName = "DataCardHeader"; + +// Content component (flexible - grows to fill space) +export const DataCardContent = React.forwardRef( + ({ className, children, ...props }, ref) => { + return ( +
+ {children} +
+ ); + } +); + +DataCardContent.displayName = "DataCardContent"; + +// Footer component (anchored to bottom) +export const DataCardFooter = React.forwardRef( + ({ className, children, ...props }, ref) => { + return ( +
+ {children} +
+ ); + } +); + +DataCardFooter.displayName = "DataCardFooter"; diff --git a/archon-ui-main/src/features/ui/primitives/draggable-card.tsx b/archon-ui-main/src/features/ui/primitives/draggable-card.tsx new file mode 100644 index 00000000..7b00f8b3 --- /dev/null +++ b/archon-ui-main/src/features/ui/primitives/draggable-card.tsx @@ -0,0 +1,79 @@ +import React from "react"; +import { useDrag, useDrop } from "react-dnd"; +import { Card, type CardProps } from "./card"; + +interface DraggableCardProps extends Omit { + // Drag and drop + itemType: string; + itemId: string; + index: number; + onDrop?: (draggedId: string, targetIndex: number) => void; + + // Visual states + isDragging?: boolean; + onDragStart?: () => void; + onDragEnd?: () => void; +} + +export const DraggableCard = React.forwardRef( + ({ + itemType, + itemId, + index, + onDrop, + onDragStart, + onDragEnd, + children, + className, + ...cardProps + }, ref) => { + const [{ isDragging }, drag] = useDrag({ + type: itemType, + item: { id: itemId, index }, + collect: (monitor) => ({ + isDragging: !!monitor.isDragging(), + }), + end: () => { + onDragEnd?.(); + }, + }); + + const [{ isOver }, drop] = useDrop({ + accept: itemType, + hover: (draggedItem: { id: string; index: number }) => { + if (draggedItem.id === itemId) return; + if (draggedItem.index === index) return; + + if (onDrop) { + onDrop(draggedItem.id, index); + draggedItem.index = index; + } + }, + collect: (monitor) => ({ + isOver: !!monitor.isOver(), + }), + }); + + const combinedRef = (node: HTMLDivElement | null) => { + drag(drop(node)); + if (typeof ref === 'function') { + ref(node); + } else if (ref) { + ref.current = node; + } + }; + + return ( +
+ + {children} + +
+ ); + } +); + +DraggableCard.displayName = "DraggableCard"; diff --git a/archon-ui-main/src/features/ui/primitives/index.ts b/archon-ui-main/src/features/ui/primitives/index.ts index 163ce03f..db1f8c27 100644 --- a/archon-ui-main/src/features/ui/primitives/index.ts +++ b/archon-ui-main/src/features/ui/primitives/index.ts @@ -14,6 +14,10 @@ export * from "./alert-dialog"; // Export all primitives export * from "./button"; +export * from "./card"; +export * from "./data-card"; +export * from "./draggable-card"; +export * from "./selectable-card"; export * from "./combobox"; export * from "./dialog"; export * from "./dropdown-menu"; diff --git a/archon-ui-main/src/features/ui/primitives/selectable-card.tsx b/archon-ui-main/src/features/ui/primitives/selectable-card.tsx new file mode 100644 index 00000000..6d5d30dc --- /dev/null +++ b/archon-ui-main/src/features/ui/primitives/selectable-card.tsx @@ -0,0 +1,72 @@ +import React from "react"; +import { motion } from "framer-motion"; +import { Card, type CardProps } from "./card"; +import { cn } from "./styles"; + +interface SelectableCardProps extends Omit { + // Selection state + isSelected?: boolean; + onSelect?: () => void; + + // Visual states + isPinned?: boolean; + showAuroraGlow?: boolean; // Aurora effect for selected state + + // Selection colors + selectedBorderColor?: string; + selectedShadow?: string; + pinnedBorderColor?: string; + pinnedShadow?: string; +} + +export const SelectableCard = React.forwardRef( + ({ + isSelected = false, + isPinned = false, + showAuroraGlow = false, + onSelect, + selectedBorderColor = "border-purple-400/60 dark:border-purple-500/60", + selectedShadow = "shadow-[0_0_15px_rgba(168,85,247,0.4),0_0_10px_rgba(147,51,234,0.3)] dark:shadow-[0_0_20px_rgba(168,85,247,0.5),0_0_15px_rgba(147,51,234,0.4)]", + pinnedBorderColor = "border-purple-500/80 dark:border-purple-500/80", + pinnedShadow = "shadow-[0_0_15px_rgba(168,85,247,0.3)]", + children, + className, + ...cardProps + }, ref) => { + return ( + +
+ {/* Aurora glow effect for selected state */} + {isSelected && showAuroraGlow && ( +
+
+
+ )} + + + {children} + +
+ + ); + } +); + +SelectableCard.displayName = "SelectableCard"; diff --git a/archon-ui-main/src/features/ui/primitives/styles.ts b/archon-ui-main/src/features/ui/primitives/styles.ts index ada12a91..f24dcc11 100644 --- a/archon-ui-main/src/features/ui/primitives/styles.ts +++ b/archon-ui-main/src/features/ui/primitives/styles.ts @@ -136,11 +136,11 @@ export const glassCard = { // Glass transparency levels - Theme-aware for better color visibility transparency: { - clear: "bg-white/[0.01] dark:bg-black/[0.01]", // 1% - almost invisible - light: "bg-white/[0.03] dark:bg-black/[0.05]", // 3-5% - very subtle glass - medium: "bg-white/[0.05] dark:bg-black/[0.08]", // 5-8% - standard glass - frosted: "bg-white/[0.08] dark:bg-black/[0.12]", // 8-12% - frosted glass - solid: "bg-white/[0.12] dark:bg-black/[0.20]" // 12-20% - more visible + clear: "bg-white/[0.02] dark:bg-white/[0.01]", // Very transparent - see through + light: "bg-white/[0.08] dark:bg-white/[0.05]", // Light glass - see through clearly + medium: "bg-white/[0.15] dark:bg-white/[0.08]", // Medium glass - lighter in dark mode + frosted: "bg-white/[0.40] dark:bg-black/[0.40]", // Frosted - white in light, black in dark + solid: "bg-white/[0.90] dark:bg-black/[0.95]" // Solid - opaque }, // Colored glass tints - BRIGHT NEON COLORS with higher opacity @@ -197,7 +197,7 @@ export const glassCard = { } }, - // Neon glow effects - BRIGHTER & MORE INTENSE + // Neon glow effects - BRIGHTER & MORE INTENSE (default = md size) variants: { none: { border: "border-gray-300/20 dark:border-white/10", @@ -241,8 +241,192 @@ export const glassCard = { } }, + // Outer glow size variants (static classes for each color) + outerGlowSizes: { + cyan: { + sm: "shadow-[0_0_20px_rgba(34,211,238,0.3)]", + md: "shadow-[0_0_40px_rgba(34,211,238,0.4)]", + lg: "shadow-[0_0_70px_rgba(34,211,238,0.5)]", + xl: "shadow-[0_0_100px_rgba(34,211,238,0.6)]" + }, + purple: { + sm: "shadow-[0_0_20px_rgba(168,85,247,0.3)]", + md: "shadow-[0_0_40px_rgba(168,85,247,0.4)]", + lg: "shadow-[0_0_70px_rgba(168,85,247,0.5)]", + xl: "shadow-[0_0_100px_rgba(168,85,247,0.6)]" + }, + blue: { + sm: "shadow-[0_0_20px_rgba(59,130,246,0.3)]", + md: "shadow-[0_0_40px_rgba(59,130,246,0.4)]", + lg: "shadow-[0_0_70px_rgba(59,130,246,0.5)]", + xl: "shadow-[0_0_100px_rgba(59,130,246,0.6)]" + }, + pink: { + sm: "shadow-[0_0_20px_rgba(236,72,153,0.3)]", + md: "shadow-[0_0_40px_rgba(236,72,153,0.4)]", + lg: "shadow-[0_0_70px_rgba(236,72,153,0.5)]", + xl: "shadow-[0_0_100px_rgba(236,72,153,0.6)]" + }, + green: { + sm: "shadow-[0_0_20px_rgba(16,185,129,0.3)]", + md: "shadow-[0_0_40px_rgba(16,185,129,0.4)]", + lg: "shadow-[0_0_70px_rgba(16,185,129,0.5)]", + xl: "shadow-[0_0_100px_rgba(16,185,129,0.6)]" + }, + orange: { + sm: "shadow-[0_0_20px_rgba(251,146,60,0.3)]", + md: "shadow-[0_0_40px_rgba(251,146,60,0.4)]", + lg: "shadow-[0_0_70px_rgba(251,146,60,0.5)]", + xl: "shadow-[0_0_100px_rgba(251,146,60,0.6)]" + }, + red: { + sm: "shadow-[0_0_20px_rgba(239,68,68,0.3)]", + md: "shadow-[0_0_40px_rgba(239,68,68,0.4)]", + lg: "shadow-[0_0_70px_rgba(239,68,68,0.5)]", + xl: "shadow-[0_0_100px_rgba(239,68,68,0.6)]" + } + }, + + // Inner glow variants (static classes for each color) - WIDER range than outer + innerGlowSizes: { + cyan: { + sm: "shadow-[inset_0_0_15px_rgba(34,211,238,0.2)]", + md: "shadow-[inset_0_0_40px_rgba(34,211,238,0.3)]", + lg: "shadow-[inset_0_0_80px_rgba(34,211,238,0.4)]", + xl: "shadow-[inset_0_0_120px_rgba(34,211,238,0.5)]" + }, + purple: { + sm: "shadow-[inset_0_0_15px_rgba(168,85,247,0.2)]", + md: "shadow-[inset_0_0_40px_rgba(168,85,247,0.3)]", + lg: "shadow-[inset_0_0_80px_rgba(168,85,247,0.4)]", + xl: "shadow-[inset_0_0_120px_rgba(168,85,247,0.5)]" + }, + blue: { + sm: "shadow-[inset_0_0_15px_rgba(59,130,246,0.2)]", + md: "shadow-[inset_0_0_40px_rgba(59,130,246,0.3)]", + lg: "shadow-[inset_0_0_80px_rgba(59,130,246,0.4)]", + xl: "shadow-[inset_0_0_120px_rgba(59,130,246,0.5)]" + }, + pink: { + sm: "shadow-[inset_0_0_15px_rgba(236,72,153,0.2)]", + md: "shadow-[inset_0_0_40px_rgba(236,72,153,0.3)]", + lg: "shadow-[inset_0_0_80px_rgba(236,72,153,0.4)]", + xl: "shadow-[inset_0_0_120px_rgba(236,72,153,0.5)]" + }, + green: { + sm: "shadow-[inset_0_0_15px_rgba(16,185,129,0.2)]", + md: "shadow-[inset_0_0_40px_rgba(16,185,129,0.3)]", + lg: "shadow-[inset_0_0_80px_rgba(16,185,129,0.4)]", + xl: "shadow-[inset_0_0_120px_rgba(16,185,129,0.5)]" + }, + orange: { + sm: "shadow-[inset_0_0_15px_rgba(251,146,60,0.2)]", + md: "shadow-[inset_0_0_40px_rgba(251,146,60,0.3)]", + lg: "shadow-[inset_0_0_80px_rgba(251,146,60,0.4)]", + xl: "shadow-[inset_0_0_120px_rgba(251,146,60,0.5)]" + }, + red: { + sm: "shadow-[inset_0_0_15px_rgba(239,68,68,0.2)]", + md: "shadow-[inset_0_0_40px_rgba(239,68,68,0.3)]", + lg: "shadow-[inset_0_0_80px_rgba(239,68,68,0.4)]", + xl: "shadow-[inset_0_0_120px_rgba(239,68,68,0.5)]" + } + }, + + // Hover glow variants - size-matched (brighter, same size) + outerGlowHover: { + cyan: { + sm: "hover:shadow-[0_0_20px_rgba(34,211,238,0.5)]", + md: "hover:shadow-[0_0_40px_rgba(34,211,238,0.6)]", + lg: "hover:shadow-[0_0_70px_rgba(34,211,238,0.7)]", + xl: "hover:shadow-[0_0_100px_rgba(34,211,238,0.8)]" + }, + purple: { + sm: "hover:shadow-[0_0_20px_rgba(168,85,247,0.5)]", + md: "hover:shadow-[0_0_40px_rgba(168,85,247,0.6)]", + lg: "hover:shadow-[0_0_70px_rgba(168,85,247,0.7)]", + xl: "hover:shadow-[0_0_100px_rgba(168,85,247,0.8)]" + }, + blue: { + sm: "hover:shadow-[0_0_20px_rgba(59,130,246,0.5)]", + md: "hover:shadow-[0_0_40px_rgba(59,130,246,0.6)]", + lg: "hover:shadow-[0_0_70px_rgba(59,130,246,0.7)]", + xl: "hover:shadow-[0_0_100px_rgba(59,130,246,0.8)]" + }, + pink: { + sm: "hover:shadow-[0_0_20px_rgba(236,72,153,0.5)]", + md: "hover:shadow-[0_0_40px_rgba(236,72,153,0.6)]", + lg: "hover:shadow-[0_0_70px_rgba(236,72,153,0.7)]", + xl: "hover:shadow-[0_0_100px_rgba(236,72,153,0.8)]" + }, + green: { + sm: "hover:shadow-[0_0_20px_rgba(16,185,129,0.5)]", + md: "hover:shadow-[0_0_40px_rgba(16,185,129,0.6)]", + lg: "hover:shadow-[0_0_70px_rgba(16,185,129,0.7)]", + xl: "hover:shadow-[0_0_100px_rgba(16,185,129,0.8)]" + }, + orange: { + sm: "hover:shadow-[0_0_20px_rgba(251,146,60,0.5)]", + md: "hover:shadow-[0_0_40px_rgba(251,146,60,0.6)]", + lg: "hover:shadow-[0_0_70px_rgba(251,146,60,0.7)]", + xl: "hover:shadow-[0_0_100px_rgba(251,146,60,0.8)]" + }, + red: { + sm: "hover:shadow-[0_0_20px_rgba(239,68,68,0.5)]", + md: "hover:shadow-[0_0_40px_rgba(239,68,68,0.6)]", + lg: "hover:shadow-[0_0_70px_rgba(239,68,68,0.7)]", + xl: "hover:shadow-[0_0_100px_rgba(239,68,68,0.8)]" + } + }, + + innerGlowHover: { + cyan: { + sm: "hover:shadow-[inset_0_0_15px_rgba(34,211,238,0.4)]", + md: "hover:shadow-[inset_0_0_40px_rgba(34,211,238,0.5)]", + lg: "hover:shadow-[inset_0_0_80px_rgba(34,211,238,0.6)]", + xl: "hover:shadow-[inset_0_0_120px_rgba(34,211,238,0.7)]" + }, + purple: { + sm: "hover:shadow-[inset_0_0_15px_rgba(168,85,247,0.4)]", + md: "hover:shadow-[inset_0_0_40px_rgba(168,85,247,0.5)]", + lg: "hover:shadow-[inset_0_0_80px_rgba(168,85,247,0.6)]", + xl: "hover:shadow-[inset_0_0_120px_rgba(168,85,247,0.7)]" + }, + blue: { + sm: "hover:shadow-[inset_0_0_15px_rgba(59,130,246,0.4)]", + md: "hover:shadow-[inset_0_0_40px_rgba(59,130,246,0.5)]", + lg: "hover:shadow-[inset_0_0_80px_rgba(59,130,246,0.6)]", + xl: "hover:shadow-[inset_0_0_120px_rgba(59,130,246,0.7)]" + }, + pink: { + sm: "hover:shadow-[inset_0_0_15px_rgba(236,72,153,0.4)]", + md: "hover:shadow-[inset_0_0_40px_rgba(236,72,153,0.5)]", + lg: "hover:shadow-[inset_0_0_80px_rgba(236,72,153,0.6)]", + xl: "hover:shadow-[inset_0_0_120px_rgba(236,72,153,0.7)]" + }, + green: { + sm: "hover:shadow-[inset_0_0_15px_rgba(16,185,129,0.4)]", + md: "hover:shadow-[inset_0_0_40px_rgba(16,185,129,0.5)]", + lg: "hover:shadow-[inset_0_0_80px_rgba(16,185,129,0.6)]", + xl: "hover:shadow-[inset_0_0_120px_rgba(16,185,129,0.7)]" + }, + orange: { + sm: "hover:shadow-[inset_0_0_15px_rgba(251,146,60,0.4)]", + md: "hover:shadow-[inset_0_0_40px_rgba(251,146,60,0.5)]", + lg: "hover:shadow-[inset_0_0_80px_rgba(251,146,60,0.6)]", + xl: "hover:shadow-[inset_0_0_120px_rgba(251,146,60,0.7)]" + }, + red: { + sm: "hover:shadow-[inset_0_0_15px_rgba(239,68,68,0.4)]", + md: "hover:shadow-[inset_0_0_40px_rgba(239,68,68,0.5)]", + lg: "hover:shadow-[inset_0_0_80px_rgba(239,68,68,0.6)]", + xl: "hover:shadow-[inset_0_0_120px_rgba(239,68,68,0.7)]" + } + }, + // Size variants sizes: { + none: "p-0", sm: "p-4", md: "p-6", lg: "p-8", diff --git a/archon-ui-main/src/features/ui/primitives/tabs.tsx b/archon-ui-main/src/features/ui/primitives/tabs.tsx index b74f7138..3b02d25c 100644 --- a/archon-ui-main/src/features/ui/primitives/tabs.tsx +++ b/archon-ui-main/src/features/ui/primitives/tabs.tsx @@ -5,16 +5,20 @@ import { cn } from "./styles"; // Root export const Tabs = TabsPrimitive.Root; -// List +// List - styled like pill navigation export const TabsList = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - - {/* Subtle neon glow effect */} -
- {props.children} - + )); TabsList.displayName = TabsPrimitive.List.displayName; @@ -58,29 +62,30 @@ export const TabsTrigger = React.forwardRef< }, }; + const activeClasses = { + blue: "data-[state=active]:bg-blue-500/20 dark:data-[state=active]:bg-blue-400/20 data-[state=active]:text-blue-700 dark:data-[state=active]:text-blue-300 data-[state=active]:border data-[state=active]:border-blue-400/50 data-[state=active]:shadow-[0_0_10px_rgba(59,130,246,0.5)]", + purple: "data-[state=active]:bg-purple-500/20 dark:data-[state=active]:bg-purple-400/20 data-[state=active]:text-purple-700 dark:data-[state=active]:text-purple-300 data-[state=active]:border data-[state=active]:border-purple-400/50 data-[state=active]:shadow-[0_0_10px_rgba(168,85,247,0.5)]", + pink: "data-[state=active]:bg-pink-500/20 dark:data-[state=active]:bg-pink-400/20 data-[state=active]:text-pink-700 dark:data-[state=active]:text-pink-300 data-[state=active]:border data-[state=active]:border-pink-400/50 data-[state=active]:shadow-[0_0_10px_rgba(236,72,153,0.5)]", + orange: "data-[state=active]:bg-orange-500/20 dark:data-[state=active]:bg-orange-400/20 data-[state=active]:text-orange-700 dark:data-[state=active]:text-orange-300 data-[state=active]:border data-[state=active]:border-orange-400/50 data-[state=active]:shadow-[0_0_10px_rgba(251,146,60,0.5)]", + cyan: "data-[state=active]:bg-cyan-500/20 dark:data-[state=active]:bg-cyan-400/20 data-[state=active]:text-cyan-700 dark:data-[state=active]:text-cyan-300 data-[state=active]:border data-[state=active]:border-cyan-400/50 data-[state=active]:shadow-[0_0_10px_rgba(34,211,238,0.5)]", + green: "data-[state=active]:bg-green-500/20 dark:data-[state=active]:bg-green-400/20 data-[state=active]:text-green-700 dark:data-[state=active]:text-green-300 data-[state=active]:border data-[state=active]:border-green-400/50 data-[state=active]:shadow-[0_0_10px_rgba(16,185,129,0.5)]", + }; + return ( {props.children} - {/* Active state neon indicator - only show when active */} - ); });