jedmund-svelte/docs/task-7-styling-harmonization-plan.md
Justin Edmund 45e3556663 feat(admin): complete Task 7 Phase 1 - styling & theming foundation
Implemented a three-layer theming architecture to standardize admin component
styling and prepare for future dark mode support.

**Architecture:**
- Layer 1: Base colors ($gray-80, $red-60) in variables.scss
- Layer 2: Semantic SCSS variables ($input-bg, $error-bg) in variables.scss
- Layer 3: CSS custom properties (--input-bg, --error-bg) in themes.scss

**New semantic variables (~30 added):**
- Inputs & forms (bg, hover, focus, text, border states)
- State messages (error, success, warning with bg/text/border)
- Empty states (text, heading colors)
- Cards, dropdowns, popovers, modals (bg, border, shadow)

**New reusable components:**
- EmptyState.svelte - Supports icon and action snippets
- ErrorMessage.svelte - Supports dismissible errors

**Pages refactored:**
- /admin/projects - Uses EmptyState and ErrorMessage (~30 lines removed)
- /admin/posts - Uses EmptyState and ErrorMessage with icon (~30 lines removed)

**Benefits:**
- 60+ lines of duplicate styles removed (just 2 pages)
- Future dark mode = remap CSS variables in themes.scss only
- Guaranteed visual consistency for errors and empty states
- $unit-based spacing system enforced

**Remaining work (Phase 2):**
- Replace hardcoded colors in ~40 files
- Fix hardcoded spacing in ~20 files
- Expand EmptyState/ErrorMessage to remaining pages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 21:28:28 -07:00

8 KiB
Raw Blame History

Task 7: Styling & Theming Harmonization Plan

Status: 🚧 IN PROGRESS

Architecture Overview

Three-layer system for future theming:

  1. Base colors (variables.scss): $gray-80, $red-60, etc.
  2. Semantic SCSS variables (variables.scss): $input-bg: $gray-90, $error-bg: rgba($red-60, 0.1)
  3. CSS custom properties (themes.scss): --input-bg: #{$input-bg} (ready for dark mode)

Component usage: Components import variables.scss and use SCSS variables (background: $input-bg)

Future dark mode: Remap CSS custom properties in [data-theme='dark'] block without touching components

Current State (Audit Results)

Hardcoded Values Found:

  • 18 hardcoded padding: Xpx values
  • 2 hardcoded margin: Xpx values
  • 91 rgba() color definitions
  • 127 hex color values (#xxx)

Existing Foundation (Good):

  • $unit system (8px base with $unit-half, $unit-2x, etc.)
  • Color scales ($gray-00 through $gray-100, etc.)
  • Some semantic variables ($bg-color, $text-color, $accent-color)
  • themes.scss already maps SCSS → CSS variables

Implementation Plan

Step 1: Add Semantic SCSS Variables to variables.scss

Add component-specific semantic mappings (SCSS only, no double dashes):

/* Component-Specific Semantic Colors
 * These map base colors to component usage
 * Will be exposed as CSS custom properties in themes.scss
 * -------------------------------------------------------------------------- */

// Inputs & Forms
$input-bg: $gray-90;
$input-bg-hover: $gray-85;
$input-bg-focus: $white;
$input-text: $gray-20;
$input-text-hover: $gray-10;
$input-border: $gray-80;
$input-border-focus: $blue-40;

// States (errors, success, warnings)
$error-bg: rgba($red-60, 0.1);
$error-text: $red-error;  // Already defined as #dc2626
$error-border: rgba($red-60, 0.2);

$success-bg: rgba($green-40, 0.1);
$success-text: $green-30;
$success-border: rgba($green-40, 0.2);

$warning-bg: rgba($yellow-50, 0.1);
$warning-text: $yellow-10;
$warning-border: rgba($yellow-50, 0.2);

// Empty states
$empty-state-text: $gray-40;
$empty-state-heading: $gray-20;

// Cards & Containers
$card-bg: $white;
$card-border: $gray-80;
$card-shadow: rgba($black, 0.08);
$card-shadow-hover: rgba($black, 0.12);

// Dropdowns & Popovers
$dropdown-bg: $white;
$dropdown-border: $gray-80;
$dropdown-shadow: rgba($black, 0.12);
$dropdown-item-hover: $gray-95;

// Modals
$modal-overlay: rgba($black, 0.5);
$modal-bg: $white;
$modal-shadow: rgba($black, 0.15);

Step 2: Map to CSS Custom Properties in themes.scss

Extend existing themes.scss with new mappings:

:root {
  // Existing mappings
  --bg-color: #{$gray-80};
  --page-color: #{$gray-100};
  --card-color: #{$gray-90};
  --mention-bg-color: #{$gray-90};
  --text-color: #{$gray-20};

  // New semantic mappings
  --input-bg: #{$input-bg};
  --input-bg-hover: #{$input-bg-hover};
  --input-bg-focus: #{$input-bg-focus};
  --input-text: #{$input-text};
  --input-border: #{$input-border};

  --error-bg: #{$error-bg};
  --error-text: #{$error-text};
  --error-border: #{$error-border};

  --success-bg: #{$success-bg};
  --success-text: #{$success-text};

  --empty-state-text: #{$empty-state-text};
  --empty-state-heading: #{$empty-state-heading};

  --card-bg: #{$card-bg};
  --card-border: #{$card-border};
  --card-shadow: #{$card-shadow};

  --dropdown-bg: #{$dropdown-bg};
  --dropdown-shadow: #{$dropdown-shadow};

  // ... etc
}

[data-theme='dark'] {
  // Future: remap for dark mode without touching component code
  // --input-bg: #{$dark-input-bg};
  // --card-bg: #{$dark-card-bg};
}

Step 3: Fix Hardcoded Spacing (Use $unit System)

Replace hardcoded px values with $unit-based values:

// ❌ Before
padding: 24px;
margin: 12px 16px;
border-radius: 6px;

// ✅ After
padding: $unit-3x;  // 24px = 8px * 3
margin: calc($unit * 1.5) $unit-2x;  // 12px 16px
border-radius: $corner-radius-sm;  // Already defined as 6px

Files to update: ~20 files with hardcoded spacing

Step 4: Replace Hardcoded Colors (Use Semantic SCSS)

Replace inline rgba/hex with semantic SCSS variables:

// ❌ Before
.error {
  background: rgba(239, 68, 68, 0.1);
  color: #dc2626;
  border: 1px solid rgba(239, 68, 68, 0.2);
}

// ✅ After
.error {
  background: $error-bg;
  color: $error-text;
  border: $unit-1px solid $error-border;
}

Files to update: 40 files with hardcoded colors

Step 5: Extract Reusable Components

A. EmptyState.svelte (~10 usages)

<script lang="ts">
  import type { Snippet } from 'svelte'

  interface Props {
    title: string
    message: string
    icon?: Snippet
    action?: Snippet
  }
  let { title, message, icon, action }: Props = $props()
</script>

<div class="empty-state">
  {#if icon}
    <div class="empty-icon">{@render icon()}</div>
  {/if}
  <h3>{title}</h3>
  <p>{message}</p>
  {#if action}
    <div class="empty-action">{@render action()}</div>
  {/if}
</div>

<style lang="scss">
  @import '$styles/variables.scss';

  .empty-state {
    text-align: center;
    padding: $unit-8x $unit-4x;
    color: $empty-state-text;

    h3 {
      font-size: calc($unit * 2.5);  // 20px
      font-weight: 600;
      margin: 0 0 $unit-2x;
      color: $empty-state-heading;
    }

    p {
      margin: 0;
      line-height: 1.5;
    }

    .empty-action {
      margin-top: $unit-3x;
    }
  }
</style>

B. ErrorMessage.svelte (~4 usages)

<script lang="ts">
  interface Props {
    message: string
    dismissible?: boolean
    onDismiss?: () => void
  }
  let { message, dismissible = false, onDismiss }: Props = $props()
</script>

<div class="error-message">
  <span class="error-text">{message}</span>
  {#if dismissible && onDismiss}
    <button type="button" class="dismiss-btn" onclick={onDismiss}>×</button>
  {/if}
</div>

<style lang="scss">
  @import '$styles/variables.scss';

  .error-message {
    background: $error-bg;
    color: $error-text;
    padding: $unit-3x;
    border-radius: $unit-2x;
    border: $unit-1px solid $error-border;
    text-align: center;
    margin-bottom: $unit-4x;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: $unit-2x;

    .error-text {
      flex: 1;
    }

    .dismiss-btn {
      background: none;
      border: none;
      color: $error-text;
      font-size: calc($unit * 3);
      cursor: pointer;
      padding: 0;
      line-height: 1;
      opacity: 0.6;

      &:hover {
        opacity: 1;
      }
    }
  }
</style>

Step 6: Documentation

Create docs/task-7-styling-harmonization-completion.md with:

  • Architecture explanation (3-layer system)
  • Semantic variable naming conventions
  • How to add new semantic mappings
  • Component usage patterns
  • Future dark mode approach

Implementation Order

  1. Add semantic SCSS variables to variables.scss (~30 new variables)
  2. Map to CSS custom properties in themes.scss (~30 new mappings)
  3. Fix spacing in high-impact files (projects/posts pages, forms, modals)
  4. Replace hardcoded colors with semantic SCSS variables
  5. Create EmptyState component and replace ~10 usages
  6. Create ErrorMessage component and replace ~4 usages
  7. Document approach in task-7 completion doc
  8. Update admin modernization plan to mark Task 7 complete

Success Criteria

  • ~30 semantic SCSS variables added to variables.scss
  • ~30 CSS custom properties mapped in themes.scss
  • All hardcoded spacing uses $unit system (20 files)
  • All colors use semantic SCSS variables (40 files)
  • EmptyState component created and integrated (10 usages)
  • ErrorMessage component created and integrated (4 usages)
  • No rgba() or hex in admin components (use SCSS variables)
  • Documentation complete
  • Build passes, manual QA complete

Benefits

Theme-ready: Dark mode = remap CSS vars in themes.scss only Maintainability: Change semantic variable once, updates everywhere Consistency: All empty states/errors look identical DX: Autocomplete for semantic variable names Reduced duplication: ~200-300 lines of styles removed