trying to make the ui reviews programmatic

This commit is contained in:
sean-eskerium
2025-10-09 07:59:54 -04:00
parent 4cb7c46d6e
commit 70b6e70a95
10 changed files with 1013 additions and 157 deletions

View File

@@ -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.

View 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

View File

@@ -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)}

View File

@@ -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>
);
};

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}

View File

@@ -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>

View 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";

View File

@@ -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";