code rabbit updates

This commit is contained in:
sean-eskerium
2025-10-09 20:19:51 -04:00
parent 98946817b4
commit c3f42504ea
10 changed files with 238 additions and 491 deletions

View File

@@ -197,18 +197,96 @@ grep -rn "type=\"checkbox\"\|type=\"radio\"" [path] --include="*.tsx"
---
## 5. PRIMITIVES LIBRARY
## 5. CENTRALIZED STYLING (styles.ts)
### CRITICAL RULE: Use glassCard & glassmorphism from styles.ts
**Location**: `@/features/ui/primitives/styles.ts`
All styling definitions MUST come from the centralized `glassCard` and `glassmorphism` objects in styles.ts. Do NOT duplicate style objects in components.
### Anti-Patterns
```tsx
// ❌ WRONG - Duplicating style definitions
const edgeColors = {
cyan: { solid: "bg-cyan-500", gradient: "from-cyan-500/40", border: "border-cyan-500/30" },
// ... more colors
};
// ❌ WRONG - Local variant objects
const colorVariants = {
cyan: "shadow-cyan-500/20",
blue: "shadow-blue-500/20",
};
```
### Good Examples
```tsx
// ✅ CORRECT - Use centralized definitions
const edgeStyle = glassCard.edgeColors[edgeColor];
<div className={edgeStyle.border}>
<div className={edgeStyle.solid} />
<div className={edgeStyle.gradient} />
</div>
// ✅ CORRECT - Use glassCard variants
const glowVariant = glassCard.variants[glowColor];
<div className={cn(glowVariant.border, glowVariant.glow, glowVariant.hover)} />
// ✅ CORRECT - Use glassmorphism tokens
<div className={cn(glassmorphism.background.card, glassmorphism.border.default)} />
```
### What's in styles.ts
**glassCard object:**
- `blur` - Blur intensity levels (sm, md, lg, xl, 2xl, 3xl)
- `transparency` - Glass transparency (clear, light, medium, frosted, solid)
- `variants` - Color variants with border, glow, hover (purple, blue, cyan, green, orange, pink, red)
- `edgeColors` - Edge-lit styling with solid, gradient, border, bg
- `tints` - Colored glass tints
- `sizes` - Padding variants (none, sm, md, lg, xl)
- `outerGlowSizes` - Glow size variants per color
- `innerGlowSizes` - Inner glow size variants per color
- `edgeLit` - Edge-lit effects (position, color with line/glow/gradient)
**glassmorphism object:**
- `background` - Background variations
- `border` - Border styles
- `interactive` - Interactive states
- `animation` - Animation presets
- `shadow` - Shadow effects with neon glow
### Automated Scans
```bash
# Check for duplicate edge color definitions
grep -rn "const edgeColors = {" [path]/primitives --include="*.tsx"
# Check for duplicate variant objects (should use glassCard.variants)
grep -rn "const.*Variants = {" [path]/primitives --include="*.tsx" -A 3 | grep "cyan:\|blue:\|purple:"
# Check imports - all primitives should import from styles.ts
grep -rn "from \"./styles\"" [path]/primitives --include="*.tsx" --files-without-match
```
**Fix Pattern**: Import glassCard/glassmorphism from styles.ts, use object properties instead of duplicating
---
## 6. PRIMITIVES LIBRARY
### Archon Components
- **Card** - For all glassmorphism effects
- **DataCard** - Cards with header/content/footer slots
- **PillNavigation** - Tab navigation (NEVER create custom)
- **styles.ts** - Import `glassCard`, `glassmorphism` for styling
- **styles.ts** - Central styling definitions (ALWAYS import)
### Rules
- **Use Card props** - blur, transparency, edgePosition, glowColor (don't hardcode)
- **Import from styles.ts** - Don't duplicate blur/glow classes
- **All primitive props must affect rendering** - No unused props
- **Use glassCard for card styling** - edgeColors, variants, tints, sizes
- **Use glassmorphism for general styling** - background, border, shadow, animation
### Anti-Patterns
```tsx
@@ -269,11 +347,14 @@ grep -rn "const blurClasses\|backdrop-blur-md" [path]/primitives --include="*.ts
// ❌ role="button" without keyboard
<div onClick={handler} role="button"> // Missing onKeyDown!
// ❌ Clickable icon without button wrapper
<ChevronRight onClick={handler} className="cursor-pointer" />
```
### Good Examples
```tsx
// ✅ Full keyboard support
// ✅ Full keyboard support on div
<div
role="button"
tabIndex={0}
@@ -286,6 +367,23 @@ grep -rn "const blurClasses\|backdrop-blur-md" [path]/primitives --include="*.ts
}}
aria-selected={isSelected}
>
// ✅ Clickable icon wrapped in button
<button
type="button"
aria-label="Expand menu"
aria-expanded={isExpanded}
onClick={handler}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handler();
}
}}
className="focus:outline-none focus:ring-2"
>
<ChevronRight className="h-4 w-4" />
</button>
```
### Automated Scans
@@ -293,9 +391,14 @@ grep -rn "const blurClasses\|backdrop-blur-md" [path]/primitives --include="*.ts
# Interactive divs without keyboard
grep -rn "onClick.*role=\"button\"" [path] --include="*.tsx"
# Then manually verify onKeyDown exists
# Icons with onClick (should be wrapped in button)
grep -rn "<[A-Z].*onClick={" [path] --include="*.tsx" | grep -v "<button\|<Button"
```
**Fix Pattern**: Add onKeyDown handler with Enter/Space, add tabIndex={0}, add ARIA
**Fix Pattern**:
- Add onKeyDown handler with Enter/Space, add tabIndex={0}, add ARIA
- Wrap clickable icons in `<button type="button">` with proper ARIA attributes
---
@@ -304,8 +407,10 @@ grep -rn "onClick.*role=\"button\"" [path] --include="*.tsx"
### Rules
- **Async functions return Promise<void>** - Not just `void` if awaited
- **All props must be used** - If prop in interface, must affect rendering
- **Color types consistent** - Use "green" not "emerald" across components
- **Color types consistent** - Use "green" not "emerald" across components (avoid emerald entirely)
- **Run `tsc --noEmit`** to catch type errors
- **Use `satisfies` for lookup objects** - Enforce type coverage on color variants
- **120 character line limit** - Split long class strings into arrays with `.join(" ")`
### Anti-Patterns
```tsx
@@ -319,6 +424,18 @@ return <div> {/* glowColor never used! */}
// ❌ Color type mismatch
// PillNavigation: colorVariant?: "emerald"
// Select: color?: "green" // Should match!
// ❌ Long class strings (exceeds 120 chars)
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",
};
// ❌ Lookup objects without type safety
const colorClasses = {
cyan: "bg-cyan-500",
blue: "bg-blue-500",
// What if we forget purple? No compile error!
};
```
### Good Examples
@@ -331,8 +448,28 @@ interface CardProps { glowColor?: string }
const glow = glassCard.variants[glowColor];
return <div className={glow.border} />
// ✅ Consistent color types
// ✅ Consistent color types (always use "green", never "emerald")
type Color = "purple" | "blue" | "cyan" | "green" | "orange" | "pink";
// ✅ Split long class strings (under 120 chars per line)
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",
].join(" "),
};
// ✅ Type-safe lookup objects with satisfies
type Color = "purple" | "blue" | "cyan" | "green" | "orange" | "pink";
const colorClasses = {
purple: "bg-purple-500",
blue: "bg-blue-500",
cyan: "bg-cyan-500",
green: "bg-green-500",
orange: "bg-orange-500",
pink: "bg-pink-500",
} satisfies Record<Color, string>; // TypeScript enforces all colors present!
```
### Automated Scans
@@ -340,16 +477,25 @@ type Color = "purple" | "blue" | "cyan" | "green" | "orange" | "pink";
# TypeScript errors
npx tsc --noEmit [path] 2>&1 | grep "error TS"
# Color type inconsistencies
grep -rn "emerald.*type.*Color" [path] --include="*.tsx"
grep -rn "green.*type.*Color" [path] --include="*.tsx"
# Color type inconsistencies (must use "green" not "emerald")
grep -rn "emerald" [path] --include="*.tsx" --include="*.ts"
# Line length violations (over 120 chars)
grep -rn ".\{121,\}" [path] --include="*.tsx" | grep className
# Lookup objects without satisfies
grep -rn "const.*Classes = {" [path]/primitives --include="*.tsx" -A 5 | grep -v "satisfies"
# Unused props (manual)
grep -rn "interface.*Props" [path]/primitives --include="*.tsx" -A 10
# Then verify each prop name appears in return statement
```
**Fix Pattern**: Update types to match implementation, wire props to rendering, use consistent names
**Fix Pattern**:
- Split long strings into arrays: `["class1", "class2"].join(" ")`
- Add `satisfies Record<ColorType, string>` to lookup objects
- Replace all "emerald" with "green" + update RGB to green-500 (34,197,94)
- Wire all props to rendering
---
@@ -418,16 +564,20 @@ Run ALL these scans during review:
- Non-responsive grids: `grep -rn "grid-cols-[2-9]" [path] | grep -v "md:\|lg:"`
- Unconstrained scroll: `grep -rn "overflow-x-auto" [path]` (verify w-full parent)
- Native HTML: `grep -rn "<select>\|type=\"checkbox\"" [path]`
- Emerald usage: `grep -rn "emerald" [path] --include="*.tsx" --include="*.ts"` (must use "green")
### High Priority
- Missing keyboard: `grep -rn "onClick.*role=\"button\"" [path]` (verify onKeyDown)
- Clickable icons: `grep -rn "<[A-Z].*onClick={" [path] --include="*.tsx" | grep -v "<button\|<Button"`
- Missing dark mode: `grep -rn "bg-.*-[0-9]" [path] | grep -v "dark:"`
- Hardcoded glass: `grep -rn "backdrop-blur.*bg-white/.*border" [path]`
- Missing min-w-0: `grep -rn "flex-1" [path] | grep -v "min-w-0"`
- Duplicate styling: `grep -rn "const edgeColors = {\|const.*Variants = {" [path]/primitives`
### Medium Priority
- TypeScript: `npx tsc --noEmit [path] 2>&1 | grep "error TS"`
- Color mismatches: `grep -rn "emerald\|green" [path] | grep "type.*Color"`
- Line length: `grep -rn ".\{121,\}" [path] --include="*.tsx" | grep className`
- Missing satisfies: `grep -rn "const.*Classes = {" [path]/primitives -A 5 | grep -v "satisfies"`
- Props unused: Manual check interfaces vs usage
---

View File

@@ -357,7 +357,7 @@ const SidebarProjectCard = ({
icon={<Activity className="w-3 h-3" />}
/>
<StatPill
color="emerald"
color="green"
value={project.taskCounts.done}
size="sm"
icon={<CheckCircle2 className="w-3 h-3" />}

View File

@@ -5,7 +5,7 @@ export const StaticColors = () => {
const semanticColors = [
{ name: "Primary", hex: "#3b82f6", tailwind: "blue-500", usage: "Primary actions, links, focus states" },
{ name: "Secondary", hex: "#6b7280", tailwind: "gray-500", usage: "Secondary actions, neutral elements" },
{ name: "Success", hex: "#10b981", tailwind: "emerald-500", usage: "Success states, confirmations" },
{ name: "Success", hex: "#22c55e", tailwind: "green-500", usage: "Success states, confirmations" },
{ name: "Warning", hex: "#f97316", tailwind: "orange-500", usage: "Warnings, cautions" },
{ name: "Error", hex: "#ef4444", tailwind: "red-500", usage: "Errors, destructive actions" },
];

View File

@@ -68,29 +68,7 @@ export const Card = React.forwardRef<HTMLDivElement, CardProps>(
return glassCard.outerGlowHover?.[glowColor]?.[glowSize] || glowVariant.hover;
};
// Edge color mappings
const edgeColors = {
purple: { solid: "bg-purple-500", gradient: "from-purple-500/40", border: "border-purple-500/30" },
blue: { solid: "bg-blue-500", gradient: "from-blue-500/40", border: "border-blue-500/30" },
cyan: { solid: "bg-cyan-500", gradient: "from-cyan-500/40", border: "border-cyan-500/30" },
green: { solid: "bg-green-500", gradient: "from-green-500/40", border: "border-green-500/30" },
orange: { solid: "bg-orange-500", gradient: "from-orange-500/40", border: "border-orange-500/30" },
pink: { solid: "bg-pink-500", gradient: "from-pink-500/40", border: "border-pink-500/30" },
red: { solid: "bg-red-500", gradient: "from-red-500/40", border: "border-red-500/30" },
};
const edgeStyle = edgeColors[edgeColor];
// Tint backgrounds for edge-lit cards
const tintBackgrounds = {
purple: "bg-gradient-to-br from-purple-500/15 to-purple-600/5",
blue: "bg-gradient-to-br from-blue-500/15 to-blue-600/5",
cyan: "bg-gradient-to-br from-cyan-500/15 to-cyan-600/5",
green: "bg-gradient-to-br from-green-500/15 to-green-600/5",
orange: "bg-gradient-to-br from-orange-500/15 to-orange-600/5",
pink: "bg-gradient-to-br from-pink-500/15 to-pink-600/5",
red: "bg-gradient-to-br from-red-500/15 to-red-600/5",
};
const edgeStyle = glassCard.edgeColors[edgeColor];
if (hasEdge) {
// Edge-lit card with actual div elements (not pseudo-elements)
@@ -129,9 +107,7 @@ export const Card = React.forwardRef<HTMLDivElement, CardProps>(
{/* Glow bleeding into card */}
<div className={cn(config.glow, "blur-lg pointer-events-none z-10", edgeStyle.gradient)} />
{/* Content with tinted background - INHERIT flex classes */}
<div className={cn("backdrop-blur-sm", tintBackgrounds[edgeColor], glassCard.sizes[size], flexClasses)}>
{children}
</div>
<div className={cn("backdrop-blur-sm", edgeStyle.bg, glassCard.sizes[size], flexClasses)}>{children}</div>
</div>
);
}

View File

@@ -24,10 +24,10 @@ const checkboxVariants = {
focusRing: "focus-visible:ring-blue-500",
},
green: {
checked: "data-[state=checked]:bg-emerald-500/20 data-[state=checked]:border-emerald-500",
glow: "data-[state=checked]:shadow-[0_0_15px_rgba(16,185,129,0.5)]",
indicator: "text-emerald-400 drop-shadow-[0_0_3px_rgba(16,185,129,0.7)]",
focusRing: "focus-visible:ring-emerald-500",
checked: "data-[state=checked]:bg-green-500/20 data-[state=checked]:border-green-500",
glow: "data-[state=checked]:shadow-[0_0_15px_rgba(34,197,94,0.5)]",
indicator: "text-green-400 drop-shadow-[0_0_3px_rgba(34,197,94,0.7)]",
focusRing: "focus-visible:ring-green-500",
},
pink: {
checked: "data-[state=checked]:bg-pink-500/20 data-[state=checked]:border-pink-500",

View File

@@ -42,7 +42,7 @@ export const DataCard = React.forwardRef<HTMLDivElement, DataCardProps>(
ref={ref}
className={cn(
glassCard.base,
glassCard.edgeLit.color[edgeColor].border || "border-gray-300/20 dark:border-white/10",
glassCard.edgeColors[edgeColor].border || "border-gray-300/20 dark:border-white/10",
"min-h-[240px]",
className,
)}
@@ -80,17 +80,18 @@ export const DataCard = React.forwardRef<HTMLDivElement, DataCardProps>(
}
// Standard card (no edge-lit)
const glowClasses = !hasEdge && hasGlow ? [glowVariant.border, glowVariant.glow, glowVariant.hover] : [];
return (
<div
ref={ref}
className={cn(
"relative rounded-xl overflow-hidden border min-h-[240px]",
"relative rounded-xl overflow-hidden min-h-[240px]",
glassCard.blur[blur],
glassCard.transparency[transparency],
"flex flex-col",
hasGlow ? glowVariant.border : "border-gray-300/20 dark:border-white/10",
hasGlow && glowVariant.glow,
hasGlow && glowVariant.hover,
hasGlow ? "" : "border border-gray-300/20 dark:border-white/10",
...glowClasses,
className,
)}
{...props}

View File

@@ -112,13 +112,24 @@ export const PillNavigation = ({
</div>
)}
<ChevronRight
<button
type="button"
className={cn(
"w-4 h-4 transition-transform duration-300 ml-2 cursor-pointer",
"ml-2 flex h-6 w-6 items-center justify-center rounded-full transition-transform duration-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-transparent focus:ring-current",
isThisExpanded ? "-rotate-90" : "rotate-0",
)}
aria-label={isThisExpanded ? `Collapse ${item.label}` : `Expand ${item.label}`}
aria-expanded={isThisExpanded}
onClick={() => onSectionClick(item.id)}
/>
onKeyDown={(event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
onSectionClick(item.id);
}
}}
>
<ChevronRight className="h-4 w-4" />
</button>
</div>
) : (
/* Regular pill for non-selected items */

View File

@@ -22,8 +22,8 @@ const selectColorVariants = {
},
green: {
trigger:
"hover:border-emerald-400/50 hover:shadow-[0_0_15px_rgba(16,185,129,0.3)] focus:border-emerald-500 focus:shadow-[0_0_20px_rgba(16,185,129,0.4)]",
item: "hover:bg-emerald-500/20 dark:hover:bg-emerald-400/20 data-[state=checked]:bg-emerald-500/30 dark:data-[state=checked]:bg-emerald-400/30 data-[state=checked]:text-emerald-700 dark:data-[state=checked]:text-emerald-300",
"hover:border-green-400/50 hover:shadow-[0_0_15px_rgba(34,197,94,0.3)] focus:border-green-500 focus:shadow-[0_0_20px_rgba(34,197,94,0.4)]",
item: "hover:bg-green-500/20 dark:hover:bg-green-400/20 data-[state=checked]:bg-green-500/30 dark:data-[state=checked]:bg-green-400/30 data-[state=checked]:text-green-700 dark:data-[state=checked]:text-green-300",
},
pink: {
trigger:
@@ -107,7 +107,7 @@ export const SelectContent = React.forwardRef<
const glowColor = {
purple: "shadow-purple-500/20 dark:shadow-purple-500/30",
blue: "shadow-blue-500/20 dark:shadow-blue-500/30",
green: "shadow-emerald-500/20 dark:shadow-emerald-500/30",
green: "shadow-green-500/20 dark:shadow-green-500/30",
pink: "shadow-pink-500/20 dark:shadow-pink-500/30",
orange: "shadow-orange-500/20 dark:shadow-orange-500/30",
cyan: "shadow-cyan-500/20 dark:shadow-cyan-500/30",

View File

@@ -13,7 +13,8 @@ export const TabsList = React.forwardRef<
<TabsPrimitive.List
ref={ref}
className={cn(
"backdrop-blur-sm bg-white/40 dark:bg-white/5 border border-white/30 dark:border-white/15 rounded-full p-1 shadow-lg inline-flex gap-1",
"backdrop-blur-sm bg-white/40 dark:bg-white/5 border border-white/30 dark:border-white/15",
"rounded-full p-1 shadow-lg inline-flex gap-1",
className,
)}
role="tablist"
@@ -23,23 +24,52 @@ export const TabsList = React.forwardRef<
TabsList.displayName = TabsPrimitive.List.displayName;
// Trigger
type TabColor = "blue" | "purple" | "pink" | "orange" | "cyan" | "green";
export const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> & {
color?: "blue" | "purple" | "pink" | "orange" | "cyan" | "green";
color?: TabColor;
}
>(({ className, color = "blue", ...props }, ref) => {
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)]",
};
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)]",
].join(" "),
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)]",
].join(" "),
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)]",
].join(" "),
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)]",
].join(" "),
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)]",
].join(" "),
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(34,197,94,0.5)]",
].join(" "),
} satisfies Record<TabColor, string>;
const focusRingClasses = {
blue: "focus-visible:ring-blue-500",
@@ -47,8 +77,8 @@ export const TabsTrigger = React.forwardRef<
pink: "focus-visible:ring-pink-500",
orange: "focus-visible:ring-orange-500",
cyan: "focus-visible:ring-cyan-500",
green: "focus-visible:ring-emerald-500",
};
green: "focus-visible:ring-green-500",
} satisfies Record<TabColor, string>;
return (
<TabsPrimitive.Trigger
@@ -79,7 +109,8 @@ export const TabsContent = React.forwardRef<
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2",
"focus-visible:ring-ring focus-visible:ring-offset-2",
className,
)}
{...props}

View File

@@ -1,422 +0,0 @@
# UI Consistency Review
**Date**: 2025-10-09
**Scope**: `archon-ui-main/src/features/style-guide`
**Components Analyzed**: 18
---
## Overall Scores
| Category | Score | Assessment |
|----------|-------|------------|
| Reusability | 9/10 | Excellent |
| Radix Usage | 10/10 | Perfect |
| Primitives Usage | 10/10 | Perfect |
| Styling Consistency | 8/10 | Very Good |
**Overall Grade**: A- - Excellent adherence to design system with minor color type inconsistency
---
## Executive Summary
The style-guide feature demonstrates **exemplary** implementation of Archon's design system. This is one of the cleanest, most consistent feature implementations in the codebase. The code follows nearly all best practices:
### ✅ Strengths
- **Zero dynamic class construction** - All color variants use proper static lookup objects
- **100% Radix UI adoption** - All form elements use Radix primitives
- **100% Card primitive usage** - Every card uses the Card primitive with proper props
- **Responsive grids everywhere** - All layouts use proper breakpoints
- **Perfect PillNavigation usage** - Uses the shared component, no duplication
- **Working drag-and-drop** - DraggableCard implementation is functional
- **Working filters** - Filter state correctly affects rendered data
- **Excellent text truncation** - All dynamic content has proper truncation
- **Full dark mode support** - Every color has dark: variant
### ⚠️ Areas for Improvement
1. **Color type inconsistency** - Primitive uses "emerald", shared component uses "green" (not critical, but inconsistent)
2. **Duplicate PillNavigation component** - Two implementations exist (shared/ vs ui/primitives/)
**Impact**: None of the issues are critical. This feature could ship as-is with zero functional problems.
---
## Component-by-Component Analysis
### StaticCards.tsx ✅
**Scores:**
- Reusability: 10/10
- Radix Usage: 10/10
- Primitives Usage: 10/10
- Styling Consistency: 10/10
**Perfect Implementation** - This file is a **textbook example** of how to use the Archon design system:
**Highlights:**
- Lines 151-156: Static color lookup object (perfect Tailwind v4 compliance)
- Lines 37-42: Card primitive with all props working (transparency, blur, size)
- Lines 85-95: Proper glowColor, glowType, glowSize props
- Lines 182-192: Edge-lit cards using edgePosition/edgeColor props
- Lines 203-214: Fully functional drag-and-drop with state management
- Lines 224: Responsive grid with `grid-cols-1 md:grid-cols-2`
- Lines 253: Responsive grid with `grid-cols-1 sm:grid-cols-2 md:grid-cols-3`
**Why this is excellent:**
```tsx
// Lines 151-156 - Static color classes (NOT dynamic)
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",
// ... all properties included statically
};
```
This ensures Tailwind generates all CSS at build time.
**Issues Found:** None
---
### StaticForms.tsx ✅
**Scores:**
- Reusability: 10/10
- Radix Usage: 10/10
- Primitives Usage: 10/10
- Styling Consistency: 10/10
**Perfect Radix Usage** - Shows every form element using Radix primitives:
**Highlights:**
- Lines 4-8: Imports all from `@/features/ui/primitives/`
- Lines 68-78: Checkbox with color variants (cyan, purple)
- Lines 89-98: Switch component usage
- Lines 109-133: Select dropdown with color variants
- Lines 38, 42: Input component with proper Label association
- Lines 53-59: Textarea with glassmorphism styling
**Issues Found:** None
---
### KnowledgeLayoutExample.tsx ✅
**Scores:**
- Reusability: 10/10
- Radix Usage: 10/10
- Primitives Usage: 10/10
- Styling Consistency: 10/10
**Perfect Layout Pattern** - Demonstrates switchable views with functional filtering:
**Highlights:**
- Lines 72-75: `useMemo` for efficient filtering (state correctly affects data)
- Lines 134: Responsive grid `grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4`
- Lines 141-159: Proper horizontal scroll with `w-full` wrapper
- Lines 202-266: DataCard primitive with all slots (Header, Content, Footer)
- Lines 233: Text truncation `line-clamp-2` on dynamic titles
- Lines 235: URL truncation with `truncate`
- Lines 169-186: GroupedCard component usage
**Functional Logic:**
```tsx
// Lines 72-75 - Filter actually works!
const filteredItems = useMemo(() => {
if (typeFilter === "all") return MOCK_KNOWLEDGE_ITEMS;
return MOCK_KNOWLEDGE_ITEMS.filter((item) => item.type === typeFilter);
}, [typeFilter]);
```
**Issues Found:** None
---
### StyleGuideView.tsx ✅
**Scores:**
- Reusability: 10/10
- Radix Usage: 10/10
- Primitives Usage: 10/10
- Styling Consistency: 10/10
**Perfect PillNavigation Usage:**
- Lines 33-42: Uses shared PillNavigation component
- All props properly configured (colorVariant, showIcons, hasSubmenus)
**Issues Found:** None
---
### StaticToggles.tsx ✅
**Scores:**
- Reusability: 10/10
- Radix Usage: 10/10
- Primitives Usage: 10/10
- Styling Consistency: 10/10
**Highlights:**
- Lines 33-67: PowerButton with functional state management
- Lines 79-86: Switch primitive usage
- Proper Label associations with `useId()`
**Issues Found:** None
---
### PillNavigation (Duplication Issue) ⚠️
**Locations:**
- `/features/style-guide/shared/PillNavigation.tsx`
- `/features/ui/primitives/pill-navigation.tsx`
**Scores:**
- Reusability: 7/10 (duplicate implementation)
- Radix Usage: 10/10
- Primitives Usage: 10/10
- Styling Consistency: 7/10 (color type mismatch)
**Issue #1: Duplicate Component**
- **Problem**: Two identical implementations of PillNavigation exist
- **Impact**: Medium - Maintenance burden, potential for divergence
- **Location**:
- `archon-ui-main/src/features/style-guide/shared/PillNavigation.tsx` (154 lines)
- `archon-ui-main/src/features/ui/primitives/pill-navigation.tsx` (154 lines)
**Issue #2: Color Type Inconsistency**
- **Problem**: Primitive accepts `"green"` but renders using `emerald` classes
- **Impact**: Low - Works functionally, but confusing for developers
- **Location**:
- Primitive (line 19): `colorVariant?: "blue" | "orange" | "cyan" | "purple" | "green"`
- Primitive (line 54-56): `green: isSelected ? "bg-emerald-500/20 ... text-emerald-700"`
- Style-guide version (line 19): Uses `"emerald"` in type definition
**Current Behavior:**
```tsx
// ui/primitives/pill-navigation.tsx:19
colorVariant?: "blue" | "orange" | "cyan" | "purple" | "green"
// ui/primitives/pill-navigation.tsx:54-56
green: isSelected
? "bg-emerald-500/20 dark:bg-emerald-400/20 text-emerald-700 ..."
```
**Why this matters:**
- Developer passes `color="green"` but CSS uses `emerald` classes
- Inconsistent naming convention (all others match: cyan→cyan, blue→blue)
**Recommendation:**
1. **Remove** `archon-ui-main/src/features/style-guide/shared/PillNavigation.tsx`
2. **Update imports** in style-guide components to use `@/features/ui/primitives/pill-navigation`
3. **Decide on naming**: Either:
- Change type to `"emerald"` and require `colorVariant="emerald"`, or
- Change CSS classes from `emerald-*` to `green-*`
- (Recommend the latter for consistency with "green" being more intuitive)
---
## Critical Issues (Must Fix)
**None** - No critical issues found. All anti-patterns were avoided.
---
## Medium Priority Issues
### 1. Duplicate PillNavigation Component
- **Files**:
- `/features/style-guide/shared/PillNavigation.tsx` (should be removed)
- `/features/ui/primitives/pill-navigation.tsx` (canonical version)
- **Problem**: Two identical 154-line implementations
- **Why**: Maintenance burden, potential for divergence
- **Fix**:
```tsx
// In all style-guide components, change:
import { PillNavigation } from '../shared/PillNavigation';
// To:
import { PillNavigation } from '@/features/ui/primitives/pill-navigation';
// Then delete: features/style-guide/shared/PillNavigation.tsx
```
### 2. Color Type Inconsistency ("green" vs "emerald")
- **File**: `archon-ui-main/src/features/ui/primitives/pill-navigation.tsx`
- **Problem**: Type accepts `"green"` but CSS uses `emerald` classes
- **Why**: Confusing for developers, inconsistent with other color names
- **Fix Option 1** (Recommended - Use "green" everywhere):
```tsx
// Line 54-56: Change from:
green: isSelected
? "bg-emerald-500/20 dark:bg-emerald-400/20 text-emerald-700 dark:text-emerald-300 border border-emerald-400/50 shadow-[0_0_10px_rgba(16,185,129,0.5)]"
// To:
green: isSelected
? "bg-green-500/20 dark:bg-green-400/20 text-green-700 dark:text-green-300 border border-green-400/50 shadow-[0_0_10px_rgba(34,197,94,0.5)]"
```
**Fix Option 2** (Alternative - Use "emerald" everywhere):
```tsx
// Line 19: Change type from:
colorVariant?: "blue" | "orange" | "cyan" | "purple" | "green"
// To:
colorVariant?: "blue" | "orange" | "cyan" | "purple" | "emerald"
```
---
## Recommendations
### Immediate Actions
1. **Remove duplicate PillNavigation** - Delete `/features/style-guide/shared/PillNavigation.tsx` and update imports (5 minutes)
2. **Resolve color naming** - Decide "green" vs "emerald" and make consistent (2 minutes)
### Pattern Improvements
**None needed** - This feature is already a best-practice reference implementation.
### Refactoring Priorities
1. **Low Priority**: Deduplicate PillNavigation (1 hour including testing all style-guide pages)
2. **Low Priority**: Color naming consistency (10 minutes)
---
## Design System Compliance
### Primitives Used Correctly ✅
**All components perfectly use primitives:**
- Card with transparency, blur, glowColor, glowType, glowSize, edgePosition, edgeColor
- DataCard with Header, Content, Footer slots
- SelectableCard with isSelected, showAuroraGlow, onSelect
- DraggableCard with functional drag-and-drop
- PillNavigation (though duplicated)
- GroupedCard with progressive scaling
- Button, Input, Label, Checkbox, Switch, Select (all Radix)
### Radix Compliance ✅
**Perfect Radix adoption:**
- No native `<select>`, `<option>`, `<input type="checkbox">`, `<input type="radio">` anywhere
- All form controls use Radix primitives from `/features/ui/primitives/`
- Proper composition with asChild (where applicable)
### Styling Patterns ✅
**Excellent consistency:**
- Edge-lit cards: 100% use Card primitive with edgePosition/edgeColor (no hardcoding)
- Pill navigation: 100% use PillNavigation component (no custom implementations in showcases)
- Glass effects: All use Card primitive blur prop, no manual backdrop-blur
- Dark mode: Every color has dark: variant
- Text truncation: All dynamic text has truncate or line-clamp
- Responsive grids: All use breakpoints (md:, lg:, xl:)
### Anti-Pattern Avoidance ✅
**Zero violations found:**
- ✅ No dynamic class construction (all use static lookup objects)
- ✅ No non-responsive grids (all use breakpoints)
- ✅ No native HTML form elements (100% Radix)
- ✅ No unconstrained horizontal scroll
- ✅ No missing dark mode variants
- ✅ No hardcoded glassmorphism (all use Card primitive)
- ✅ No missing text truncation
- ✅ Functional UI logic (filters work, drag-drop works, state affects rendering)
---
## Automated Scan Results
### Critical Scans (Breaking Issues) ✅
- ✅ **Dynamic classes**: None found
- ✅ **Non-responsive grids**: None found
- ✅ **Unconstrained scroll**: None found (all have w-full wrapper)
- ✅ **Native HTML**: None found
### High Priority Scans ✅
- ✅ **Missing keyboard**: No interactive divs without button
- ✅ **Missing dark mode**: All colors have dark: variant
- ✅ **Hardcoded glass**: None found (all use Card primitive)
- ✅ **Missing min-w-0**: No flex-1 without min-w-0
### Medium Priority Scans ⚠️
- ⚠️ **Color mismatch**: "green" type → emerald classes (non-critical)
- ⚠️ **Duplicate component**: Two PillNavigation implementations
---
## Testing Notes
**Tested Scenarios:**
1. ✅ All card variants render correctly (base, outer glow, inner glow, edge-lit)
2. ✅ Card props (transparency, blur, glow, edge) all affect rendering
3. ✅ Drag-and-drop reordering works (DraggableCard has state + onDrop)
4. ✅ Selectable cards show selection state (SelectableCard has isSelected prop working)
5. ✅ Type filter in KnowledgeLayoutExample filters data
6. ✅ View mode toggle (grid/table) changes layout
7. ✅ Form elements all use Radix primitives
8. ✅ PillNavigation color variants all work
9. ✅ Responsive grids collapse on mobile breakpoints
10. ✅ Dark mode works across all components
**No broken functionality detected.**
---
## Next Steps
### For Development Team
**Priority 1 (Optional):**
1. Deduplicate PillNavigation component (~1 hour)
- Delete `/features/style-guide/shared/PillNavigation.tsx`
- Update 2-3 imports in style-guide components
- Test all style-guide pages
**Priority 2 (Optional):**
2. Resolve "green" vs "emerald" naming (~10 minutes)
- Choose one naming convention
- Update either type definition or CSS classes
- Grep codebase for usage and update
**Priority 3:**
3. Use style-guide as reference for other features
- This implementation is **gold standard** for Archon UI
- Copy patterns from here when building new features
### For Code Reviewers
**Accept this PR without changes** - The two issues found are:
- Non-critical (don't affect functionality)
- Low priority (maintenance/consistency concerns only)
- Can be addressed in future cleanup PR
---
## Estimated Effort
- **Full refactor**: 1.5 hours (deduplicate + color naming)
- **Current state**: **Production-ready as-is**
---
## Conclusion
The style-guide feature is an **exemplary implementation** of the Archon design system. It demonstrates:
- Perfect use of primitives
- Zero anti-patterns
- 100% Radix adoption
- Functional UI logic throughout
- Responsive design everywhere
- Full dark mode support
**This should be used as a reference implementation for all other features.**
The only issues are minor maintenance concerns (duplication, naming) that don't affect functionality. This code could ship to production today with zero user-facing problems.
**Recommendation: Approve and use as design system reference.**