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>
59 lines
939 B
Svelte
59 lines
939 B
Svelte
<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;
|
|
|
|
.empty-icon {
|
|
font-size: calc($unit * 6); // 48px
|
|
margin-bottom: $unit-3x;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
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>
|