summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/app.css113
-rw-r--r--src/features/workspaces/ui/HomeHeader.svelte69
-rw-r--r--src/features/workspaces/ui/HomeHeader.test.ts5
3 files changed, 152 insertions, 35 deletions
diff --git a/src/app.css b/src/app.css
index 438dbb5..3f3f8ae 100644
--- a/src/app.css
+++ b/src/app.css
@@ -66,6 +66,119 @@
}
}
+/* Navbar buttons — the navbar's (`.app-navbar`) raised button, in the
+ same depth language as the rest of the UI: at rest they're RAISED (a
+ gradient a step lighter than the bar, a light highlight inside the top edge,
+ a thin dark edge + small drop below, light primary-blue text), and when
+ PRESSED they flip to the inset well (dark inner shadow) — pushed into the
+ bar. `.navbar-btn-primary` is the main action: the same button, tinted with
+ the theme's primary colour. `.navbar-btn-icon` is the square icon-only form.
+ Self-contained (not DaisyUI .btn) so nothing fights the gradients; colours
+ all come from the theme. */
+@layer components {
+ .navbar-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.375rem;
+ height: 2rem;
+ padding-inline: 0.75rem;
+ border-radius: var(--radius-field);
+ font-size: 0.875rem;
+ font-weight: 600;
+ line-height: 1;
+ white-space: nowrap;
+ cursor: pointer;
+ user-select: none;
+ /* The theme's primary, lifted toward white: crisp on the grey gradient
+ (plain primary read dim, and an engraved text-shadow muddied it at this
+ size — the shadow stays on the big titles only). */
+ color: color-mix(in srgb, var(--color-primary) 75%, white);
+ border: 1px solid rgb(0 0 0 / 0.55);
+ background-image: linear-gradient(
+ to bottom,
+ color-mix(in srgb, var(--color-base-100) 72%, var(--color-base-content)),
+ color-mix(in srgb, var(--color-base-100) 90%, var(--color-base-content))
+ );
+ box-shadow:
+ inset 0 1px 0 rgb(255 255 255 / 0.12),
+ 0 1px 2px rgb(0 0 0 / 0.45);
+ transition:
+ filter 120ms ease-out,
+ box-shadow 120ms ease-out;
+ }
+ .navbar-btn:hover {
+ filter: brightness(1.12);
+ }
+ .navbar-btn:active,
+ .navbar-btn[aria-expanded="true"] {
+ /* Pressed → the inset well: pushed into the bar. */
+ filter: none;
+ background-image: none;
+ background-color: color-mix(in srgb, var(--color-base-100) 82%, black);
+ box-shadow:
+ inset 0 1px 5px rgb(0 0 0 / 0.7),
+ 0 1px 0 color-mix(in srgb, var(--color-base-content) 10%, transparent);
+ }
+ .navbar-btn:focus-visible {
+ outline: 2px solid var(--color-primary);
+ outline-offset: 2px;
+ }
+ .navbar-btn-icon {
+ width: 2rem;
+ padding-inline: 0;
+ }
+ /* Secondary navbar actions: visually just text — no background, border or
+ shadow. The light primary-blue colour (same as the buttons' text) and the
+ spacing mark them as clickable; hover brightens + underlines, press dims. */
+ .navbar-link {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.375rem;
+ height: 2rem;
+ min-width: 2rem; /* icon-only (compact) links keep a comfortable tap target */
+ padding-inline: 0.375rem;
+ border-radius: var(--radius-field);
+ font-size: 0.875rem;
+ font-weight: 600;
+ line-height: 1;
+ white-space: nowrap;
+ cursor: pointer;
+ user-select: none;
+ background: none;
+ border: 0;
+ color: color-mix(in srgb, var(--color-primary) 75%, white);
+ text-underline-offset: 4px;
+ transition: color 120ms ease-out;
+ }
+ .navbar-link:hover {
+ color: color-mix(in srgb, var(--color-primary) 45%, white);
+ text-decoration: underline;
+ }
+ .navbar-link:active {
+ color: var(--color-primary);
+ }
+ .navbar-link:focus-visible {
+ outline: 2px solid var(--color-primary);
+ outline-offset: 2px;
+ }
+ .navbar-btn-primary {
+ color: var(--color-primary-content);
+ background-image: linear-gradient(
+ to bottom,
+ color-mix(in srgb, var(--color-primary) 82%, white),
+ color-mix(in srgb, var(--color-primary) 88%, black)
+ );
+ box-shadow:
+ inset 0 1px 0 rgb(255 255 255 / 0.35),
+ 0 1px 2px rgb(0 0 0 / 0.45);
+ }
+ .navbar-btn-primary:active {
+ background-color: color-mix(in srgb, var(--color-primary) 80%, black);
+ }
+}
+
/* Raised — the INVERSE of `.inset-well`, for things that pop OUT of the page
(the home page's workspace tiles). Where the well has a dark shadow INSIDE
under its top edge and a light catch-line OUTSIDE below it, a raised surface
diff --git a/src/features/workspaces/ui/HomeHeader.svelte b/src/features/workspaces/ui/HomeHeader.svelte
index 8090489..5329b2d 100644
--- a/src/features/workspaces/ui/HomeHeader.svelte
+++ b/src/features/workspaces/ui/HomeHeader.svelte
@@ -1,4 +1,8 @@
<script lang="ts">
+ import ArchiveIcon from "phosphor-svelte/lib/ArchiveIcon";
+ import DotsThreeIcon from "phosphor-svelte/lib/DotsThreeIcon";
+ import PlusIcon from "phosphor-svelte/lib/PlusIcon";
+ import PuzzlePieceIcon from "phosphor-svelte/lib/PuzzlePieceIcon";
import { isValidSlug } from "../logic/route";
/** The create outcome the page reports back to the popover. */
@@ -78,68 +82,65 @@
</span>
</div>
- <div class="flex items-center gap-2" class:gap-1={compact}>
+ <!-- Navbar actions (app.css): "New workspace" is the one raised button
+ (`.navbar-btn`, primary-tinted); the secondary actions are plain text
+ links (`.navbar-link`) — the light-blue colour + spacing say clickable
+ without button chrome. Compact: labels collapse to Phosphor icons. -->
+ <div class="flex items-center gap-1" class:gap-3={!compact}>
<!-- New workspace -->
<button
type="button"
- class="btn btn-primary btn-sm"
- class:btn-circle={compact}
+ class="navbar-btn navbar-btn-primary"
+ class:navbar-btn-icon={compact}
aria-label="New workspace"
title="New workspace"
onclick={() => (popoverOpen ? closePopover() : openPopover())}
>
- {#if compact}
- <span aria-hidden="true" class="text-lg leading-none">+</span>
- {:else}
- New workspace
- {/if}
+ <PlusIcon size={16} weight="bold" aria-hidden="true" />
+ {#if !compact}<span>New workspace</span>{/if}
</button>
{#if withPlugins}
<button
type="button"
- class="btn btn-outline btn-sm"
- class:btn-circle={compact}
+ class="navbar-link"
aria-label="Plugins"
title="Open the plugin manager (load/unload plugins globally)"
onclick={onOpenPlugins}
>
- {#if compact}
- <span aria-hidden="true">⚡</span>
- {:else}
- Plugins
- {/if}
+ {#if compact}<PuzzlePieceIcon size={18} aria-hidden="true" />{:else}<span>Plugins</span>{/if}
</button>
{/if}
+ <!-- Archived workspaces -->
+ <button
+ type="button"
+ class="navbar-link"
+ aria-label="Archived workspaces"
+ title="View archived workspaces"
+ onclick={onOpenArchived}
+ >
+ {#if compact}<ArchiveIcon size={18} aria-hidden="true" />{:else}<span>Archived</span>{/if}
+ </button>
+
{#if withSort}
+ <!-- Overflow last, like a navbar's trailing menu. -->
<div class="dropdown dropdown-end">
- <button type="button" class="btn btn-ghost btn-sm" aria-label="More actions" title="More">
- ⋯
+ <button
+ type="button"
+ class="navbar-link"
+ aria-label="More actions"
+ title="More"
+ >
+ <DotsThreeIcon size={20} weight="bold" aria-hidden="true" />
</button>
- <ul class="dropdown-content menu z-50 w-52 rounded-box border border-base-content/15 bg-base-100 p-2 shadow-xl">
+ <ul class="dropdown-content menu z-50 mt-1 w-52 rounded-box border border-base-content/15 bg-base-100 p-2 shadow-xl">
<li>
<button type="button" onclick={onSort}>Reset arrangement by recent activity</button>
</li>
</ul>
</div>
{/if}
-
- <!-- Archived workspaces -->
- <button
- type="button"
- class="btn btn-ghost btn-sm"
- class:btn-circle={compact}
- aria-label="Archived workspaces"
- title="View archived workspaces"
- onclick={onOpenArchived}
- >
- {#if compact}
- <span aria-hidden="true">🗄</span>
- {:else}
- Archived
- {/if}
- </button>
</div>
</header>
diff --git a/src/features/workspaces/ui/HomeHeader.test.ts b/src/features/workspaces/ui/HomeHeader.test.ts
index 40b060f..4db8b04 100644
--- a/src/features/workspaces/ui/HomeHeader.test.ts
+++ b/src/features/workspaces/ui/HomeHeader.test.ts
@@ -86,6 +86,9 @@ describe("HomeHeader", () => {
it("compact mode collapses the primary button to an icon", () => {
const p = props();
render(HomeHeader, { props: { ...p, compact: true } });
- expect(screen.getByRole("button", { name: "New workspace" })).toHaveTextContent("+");
+ const button = screen.getByRole("button", { name: "New workspace" });
+ expect(button).toHaveClass("navbar-btn-icon");
+ expect(button).toHaveTextContent(""); // icon only — the label collapses
+ expect(button.querySelector("svg")).not.toBeNull();
});
});