diff --git a/DESIGN.md b/DESIGN.md index 4d09b5c562..4a8f187df2 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -35,8 +35,8 @@ change. - **One accent.** Coolify purple in both themes (`#6b16ed`, hover `#7317ff`, pressed `#5a12c7`, depth `#4a0fa3`, light text and icons `#a391ff`). Yellow is only for real warnings. The dark sidebar, including the logo cell, is - purple-black (`#120e1a`) with a faint brand wash. The page canvas stays - neutral. + purple-black (`#120e1a`) with a faint brand wash. The dark command palette + has a light purple tint. The page canvas stays neutral. - **Quiet text.** Sentence case. A clear text ladder: primary, dim, faint. No em dashes in UI copy. @@ -142,7 +142,8 @@ Rules for the tokens: `--ui-menu-bg`, never a translucent value, or the content behind shows through. - Keep the page canvas, cards, menus, and logs pure neutral (chroma 0). The - dark sidebar and the logo cell above it are the tinted surface. + dark sidebar, the logo cell above it, and the dark command palette are the + tinted surfaces. - **Dark lightness level.** The canvas is at 14.48% and the header panel at 19.13%. To change the level, move every dark surface and border token by the same @@ -232,7 +233,7 @@ Class names below are examples. Keep the values and structure. border-color: #8a4dff; background: linear-gradient(180deg, var(--ui-accent-top), var(--ui-accent-bottom)); --ui-depth-color: var(--ui-accent-edge); - box-shadow: 0 2px 0 var(--ui-accent-edge), 0 0 16px rgb(115 23 255 / 0.50); + box-shadow: 0 2px 0 var(--ui-accent-edge); /* no glow: the row clips it */ } ``` @@ -242,6 +243,17 @@ Class names below are examples. Keep the values and structure. hidden until the row is hovered, focused, or current. - Group rows under section labels (11px in light mode; 10.5px uppercase muted lavender in dark mode), and separate groups with a 1px divider. +- Do not put a glow (a blurred `box-shadow`) on a tile. The current row clips + it, so it shows as a dim square behind the tile. +- In the collapsed sidebar, Search is a tile like the other items: no row fill + and no frame around it. Only the expanded sidebar shows Search as an input. +- A menu that replaces another menu in place starts with a back row: a + centered label and a 12px chevron on the side of the direction. The back row + points left (`‹ Dashboard`). The row that opens the resource menu again from + the main menu points right (`adonisjs ›`) and shows the resource name. +- Keep the menu items in the same place on every page. When a page has no + resource menu, keep an empty row of the same height where the resource row + would be. ### Card (outer box, title row, well) @@ -357,6 +369,17 @@ Class names below are examples. Keep the values and structure. - Grid view tiles of a collection are cards without a well. In dark mode they are `--ui-panel`, the same as the header bar. +### View switcher + +- A segmented control (`.view-toggle`) on the right of a collection toolbar. +- Table and grid buttons are icon-only (30px square, 14px icon). The name is in + `title` and `aria-label`. +- Show only the view that is not current. In table view the control shows the + grid icon; in grid view, the list icon. Do not show a selected pill for the + current view. +- Text links stay labelled: the `Canvas` link with its "Try it" badge, and the + `List view` link that is the only way back from a canvas. + ### Status badge ```css @@ -388,6 +411,22 @@ Class names below are examples. Keep the values and structure. - Toasts are compact cards (max width 26rem) with a status icon tile, a title, an optional description, and a dismiss button. They close after 4 seconds and pause on hover. +- The toast edge blends into the toast background. In dark mode it is + `rgb(255 255 255 / 0.05)`, not the control border. +- The status icon tile is 3D, like a navigation tile: a 1px border and a 2px + bottom edge in the status color (`currentColor` at 22% and 30%). + +### Command palette + +- In dark mode the palette has a light brand tint: the shell is + `color-mix(in oklab, #6b16ed 10%, )` with a + `rgb(163 145 255 / 0.22)` ring, and the results body is 6% purple with a + `rgb(163 145 255 / 0.12)` ring. The search icon is `#a391ff`. The + placeholder is muted lavender. +- On the selected (focused) row in dark mode, the parts on a purple fill are + white: the quick-command and type badges, the icon tile glyph (for example + the plus of a create row), and the chevron. Purple text on a purple fill is + not readable. ### Logs and code @@ -468,8 +507,8 @@ The interface is compact and product-focused: - outline Reicon glyphs through ``; - the Coolify purple brand accent in both themes (`#6b16ed`); yellow only for real warnings, such as "Update available"; -- the active sidebar item is a purple gradient tile with a soft glow. The row - itself stays transparent; +- the active sidebar item is a purple gradient tile with a 2px depth edge and + no glow. The row itself stays transparent; - sentence-case labels and headings; - never use the em dash (`—`) in UI copy. Prefer a period, colon, comma, or ASCII hyphen (`-`) for empty cells and separators. @@ -596,10 +635,10 @@ value. The dark ladder is `--color-app` 10, `--color-panel` 20, `--coollabs-elevated` 22, `--coollabs-base` 28, `--coollabs-recessed` 34 (sRGB, r=g=b), which reads as distinct surfaces. -Temperature: the page canvas, cards, menus, listboxes, modals, logs, and the -command palette stay **pure neutral gray** (r=g=b). Do not tint those. The -dark sidebar (`--color-sidebar`, `#120e1a`) is the exception: it carries a -faint brand-purple wash. The light page canvas uses `bg-neutral-50` (not +Temperature: the page canvas, cards, menus, listboxes, modals, and logs stay +**pure neutral gray** (r=g=b). Do not tint those. The dark sidebar +(`--color-sidebar`, `#120e1a`) and the dark command palette are the exceptions: +they carry a faint brand-purple wash. The light page canvas uses `bg-neutral-50` (not `bg-gray-50`, which is faintly cool) so it matches the neutral cards and chrome. The only intentional color is the purple brand accent. Yellow is for warnings. - **Layer cards** use the outer box and inset well from section 5. @@ -608,7 +647,10 @@ The only intentional color is the purple brand accent. Yellow is for warnings. Nested pages keep the full trail (project, environment, resource). - **Pagination** is hidden when there is only one page (both arrows disabled). - **Project cards** spell out their counts: "1 environment · 35 resources". -- **The view switcher** shows every view. The current one is a purple pill. +- **The view switcher** (`x-canvas-view-toggle`, + `x-shared-variables.view-controls`) shows only the other view, as an icon + button: the grid icon in table view, the list icon in grid view. The labelled + `Canvas` link and the `List view` link on canvas pages keep their text. - **Tables and collection tiles** outside a layer card use the `rounded-xl border … dark:bg-white/[0.05]` shell plus the crisp `--coollabs-hairline` ring; in light they are `bg-white` with the ring and @@ -635,7 +677,13 @@ color. Row-hover states keep the lighter `dark:hover:bg-white/[0.025]`. - With the default tile items (not Appearance → Navigation items → Flat), each icon sits in a 30px 3D tile (16px icon, 6px padding, 1px border) in a 36px row. - With tile items, the row has no fill: the current item shows through its - accent tile (purple light, yellow dark) and its brighter label. + purple accent tile and its brighter label. The dark active tile has a 2px + depth edge and no glow, because the row (`overflow: hidden`) clips a glow + into a square. +- In the collapsed desktop sidebar, Search is a tile like the other items. The + row fill is removed in `@layer utilities` (the button has a layered + `!important` fill utility that an unlayered rule cannot beat), and the dark + tile uses the same colors as the other tiles. - With Flat items, active rows are rounded pills (`rounded-md`) with a solid neutral selected fill (`bg-black/5` light, `bg-white/6` dark) and no accent rail. Hover rows use the same radius. Do not use accent-tinted gradients on nav @@ -662,6 +710,16 @@ in-flow resource heading, or legacy `.navbar-main` tabs to one resource type. Keep route-derived active state in Blade/Livewire. Do not rely only on Alpine state because it can disappear after polling or a Livewire morph. +From `xl`, the resource menu replaces the main menu in the main sidebar +(`x-main-nav-menu`, `registerMainNavContext()`, `components/navbar.blade.php`). +Its back row (`‹ Dashboard`, `‹ Servers`, `‹ Service`) shows the menu one depth +lower, without a page load. The main menu then starts with a row that opens the +deepest resource menu again: the resource name with a right chevron +(`adonisjs ›`). Pass the name as `name` to `registerMainNavContext()` or +``; without a name the row says "Resource menu". Pages +with no resource menu keep an invisible row of the same height, so the main +menu items do not move between pages. + The global topbar owns the current resource identity, its compact status badges, configuration warnings (`#configuration-warning-hud-slot`), and the resource actions (`#resource-action-hud-slot`). If a resource is missing from @@ -1169,7 +1227,11 @@ top-anchored overlay: - create rows use a neutral plus tile that only picks up the accent when the row is focused; - type pills and quickcommand chips stay recessed; they tint with the accent - only on the focused row; + only on the focused row. In dark mode the text of the focused row's chips, + its icon tile glyph, and its chevron are white; +- in dark mode the shell, header, and results body carry the brand-purple + tint from Part 1 (rules after the dark `.view-toggle` rules in + `resources/css/app.css`). Light mode stays neutral; - neutral thin scrollbar inside the results body (not brand-colored); - create-resource modals opened from the palette reuse the standard `application-settings-section` layer-card shell. @@ -1186,7 +1248,10 @@ server → destination → project → environment create flow. Current toast behavior: - compact layered card, maximum width 26rem; -- Reicon status tile for success, info, warning, danger, or default; +- Reicon status tile for success, info, warning, danger, or default. The tile + (`.toast-icon`) is 3D: a border and a 2px bottom edge in the status color; +- a faint edge (`.toast-card`, dark `rgb(255 255 255 / 0.05)`) that blends into + the toast background; - title plus optional description; - dismiss and copy-details actions; - normally up to four stacked notifications, without evicting persistent notices; diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 48aa00a907..5ea7adb125 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -4626,11 +4626,12 @@ function wireNavigate(): string * Registers a resource menu that the main sidebar shows in place of its main menu on wide screens * (see components/navbar.blade.php). The menu teleports itself into #main-nav-context-{depth}. * Back on a menu shows the menu one depth lower, and Back at depth 0 shows the main menu. + * The main menu then shows a row (labelled with $name) that opens the deepest resource menu again. */ -function registerMainNavContext(int $depth, string $label, string $backTitle): void +function registerMainNavContext(int $depth, string $label, string $backTitle, ?string $name = null): void { $levels = request()->attributes->get('mainNavContext', []); - $levels[$depth] = ['label' => $label, 'backTitle' => $backTitle]; + $levels[$depth] = ['label' => $label, 'backTitle' => $backTitle, 'name' => $name]; ksort($levels); request()->attributes->set('mainNavContext', $levels); diff --git a/resources/css/app.css b/resources/css/app.css index e80d4503a9..f605a36497 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -5576,6 +5576,12 @@ html:where(:not([data-nav-items="flat"])) .menu-subitem { background-color: oklch(100% 0 0 / 0.07) !important; } + /* The collapsed desktop rail shows Search as a tile only, without the row fill. */ + html .app-sidebar nav.sidebar-collapsed .menu-search.menu-item, + html .app-sidebar nav.sidebar-collapsed .menu-search.menu-item:hover { + background-color: transparent !important; + } + @media (max-width: 1023.98px) { html:where(:not([data-nav-items="flat"])) nav.sidebar-collapsed .menu-search.menu-item { background-color: var(--color-neutral-100) !important; @@ -5599,7 +5605,7 @@ html.dark:where(:not([data-nav-items="flat"])) :is(.menu-item-active, .menu-subi border-color: #8a4dff !important; background: linear-gradient(180deg, var(--color-brand-hover) 0%, var(--color-brand-press) 100%) !important; --button-depth-color: var(--color-brand-depth) !important; - box-shadow: 0 2px 0 var(--color-brand-depth), 0 0 16px rgb(115 23 255 / 0.50) !important; + box-shadow: 0 2px 0 var(--color-brand-depth) !important; } html.dark:where(:not([data-nav-items="flat"])) :is(.menu-item-active, .menu-subitem-active):active .menu-item-icon:not(.text-error) { @@ -6040,6 +6046,19 @@ html.dark :is(.app-sidebar, .mobile-sidebar-sheet) .menu-search kbd { border-color: rgb(163 145 255 / 0.18) !important; } +/* The collapsed rail shows Search as a tile like the other items: no row frame (the fill is + removed in the utilities layer above), the dark tile. */ +html.dark .app-sidebar nav.sidebar-collapsed .menu-search { + box-shadow: none !important; +} + +html.dark:where(:not([data-nav-items="flat"])) .app-sidebar nav.sidebar-collapsed .menu-search.menu-item .menu-item-icon { + border-color: rgb(163 145 255 / 0.13); + background: rgb(107 22 237 / 0.07); + color: #b9adff; + --button-depth-color: rgb(255 255 255 / 0.05); +} + html.dark :is(.app-sidebar, .mobile-sidebar-sheet) nav:not(.sidebar-collapsed) .nav-section { font-family: var(--font-mono); font-size: 10.5px; @@ -6118,6 +6137,44 @@ html.dark .view-toggle .bg-coollabs { color: #d9d0ff; } +/* Toasts: a faint edge that blends into the toast background. */ +html.dark .toast-card { + box-shadow: 0 0 0 1px rgb(255 255 255 / 0.05), var(--shadow-dropdown); +} + +/* Toast icon tile: the 3D depth of the sidebar tiles, tinted with the icon color of the toast type. */ +.toast-icon { + border: 1px solid color-mix(in oklab, currentColor 22%, transparent); + box-shadow: 0 2px 0 color-mix(in oklab, currentColor 30%, transparent); +} + +/* Command palette: the purple brand tint of the dark shell. */ +html.dark .command-palette { + background: color-mix(in oklab, #6b16ed 10%, var(--coollabs-elevated)); + box-shadow: 0 0 0 1px rgb(163 145 255 / 0.22), var(--shadow-dropdown); +} + +html.dark .command-palette-header { + background: transparent; +} + +html.dark .command-palette-header-icon:not(.is-loading) { + color: var(--color-brand-light); +} + +html.dark .command-palette-input::placeholder { + color: oklch(72% 0.05 300); +} + +html.dark .command-palette-body { + background: color-mix(in oklab, #6b16ed 6%, var(--coollabs-base)); + box-shadow: 0 0 0 1px rgb(163 145 255 / 0.12); +} + +html.dark .command-palette-item:is(:focus, :focus-visible) :is(.command-palette-quickcommand, .command-palette-type-badge, .command-palette-item-chevron, .command-palette-item-icon) { + color: #fff; +} + html.dark .resource-glyph { border-color: rgb(163 145 255 / 0.20); background: rgb(107 22 237 / 0.10); diff --git a/resources/views/components/application/configuration-sidebar.blade.php b/resources/views/components/application/configuration-sidebar.blade.php index 29f56af40d..2b4c27bd05 100644 --- a/resources/views/components/application/configuration-sidebar.blade.php +++ b/resources/views/components/application/configuration-sidebar.blade.php @@ -254,7 +254,7 @@ @php // The main sidebar shows this menu on wide screens (see components/navbar.blade.php). - registerMainNavContext(0, 'Dashboard', 'Back to the main menu'); + registerMainNavContext(0, 'Dashboard', 'Back to the main menu', $application->name); @endphp {{-- Rendered twice: in the page (below xl), and teleported into the main sidebar (xl and up). diff --git a/resources/views/components/canvas-view-toggle.blade.php b/resources/views/components/canvas-view-toggle.blade.php index d092d1da29..37fc4cace1 100644 --- a/resources/views/components/canvas-view-toggle.blade.php +++ b/resources/views/components/canvas-view-toggle.blade.php @@ -6,8 +6,8 @@ // One list view (no table / grid choice): nothing to switch to on the list page. $singleList = count($views) === 1; @endphp -{{-- Table / grid / canvas switcher. On the list page, table and grid switch the - parent's Alpine viewMode; the canvas page shows one "List view" link back +{{-- Table / grid / canvas switcher. On the list page, an icon button switches the + parent's Alpine viewMode to the other view; the canvas page shows one "List view" link back (?view= makes the list the remembered view again, see RemembersCanvasView). Pages with one list view pass :views="['list' => [...]]". Without a canvasUrl it is only a table / grid switcher. --}} @@ -22,16 +22,13 @@ List view @else - {{-- Every view stays visible. The current one is the selected pill. --}} + {{-- Only the views that are not current show, as icons: the button switches to that view. --}} @unless ($singleList) @foreach ($views as $mode => [$label, $icon]) - @endforeach @endunless diff --git a/resources/views/components/database/configuration-sidebar.blade.php b/resources/views/components/database/configuration-sidebar.blade.php index 9b17308b85..e7977a0baf 100644 --- a/resources/views/components/database/configuration-sidebar.blade.php +++ b/resources/views/components/database/configuration-sidebar.blade.php @@ -64,7 +64,7 @@ @php // The main sidebar shows this menu on wide screens (see components/navbar.blade.php). - registerMainNavContext(0, 'Dashboard', 'Back to the main menu'); + registerMainNavContext(0, 'Dashboard', 'Back to the main menu', $database->name); @endphp {{-- Rendered twice: in the page (below xl), and teleported into the main sidebar (xl and up). diff --git a/resources/views/components/main-nav-menu.blade.php b/resources/views/components/main-nav-menu.blade.php index 3dc4687d2e..aeba3fdae1 100644 --- a/resources/views/components/main-nav-menu.blade.php +++ b/resources/views/components/main-nav-menu.blade.php @@ -1,7 +1,7 @@ -@props(['backLabel', 'backTitle' => 'Back to the main menu', 'depth' => 0, 'navOnly' => false]) +@props(['backLabel', 'backTitle' => 'Back to the main menu', 'depth' => 0, 'name' => null, 'navOnly' => false]) @php // The main sidebar replaces its menu with this one on wide screens (see components/navbar.blade.php). - registerMainNavContext($depth, $backLabel, $backTitle); + registerMainNavContext($depth, $backLabel, $backTitle, $name); @endphp {{-- A page menu, rendered twice: in the page (below xl, CSS hides it on xl), and teleported into the main sidebar (xl and up). The slot must not contain Livewire components (they would render twice). diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index 205029c7f3..a7d8efa5e1 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -178,6 +178,7 @@ // components/main-nav-menu.blade.php). Each one teleports itself into #main-nav-context-{depth}; // only the desktop sidebar has the slots. $navContext = $desktop ? request()->attributes->get('mainNavContext', []) : []; + $navContextName = collect($navContext)->pluck('name')->filter()->last() ?? 'Resource menu'; @endphp {{-- Wide screens on a resource page: the deepest resource menu replaces the main menu. Back shows the menu one depth lower, and then the main menu (or its submenu), without leaving the page. --}} @@ -255,6 +256,25 @@ @endforeach
  • $navContext]) :class="{ 'xl:hidden': navLevel !== -1 }">
      + @if ($navContext) + {{-- Wide screens: the main menu of a resource page opens the deepest resource menu again. + Other pages keep an empty row, so the menu items do not move between pages. --}} + + @elseif ($desktop) + + @endif @foreach ($navSections as $sectionLabel => $sectionItems) {{-- The first section needs no title. --}} @unless ($loop->first) diff --git a/resources/views/components/server/sidebar.blade.php b/resources/views/components/server/sidebar.blade.php index 663a0ef06f..f361cc2c3f 100644 --- a/resources/views/components/server/sidebar.blade.php +++ b/resources/views/components/server/sidebar.blade.php @@ -220,7 +220,7 @@ @php // The main sidebar shows this menu on wide screens (see components/navbar.blade.php). - registerMainNavContext(0, 'Servers', 'Back to the main menu'); + registerMainNavContext(0, 'Servers', 'Back to the main menu', $server->name); @endphp {{-- Rendered twice: in the page (below xl), and teleported into the main sidebar (xl and up). diff --git a/resources/views/components/service/configuration-sidebar.blade.php b/resources/views/components/service/configuration-sidebar.blade.php index 5682c8c282..8af07eaf9d 100644 --- a/resources/views/components/service/configuration-sidebar.blade.php +++ b/resources/views/components/service/configuration-sidebar.blade.php @@ -51,7 +51,7 @@ ->filter(fn ($items) => $items->isNotEmpty()); @endphp -