mirror of
https://github.com/coleam00/Archon.git
synced 2025-12-24 02:39:17 -05:00
trying to make the ui reviews programmatic
This commit is contained in:
@@ -283,102 +283,204 @@ Based on the argument:
|
||||
Use grep/glob to find:
|
||||
|
||||
```bash
|
||||
# CRITICAL: Dynamic Tailwind class construction (WILL NOT WORK)
|
||||
grep -r "bg-\${.*}\|text-\${.*}\|border-\${.*}\|shadow-\${.*}" [path] --include="*.tsx"
|
||||
grep -r "\`bg-.*-.*\`\|\`text-.*-.*\`\|\`border-.*-.*\`" [path] --include="*.tsx"
|
||||
|
||||
# CRITICAL: Unconstrained horizontal scroll (BREAKS LAYOUT)
|
||||
grep -r "overflow-x-auto" [path] --include="*.tsx" | grep -v "w-full"
|
||||
|
||||
# CRITICAL: min-w-max without parent width constraint
|
||||
grep -r "min-w-max" [path] --include="*.tsx"
|
||||
|
||||
# Non-responsive grids (BREAKS MOBILE)
|
||||
grep -r "grid-cols-[2-9]" [path] --include="*.tsx" | grep -v "md:\|lg:\|sm:\|xl:"
|
||||
|
||||
# Fixed widths without max-width constraints
|
||||
grep -r "w-\[0-9\]\|w-96\|w-80\|w-72" [path] --include="*.tsx" | grep -v "max-w-"
|
||||
|
||||
# Hardcoded edge-lit implementations (should use Card primitive)
|
||||
grep -r "absolute inset-x-0 top-0" [path]
|
||||
grep -r "absolute inset-x-0 top-0.*bg-gradient-to-b" [path] --include="*.tsx"
|
||||
|
||||
# Native HTML form elements (should use Radix)
|
||||
grep -r "<select>\|<option>\|<input type=\"checkbox\"" [path]
|
||||
grep -r "<select>\|<option>\|<input type=\"checkbox\"\|<input type=\"radio\"" [path] --include="*.tsx"
|
||||
|
||||
# Hardcoded pill navigation (should use PillNavigation component)
|
||||
grep -r "backdrop-blur-sm bg-white/40.*rounded-full" [path]
|
||||
grep -r "backdrop-blur-sm bg-white/40.*rounded-full.*flex gap-1" [path] --include="*.tsx"
|
||||
|
||||
# Manual glassmorphism (should use Card primitive or styles.ts)
|
||||
grep -r "bg-gradient-to-b from-white/\|from-purple-100/" [path]
|
||||
|
||||
# Hardcoded colors instead of semantic tokens
|
||||
grep -r "#[0-9a-fA-F]{6}" [path]
|
||||
|
||||
# CRITICAL: Dynamic Tailwind class construction (WILL NOT WORK)
|
||||
grep -r "bg-\${.*}\|text-\${.*}\|border-\${.*}\|shadow-\${.*}" [path]
|
||||
grep -r "\.replace.*rgba\|\.replace.*VAR" [path]
|
||||
|
||||
# CRITICAL: Template literal Tailwind classes (WILL NOT WORK)
|
||||
grep -r "\`bg-.*-.*\`\|\`text-.*-.*\`\|\`border-.*-.*\`" [path]
|
||||
# Missing text truncation on titles/headings
|
||||
grep -r "<h[1-6].*className.*{" [path] --include="*.tsx" | grep -v "truncate\|line-clamp"
|
||||
|
||||
# Not using pre-defined classes from styles.ts
|
||||
grep -r "glassCard\.variants\|glassmorphism\." [path] --files-without-match
|
||||
grep -r "glassCard\.variants\|glassmorphism\." [path] --files-without-match --include="*.tsx"
|
||||
```
|
||||
|
||||
## Critical Anti-Patterns
|
||||
|
||||
**IMPORTANT:** Read `PRPs/ai_docs/TAILWIND_RESPONSIVE_BEST_PRACTICES.md` for complete anti-pattern reference before starting review.
|
||||
|
||||
### 🔴 **BREAKING: Dynamic Tailwind Class Construction**
|
||||
|
||||
**Problem:**
|
||||
```tsx
|
||||
// ❌ BROKEN - Tailwind processes at BUILD time, not runtime
|
||||
// BROKEN - Tailwind processes at BUILD time, not runtime
|
||||
const color = "cyan";
|
||||
className={`bg-${color}-500`} // CSS won't be generated!
|
||||
className={`bg-${color}-500`} // CSS won't be generated
|
||||
|
||||
// ❌ BROKEN - String replacement at runtime
|
||||
// BROKEN - String interpolation
|
||||
const glow = `shadow-[0_0_30px_rgba(${rgba},0.4)]`;
|
||||
|
||||
// ❌ BROKEN - Template literals with variables
|
||||
// BROKEN - Template literals with variables
|
||||
<div className={`text-${textColor}-700`} />
|
||||
```
|
||||
|
||||
**Why it fails:**
|
||||
- Tailwind scans code at BUILD time to generate CSS
|
||||
- Tailwind scans code as plain text at BUILD time
|
||||
- Dynamic strings aren't scanned - no CSS generated
|
||||
- Results in missing styles at runtime
|
||||
|
||||
**Solution:**
|
||||
```tsx
|
||||
// ✅ CORRECT - Static class lookup
|
||||
// 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
|
||||
// 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)
|
||||
// CORRECT - Inline arbitrary values (scanned by Tailwind)
|
||||
className="shadow-[0_0_30px_rgba(34,211,238,0.4)]"
|
||||
```
|
||||
|
||||
### 🔴 **BREAKING: Unconstrained Horizontal Scroll**
|
||||
|
||||
**Problem:**
|
||||
```tsx
|
||||
// BROKEN - Forces entire page width to expand
|
||||
<div className="overflow-x-auto">
|
||||
<div className="flex gap-4 min-w-max">
|
||||
{/* Wide content */}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Why it fails:**
|
||||
- `min-w-max` forces container to expand beyond viewport
|
||||
- Parent has no width constraint
|
||||
- Entire page becomes horizontally scrollable
|
||||
- UI controls shift off-screen
|
||||
|
||||
**Solution:**
|
||||
```tsx
|
||||
// CORRECT - Constrain parent, scroll child only
|
||||
<div className="w-full">
|
||||
<div className="overflow-x-auto -mx-6 px-6">
|
||||
<div className="flex gap-4 min-w-max">
|
||||
{/* Scrolls within container only */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 🔴 **Not Using styles.ts Pre-Defined Classes**
|
||||
|
||||
**Problem:**
|
||||
```tsx
|
||||
// ❌ WRONG - Hardcoding glassmorphism
|
||||
// WRONG - Hardcoding glassmorphism
|
||||
<div className="backdrop-blur-md bg-white/10 border border-gray-200 rounded-lg">
|
||||
|
||||
// ❌ WRONG - Not using existing glassCard.variants
|
||||
// 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
|
||||
// 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
|
||||
// CORRECT - Use Card primitive with props
|
||||
<Card glowColor="cyan" edgePosition="top" edgeColor="purple" />
|
||||
```
|
||||
|
||||
### 🔴 **Non-Responsive Grid Layouts**
|
||||
|
||||
**Problem:**
|
||||
```tsx
|
||||
// BROKEN - Fixed columns break on mobile
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```tsx
|
||||
// CORRECT - Responsive columns
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
```
|
||||
|
||||
### 🔴 **Missing Text Truncation**
|
||||
|
||||
**Problem:**
|
||||
```tsx
|
||||
// WRONG - Long text breaks layout
|
||||
<h3 className="font-medium">{longTitle}</h3>
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```tsx
|
||||
// CORRECT - Truncate or clamp
|
||||
<h3 className="font-medium truncate">{longTitle}</h3>
|
||||
<h3 className="font-medium line-clamp-2">{longTitle}</h3>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Questions to Answer
|
||||
|
||||
1. **Does this component duplicate existing primitives?**
|
||||
2. **Should this be refactored to use Card with edgePosition/edgeColor?**
|
||||
3. **Are there native HTML elements that should be Radix?**
|
||||
4. **Is the glassmorphism consistent with the design system?**
|
||||
5. **Can multiple components be consolidated into one reusable primitive?**
|
||||
2. **Are there any dynamic Tailwind class constructions? (BREAKING)**
|
||||
3. **Is horizontal scroll properly constrained with w-full parent?**
|
||||
4. **Are grids responsive with breakpoint variants?**
|
||||
5. **Should this be refactored to use Card with edgePosition/edgeColor?**
|
||||
6. **Are there native HTML elements that should be Radix?**
|
||||
7. **Is text truncation in place for dynamic content?**
|
||||
8. **Is the glassmorphism consistent with the design system?**
|
||||
9. **Can multiple components be consolidated into one reusable primitive?**
|
||||
10. **Does the layout work at 375px, 768px, 1024px, and 1440px widths?**
|
||||
|
||||
---
|
||||
|
||||
Start the review now and save the report to `ui-consistency-review-[feature].md` in the project root.
|
||||
## Execution Flow
|
||||
|
||||
### Step 1: Perform Review
|
||||
|
||||
Start the review and save the report to `ui-consistency-review-[feature].md` in the project root.
|
||||
|
||||
### Step 2: Generate PRP for Fixes
|
||||
|
||||
After completing the review report, automatically kick off PRP creation for the identified fixes:
|
||||
|
||||
```
|
||||
/prp-claude-code:prp-claude-code-create UI Consistency Fixes - [feature-name]
|
||||
```
|
||||
|
||||
**Context to provide to PRP:**
|
||||
- Reference the generated `ui-consistency-review-[feature].md` report
|
||||
- List all critical issues that need fixing
|
||||
- Specify exact files that need refactoring
|
||||
- Include anti-patterns found and their correct implementations
|
||||
- Reference `PRPs/ai_docs/TAILWIND_RESPONSIVE_BEST_PRACTICES.md` for patterns
|
||||
|
||||
**PRP should include:**
|
||||
- Task for each critical issue (dynamic classes, unconstrained scroll, etc.)
|
||||
- Task for each component needing primitive refactor
|
||||
- Task for responsive breakpoint additions
|
||||
- Task for text truncation additions
|
||||
- Validation task to run UI consistency review again to verify fixes
|
||||
|
||||
---
|
||||
|
||||
Start the review now.
|
||||
|
||||
489
PRPs/ai_docs/TAILWIND_RESPONSIVE_BEST_PRACTICES.md
Normal file
489
PRPs/ai_docs/TAILWIND_RESPONSIVE_BEST_PRACTICES.md
Normal file
@@ -0,0 +1,489 @@
|
||||
# Tailwind CSS & Responsive Design Best Practices
|
||||
|
||||
## Critical Tailwind CSS Rules
|
||||
|
||||
### Rule 1: NO Dynamic Class Name Construction
|
||||
|
||||
Tailwind processes your source code as **plain text at BUILD time**, NOT at runtime. It cannot understand variables, string concatenation, or template literals.
|
||||
|
||||
#### BROKEN Patterns (Will Not Work)
|
||||
|
||||
```tsx
|
||||
// BROKEN - Tailwind won't generate these classes
|
||||
const color = "cyan";
|
||||
<div className={`bg-${color}-500`} /> // CSS NOT GENERATED
|
||||
|
||||
// BROKEN - Template literal concatenation
|
||||
<div className={`text-${textColor}-700 border-${borderColor}-500`} />
|
||||
|
||||
// BROKEN - String interpolation
|
||||
const glow = `shadow-[0_0_30px_rgba(${rgba},0.4)]`;
|
||||
|
||||
// BROKEN - Computed class names
|
||||
className={isActive ? `bg-${activeColor}-500` : `bg-${inactiveColor}-500`}
|
||||
```
|
||||
|
||||
**Why These Fail:**
|
||||
- Tailwind scans code as text looking for class tokens
|
||||
- `bg-cyan-500` exists as a token → CSS generated
|
||||
- `bg-${color}-500` does NOT exist as a token → NO CSS generated
|
||||
- At runtime, browser receives class name with no matching CSS
|
||||
|
||||
#### CORRECT Patterns (Use These)
|
||||
|
||||
```tsx
|
||||
// CORRECT - Static class lookup object
|
||||
const colorClasses = {
|
||||
cyan: "bg-cyan-500 text-cyan-700 border-cyan-500/50",
|
||||
purple: "bg-purple-500 text-purple-700 border-purple-500/50",
|
||||
blue: "bg-blue-500 text-blue-700 border-blue-500/50",
|
||||
};
|
||||
<div className={colorClasses[color]} />
|
||||
|
||||
// CORRECT - Conditional with complete class names
|
||||
<div className={isActive ? "bg-cyan-500 text-white" : "bg-gray-500 text-gray-300"} />
|
||||
|
||||
// CORRECT - Use cn() helper with complete class strings
|
||||
<div className={cn(
|
||||
"base-classes",
|
||||
isActive && "bg-cyan-500 text-white",
|
||||
isPinned && "border-purple-500 shadow-lg"
|
||||
)} />
|
||||
|
||||
// CORRECT - Static arbitrary values (scanned at build time)
|
||||
<div className="shadow-[0_0_30px_rgba(34,211,238,0.4)]" />
|
||||
|
||||
// CORRECT - Pre-defined classes from styles.ts
|
||||
import { glassCard } from '@/features/ui/primitives/styles';
|
||||
className={cn(glassCard.base, glassCard.variants.cyan.glow)}
|
||||
```
|
||||
|
||||
### Rule 2: Use Static Class Mappings
|
||||
|
||||
When you need variant styling based on props or state:
|
||||
|
||||
```tsx
|
||||
// CORRECT Pattern
|
||||
interface ButtonProps {
|
||||
variant: "primary" | "secondary" | "danger";
|
||||
}
|
||||
|
||||
const Button = ({ variant }: ButtonProps) => {
|
||||
const variantClasses = {
|
||||
primary: "bg-blue-500 hover:bg-blue-600 text-white",
|
||||
secondary: "bg-gray-200 hover:bg-gray-300 text-gray-900",
|
||||
danger: "bg-red-500 hover:bg-red-600 text-white",
|
||||
};
|
||||
|
||||
return <button className={variantClasses[variant]}>Click me</button>;
|
||||
};
|
||||
```
|
||||
|
||||
### Rule 3: Arbitrary Values for One-Off Styles
|
||||
|
||||
When you need a specific value not in your theme:
|
||||
|
||||
```tsx
|
||||
// CORRECT - Arbitrary values are scanned at build time
|
||||
<div className="w-[347px]" />
|
||||
<div className="shadow-[0_0_30px_rgba(34,211,238,0.4)]" />
|
||||
<div className="bg-[#316ff6]" />
|
||||
|
||||
// These work because the complete string exists in source code
|
||||
```
|
||||
|
||||
## Responsive Design Best Practices
|
||||
|
||||
### Rule 1: Mobile-First Approach
|
||||
|
||||
Always design for mobile first, then layer styles for larger screens.
|
||||
|
||||
```tsx
|
||||
// CORRECT - Mobile first, then larger screens
|
||||
<div className="w-full md:w-1/2 lg:w-1/3">
|
||||
|
||||
// WRONG - Desktop first (harder to override)
|
||||
<div className="w-1/3 sm:w-full">
|
||||
```
|
||||
|
||||
### Rule 2: Responsive Grid Columns
|
||||
|
||||
Never use fixed grid columns without responsive breakpoints.
|
||||
|
||||
```tsx
|
||||
// WRONG - Fixed 4 columns breaks on mobile
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
|
||||
// CORRECT - Responsive columns
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
```
|
||||
|
||||
### Rule 3: Constrain Horizontal Scroll Containers
|
||||
|
||||
**CRITICAL:** When creating horizontally scrollable content, you MUST constrain the parent container.
|
||||
|
||||
```tsx
|
||||
// WRONG - Entire page becomes scrollable
|
||||
<div className="overflow-x-auto">
|
||||
<div className="flex gap-4 min-w-max">
|
||||
{/* Wide cards */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// CORRECT - Scroll isolated to specific container
|
||||
<div className="w-full">
|
||||
<div className="overflow-x-auto -mx-6 px-6">
|
||||
<div className="flex gap-4 min-w-max">
|
||||
{/* Only cards scroll, page stays within viewport */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Why This Matters:**
|
||||
- Without `w-full` parent, `min-w-max` forces entire page width to expand
|
||||
- UI controls shift off-screen
|
||||
- Layout breaks at all viewport sizes
|
||||
- User cannot access navigation or buttons
|
||||
|
||||
### Rule 4: Responsive Flexbox
|
||||
|
||||
Use flex-wrap and responsive flex directions:
|
||||
|
||||
```tsx
|
||||
// WRONG - Overflows on small screens
|
||||
<div className="flex gap-4">
|
||||
<div className="w-64">Sidebar</div>
|
||||
<div className="w-96">Content</div>
|
||||
</div>
|
||||
|
||||
// CORRECT - Wraps on mobile, horizontal on desktop
|
||||
<div className="flex flex-col md:flex-row gap-4">
|
||||
<div className="w-full md:w-64">Sidebar</div>
|
||||
<div className="flex-1">Content</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Rule 5: Text Truncation & Overflow
|
||||
|
||||
Always handle long text content:
|
||||
|
||||
```tsx
|
||||
// WRONG - Long titles break layout
|
||||
<h3 className="font-medium">{veryLongTitle}</h3>
|
||||
|
||||
// CORRECT - Single line truncate
|
||||
<h3 className="font-medium truncate">{veryLongTitle}</h3>
|
||||
|
||||
// CORRECT - Multi-line clamp
|
||||
<h3 className="font-medium line-clamp-2">{veryLongTitle}</h3>
|
||||
|
||||
// CORRECT - Wrap with proper width constraint
|
||||
<h3 className="font-medium break-words max-w-full">{veryLongTitle}</h3>
|
||||
```
|
||||
|
||||
## Common Layout Anti-Patterns
|
||||
|
||||
### Anti-Pattern 1: Fixed Widths Without Constraints
|
||||
|
||||
```tsx
|
||||
// WRONG - Fixed width can overflow viewport
|
||||
<div className="w-96 p-8">
|
||||
|
||||
// CORRECT - Max width with full width fallback
|
||||
<div className="w-full max-w-96 p-8">
|
||||
```
|
||||
|
||||
### Anti-Pattern 2: Absolute Positioning for UI Controls
|
||||
|
||||
```tsx
|
||||
// WRONG - Controls shift off-screen when content overflows
|
||||
<div className="absolute top-8 right-8">
|
||||
<Button>Toggle View</Button>
|
||||
</div>
|
||||
|
||||
// CORRECT - Use flexbox layout
|
||||
<div className="flex justify-end mb-4">
|
||||
<Button>Toggle View</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Anti-Pattern 3: Non-Responsive Tables
|
||||
|
||||
```tsx
|
||||
// WRONG - Table breaks on mobile
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Column 1</th>
|
||||
<th>Column 2</th>
|
||||
<th>Column 3</th>
|
||||
<th>Column 4</th>
|
||||
<th>Column 5</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
|
||||
// CORRECT - Wrap in overflow container
|
||||
<div className="overflow-x-auto w-full">
|
||||
<table className="w-full">
|
||||
{/* Table can scroll horizontally on mobile */}
|
||||
</table>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Anti-Pattern 4: Padding on Scroll Container
|
||||
|
||||
```tsx
|
||||
// WRONG - Padding prevents content from reaching edge
|
||||
<div className="overflow-x-auto px-8 py-8">
|
||||
<div className="flex gap-4 min-w-max">
|
||||
|
||||
// CORRECT - Use negative margin to compensate
|
||||
<div className="w-full">
|
||||
<div className="overflow-x-auto -mx-6 px-6">
|
||||
<div className="flex gap-4 min-w-max">
|
||||
```
|
||||
|
||||
### Anti-Pattern 5: Missing Width Constraints on Scrollable Parents
|
||||
|
||||
```tsx
|
||||
// WRONG - Parent has no width, child forces expansion
|
||||
<div>
|
||||
<div className="overflow-x-auto">
|
||||
<div className="flex gap-4 min-w-max">
|
||||
{/* Forces parent to expand beyond viewport */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// CORRECT - Explicit width constraint
|
||||
<div className="w-full max-w-7xl mx-auto">
|
||||
<div className="overflow-x-auto">
|
||||
<div className="flex gap-4 min-w-max">
|
||||
{/* Scrolls within constrained parent */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Radix UI Integration Best Practices
|
||||
|
||||
### Use Radix Primitives for All Interactive Elements
|
||||
|
||||
```tsx
|
||||
// WRONG - Native HTML elements
|
||||
<select>
|
||||
<option value="1">Option 1</option>
|
||||
</select>
|
||||
<input type="checkbox" />
|
||||
<input type="radio" />
|
||||
|
||||
// CORRECT - Radix primitives
|
||||
import { Select, SelectTrigger, SelectContent, SelectItem } from '@/features/ui/primitives/select';
|
||||
import { Checkbox } from '@/features/ui/primitives/checkbox';
|
||||
import { RadioGroup, RadioGroupItem } from '@/features/ui/primitives/radio-group';
|
||||
```
|
||||
|
||||
### Modal/Dialog Responsive Patterns
|
||||
|
||||
```tsx
|
||||
// CORRECT - Responsive dialog sizes
|
||||
<DialogContent className="w-full max-w-md md:max-w-2xl lg:max-w-4xl">
|
||||
{/* Adapts to screen size */}
|
||||
</DialogContent>
|
||||
```
|
||||
|
||||
## Component Reusability Patterns
|
||||
|
||||
### Pattern 1: Card Primitives Over Hardcoded Styles
|
||||
|
||||
```tsx
|
||||
// WRONG - Hardcoding glassmorphism and edge-lit effects
|
||||
<div className="relative rounded-xl overflow-hidden">
|
||||
<div className="absolute inset-x-0 top-0 h-[2px] bg-cyan-500" />
|
||||
<div className="absolute inset-x-0 top-0 h-16 bg-gradient-to-b from-cyan-500/40 to-transparent blur-lg" />
|
||||
<div className="backdrop-blur-md bg-white/10 border border-gray-200 p-4">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// CORRECT - Use Card primitive with props
|
||||
<Card edgePosition="top" edgeColor="cyan" blur="lg" transparency="light">
|
||||
{children}
|
||||
</Card>
|
||||
```
|
||||
|
||||
### Pattern 2: Static Class Objects from styles.ts
|
||||
|
||||
```tsx
|
||||
// WRONG - Hardcoding repeated glass effects
|
||||
<div className="backdrop-blur-md bg-white/10 border border-gray-200 rounded-lg">
|
||||
|
||||
// CORRECT - Use pre-defined classes
|
||||
import { glassCard } from '@/features/ui/primitives/styles';
|
||||
<div className={cn(glassCard.base, glassCard.blur.md, glassCard.transparency.light)}>
|
||||
```
|
||||
|
||||
### Pattern 3: Shared Navigation Components
|
||||
|
||||
```tsx
|
||||
// WRONG - Custom pill navigation each time
|
||||
<div className="backdrop-blur-sm bg-white/40 dark:bg-white/5 border rounded-full p-1">
|
||||
<div className="flex gap-1">
|
||||
{items.map(item => (
|
||||
<button className="px-6 py-2.5 rounded-full text-sm font-medium">
|
||||
{item.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// CORRECT - Use PillNavigation component
|
||||
<PillNavigation
|
||||
items={items}
|
||||
activeSection={activeSection}
|
||||
onSectionClick={setActiveSection}
|
||||
colorVariant="orange"
|
||||
size="small"
|
||||
/>
|
||||
```
|
||||
|
||||
## Testing Responsive Layouts
|
||||
|
||||
### Required Viewport Tests
|
||||
|
||||
Test every layout at these breakpoints:
|
||||
- **375px** - iPhone SE (smallest modern phone)
|
||||
- **768px** - Tablet portrait
|
||||
- **1024px** - Tablet landscape / small laptop
|
||||
- **1440px** - Desktop
|
||||
- **1920px** - Large desktop
|
||||
|
||||
### Responsive Checklist
|
||||
|
||||
For every component:
|
||||
- [ ] All UI controls visible at all breakpoints
|
||||
- [ ] No horizontal page scroll (only intentional container scroll)
|
||||
- [ ] Text truncates or wraps appropriately
|
||||
- [ ] Images scale responsively
|
||||
- [ ] Grid adapts to screen size
|
||||
- [ ] Flexbox wraps or changes direction on mobile
|
||||
- [ ] Modals/dialogs fit within viewport
|
||||
- [ ] Touch targets are minimum 44x44px on mobile
|
||||
|
||||
## Common Mistakes Summary
|
||||
|
||||
### 1. Dynamic Tailwind Classes
|
||||
**Problem:** String interpolation like `bg-${color}-500`
|
||||
**Solution:** Static class object lookup
|
||||
|
||||
### 2. Unconstrained Scroll Containers
|
||||
**Problem:** `min-w-max` without `w-full` parent
|
||||
**Solution:** Always constrain parent width
|
||||
|
||||
### 3. Fixed Grid Columns
|
||||
**Problem:** `grid-cols-4` on all screen sizes
|
||||
**Solution:** `grid-cols-1 md:grid-cols-2 lg:grid-cols-4`
|
||||
|
||||
### 4. Missing Text Truncation
|
||||
**Problem:** Long text breaks layout
|
||||
**Solution:** Add `truncate` or `line-clamp-*`
|
||||
|
||||
### 5. Hardcoded Component Styles
|
||||
**Problem:** Duplicating glassmorphism/edge-lit patterns
|
||||
**Solution:** Use Card/DataCard primitives
|
||||
|
||||
### 6. Absolute Positioned Controls
|
||||
**Problem:** Fixed position elements shift off-screen
|
||||
**Solution:** Use flexbox for layout positioning
|
||||
|
||||
### 7. Native HTML Form Elements
|
||||
**Problem:** Using `<select>`, `<input type="checkbox">`, etc.
|
||||
**Solution:** Use Radix UI primitives
|
||||
|
||||
## Archon-Specific Patterns
|
||||
|
||||
### Overflow Scroll Pattern
|
||||
|
||||
```tsx
|
||||
// Standard pattern for horizontally scrollable cards
|
||||
<div className="w-full">
|
||||
<div className="overflow-x-auto -mx-6 px-6 py-8">
|
||||
<div className="flex gap-4 min-w-max">
|
||||
{items.map(item => <Card key={item.id} />)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Responsive Grid Pattern
|
||||
|
||||
```tsx
|
||||
// Standard grid for card layouts
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
{items.map(item => <Card key={item.id} />)}
|
||||
</div>
|
||||
```
|
||||
|
||||
### Responsive Flex Layout
|
||||
|
||||
```tsx
|
||||
// Sidebar + main content pattern
|
||||
<div className="flex flex-col lg:flex-row gap-6">
|
||||
<aside className="w-full lg:w-64 flex-shrink-0">
|
||||
{/* Sidebar */}
|
||||
</aside>
|
||||
<main className="flex-1 min-w-0">
|
||||
{/* Main content - min-w-0 allows truncation */}
|
||||
</main>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Card Primitive Usage
|
||||
|
||||
```tsx
|
||||
// Use Card primitive props instead of hardcoding
|
||||
<Card
|
||||
blur="lg" // sm, md, lg, xl, 2xl, 3xl
|
||||
transparency="light" // clear, light, medium, frosted, solid
|
||||
edgePosition="top" // none, top, left, right, bottom
|
||||
edgeColor="cyan" // purple, blue, cyan, green, orange, pink, red
|
||||
glowColor="cyan" // For glow effects
|
||||
glowType="outer" // inner or outer
|
||||
>
|
||||
{children}
|
||||
</Card>
|
||||
```
|
||||
|
||||
## Pre-Flight Checklist
|
||||
|
||||
Before committing any UI component:
|
||||
|
||||
### Tailwind Rules
|
||||
- [ ] No dynamic class name construction (`bg-${var}-500`)
|
||||
- [ ] No template literal class building
|
||||
- [ ] All variant classes defined in static objects
|
||||
- [ ] Arbitrary values written as complete static strings
|
||||
|
||||
### Responsive Rules
|
||||
- [ ] Horizontal scroll containers have `w-full` parent wrapper
|
||||
- [ ] Grids use responsive column breakpoints
|
||||
- [ ] Flex layouts adapt at appropriate breakpoints
|
||||
- [ ] Text content has truncation or wrapping
|
||||
- [ ] Fixed widths have `max-w-*` constraints
|
||||
- [ ] Layout toggle buttons always visible
|
||||
|
||||
### Component Rules
|
||||
- [ ] Using Card primitive for glassmorphism
|
||||
- [ ] Using Radix primitives for all interactive elements
|
||||
- [ ] Using shared components (PillNavigation, etc.)
|
||||
- [ ] No duplicated styling patterns
|
||||
|
||||
### Testing
|
||||
- [ ] Tested at 375px width (mobile)
|
||||
- [ ] Tested at 768px width (tablet)
|
||||
- [ ] Tested at 1024px width (laptop)
|
||||
- [ ] Tested at 1440px+ width (desktop)
|
||||
- [ ] All UI controls accessible at all sizes
|
||||
- [ ] No unintended horizontal page scroll
|
||||
@@ -6,7 +6,7 @@ import { RadioGroup, RadioGroupItem } from '@/features/ui/primitives/radio-group
|
||||
import { LivePreview } from '../shared/LivePreview';
|
||||
import { CodeDisplay } from '../shared/CodeDisplay';
|
||||
import { ConfigPanel } from '../shared/ConfigPanel';
|
||||
import { Eye, Code } from 'lucide-react';
|
||||
import { Eye, Code, Moon, Sun, Volume2, VolumeX, Wifi, WifiOff } from 'lucide-react';
|
||||
import type { LabelPosition } from '../types';
|
||||
|
||||
interface ToggleConfig {
|
||||
@@ -131,37 +131,82 @@ import { Label } from '@/features/ui/primitives/label';`;
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview or Code Content (3/4 width) */}
|
||||
<div className="col-span-3">
|
||||
<div className="col-span-3 space-y-6">
|
||||
{activeTab === 'preview' ? (
|
||||
<LivePreview>
|
||||
<div className={`flex ${layoutClasses[config.labelPosition]} ${gapClasses[config.labelPosition]} items-center`}>
|
||||
{(config.labelPosition === 'top' || config.labelPosition === 'left') ? (
|
||||
<>
|
||||
<Label htmlFor="preview-toggle">
|
||||
{config.labelText}
|
||||
</Label>
|
||||
<Switch
|
||||
id="preview-toggle"
|
||||
checked={toggleState}
|
||||
onCheckedChange={setToggleState}
|
||||
disabled={config.disabled}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Switch
|
||||
id="preview-toggle"
|
||||
checked={toggleState}
|
||||
onCheckedChange={setToggleState}
|
||||
disabled={config.disabled}
|
||||
/>
|
||||
<Label htmlFor="preview-toggle">
|
||||
{config.labelText}
|
||||
</Label>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<LivePreview>
|
||||
<div className={`flex ${layoutClasses[config.labelPosition]} ${gapClasses[config.labelPosition]} items-center`}>
|
||||
{(config.labelPosition === 'top' || config.labelPosition === 'left') ? (
|
||||
<>
|
||||
<Label htmlFor="preview-toggle">
|
||||
{config.labelText}
|
||||
</Label>
|
||||
<Switch
|
||||
id="preview-toggle"
|
||||
checked={toggleState}
|
||||
onCheckedChange={setToggleState}
|
||||
disabled={config.disabled}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Switch
|
||||
id="preview-toggle"
|
||||
checked={toggleState}
|
||||
onCheckedChange={setToggleState}
|
||||
disabled={config.disabled}
|
||||
/>
|
||||
<Label htmlFor="preview-toggle">
|
||||
{config.labelText}
|
||||
</Label>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</LivePreview>
|
||||
|
||||
{/* Icon Switch Examples */}
|
||||
<div className="space-y-4">
|
||||
<h4 className="text-sm font-semibold text-gray-900 dark:text-white">Switches with Icons</h4>
|
||||
<LivePreview>
|
||||
<div className="space-y-6">
|
||||
{/* Dark Mode Toggle */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Label>Dark Mode</Label>
|
||||
<Switch
|
||||
size="lg"
|
||||
color="purple"
|
||||
iconOn={<Sun className="w-5 h-5" />}
|
||||
iconOff={<Moon className="w-5 h-5" />}
|
||||
defaultChecked
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Volume Toggle */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Label>Audio</Label>
|
||||
<Switch
|
||||
size="md"
|
||||
color="blue"
|
||||
iconOn={<Volume2 className="w-3 h-3" />}
|
||||
iconOff={<VolumeX className="w-3 h-3" />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* WiFi Toggle */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Label>WiFi</Label>
|
||||
<Switch
|
||||
size="md"
|
||||
color="cyan"
|
||||
iconOn={<Wifi className="w-3 h-3" />}
|
||||
iconOff={<WifiOff className="w-3 h-3" />}
|
||||
defaultChecked
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</LivePreview>
|
||||
</div>
|
||||
</LivePreview>
|
||||
</>
|
||||
) : (
|
||||
<CodeDisplay
|
||||
code={generateCode(config)}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import { Grid, List, Asterisk, Terminal, FileCode, Globe, FileText, Calendar } from "lucide-react";
|
||||
import { Grid, List, Asterisk, Terminal, FileCode, Globe, FileText, Calendar, Code } from "lucide-react";
|
||||
import { Button } from "@/features/ui/primitives/button";
|
||||
import { DataCard, DataCardHeader, DataCardContent, DataCardFooter } from "@/features/ui/primitives/data-card";
|
||||
import { GroupedCard } from "@/features/ui/primitives/grouped-card";
|
||||
import { StatPill } from "@/features/ui/primitives/pill";
|
||||
import { Input } from "@/features/ui/primitives/input";
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/features/ui/primitives/toggle-group";
|
||||
@@ -15,6 +16,7 @@ const MOCK_KNOWLEDGE_ITEMS = [
|
||||
url: "https://react.dev",
|
||||
date: "2024-01-15",
|
||||
chunks: 145,
|
||||
codeExamples: 23,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
@@ -23,6 +25,7 @@ const MOCK_KNOWLEDGE_ITEMS = [
|
||||
url: null,
|
||||
date: "2024-01-20",
|
||||
chunks: 23,
|
||||
codeExamples: 0,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
@@ -31,6 +34,7 @@ const MOCK_KNOWLEDGE_ITEMS = [
|
||||
url: "https://fastapi.tiangolo.com",
|
||||
date: "2024-01-18",
|
||||
chunks: 89,
|
||||
codeExamples: 15,
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
@@ -39,6 +43,7 @@ const MOCK_KNOWLEDGE_ITEMS = [
|
||||
url: "https://tailwindcss.com",
|
||||
date: "2024-01-22",
|
||||
chunks: 112,
|
||||
codeExamples: 31,
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
@@ -47,6 +52,7 @@ const MOCK_KNOWLEDGE_ITEMS = [
|
||||
url: null,
|
||||
date: "2024-01-10",
|
||||
chunks: 15,
|
||||
codeExamples: 0,
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
@@ -55,6 +61,7 @@ const MOCK_KNOWLEDGE_ITEMS = [
|
||||
url: "https://www.typescriptlang.org/docs",
|
||||
date: "2024-01-25",
|
||||
chunks: 203,
|
||||
codeExamples: 47,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -125,36 +132,61 @@ export const KnowledgeLayoutExample = () => {
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
// Table View - Overflow-x-auto wrapper
|
||||
// Table View - matching TaskView standard pattern
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-white/10">
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<tr className="bg-gradient-to-r from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 border-b-2 border-gray-200 dark:border-gray-700">
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Title
|
||||
</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Type
|
||||
</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Source
|
||||
</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Chunks
|
||||
</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Date
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{MOCK_KNOWLEDGE_ITEMS.map((item) => (
|
||||
<KnowledgeTableRow key={item.id} item={item} />
|
||||
{MOCK_KNOWLEDGE_ITEMS.map((item, index) => (
|
||||
<KnowledgeTableRow key={item.id} item={item} index={index} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Grouped Card Example */}
|
||||
<div className="mt-8 space-y-4">
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-200">Grouped Knowledge Cards</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Multiple related items stacked together with progressive scaling and fading edge lights
|
||||
</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
<GroupedCard
|
||||
cards={[
|
||||
{ id: "1", title: "React Hooks Guide", edgeColor: "cyan" },
|
||||
{ id: "2", title: "React Components", edgeColor: "cyan" },
|
||||
{ id: "3", title: "React Patterns", edgeColor: "cyan" },
|
||||
]}
|
||||
className="h-[280px]"
|
||||
/>
|
||||
<GroupedCard
|
||||
cards={[
|
||||
{ id: "4", title: "API Documentation", edgeColor: "purple" },
|
||||
{ id: "5", title: "API Examples", edgeColor: "purple" },
|
||||
]}
|
||||
className="h-[280px]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -173,6 +205,7 @@ const KnowledgeCard = ({ item }: { item: typeof MOCK_KNOWLEDGE_ITEMS[0] }) => {
|
||||
<DataCard
|
||||
edgePosition="top"
|
||||
edgeColor={getEdgeColor()}
|
||||
blur="lg"
|
||||
className="cursor-pointer hover:shadow-[0_0_30px_rgba(6,182,212,0.2)] transition-shadow"
|
||||
>
|
||||
<DataCardHeader>
|
||||
@@ -213,51 +246,66 @@ const KnowledgeCard = ({ item }: { item: typeof MOCK_KNOWLEDGE_ITEMS[0] }) => {
|
||||
<Calendar className="w-3 h-3" />
|
||||
<span>{item.date}</span>
|
||||
</div>
|
||||
<StatPill
|
||||
color="orange"
|
||||
value={item.chunks}
|
||||
icon={<FileText className="w-3.5 h-3.5" />}
|
||||
size="sm"
|
||||
onClick={() => console.log('View documents')}
|
||||
className="cursor-pointer hover:scale-105 transition-transform"
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<StatPill
|
||||
color="orange"
|
||||
value={item.chunks}
|
||||
icon={<FileText className="w-3.5 h-3.5" />}
|
||||
size="sm"
|
||||
onClick={() => console.log('View documents')}
|
||||
className="cursor-pointer hover:scale-105 transition-transform"
|
||||
/>
|
||||
<StatPill
|
||||
color="blue"
|
||||
value={item.codeExamples}
|
||||
icon={<Code className="w-3.5 h-3.5" />}
|
||||
size="sm"
|
||||
onClick={() => console.log('View code examples')}
|
||||
className="cursor-pointer hover:scale-105 transition-transform"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DataCardFooter>
|
||||
</DataCard>
|
||||
);
|
||||
};
|
||||
|
||||
// Table Row Component
|
||||
const KnowledgeTableRow = ({ item }: { item: typeof MOCK_KNOWLEDGE_ITEMS[0] }) => {
|
||||
// Table Row Component - matching TaskView standard pattern
|
||||
const KnowledgeTableRow = ({ item, index }: { item: typeof MOCK_KNOWLEDGE_ITEMS[0]; index: number }) => {
|
||||
return (
|
||||
<tr className="border-b border-white/5 hover:bg-white/5 dark:hover:bg-white/5 transition-colors cursor-pointer">
|
||||
<td className="py-3 px-4">
|
||||
<tr className={cn(
|
||||
"group transition-all duration-200 cursor-pointer",
|
||||
index % 2 === 0 ? "bg-white/50 dark:bg-black/50" : "bg-gray-50/80 dark:bg-gray-900/30",
|
||||
"hover:bg-gradient-to-r hover:from-cyan-50/70 hover:to-purple-50/70 dark:hover:from-cyan-900/20 dark:hover:to-purple-900/20",
|
||||
"border-b border-gray-200 dark:border-gray-800",
|
||||
)}>
|
||||
<td className="px-4 py-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{item.url ? (
|
||||
<Globe className="w-4 h-4 text-cyan-500 flex-shrink-0" />
|
||||
) : (
|
||||
<FileText className="w-4 h-4 text-purple-500 flex-shrink-0" />
|
||||
)}
|
||||
<span className="text-sm text-gray-900 dark:text-white">{item.title}</span>
|
||||
<span className="font-medium text-sm text-gray-900 dark:text-white">{item.title}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<td className="px-4 py-2">
|
||||
<span
|
||||
className={cn(
|
||||
"px-2 py-0.5 text-xs rounded border",
|
||||
"px-2 py-1 text-xs rounded-md font-medium inline-block",
|
||||
item.type === "technical"
|
||||
? "bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 border-cyan-500/30"
|
||||
: "bg-purple-500/10 text-purple-600 dark:text-purple-400 border-purple-500/30",
|
||||
? "bg-cyan-500/10 text-cyan-600 dark:text-cyan-400"
|
||||
: "bg-purple-500/10 text-purple-600 dark:text-purple-400",
|
||||
)}
|
||||
>
|
||||
{item.type}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400 max-w-xs truncate">
|
||||
<td className="px-4 py-2 text-sm text-gray-700 dark:text-gray-300 max-w-xs truncate">
|
||||
{item.url || "Uploaded Document"}
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">{item.chunks}</td>
|
||||
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">{item.date}</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-600 dark:text-gray-400">{item.chunks}</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-600 dark:text-gray-400">{item.date}</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { LayoutGrid, List, ListTodo, Activity, CheckCircle2, FileText, Search, Table as TableIcon, Tag, User, Trash2, Pin, Copy } from "lucide-react";
|
||||
import { LayoutGrid, List, ListTodo, Activity, CheckCircle2, FileText, Search, Table as TableIcon, Tag, User, Trash2, Pin, Copy, Edit } from "lucide-react";
|
||||
import { StatPill } from "@/features/ui/primitives/pill";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import { Button } from "@/features/ui/primitives/button";
|
||||
@@ -9,6 +10,7 @@ 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";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/features/ui/primitives/tooltip";
|
||||
|
||||
const MOCK_PROJECTS = [
|
||||
{
|
||||
@@ -93,17 +95,19 @@ export const ProjectsLayoutExample = () => {
|
||||
|
||||
{layoutMode === "horizontal" ? (
|
||||
<>
|
||||
{/* Horizontal Project Cards */}
|
||||
<div className="overflow-x-auto overflow-y-visible py-8 px-8">
|
||||
<div className="flex gap-4 min-w-max">
|
||||
{MOCK_PROJECTS.map((project) => (
|
||||
<ProjectCardExample
|
||||
key={project.id}
|
||||
project={project}
|
||||
isSelected={selectedId === project.id}
|
||||
onSelect={() => setSelectedId(project.id)}
|
||||
/>
|
||||
))}
|
||||
{/* Horizontal Project Cards - ONLY cards scroll, not whole page */}
|
||||
<div className="w-full">
|
||||
<div className="overflow-x-auto overflow-y-visible py-8 -mx-6 px-6">
|
||||
<div className="flex gap-4 min-w-max">
|
||||
{MOCK_PROJECTS.map((project) => (
|
||||
<ProjectCardExample
|
||||
key={project.id}
|
||||
project={project}
|
||||
isSelected={selectedId === project.id}
|
||||
onSelect={() => setSelectedId(project.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -241,7 +245,7 @@ export const ProjectsLayoutExample = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// Sidebar Project Card with task counts
|
||||
// Sidebar Project Card - mini card style with StatPills
|
||||
const SidebarProjectCard = ({
|
||||
project,
|
||||
isSelected,
|
||||
@@ -252,27 +256,56 @@ const SidebarProjectCard = ({
|
||||
onSelect: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSelect}
|
||||
<Card
|
||||
blur="md"
|
||||
transparency="light"
|
||||
className={cn(
|
||||
"w-full text-left px-3 py-2 rounded-lg transition-all duration-200",
|
||||
"cursor-pointer transition-all duration-200 p-3",
|
||||
isSelected
|
||||
? "bg-purple-500/10 dark:bg-purple-400/10 text-purple-700 dark:text-purple-300 border-l-2 border-purple-500"
|
||||
: "text-gray-600 dark:text-gray-400 hover:bg-white/5 dark:hover:bg-white/5 border-l-2 border-transparent",
|
||||
? "border-purple-500/60 shadow-[0_0_12px_rgba(168,85,247,0.4)]"
|
||||
: "border-gray-300/20 dark:border-white/10 hover:border-purple-400/40 hover:shadow-[0_0_8px_rgba(168,85,247,0.2)]",
|
||||
)}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<div className="font-medium text-sm line-clamp-1 mb-1">{project.title}</div>
|
||||
{project.pinned && <div className="text-xs text-purple-600 dark:text-purple-400 mb-1">Pinned</div>}
|
||||
{/* Task counts */}
|
||||
<div className="flex gap-2 text-xs">
|
||||
<span className="text-pink-600 dark:text-pink-400">{project.taskCounts.todo} todo</span>
|
||||
<span className="text-blue-600 dark:text-blue-400">
|
||||
{project.taskCounts.doing + project.taskCounts.review} doing
|
||||
</span>
|
||||
<span className="text-green-600 dark:text-green-400">{project.taskCounts.done} done</span>
|
||||
<div className="space-y-2">
|
||||
{/* Title */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className={cn(
|
||||
"font-medium text-sm line-clamp-1",
|
||||
isSelected ? "text-purple-700 dark:text-purple-300" : "text-gray-700 dark:text-gray-300"
|
||||
)}>
|
||||
{project.title}
|
||||
</h4>
|
||||
{project.pinned && (
|
||||
<div className="flex items-center gap-1 px-1.5 py-0.5 bg-purple-500 text-white text-[9px] font-bold rounded-full">
|
||||
<Pin className="w-2.5 h-2.5" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Status Pills - horizontal layout with icons */}
|
||||
<div className="flex items-center gap-1.5">
|
||||
<StatPill
|
||||
color="pink"
|
||||
value={project.taskCounts.todo}
|
||||
size="sm"
|
||||
icon={<ListTodo className="w-3 h-3" />}
|
||||
/>
|
||||
<StatPill
|
||||
color="blue"
|
||||
value={project.taskCounts.doing + project.taskCounts.review}
|
||||
size="sm"
|
||||
icon={<Activity className="w-3 h-3" />}
|
||||
/>
|
||||
<StatPill
|
||||
color="emerald"
|
||||
value={project.taskCounts.done}
|
||||
size="sm"
|
||||
icon={<CheckCircle2 className="w-3 h-3" />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -300,13 +333,14 @@ const ProjectCardExample = ({
|
||||
showAuroraGlow={isSelected}
|
||||
onSelect={onSelect}
|
||||
size="none"
|
||||
blur="xl"
|
||||
className={cn(
|
||||
"w-72 min-h-[180px] flex flex-col",
|
||||
getBackgroundClass(),
|
||||
)}
|
||||
>
|
||||
{/* Main content */}
|
||||
<div className="flex-1 p-4 pb-2">
|
||||
<div className="flex-1 p-3 pb-2">
|
||||
{/* Title */}
|
||||
<div className="flex flex-col items-center justify-center mb-4 min-h-[48px]">
|
||||
<h3
|
||||
@@ -402,11 +436,13 @@ const ProjectCardExample = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom bar with pin badge and action icons */}
|
||||
{/* Bottom bar with action icons */}
|
||||
<div className="flex items-center justify-between px-3 py-2 mt-auto border-t border-gray-200/30 dark:border-gray-700/20">
|
||||
{/* Pinned indicator with icon */}
|
||||
{project.pinned ? (
|
||||
<div className="px-2 py-0.5 bg-purple-500 text-white text-[10px] font-bold rounded-full shadow-lg shadow-purple-500/30">
|
||||
DEFAULT
|
||||
<div className="flex items-center gap-1 px-2 py-0.5 bg-purple-500 text-white text-[10px] font-bold rounded-full shadow-lg shadow-purple-500/30">
|
||||
<Pin className="w-2.5 h-2.5" />
|
||||
<span>PINNED</span>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
@@ -484,7 +520,7 @@ const KanbanBoardView = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// Task Card using DraggableCard primitive
|
||||
// Task Card using DraggableCard primitive with actions
|
||||
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)]" };
|
||||
@@ -508,7 +544,7 @@ const TaskCardExample = ({ task, index }: { task: typeof MOCK_TASKS[0]; index: n
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex flex-col h-full p-3">
|
||||
{/* Header with feature tag */}
|
||||
{/* Header with feature tag and actions */}
|
||||
<div className="flex items-center gap-2 mb-2 pl-1.5">
|
||||
{task.feature && (
|
||||
<div className="px-2 py-1 rounded-md text-xs font-medium flex items-center gap-1 backdrop-blur-md bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 shadow-sm">
|
||||
@@ -516,6 +552,36 @@ const TaskCardExample = ({ task, index }: { task: typeof MOCK_TASKS[0]; index: n
|
||||
{task.feature}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Action buttons - matching TaskCard.tsx pattern */}
|
||||
<div className="ml-auto flex items-center gap-1">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="p-1 rounded hover:bg-cyan-500/10 text-gray-500 hover:text-cyan-500 transition-colors"
|
||||
>
|
||||
<Edit className="w-3 h-3" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Edit task</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="p-1 rounded hover:bg-red-500/10 text-gray-500 hover:text-red-500 transition-colors"
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Delete task</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
@@ -528,7 +594,7 @@ const TaskCardExample = ({ task, index }: { task: typeof MOCK_TASKS[0]; index: n
|
||||
|
||||
{/* Footer with assignee and priority */}
|
||||
<div className="flex items-center justify-between mt-auto pt-2 pl-1.5 pr-3">
|
||||
{/* Assignee card-style */}
|
||||
{/* Assignee card-style - matching TaskCard.tsx */}
|
||||
<div className="flex items-center gap-1.5 px-2 py-1 rounded-md bg-white/50 dark:bg-black/30 border border-gray-200 dark:border-gray-700 text-xs">
|
||||
<User className="w-3 h-3 text-gray-500 dark:text-gray-400" />
|
||||
<span className="text-gray-700 dark:text-gray-300">{task.assignee}</span>
|
||||
|
||||
@@ -8,7 +8,7 @@ interface ConfigPanelProps {
|
||||
}
|
||||
|
||||
export const ConfigPanel = ({ title, children, className }: ConfigPanelProps) => (
|
||||
<Card className={cn("space-y-4", className)}>
|
||||
<Card blur="lg" className={cn("space-y-4", className)}>
|
||||
{title && <h3 className="font-semibold text-lg mb-2 text-gray-900 dark:text-white">{title}</h3>}
|
||||
{children}
|
||||
</Card>
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { RotateCcw } from "lucide-react";
|
||||
import { Card } from "@/features/ui/primitives/card";
|
||||
import { Button } from "@/features/ui/primitives/button";
|
||||
|
||||
export const StaticEffects = () => {
|
||||
const [animationKey, setAnimationKey] = useState(0);
|
||||
|
||||
const replayAnimation = () => {
|
||||
setAnimationKey(prev => prev + 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
@@ -77,8 +86,19 @@ export const StaticEffects = () => {
|
||||
|
||||
{/* Entrance Animations */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-4 text-gray-800 dark:text-gray-200">Stagger Entrance</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-200">Stagger Entrance</h3>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={replayAnimation}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<RotateCcw className="w-4 h-4" />
|
||||
Replay
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-2" key={animationKey}>
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
|
||||
@@ -8,63 +8,69 @@ export const StaticTables = () => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Basic Table */}
|
||||
{/* Standard Table - matching TaskView pattern */}
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold mb-3 text-gray-800 dark:text-gray-200">Standard Table</h3>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-white/10">
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<tr className="bg-gradient-to-r from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 border-b-2 border-gray-200 dark:border-gray-700">
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Name
|
||||
</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Status
|
||||
</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Count
|
||||
</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Date
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr className="border-b border-white/5 hover:bg-white/5 dark:hover:bg-white/5 transition-colors">
|
||||
<td className="py-3 px-4 text-sm text-gray-900 dark:text-white">React Documentation</td>
|
||||
<td className="py-3 px-4">
|
||||
<span className="px-2 py-1 text-xs rounded-md bg-green-500/10 text-green-600 dark:text-green-400">
|
||||
<tr className="bg-white/50 dark:bg-black/50 hover:bg-gradient-to-r hover:from-cyan-50/70 hover:to-purple-50/70 dark:hover:from-cyan-900/20 dark:hover:to-purple-900/20 border-b border-gray-200 dark:border-gray-800 transition-all duration-200">
|
||||
<td className="px-4 py-2">
|
||||
<span className="font-medium text-sm text-gray-900 dark:text-white">React Documentation</span>
|
||||
</td>
|
||||
<td className="px-4 py-2">
|
||||
<span className="px-2 py-1 text-xs rounded-md font-medium bg-green-500/10 text-green-600 dark:text-green-400">
|
||||
Active
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">145</td>
|
||||
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">2024-01-15</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-600 dark:text-gray-400">145</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-600 dark:text-gray-400">2024-01-15</td>
|
||||
</tr>
|
||||
<tr className="border-b border-white/5 hover:bg-white/5 dark:hover:bg-white/5 transition-colors">
|
||||
<td className="py-3 px-4 text-sm text-gray-900 dark:text-white">API Integration</td>
|
||||
<td className="py-3 px-4">
|
||||
<span className="px-2 py-1 text-xs rounded-md bg-yellow-500/10 text-yellow-600 dark:text-yellow-400">
|
||||
<tr className="bg-gray-50/80 dark:bg-gray-900/30 hover:bg-gradient-to-r hover:from-cyan-50/70 hover:to-purple-50/70 dark:hover:from-cyan-900/20 dark:hover:to-purple-900/20 border-b border-gray-200 dark:border-gray-800 transition-all duration-200">
|
||||
<td className="px-4 py-2">
|
||||
<span className="font-medium text-sm text-gray-900 dark:text-white">API Integration</span>
|
||||
</td>
|
||||
<td className="px-4 py-2">
|
||||
<span className="px-2 py-1 text-xs rounded-md font-medium bg-yellow-500/10 text-yellow-600 dark:text-yellow-400">
|
||||
Processing
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">89</td>
|
||||
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">2024-01-18</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-600 dark:text-gray-400">89</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-600 dark:text-gray-400">2024-01-18</td>
|
||||
</tr>
|
||||
<tr className="border-b border-white/5 hover:bg-white/5 dark:hover:bg-white/5 transition-colors">
|
||||
<td className="py-3 px-4 text-sm text-gray-900 dark:text-white">TypeScript Guide</td>
|
||||
<td className="py-3 px-4">
|
||||
<span className="px-2 py-1 text-xs rounded-md bg-cyan-500/10 text-cyan-600 dark:text-cyan-400">
|
||||
<tr className="bg-white/50 dark:bg-black/50 hover:bg-gradient-to-r hover:from-cyan-50/70 hover:to-purple-50/70 dark:hover:from-cyan-900/20 dark:hover:to-purple-900/20 border-b border-gray-200 dark:border-gray-800 transition-all duration-200">
|
||||
<td className="px-4 py-2">
|
||||
<span className="font-medium text-sm text-gray-900 dark:text-white">TypeScript Guide</span>
|
||||
</td>
|
||||
<td className="px-4 py-2">
|
||||
<span className="px-2 py-1 text-xs rounded-md font-medium bg-cyan-500/10 text-cyan-600 dark:text-cyan-400">
|
||||
Complete
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">203</td>
|
||||
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">2024-01-20</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-600 dark:text-gray-400">203</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-600 dark:text-gray-400">2024-01-20</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-3">
|
||||
Features: Hover row highlight, status badges, responsive overflow
|
||||
Features: Gradient header, alternating rows, hover gradient, consistent spacing
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
79
archon-ui-main/src/features/ui/primitives/grouped-card.tsx
Normal file
79
archon-ui-main/src/features/ui/primitives/grouped-card.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import React from "react";
|
||||
import { DataCard } from "./data-card";
|
||||
import { cn } from "./styles";
|
||||
|
||||
interface GroupedCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
cards: Array<{
|
||||
id: string;
|
||||
title: string;
|
||||
edgeColor: 'purple' | 'blue' | 'cyan' | 'green' | 'orange' | 'pink' | 'red';
|
||||
children?: React.ReactNode;
|
||||
}>;
|
||||
maxVisible?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* GroupedCard - Stacked card component showing multiple cards in shuffle deck style
|
||||
*
|
||||
* Features:
|
||||
* - Shows 2-3 cards stacked on top of each other
|
||||
* - Top edge lights visible with fading effect
|
||||
* - Progressive scaling (each card ~5% smaller)
|
||||
* - Cards raised up behind top card with z-index layering
|
||||
*/
|
||||
export const GroupedCard = React.forwardRef<HTMLDivElement, GroupedCardProps>(
|
||||
({ cards, maxVisible = 3, className, ...props }, ref) => {
|
||||
const visibleCards = cards.slice(0, maxVisible);
|
||||
const cardCount = visibleCards.length;
|
||||
|
||||
return (
|
||||
<div ref={ref} className={cn("relative", className)} {...props}>
|
||||
{visibleCards.map((card, index) => {
|
||||
const isTop = index === 0;
|
||||
const zIndex = cardCount - index;
|
||||
const scale = 1 - (index * 0.05); // 5% smaller per card
|
||||
const yOffset = index * 8; // 8px raised per card
|
||||
const opacity = 1 - (index * 0.15); // Fade background cards slightly
|
||||
|
||||
return (
|
||||
<div
|
||||
key={card.id}
|
||||
className="absolute inset-0 transition-all duration-300"
|
||||
style={{
|
||||
zIndex,
|
||||
transform: `scale(${scale}) translateY(-${yOffset}px)`,
|
||||
opacity: isTop ? 1 : opacity,
|
||||
}}
|
||||
>
|
||||
<DataCard
|
||||
edgePosition="top"
|
||||
edgeColor={card.edgeColor}
|
||||
blur="lg"
|
||||
className={cn(
|
||||
"transition-all duration-300",
|
||||
!isTop && "pointer-events-none"
|
||||
)}
|
||||
>
|
||||
{card.children || (
|
||||
<div className="p-4">
|
||||
<h4 className="font-medium text-white">{card.title}</h4>
|
||||
</div>
|
||||
)}
|
||||
</DataCard>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{/* Spacer to maintain height based on bottom card */}
|
||||
<div style={{ paddingBottom: `${(cardCount - 1) * 8}px`, opacity: 0 }}>
|
||||
<DataCard edgePosition="top" edgeColor={visibleCards[0]?.edgeColor || "cyan"}>
|
||||
<div className="p-4">
|
||||
<h4>{visibleCards[0]?.title || "Placeholder"}</h4>
|
||||
</div>
|
||||
</DataCard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
GroupedCard.displayName = "GroupedCard";
|
||||
@@ -17,6 +17,7 @@ export * from "./button";
|
||||
export * from "./card";
|
||||
export * from "./data-card";
|
||||
export * from "./draggable-card";
|
||||
export * from "./grouped-card";
|
||||
export * from "./selectable-card";
|
||||
export * from "./combobox";
|
||||
export * from "./dialog";
|
||||
|
||||
Reference in New Issue
Block a user