Skip to content

Latest commit

 

History

History
722 lines (615 loc) · 18.3 KB

File metadata and controls

722 lines (615 loc) · 18.3 KB

Design System - Component Examples

Quick reference for implementing common UI patterns using the Afterthought design system.


🎨 Using the Design System

Option 1: Tailwind Classes (Recommended)

<button className="btn-primary">Click me</button>

Option 2: CSS Variables

<button style={{ background: 'var(--accent-primary)' }}>
  Click me
</button>

Option 3: Tailwind Custom Colors

<div className="bg-bg-secondary text-text-primary">
  Content
</div>

🧩 Component Examples

Buttons

// Primary Button
<button className="btn-primary">
  Process Image
</button>

// Secondary Button
<button className="btn-secondary">
  Cancel
</button>

// Ghost Button (for toolbars)
<button className="btn-ghost">
  <IconSettings className="w-4 h-4" />
</button>

// Danger Button
<button className="btn-danger">
  Delete
</button>

// Disabled Button
<button className="btn-primary opacity-50 cursor-not-allowed" disabled>
  Loading...
</button>

// Button with Icon
<button className="btn-primary flex items-center gap-2">
  <IconPlay className="w-4 h-4" />
  Process
</button>

Cards

// Basic Card
<div className="card">
  <h3 className="text-lg font-semibold mb-2">Card Title</h3>
  <p className="text-text-secondary text-sm">Card content goes here.</p>
</div>

// Interactive Card (clickable)
<div className="card-interactive" onClick={handleClick}>
  <h3 className="text-lg font-semibold mb-2">Clickable Card</h3>
  <p className="text-text-secondary text-sm">Hover for animation.</p>
</div>

// Card with Header and Footer
<div className="card">
  {/* Header */}
  <div className="pb-4 mb-4 border-b border-border-subtle">
    <h2 className="text-xl font-semibold">Settings</h2>
  </div>

  {/* Content */}
  <div className="space-y-4">
    <p className="text-text-secondary">Card content</p>
  </div>

  {/* Footer */}
  <div className="pt-4 mt-4 border-t border-border-subtle flex justify-end gap-3">
    <button className="btn-secondary">Cancel</button>
    <button className="btn-primary">Save</button>
  </div>
</div>

// Compact Card (tighter spacing)
<div className="bg-bg-secondary border border-border-subtle rounded-lg p-4 hover:border-border-default transition-all">
  <div className="flex items-center justify-between">
    <span className="font-medium">Compact Item</span>
    <span className="text-text-tertiary text-sm">Value</span>
  </div>
</div>

Inputs

// Text Input
<div className="space-y-2">
  <label className="block text-sm font-medium text-text-secondary">
    Email
  </label>
  <input
    type="email"
    className="input"
    placeholder="Enter your email"
  />
</div>

// Input with Error
<div className="space-y-2">
  <label className="block text-sm font-medium text-text-secondary">
    API Key
  </label>
  <input
    type="password"
    className="input border-error focus:border-error focus:ring-error/20"
    placeholder="sk-..."
  />
  <p className="text-error text-xs">Invalid API key format</p>
</div>

// Input with Success
<div className="space-y-2">
  <label className="block text-sm font-medium text-text-secondary">
    Username
  </label>
  <input
    type="text"
    className="input border-success focus:border-success focus:ring-success/20"
    placeholder="john_doe"
    value="john_doe"
  />
  <p className="text-success text-xs flex items-center gap-1">
    <IconCheck className="w-3 h-3" />
    Username available
  </p>
</div>

// Textarea
<textarea
  className="input resize-none"
  rows={4}
  placeholder="Enter description..."
/>

// Select Dropdown
<select className="input">
  <option>Choose an option</option>
  <option>Option 1</option>
  <option>Option 2</option>
</select>

Badges

// Status Badges
<span className="badge-success">Completed</span>
<span className="badge-warning">Pending</span>
<span className="badge-error">Failed</span>
<span className="badge-info">Processing</span>

// Category Badges
<span className="badge-work">Work</span>
<span className="badge-personal">Personal</span>
<span className="badge-reference">Reference</span>
<span className="badge-idea">Idea</span>
<span className="badge-other">Other</span>

// Custom Badge
<span className="px-2 py-1 bg-purple-500/10 text-purple-400 text-xs font-medium rounded-md">
  Custom
</span>

// Badge with Icon
<span className="badge-success flex items-center gap-1">
  <IconCheck className="w-3 h-3" />
  Verified
</span>

// Badge with Count
<span className="badge bg-accent/10 text-accent">
  Tasks <span className="ml-1 font-bold">12</span>
</span>

Lists

// Vertical List
<div className="space-y-3">
  {items.map((item) => (
    <div
      key={item.id}
      className="p-4 border border-border-subtle rounded-lg hover:bg-surface-hover transition-all"
    >
      <h3 className="font-medium">{item.title}</h3>
      <p className="text-text-tertiary text-sm mt-1">{item.description}</p>
    </div>
  ))}
</div>

// Compact List
<div className="space-y-2">
  {items.map((item) => (
    <div
      key={item.id}
      className="px-3 py-2 rounded-md hover:bg-surface-hover transition-all cursor-pointer"
    >
      <span className="text-sm">{item.name}</span>
    </div>
  ))}
</div>

// List with Separators
<div className="divide-y divide-border-subtle">
  {items.map((item) => (
    <div key={item.id} className="py-4 first:pt-0 last:pb-0">
      {item.content}
    </div>
  ))}
</div>

// Two-Column List
<div className="grid grid-cols-2 gap-4">
  {items.map((item) => (
    <div key={item.id} className="card">
      {item.content}
    </div>
  ))}
</div>

Modals/Dialogs

// Modal Container
<div className="fixed inset-0 z-50 flex items-center justify-center">
  {/* Backdrop */}
  <div
    className="absolute inset-0 bg-black/70 backdrop-blur-sm"
    onClick={onClose}
  />

  {/* Modal */}
  <div className="relative bg-bg-elevated border border-border-emphasis rounded-2xl shadow-modal max-w-2xl w-[90vw] max-h-[85vh] overflow-hidden flex flex-col">
    {/* Header */}
    <div className="flex items-center justify-between px-6 py-4 border-b border-border-subtle">
      <h2 className="text-xl font-semibold">Modal Title</h2>
      <button
        onClick={onClose}
        className="text-text-tertiary hover:text-text-primary transition-colors"
      >
        <IconX className="w-5 h-5" />
      </button>
    </div>

    {/* Content (scrollable) */}
    <div className="flex-1 overflow-y-auto px-6 py-4">
      <p className="text-text-secondary">Modal content goes here...</p>
    </div>

    {/* Footer */}
    <div className="flex justify-end gap-3 px-6 py-4 border-t border-border-subtle">
      <button className="btn-secondary" onClick={onClose}>
        Cancel
      </button>
      <button className="btn-primary" onClick={onSave}>
        Save Changes
      </button>
    </div>
  </div>
</div>

// Small Modal (Confirmation)
<div className="bg-bg-elevated border border-border-emphasis rounded-xl shadow-modal p-6 max-w-md">
  <h3 className="text-lg font-semibold mb-2">Delete Image?</h3>
  <p className="text-text-secondary text-sm mb-6">
    This action cannot be undone. The image will be permanently deleted.
  </p>
  <div className="flex justify-end gap-3">
    <button className="btn-secondary">Cancel</button>
    <button className="btn-danger">Delete</button>
  </div>
</div>

Loading States

// Spinner
<div className="w-6 h-6 border-2 border-border-subtle border-t-accent rounded-full animate-spin" />

// Button Loading
<button className="btn-primary opacity-75 cursor-wait" disabled>
  <div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin mr-2" />
  Processing...
</button>

// Skeleton Card
<div className="card">
  <div className="skeleton h-6 w-3/4 mb-3" />
  <div className="skeleton h-4 w-full mb-2" />
  <div className="skeleton h-4 w-5/6" />
</div>

// Full Page Loading
<div className="flex items-center justify-center h-screen">
  <div className="text-center">
    <div className="w-12 h-12 border-4 border-border-emphasis border-t-accent rounded-full animate-spin mx-auto mb-4" />
    <p className="text-text-secondary">Loading...</p>
  </div>
</div>

// Inline Loading Text
<div className="flex items-center gap-2 text-text-secondary">
  <div className="w-4 h-4 border-2 border-text-tertiary border-t-accent rounded-full animate-spin" />
  <span>Processing image...</span>
</div>

Empty States

// Basic Empty State
<div className="empty-state">
  <div className="text-6xl mb-4 opacity-50">📸</div>
  <h3 className="empty-state-title">No images yet</h3>
  <p className="empty-state-description">
    Capture an image to get started. We'll automatically detect and analyze it.
  </p>
  <button className="btn-primary mt-6">
    Learn How
  </button>
</div>

// Empty State with Action
<div className="empty-state">
  <div className="w-16 h-16 bg-surface rounded-xl flex items-center justify-center mb-4 mx-auto">
    <IconInbox className="w-8 h-8 text-text-tertiary" />
  </div>
  <h3 className="empty-state-title">No pending tasks</h3>
  <p className="empty-state-description">
    All caught up! New tasks will appear here when images are processed.
  </p>
</div>

// Empty Search Results
<div className="empty-state">
  <div className="text-4xl mb-4">🔍</div>
  <h3 className="empty-state-title">No results found</h3>
  <p className="empty-state-description">
    Try adjusting your search or filters to find what you're looking for.
  </p>
  <button className="btn-secondary mt-4">
    Clear Filters
  </button>
</div>

Tooltips (CSS only)

// Simple Tooltip
<div className="relative group inline-block">
  <button className="btn-ghost">
    <IconInfo className="w-4 h-4" />
  </button>
  <div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-bg-elevated border border-border-emphasis rounded text-xs whitespace-nowrap opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all">
    More information
    <div className="absolute top-full left-1/2 -translate-x-1/2 -mt-1 border-4 border-transparent border-t-bg-elevated" />
  </div>
</div>

Notifications/Toasts

// Success Toast
<div className="bg-bg-elevated border border-success/20 rounded-lg shadow-card p-4 flex items-start gap-3 max-w-md">
  <div className="w-5 h-5 rounded-full bg-success/10 flex items-center justify-center flex-shrink-0">
    <IconCheck className="w-3 h-3 text-success" />
  </div>
  <div className="flex-1">
    <h4 className="font-medium text-sm mb-1">Image processed</h4>
    <p className="text-text-secondary text-xs">
      Found 3 tasks and extracted text successfully.
    </p>
  </div>
  <button className="text-text-tertiary hover:text-text-primary">
    <IconX className="w-4 h-4" />
  </button>
</div>

// Error Toast
<div className="bg-bg-elevated border border-error/20 rounded-lg shadow-card p-4 flex items-start gap-3 max-w-md">
  <div className="w-5 h-5 rounded-full bg-error/10 flex items-center justify-center flex-shrink-0">
    <IconAlertCircle className="w-3 h-3 text-error" />
  </div>
  <div className="flex-1">
    <h4 className="font-medium text-sm mb-1">Processing failed</h4>
    <p className="text-text-secondary text-xs">
      Invalid API key. Please check your settings.
    </p>
  </div>
</div>

Headers/Sections

// Page Header
<header className="bg-bg-secondary border-b border-border-subtle px-6 py-4 flex items-center justify-between">
  <div>
    <h1 className="text-2xl font-bold">Afterthought</h1>
    <p className="text-text-tertiary text-sm mt-1">
      Image Manager with AI
    </p>
  </div>
  <button className="btn-ghost">
    <IconSettings className="w-5 h-5" />
  </button>
</header>

// Section Header
<div className="mb-6">
  <h2 className="text-xl font-semibold mb-1">Recent Images</h2>
  <p className="text-text-secondary text-sm">
    Automatically detected from your Pictures folder
  </p>
</div>

// Section with Action
<div className="flex items-center justify-between mb-6">
  <div>
    <h2 className="text-xl font-semibold">Tasks</h2>
    <p className="text-text-tertiary text-sm">12 pending</p>
  </div>
  <button className="btn-secondary text-sm">
    Clear completed
  </button>
</div>

Navigation Tabs

// Simple Tabs
<div className="flex gap-2 border-b border-border-subtle">
  <button
    className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
      activeTab === 'all'
        ? 'border-accent text-text-primary'
        : 'border-transparent text-text-tertiary hover:text-text-secondary'
    }`}
    onClick={() => setActiveTab('all')}
  >
    All
  </button>
  <button
    className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
      activeTab === 'pending'
        ? 'border-accent text-text-primary'
        : 'border-transparent text-text-tertiary hover:text-text-secondary'
    }`}
    onClick={() => setActiveTab('pending')}
  >
    Pending
  </button>
  <button
    className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
      activeTab === 'done'
        ? 'border-accent text-text-primary'
        : 'border-transparent text-text-tertiary hover:text-text-secondary'
    }`}
    onClick={() => setActiveTab('done')}
  >
    Done
  </button>
</div>

// Pill Tabs
<div className="flex gap-2 p-1 bg-bg-secondary rounded-lg">
  <button
    className={`px-4 py-2 text-sm font-medium rounded-md transition-all ${
      activeTab === 'all'
        ? 'bg-surface text-text-primary'
        : 'text-text-tertiary hover:text-text-secondary'
    }`}
    onClick={() => setActiveTab('all')}
  >
    All
  </button>
  <button
    className={`px-4 py-2 text-sm font-medium rounded-md transition-all ${
      activeTab === 'pending'
        ? 'bg-surface text-text-primary'
        : 'text-text-tertiary hover:text-text-secondary'
    }`}
    onClick={() => setActiveTab('pending')}
  >
    Pending
  </button>
</div>

Checkboxes & Radio Buttons

// Checkbox
<label className="flex items-center gap-3 cursor-pointer group">
  <div className={`w-5 h-5 rounded border-2 flex items-center justify-center transition-all ${
    checked
      ? 'bg-accent border-accent'
      : 'border-border-emphasis group-hover:border-accent'
  }`}>
    {checked && (
      <IconCheck className="w-3 h-3 text-white" />
    )}
  </div>
  <span className="text-sm text-text-primary">
    Remember me
  </span>
</label>

// Radio Button
<label className="flex items-center gap-3 cursor-pointer group">
  <div className={`w-5 h-5 rounded-full border-2 flex items-center justify-center transition-all ${
    selected
      ? 'border-accent'
      : 'border-border-emphasis group-hover:border-accent'
  }`}>
    {selected && (
      <div className="w-2.5 h-2.5 rounded-full bg-accent" />
    )}
  </div>
  <span className="text-sm text-text-primary">
    Option 1
  </span>
</label>

Progress Bars

// Simple Progress Bar
<div className="w-full bg-surface rounded-full h-2 overflow-hidden">
  <div
    className="bg-accent h-full transition-all duration-300"
    style={{ width: `${progress}%` }}
  />
</div>

// Progress Bar with Label
<div className="space-y-2">
  <div className="flex justify-between text-sm">
    <span className="text-text-secondary">Processing...</span>
    <span className="text-text-primary font-medium">{progress}%</span>
  </div>
  <div className="w-full bg-surface rounded-full h-2 overflow-hidden">
    <div
      className="bg-accent h-full transition-all duration-300"
      style={{ width: `${progress}%` }}
    />
  </div>
</div>

// Multi-segment Progress
<div className="w-full flex gap-1 h-2">
  <div className="bg-success flex-1 rounded-l-full" />
  <div className="bg-warning flex-1" />
  <div className="bg-surface flex-1 rounded-r-full" />
</div>

💡 Pro Tips

1. Combine Utility Classes

// Good: Descriptive, reusable pattern
<div className="flex items-center justify-between p-4 bg-bg-secondary rounded-lg hover:bg-surface-hover transition-all">

// Avoid: Too specific, hard to maintain
<div style={{ padding: '16px', background: '#141414', borderRadius: '8px' }}>

2. Use Semantic Naming

// Good: Clear purpose
<span className="badge-success">Completed</span>

// Avoid: Implementation detail
<span className="bg-green-100 text-green-800 px-2 py-1 rounded text-xs">

3. Responsive Design

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
  {/* Automatically adjusts: 1 column mobile, 2 tablet, 3 desktop */}
</div>

4. Dark Mode First

// All colors are dark by default
// No need for dark: prefix
<div className="bg-bg-secondary text-text-primary">

5. Animation Classes

// Use built-in transitions
<div className="transition-all duration-150 hover:-translate-y-0.5">
  Smooth hover effect
</div>

🎯 Common Patterns

Task Item Component

<div className="flex items-start gap-3 p-4 border border-border-subtle rounded-lg hover:bg-surface-hover transition-all">
  {/* Checkbox */}
  <button className="w-5 h-5 mt-0.5 rounded border-2 border-border-emphasis hover:border-accent transition-colors" />

  {/* Content */}
  <div className="flex-1 min-w-0">
    <h3 className="font-medium text-text-primary mb-1">Task title</h3>
    <p className="text-sm text-text-secondary mb-2">Task description</p>

    {/* Metadata */}
    <div className="flex items-center gap-2 flex-wrap">
      <span className="badge-work">Work</span>
      <span className="text-xs text-text-tertiary">✨ 95% confidence</span>
    </div>
  </div>

  {/* Priority */}
  <span className="px-2 py-1 bg-error/10 text-error text-xs font-medium rounded-md">
    High
  </span>
</div>

Image Card

<div className="card-interactive flex gap-4">
  {/* Thumbnail */}
  <div className="w-20 h-20 bg-surface rounded-lg overflow-hidden flex-shrink-0 border border-border-subtle">
    <img src={thumbnail} alt="" className="w-full h-full object-cover" />
  </div>

  {/* Content */}
  <div className="flex-1 min-w-0">
    <h3 className="font-medium text-text-primary truncate mb-1">
      Image_2024.png
    </h3>
    <p className="text-sm text-text-tertiary mb-2">
      2 hours ago
    </p>
    <div className="flex gap-2">
      <span className="badge-work">Work</span>
      <span className="badge-success">Processed</span>
    </div>
  </div>

  {/* Actions */}
  <button className="btn-primary self-start">
    Process
  </button>
</div>

Remember: Consistency is key. Use these patterns throughout your app for a cohesive user experience.