feat(ui): add elevation system for overlays and modals

- Add 3-level elevation CSS variables to themes.css for all theme variants
- elevation-1: dropdowns, pills (16% in dark mode)
- elevation-2: modals, overlays (20% in dark mode)
- elevation-3: context menus, tooltips (24% in dark mode)
- Update ContextMenu to use elevation-3
- Update Modal to use elevation-2 with theme-aware borders
- Update QuickEventOverlay to use elevation-2 with matching footer
- Update PillTimeRangeSelector dropdown to use elevation-1
- Update ConfirmationModal and FormModal to use theme variables
- Remove shadows from overlay components for cleaner look

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2025-12-13 15:00:33 +01:00
parent 8eb295d491
commit bd89871f8b
7 changed files with 104 additions and 27 deletions

View file

@ -1070,12 +1070,9 @@
position: fixed;
width: 380px;
max-height: 450px;
background: hsl(var(--color-surface-elevated-2));
background: var(--color-surface-elevated-2);
border: 1px solid hsl(var(--color-border));
border-radius: var(--radius-lg);
box-shadow:
0 20px 60px hsl(var(--color-foreground) / 0.2),
0 4px 16px hsl(var(--color-foreground) / 0.1);
z-index: 99999 !important;
display: flex;
flex-direction: column;
@ -1320,14 +1317,17 @@
.overlay-actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 0.75rem;
padding: 1rem 1.25rem;
border-top: 1px solid hsl(var(--color-border));
background: hsl(var(--color-surface));
background: var(--color-surface-elevated-2);
flex-shrink: 0;
}
.overlay-actions .btn-primary {
flex: 1;
}
.btn-ghost {
padding: 0.5rem 1rem;
border: none;

View file

@ -33,6 +33,11 @@
--color-surface: var(--theme-surface);
--color-surface-hover: var(--theme-surface-hover);
--color-surface-elevated: var(--theme-surface-elevated);
/* Elevation system - progressively lighter surfaces for overlays */
--color-surface-elevated-1: var(--theme-surface-elevated-1);
--color-surface-elevated-2: var(--theme-surface-elevated-2);
--color-surface-elevated-3: var(--theme-surface-elevated-3);
--color-muted: var(--theme-muted);
--color-muted-foreground: var(--theme-muted-foreground);
--color-border: var(--theme-border);
@ -129,6 +134,10 @@
--theme-surface: hsl(0 0% 100%);
--theme-surface-hover: hsl(0 0% 96%);
--theme-surface-elevated: hsl(0 0% 100%);
/* Elevation system - progressively lighter surfaces for overlays */
--theme-surface-elevated-1: hsl(0 0% 100%);
--theme-surface-elevated-2: hsl(0 0% 100%);
--theme-surface-elevated-3: hsl(0 0% 100%);
--theme-muted: hsl(0 0% 90%);
--theme-muted-foreground: hsl(0 0% 40%);
--theme-border: hsl(0 0% 90%);
@ -192,6 +201,10 @@
--theme-surface: hsl(0 0% 12%);
--theme-surface-hover: hsl(0 0% 16%);
--theme-surface-elevated: hsl(0 0% 14%);
/* Elevation system - progressively lighter surfaces for overlays */
--theme-surface-elevated-1: hsl(0 0% 16%);
--theme-surface-elevated-2: hsl(0 0% 20%);
--theme-surface-elevated-3: hsl(0 0% 24%);
--theme-muted: hsl(0 0% 20%);
--theme-muted-foreground: hsl(0 0% 60%);
--theme-border: hsl(0 0% 26%);
@ -244,6 +257,9 @@
--theme-surface: hsl(0 0% 100%);
--theme-surface-hover: hsl(0 0% 96%);
--theme-surface-elevated: hsl(0 0% 100%);
--theme-surface-elevated-1: hsl(0 0% 100%);
--theme-surface-elevated-2: hsl(0 0% 100%);
--theme-surface-elevated-3: hsl(0 0% 100%);
--theme-muted: hsl(0 0% 90%);
--theme-muted-foreground: hsl(0 0% 40%);
--theme-border: hsl(0 0% 90%);
@ -275,6 +291,9 @@
--theme-surface: hsl(0 0% 12%);
--theme-surface-hover: hsl(0 0% 16%);
--theme-surface-elevated: hsl(0 0% 14%);
--theme-surface-elevated-1: hsl(0 0% 16%);
--theme-surface-elevated-2: hsl(0 0% 20%);
--theme-surface-elevated-3: hsl(0 0% 24%);
--theme-muted: hsl(0 0% 20%);
--theme-muted-foreground: hsl(0 0% 60%);
--theme-border: hsl(0 0% 26%);
@ -306,6 +325,9 @@
--theme-surface: hsl(0 0% 100%);
--theme-surface-hover: hsl(120 25% 95%);
--theme-surface-elevated: hsl(0 0% 100%);
--theme-surface-elevated-1: hsl(0 0% 100%);
--theme-surface-elevated-2: hsl(0 0% 100%);
--theme-surface-elevated-3: hsl(0 0% 100%);
--theme-muted: hsl(120 25% 95%);
--theme-muted-foreground: hsl(122 20% 40%);
--theme-border: hsl(120 25% 91%);
@ -337,6 +359,9 @@
--theme-surface: hsl(120 10% 12%);
--theme-surface-hover: hsl(120 10% 16%);
--theme-surface-elevated: hsl(120 10% 14%);
--theme-surface-elevated-1: hsl(120 10% 16%);
--theme-surface-elevated-2: hsl(120 10% 20%);
--theme-surface-elevated-3: hsl(120 10% 24%);
--theme-muted: hsl(120 10% 20%);
--theme-muted-foreground: hsl(120 10% 60%);
--theme-border: hsl(120 10% 25%);
@ -368,6 +393,9 @@
--theme-surface: hsl(0 0% 100%);
--theme-surface-hover: hsl(200 10% 94%);
--theme-surface-elevated: hsl(0 0% 100%);
--theme-surface-elevated-1: hsl(0 0% 100%);
--theme-surface-elevated-2: hsl(0 0% 100%);
--theme-surface-elevated-3: hsl(0 0% 100%);
--theme-muted: hsl(200 10% 94%);
--theme-muted-foreground: hsl(200 10% 45%);
--theme-border: hsl(200 10% 88%);
@ -399,6 +427,9 @@
--theme-surface: hsl(200 10% 12%);
--theme-surface-hover: hsl(200 10% 16%);
--theme-surface-elevated: hsl(200 10% 14%);
--theme-surface-elevated-1: hsl(200 10% 16%);
--theme-surface-elevated-2: hsl(200 10% 20%);
--theme-surface-elevated-3: hsl(200 10% 24%);
--theme-muted: hsl(200 10% 20%);
--theme-muted-foreground: hsl(200 10% 60%);
--theme-border: hsl(200 10% 25%);
@ -430,6 +461,9 @@
--theme-surface: hsl(0 0% 100%);
--theme-surface-hover: hsl(199 100% 94%);
--theme-surface-elevated: hsl(0 0% 100%);
--theme-surface-elevated-1: hsl(0 0% 100%);
--theme-surface-elevated-2: hsl(0 0% 100%);
--theme-surface-elevated-3: hsl(0 0% 100%);
--theme-muted: hsl(199 100% 94%);
--theme-muted-foreground: hsl(199 50% 40%);
--theme-border: hsl(199 71% 87%);
@ -461,6 +495,9 @@
--theme-surface: hsl(199 30% 12%);
--theme-surface-hover: hsl(199 30% 16%);
--theme-surface-elevated: hsl(199 30% 14%);
--theme-surface-elevated-1: hsl(199 30% 16%);
--theme-surface-elevated-2: hsl(199 30% 20%);
--theme-surface-elevated-3: hsl(199 30% 24%);
--theme-muted: hsl(199 20% 20%);
--theme-muted-foreground: hsl(199 20% 60%);
--theme-border: hsl(199 20% 25%);
@ -493,6 +530,9 @@
--theme-surface: hsl(0 0% 12%);
--theme-surface-hover: hsl(0 0% 16%);
--theme-surface-elevated: hsl(0 0% 14%);
--theme-surface-elevated-1: hsl(0 0% 16%);
--theme-surface-elevated-2: hsl(0 0% 20%);
--theme-surface-elevated-3: hsl(0 0% 24%);
--theme-muted: hsl(0 0% 20%);
--theme-muted-foreground: hsl(0 0% 60%);
--theme-border: hsl(0 0% 26%);

View file

@ -105,11 +105,18 @@
class="menu-item"
class:disabled={item.disabled}
class:danger={item.variant === 'danger'}
class:has-toggle={item.toggle}
onclick={() => handleItemClick(item)}
role="menuitem"
disabled={item.disabled}
>
{#if item.icon}
{#if item.toggle}
<span class="item-toggle" class:checked={item.checked}>
<span class="toggle-track">
<span class="toggle-thumb"></span>
</span>
</span>
{:else if item.icon}
<span class="item-icon">
<item.icon size={16} />
</span>
@ -131,12 +138,9 @@
min-width: 180px;
max-width: 280px;
padding: 0.375rem;
background: hsl(var(--color-surface));
background: var(--color-surface-elevated-3);
border: 1px solid hsl(var(--color-border));
border-radius: var(--radius-lg);
box-shadow:
0 10px 38px -10px rgba(0, 0, 0, 0.35),
0 10px 20px -15px rgba(0, 0, 0, 0.2);
}
.menu-item {
@ -205,4 +209,44 @@
margin: 0.375rem 0.5rem;
background: hsl(var(--color-border));
}
/* Toggle switch styles */
.menu-item.has-toggle {
gap: 0.5rem;
}
.item-toggle {
display: flex;
align-items: center;
flex-shrink: 0;
}
.toggle-track {
position: relative;
width: 28px;
height: 16px;
background: hsl(var(--color-muted));
border-radius: 8px;
transition: background-color 150ms ease;
}
.toggle-thumb {
position: absolute;
top: 2px;
left: 2px;
width: 12px;
height: 12px;
background: hsl(var(--color-background));
border-radius: 50%;
transition: transform 150ms ease;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.item-toggle.checked .toggle-track {
background: hsl(var(--color-primary));
}
.item-toggle.checked .toggle-thumb {
transform: translateX(12px);
}
</style>

View file

@ -389,13 +389,10 @@
}
.glass-dropdown {
background: hsl(var(--color-surface) / 0.95);
background: color-mix(in srgb, var(--color-surface-elevated-1) 95%, transparent);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid hsl(var(--color-border));
box-shadow:
0 20px 25px -5px hsl(var(--color-foreground) / 0.1),
0 10px 10px -5px hsl(var(--color-foreground) / 0.04);
}
.dropdown-header {

View file

@ -162,8 +162,8 @@
onclick={onClose}
disabled={loading}
class="w-full flex items-center justify-center gap-2 px-4 py-3 rounded-xl font-semibold text-sm
bg-black/5 dark:bg-white/10 text-foreground
hover:bg-black/10 dark:hover:bg-white/20 hover:shadow-md
bg-foreground/5 text-foreground
hover:bg-foreground/10 hover:shadow-md
transition-all duration-200 hover:-translate-y-0.5 active:translate-y-0
disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:translate-y-0"
>

View file

@ -88,10 +88,8 @@
<form onsubmit={handleSubmit} onkeydown={handleKeydown} class="space-y-4">
<!-- Error message -->
{#if error}
<div
class="rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 p-3"
>
<Text variant="small" class="text-red-600 dark:text-red-400">
<div class="rounded-lg bg-error/10 border border-error/30 p-3">
<Text variant="small" class="text-error">
{error}
</Text>
</div>

View file

@ -65,15 +65,13 @@
<div
class="relative flex max-h-[90vh] w-full {maxWidthClasses[
maxWidth
]} flex-col rounded-2xl border border-black/10 dark:border-white/20 bg-white/80 dark:bg-white/10 backdrop-blur-xl shadow-2xl"
]} flex-col rounded-2xl border border-border bg-surface-elevated-2 backdrop-blur-xl shadow-2xl"
onclick={(e) => e.stopPropagation()}
onkeydown={(e) => e.stopPropagation()}
>
{#if showHeader}
<!-- Header -->
<div
class="flex items-center justify-between p-6 border-b border-black/10 dark:border-white/10"
>
<div class="flex items-center justify-between p-6 border-b border-border">
<div class="flex items-center gap-3 flex-1">
{#if icon}
{@render icon()}
@ -86,7 +84,7 @@
</div>
<button
onclick={onClose}
class="p-2 rounded-xl bg-black/5 dark:bg-white/10 hover:bg-black/10 dark:hover:bg-white/20 transition-all duration-200 hover:scale-105"
class="p-2 rounded-xl bg-foreground/5 hover:bg-foreground/10 transition-all duration-200 hover:scale-105"
aria-label="Close"
>
<X size={18} weight="bold" class="text-muted-foreground" />
@ -101,7 +99,7 @@
<!-- Footer (optional) -->
{#if footer}
<div class="border-t border-black/10 dark:border-white/10 p-6">
<div class="border-t border-border p-6">
{@render footer()}
</div>
{/if}