fix(ui): use dropdown-level shadow for popovers and floating menus
Replace inline modal-shadow styles on toasts, popups, and warning banners with a shared `surface-popover` utility that uses the lighter dropdown shadow instead of the heavier modal shadow. Apply the same `shadow-dropdown` swap to the deployments indicator pill and the server/deployment log floating menus. Remove now-unused console header and error-contact-link rules from app.css, and add a test asserting these surfaces consistently use the dropdown shadow.
This commit is contained in:
parent
9fbeb352a4
commit
0cdb98d969
10 changed files with 79 additions and 162 deletions
|
|
@ -563,14 +563,6 @@ .terminal-fullscreen-shell[data-console-theme="system"] {
|
|||
--console-theme-opacity: 1;
|
||||
}
|
||||
|
||||
html:not(.dark) .application-console-shell[data-console-theme="system"] .application-console-header {
|
||||
background: transparent;
|
||||
color: #52525b;
|
||||
border-color: rgb(0 0 0 / 0.1);
|
||||
backdrop-filter: none;
|
||||
}
|
||||
|
||||
html:not(.dark) .application-console-shell[data-console-theme="system"] .application-console-header [class*="text-white"],
|
||||
/* Session toolbar (selected-target trigger + label) sits over the white
|
||||
system console in light mode. Without this its text-white/* stayed white on
|
||||
white and the selected server was invisible (issue #11532). */
|
||||
|
|
@ -789,13 +781,6 @@ body.terminal-is-fullscreen .terminal-fullscreen-shell [data-terminal-mobile-too
|
|||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.application-console-header {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
background: rgb(0 0 0 / 0.24);
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
/* Session header band: an attached card-style header on top of the console,
|
||||
mirroring the page card header (elevated surface + hairline divider). The
|
||||
System theme uses the neutral card surface in both modes; colorful themes
|
||||
|
|
@ -1601,27 +1586,6 @@ .error-actions {
|
|||
margin-top: 1.75rem;
|
||||
}
|
||||
|
||||
.error-contact-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
margin-left: 0.25rem;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
color: #525252;
|
||||
text-underline-offset: 3px;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
|
||||
.dark .error-contact-link {
|
||||
color: var(--color-fg-dim);
|
||||
}
|
||||
|
||||
.error-contact-link:hover {
|
||||
color: var(--color-accent);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Layer card root: elevated shell with a hairline ring. overflow stays
|
||||
visible so floating panels (listbox, tooltips) can escape the card. */
|
||||
.application-settings-section {
|
||||
|
|
@ -2001,7 +1965,7 @@ .resource-heading-tabs-control-icon {
|
|||
margin-inline: 0.25rem;
|
||||
border-radius: 9999px;
|
||||
color: #737373;
|
||||
background: #ffffff;
|
||||
background: var(--coollabs-base);
|
||||
box-shadow:
|
||||
0 1px 2px rgba(0, 0, 0, 0.06),
|
||||
0 0 0 1px rgba(0, 0, 0, 0.08);
|
||||
|
|
@ -2075,20 +2039,6 @@ .resource-heading-overflow-items.is-measuring {
|
|||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.resource-heading-overflow-separator {
|
||||
width: 1px;
|
||||
align-self: stretch;
|
||||
margin: 0.25rem 0.25rem;
|
||||
background: color-mix(in srgb, var(--coollabs-line) 80%, transparent);
|
||||
}
|
||||
|
||||
.resource-heading-overflow.is-collapsed .resource-heading-overflow-separator {
|
||||
width: auto;
|
||||
height: 1px;
|
||||
align-self: auto;
|
||||
margin: 0.25rem 0.375rem;
|
||||
}
|
||||
|
||||
.resource-heading-overflow.is-collapsed .resource-heading-overflow-items > .button,
|
||||
.resource-heading-overflow.is-collapsed .resource-heading-overflow-items > a.button {
|
||||
width: 100%;
|
||||
|
|
@ -3403,7 +3353,7 @@ .dark .data-table-cell-dash {
|
|||
/* Shared logs viewer (deployment + runtime) — mobile-first toolbar
|
||||
Mobile stacks: search → meta → full-width actions (no side-by-side overlap). */
|
||||
.logs-viewer {
|
||||
background: #fff;
|
||||
background: var(--coollabs-base);
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
|
|
@ -3904,7 +3854,7 @@ .runtime-log-menu {
|
|||
.runtime-log-viewport {
|
||||
min-height: 12rem;
|
||||
border-radius: 0 0 8px 8px;
|
||||
background: #fafafa;
|
||||
background: var(--coollabs-elevated);
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
|
|
@ -3919,10 +3869,6 @@ .dark .runtime-log-viewport {
|
|||
color: var(--color-fg-dim);
|
||||
}
|
||||
|
||||
.env-table-detail {
|
||||
padding: 0.25rem 1rem 1.25rem;
|
||||
}
|
||||
|
||||
/* Small pill badges for table cells */
|
||||
.table-badge {
|
||||
display: inline-flex;
|
||||
|
|
@ -4073,93 +4019,6 @@ .chip-remove:hover {
|
|||
color: inherit;
|
||||
}
|
||||
|
||||
/* Detached pill options (segmented radio groups rendered as separate pills) */
|
||||
.option-pill-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.option-pill-group label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.option-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.375rem;
|
||||
height: 2rem;
|
||||
padding: 0 0.875rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--coollabs-line);
|
||||
background: var(--coollabs-elevated);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
color: #000000;
|
||||
transition: background-color 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.dark .option-pill {
|
||||
color: var(--color-fg);
|
||||
}
|
||||
|
||||
.option-pill:hover {
|
||||
background: var(--coollabs-recessed);
|
||||
}
|
||||
|
||||
.peer:checked + .option-pill {
|
||||
background: var(--coollabs-fill);
|
||||
border-color: var(--coollabs-line);
|
||||
}
|
||||
|
||||
.peer:disabled + .option-pill {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Label column + content rows inside a card body */
|
||||
.application-purpose-row {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
border-top: 1px solid var(--coollabs-fill);
|
||||
padding-top: 1.25rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.application-purpose-row {
|
||||
grid-template-columns: 13rem minmax(0, 1fr);
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.application-purpose-copy h4 {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.dark .application-purpose-copy h4 {
|
||||
color: var(--color-fg);
|
||||
}
|
||||
|
||||
.application-purpose-copy p {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
color: #71717a;
|
||||
}
|
||||
|
||||
.dark .application-purpose-copy p {
|
||||
color: var(--color-fg-dim);
|
||||
}
|
||||
|
||||
.application-details-card .application-purpose-row {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* In-card sub-headings (e.g. "Container labels", "Docker Compose") */
|
||||
.application-settings-form h4,
|
||||
.application-settings-form .application-settings-section-body h3 {
|
||||
|
|
@ -4316,7 +4175,7 @@ .command-palette {
|
|||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
background: var(--coollabs-elevated);
|
||||
box-shadow: 0 0 0 1px var(--coollabs-hairline), var(--shadow-modal);
|
||||
box-shadow: 0 0 0 1px var(--coollabs-hairline), var(--shadow-dropdown);
|
||||
}
|
||||
|
||||
.command-palette-header {
|
||||
|
|
|
|||
|
|
@ -370,6 +370,14 @@ @utility toast {
|
|||
@apply z-[1];
|
||||
}
|
||||
|
||||
/* Floating notice / popover surface: elevated fill, hairline ring, and the
|
||||
shared dropdown lift. Toasts, change-pending popovers and the deployments
|
||||
list share this so they match menus/dropdowns instead of the heavier modal. */
|
||||
@utility surface-popover {
|
||||
background: var(--coollabs-elevated);
|
||||
box-shadow: 0 0 0 1px var(--coollabs-line), var(--shadow-dropdown);
|
||||
}
|
||||
|
||||
@utility dz-button {
|
||||
@apply p-4 py-10 my-4 w-full font-bold bg-white border dark:border-coolgray-400 dark:text-white dark:bg-transparent dark:hover:bg-coolgray-400;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ class="flex h-8 items-center justify-center gap-1.5 rounded-lg px-2 text-amber-7
|
|||
</button>
|
||||
|
||||
<div x-show="open" x-cloak x-transition.opacity role="dialog"
|
||||
class="fixed top-14 left-1/2 z-[1100] w-[calc(100vw-2rem)] max-w-sm -translate-x-1/2 rounded-lg p-3 lg:absolute lg:top-full lg:right-0 lg:left-auto lg:mt-2 lg:translate-x-0"
|
||||
style="background: var(--coollabs-elevated); box-shadow: 0 0 0 1px var(--coollabs-line), var(--shadow-modal);">
|
||||
class="surface-popover fixed top-14 left-1/2 z-[1100] w-[calc(100vw-2rem)] max-w-sm -translate-x-1/2 rounded-lg p-3 lg:absolute lg:top-full lg:right-0 lg:left-auto lg:mt-2 lg:translate-x-0">
|
||||
<div class="flex items-start gap-2.5">
|
||||
<span
|
||||
class="flex size-7 shrink-0 items-center justify-center rounded-md bg-amber-100 text-amber-700 dark:bg-warning/10 dark:text-warning">
|
||||
|
|
|
|||
|
|
@ -53,8 +53,7 @@
|
|||
x-transition:leave-end="translate-y-3 opacity-0"
|
||||
class="fixed right-4 z-999 {{ $position === 'top-right' ? 'top-16' : 'bottom-4' }}">
|
||||
<template x-if="iconOnly">
|
||||
<button type="button" @click="restore()" aria-label="Restore warning" class="flex rounded-lg p-2"
|
||||
style="background: var(--coollabs-elevated); box-shadow: 0 0 0 1px var(--coollabs-line), var(--shadow-modal);">
|
||||
<button type="button" @click="restore()" aria-label="Restore warning" class="surface-popover flex rounded-lg p-2">
|
||||
@isset($icon)
|
||||
<span
|
||||
class="flex size-7 shrink-0 items-center justify-center rounded-md bg-amber-100 text-amber-700 dark:bg-warning/10 dark:text-warning">
|
||||
|
|
@ -65,12 +64,11 @@ class="flex size-7 shrink-0 items-center justify-center rounded-md bg-amber-100
|
|||
</template>
|
||||
|
||||
<template x-if="!iconOnly">
|
||||
<div class="relative flex items-start gap-2.5 rounded-lg p-3 pr-10"
|
||||
<div class="surface-popover relative flex items-start gap-2.5 rounded-lg p-3 pr-10"
|
||||
:class="compact
|
||||
? 'w-[calc(100vw-2rem)] max-w-sm cursor-pointer'
|
||||
: 'w-[calc(100vw-2rem)] max-w-sm'"
|
||||
@click="restore()"
|
||||
style="background: var(--coollabs-elevated); box-shadow: 0 0 0 1px var(--coollabs-line), var(--shadow-modal);">
|
||||
@click="restore()">
|
||||
@isset($icon)
|
||||
<div
|
||||
class="flex size-7 shrink-0 items-center justify-center rounded-md bg-amber-100 text-amber-700 dark:bg-warning/10 dark:text-warning">
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ class="flex h-8 items-center justify-center gap-1.5 rounded-lg px-2 text-amber-7
|
|||
</button>
|
||||
|
||||
<div x-show="open" x-cloak x-transition.opacity role="dialog"
|
||||
class="fixed top-14 left-1/2 z-[1100] w-[calc(100vw-2rem)] max-w-sm -translate-x-1/2 rounded-lg p-3 lg:absolute lg:top-full lg:right-0 lg:left-auto lg:mt-2 lg:translate-x-0"
|
||||
style="background: var(--coollabs-elevated); box-shadow: 0 0 0 1px var(--coollabs-line), var(--shadow-modal);">
|
||||
class="surface-popover fixed top-14 left-1/2 z-[1100] w-[calc(100vw-2rem)] max-w-sm -translate-x-1/2 rounded-lg p-3 lg:absolute lg:top-full lg:right-0 lg:left-auto lg:mt-2 lg:translate-x-0">
|
||||
<div class="flex items-start gap-2.5">
|
||||
<span
|
||||
class="flex size-7 shrink-0 items-center justify-center rounded-md bg-amber-100 text-amber-700 dark:bg-warning/10 dark:text-warning">
|
||||
|
|
|
|||
|
|
@ -103,8 +103,7 @@ class="fixed z-9999 flex w-[calc(100%-2rem)] gap-2.5 sm:max-w-[26rem]"
|
|||
x-transition:leave-start="translate-y-0 opacity-100"
|
||||
x-transition:leave-end="translate-y-1 opacity-0"
|
||||
@mouseenter="pauseToast(toast)" @mouseleave="resumeToast(toast)"
|
||||
class="relative flex w-full items-start rounded-lg group"
|
||||
style="background: var(--coollabs-elevated); box-shadow: 0 0 0 1px var(--coollabs-line), var(--shadow-modal);"
|
||||
class="surface-popover relative flex w-full items-start rounded-lg group"
|
||||
:class="{ 'p-3.5 pr-20': !toast.html, 'p-0': toast.html }">
|
||||
<template x-if="!toast.html">
|
||||
<div class="flex min-w-0 items-start gap-3">
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@
|
|||
x-transition:enter-start="translate-y-2" x-transition:enter-end="translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-150" x-transition:leave-start="translate-y-0"
|
||||
x-transition:leave-end="translate-y-2" x-cloak
|
||||
class="absolute bottom-full mb-2 w-[min(22rem,calc(100vw-2rem))] overflow-hidden rounded-xl"
|
||||
style="background: var(--coollabs-elevated); box-shadow: 0 0 0 1px var(--coollabs-line), var(--shadow-modal);">
|
||||
class="surface-popover absolute bottom-full mb-2 w-[min(22rem,calc(100vw-2rem))] overflow-hidden rounded-xl">
|
||||
<div class="max-h-96 space-y-1 overflow-y-auto p-2 scrollbar">
|
||||
@foreach ($this->deployments as $deployment)
|
||||
@php
|
||||
|
|
@ -71,8 +70,7 @@ class="shrink-0" />
|
|||
|
||||
{{-- Collapsed pill --}}
|
||||
<button type="button" @click="expanded = !expanded"
|
||||
class="flex items-center gap-2 rounded-xl border border-neutral-200 bg-white px-3.5 py-2 text-sm font-medium text-neutral-800 transition-colors hover:bg-neutral-50 dark:border-coolgray-300 dark:bg-surface dark:text-fg dark:hover:bg-raised"
|
||||
style="box-shadow: 0 0 0 1px var(--coollabs-hairline), var(--shadow-modal);"
|
||||
class="flex items-center gap-2 rounded-xl border border-neutral-200 bg-white px-3.5 py-2 text-sm font-medium text-neutral-800 shadow-dropdown transition-colors hover:bg-neutral-50 dark:border-coolgray-300 dark:bg-surface dark:text-fg dark:hover:bg-raised"
|
||||
:aria-expanded="expanded.toString()" aria-label="Active deployments">
|
||||
<svg class="loading-indicator size-3.5 shrink-0 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true">
|
||||
|
|
|
|||
|
|
@ -367,7 +367,7 @@ class="logs-viewer-btn">
|
|||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="transform opacity-100 scale-100"
|
||||
x-transition:leave-end="transform opacity-0 scale-95"
|
||||
class="absolute right-0 z-50 mt-2 w-max origin-top-right rounded-lg border border-neutral-200 bg-white p-1 shadow-modal focus:outline-none dark:border-white/[0.1] dark:bg-[#181818]">
|
||||
class="absolute right-0 z-50 mt-2 w-max origin-top-right rounded-lg border border-neutral-200 bg-white p-1 shadow-dropdown focus:outline-none dark:border-white/[0.1] dark:bg-[#181818]">
|
||||
<div>
|
||||
<button x-on:click="downloadLogs(); downloadMenuOpen = false"
|
||||
class="listbox-option text-neutral-700! hover:bg-neutral-100! dark:text-neutral-200! dark:hover:bg-white/[0.07]!">
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ class="flex size-9 shrink-0 items-center justify-center rounded-lg bg-neutral-10
|
|||
Link provider
|
||||
</button>
|
||||
<div x-cloak x-show="open" x-transition.origin.top.right
|
||||
class="absolute top-9 right-0 z-50 w-56 rounded-lg border border-neutral-200 bg-white p-1 shadow-modal dark:border-white/[0.1] dark:bg-raised">
|
||||
class="absolute top-9 right-0 z-50 w-56 rounded-lg border border-neutral-200 bg-white p-1 shadow-dropdown dark:border-white/[0.1] dark:bg-raised">
|
||||
@if (!$server->hetzner_server_id && $availableHetznerTokens->isNotEmpty())
|
||||
<x-server.provider-link-modal :server="$server" provider="hetzner"
|
||||
providerLabel="Hetzner" tokenModel="selectedHetznerTokenId"
|
||||
|
|
|
|||
57
tests/Unit/ShadowConsistencyTest.php
Normal file
57
tests/Unit/ShadowConsistencyTest.php
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
function cssSource(string $file): string
|
||||
{
|
||||
return file_get_contents(__DIR__.'/../../resources/css/'.$file);
|
||||
}
|
||||
|
||||
function bladeSource(string $path): string
|
||||
{
|
||||
return file_get_contents(__DIR__.'/../../resources/views/'.$path);
|
||||
}
|
||||
|
||||
it('gives the shared popover surface the dropdown lift, not the modal lift', function () {
|
||||
$utilities = cssSource('utilities.css');
|
||||
|
||||
expect($utilities)
|
||||
->toContain('@utility surface-popover')
|
||||
->toContain('var(--shadow-dropdown)');
|
||||
|
||||
// The surface-popover block itself must not fall back to the heavy modal shadow.
|
||||
preg_match('/@utility surface-popover\s*\{(.*?)\}/s', $utilities, $match);
|
||||
expect($match[1] ?? '')
|
||||
->toContain('var(--shadow-dropdown)')
|
||||
->not->toContain('var(--shadow-modal)');
|
||||
});
|
||||
|
||||
it('renders toasts and change-pending popovers with the shared dropdown-level surface', function (string $path) {
|
||||
$source = bladeSource($path);
|
||||
|
||||
expect($source)
|
||||
->toContain('surface-popover')
|
||||
->not->toContain('--shadow-modal');
|
||||
})->with([
|
||||
'components/toast.blade.php',
|
||||
'components/configuration-warning.blade.php',
|
||||
'components/proxy-configuration-warning.blade.php',
|
||||
'components/popup-small.blade.php',
|
||||
'livewire/deployments-indicator.blade.php',
|
||||
]);
|
||||
|
||||
it('uses the dropdown shadow on floating menus instead of the modal shadow', function (string $path) {
|
||||
expect(bladeSource($path))
|
||||
->toContain('shadow-dropdown')
|
||||
->not->toContain('shadow-modal');
|
||||
})->with([
|
||||
'livewire/server/show.blade.php',
|
||||
'livewire/project/application/deployment/show.blade.php',
|
||||
]);
|
||||
|
||||
it('gives the command palette the shared dropdown lift', function () {
|
||||
$app = cssSource('app.css');
|
||||
|
||||
preg_match('/\.command-palette\s*\{(.*?)\}/s', $app, $match);
|
||||
expect($match[1] ?? '')
|
||||
->toContain('var(--shadow-dropdown)')
|
||||
->not->toContain('var(--shadow-modal)');
|
||||
});
|
||||
Loading…
Reference in a new issue