ui: refine workspace sidebar controls

This commit is contained in:
Keisuke Hirata 2026-07-07 06:19:08 +09:00
parent 384d162b74
commit 7f6a057e22
No known key found for this signature in database
4 changed files with 168 additions and 42 deletions

View File

@ -43,7 +43,7 @@ rustPlatform.buildRustPackage rec {
filter = sourceFilter;
};
cargoHash = "sha256-9e99NfbErWlmyZqXd7d5UaJ88gx6ENbHOubqYtnjXVg=";
cargoHash = "sha256-ICEIo7wR65s6CQCzCVa1KaEou7fpnaUk64zwokb92Fc=";
depsExtraArgs = {
# Older fetchCargoVendor utilities used crates.io's API download endpoint,

View File

@ -146,6 +146,10 @@
min-width: 0;
}
.workspace-layout.sidebar-collapsed {
grid-template-columns: max-content minmax(0, 1fr);
}
.shell {
display: flex;
flex-direction: column;
@ -181,6 +185,10 @@
overflow: visible;
}
.workspace-layout.sidebar-collapsed {
grid-template-columns: 1fr;
}
.shell {
overflow: visible;
padding: var(--space-5) var(--space-4);
@ -194,32 +202,44 @@
min-width: 0;
min-height: 0;
overflow-y: auto;
padding: var(--space-6) var(--space-5);
padding: var(--space-4) var(--space-3);
}
.sidebar-header {
display: grid;
gap: var(--space-2);
margin-bottom: var(--space-2);
min-width: 0;
}
.sidebar-title-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--space-4);
margin-bottom: var(--space-6);
align-items: center;
gap: var(--space-2);
min-width: 0;
}
.workspace-label {
display: grid;
gap: var(--space-1);
flex: 1 1 auto;
min-width: 0;
}
.workspace-sidebar h1 {
margin: 0;
.workspace-name {
display: block;
min-width: 0;
overflow: hidden;
border-radius: var(--radius-soft);
padding: 0.45rem 0.6rem;
color: var(--text-strong);
font-size: 1.05rem;
font-weight: 800;
line-height: 1.25;
text-overflow: ellipsis;
white-space: nowrap;
}
.workspace-status {
margin: 0;
margin: var(--space-1) 0 0;
color: var(--text-muted);
font-size: 0.78rem;
line-height: 1.35;
@ -231,17 +251,69 @@
color: var(--danger);
}
.settings-button {
.sidebar-actions-row {
display: flex;
justify-content: flex-end;
gap: var(--space-1);
}
.sidebar-icon-button,
.sidebar-collapse-button {
flex: 0 0 auto;
display: grid;
place-items: center;
width: 32px;
height: 32px;
border: 0;
border-radius: var(--radius-soft);
background: var(--bg-subtle);
color: var(--text-muted);
cursor: not-allowed;
text-decoration: none;
}
.sidebar-collapse-button {
border: 0;
background: transparent;
cursor: pointer;
}
.sidebar-icon {
width: 18px;
height: 18px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
.sidebar-icon-button:hover,
.sidebar-icon-button:focus-visible,
.sidebar-collapse-button:hover,
.sidebar-collapse-button:focus-visible {
background: var(--interactive-hover);
color: var(--text-muted);
}
.workspace-sidebar.collapsed {
overflow: hidden;
padding-inline: var(--space-2);
}
.workspace-sidebar.collapsed .sidebar-header {
margin-bottom: 0;
}
.workspace-sidebar.collapsed .sidebar-title-row,
.workspace-sidebar.collapsed .sidebar-actions-row {
justify-content: center;
}
.workspace-sidebar.collapsed .sidebar-actions-row {
align-items: center;
flex-direction: column;
}
.workspace-sidebar.collapsed .workspace-label {
display: none;
}
.sidebar-sections {

View File

@ -10,6 +10,8 @@
repositories?: RepositoryListResponse | null;
repositoriesError?: string | null;
currentPath?: string;
collapsed?: boolean;
onToggleCollapsed?: () => void;
};
let {
@ -17,18 +19,24 @@
workspaceError = null,
repositories = null,
repositoriesError = null,
currentPath = '/'
currentPath = '/',
collapsed = false,
onToggleCollapsed
}: Props = $props();
</script>
<aside class="workspace-sidebar" aria-label="Workspace navigation">
<aside
class:collapsed
class="workspace-sidebar"
aria-label="Workspace navigation"
>
<header class="sidebar-header">
<div class="sidebar-title-row">
<div class="workspace-label">
{#if workspace}
<p class="workspace-status">{workspace.workspace_id}</p>
<h1>{workspace.display_name}</h1>
<div class="workspace-name">{workspace.display_name}</div>
{:else}
<h1>Yoi workspace</h1>
<div class="workspace-name">Yoi workspace</div>
{#if workspaceError}
<p class="workspace-status error">Workspace summary unavailable.</p>
{:else}
@ -37,20 +45,63 @@
{/if}
</div>
<button
class="sidebar-collapse-button"
type="button"
aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
aria-expanded={!collapsed}
title={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
onclick={onToggleCollapsed}
>
{#if collapsed}
<svg class="sidebar-icon" aria-hidden="true" viewBox="0 0 24 24">
<path d="m6 17 5-5-5-5" />
<path d="m13 17 5-5-5-5" />
</svg>
{:else}
<svg class="sidebar-icon" aria-hidden="true" viewBox="0 0 24 24">
<path d="m11 17-5-5 5-5" />
<path d="m18 17-5-5 5-5" />
</svg>
{/if}
</button>
</div>
<div class="sidebar-actions-row">
<a
class="settings-button"
class="sidebar-icon-button"
href="/"
aria-label="Open workspace overview"
title="Workspace overview"
>
<svg class="sidebar-icon" aria-hidden="true" viewBox="0 0 24 24">
<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8" />
<path
d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
/>
</svg>
</a>
<a
class="sidebar-icon-button"
href="/settings"
aria-label="Open Settings / Admin"
title="Settings / Admin"
>
<svg class="sidebar-icon" aria-hidden="true" viewBox="0 0 24 24">
<path
d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"
/>
<circle cx="12" cy="12" r="4" />
</svg>
</a>
</div>
</header>
{#if !collapsed}
<nav class="sidebar-sections" aria-label="Workspace sections">
<RepositoriesNavSection {repositories} {repositoriesError} {currentPath} />
<ObjectivesNavSection {currentPath} />
<WorkersNavSection {currentPath} />
</nav>
{/if}
</aside>

View File

@ -5,15 +5,18 @@
import type { LayoutProps } from './$types';
let { data, children }: LayoutProps = $props();
let sidebarCollapsed = $state(false);
</script>
<div class="workspace-layout">
<div class:sidebar-collapsed={sidebarCollapsed} class="workspace-layout">
<WorkspaceSidebar
workspace={data.workspace}
workspaceError={data.workspaceError}
repositories={data.repositories}
repositoriesError={data.repositoriesError}
currentPath={page.url.pathname}
collapsed={sidebarCollapsed}
onToggleCollapsed={() => (sidebarCollapsed = !sidebarCollapsed)}
/>
<main class="shell">
{@render children()}