Merge pull request 'feat: add sidebar and breadcrumb CSS exports' (#5) from feat/sidebar-breadcrumb-css into main

This commit was merged in pull request #5.
This commit is contained in:
2026-05-15 10:48:45 +00:00
5 changed files with 184 additions and 2 deletions

45
dist/breadcrumb.css vendored Normal file
View File

@@ -0,0 +1,45 @@
/* @bchen/ui — breadcrumb navigation
* Uses only tokens defined in dist/tokens.css.
* Companion to dist/tokens.css and dist/base.css. */
.breadcrumb {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.25rem;
padding: 0.75rem 2rem 0;
font-size: 13px;
color: var(--fg-muted);
}
.breadcrumb a {
color: var(--fg-muted);
text-decoration: none;
}
.breadcrumb a:hover {
color: var(--fg);
text-decoration: underline;
text-underline-offset: 2px;
}
.breadcrumb a:focus-visible {
color: var(--fg);
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: 2px;
}
.breadcrumb [aria-current="page"] {
color: var(--fg);
font-weight: 500;
text-decoration: none;
cursor: default;
}
.breadcrumb-sep {
color: var(--fg-muted);
user-select: none;
font-size: 11px;
opacity: 0.6;
}

105
dist/sidebar.css vendored Normal file
View File

@@ -0,0 +1,105 @@
/* @bchen/ui — sidebar layout
* Uses only tokens defined in dist/tokens.css.
* Companion to dist/tokens.css and dist/base.css. */
/* ── Desktop: two-column grid ─────────────────────────────────────────── */
@media (min-width: 768px) {
body {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 1fr;
}
.mobile-header { display: none; }
.sidebar {
display: flex;
flex-direction: column;
height: 100vh;
position: sticky;
top: 0;
border-right: var(--border);
background: var(--bg);
padding: 1rem 0;
overflow-y: auto;
}
.app-body {
min-width: 0;
display: flex;
flex-direction: column;
min-height: 100vh;
}
}
/* ── Mobile: sidebar hidden, top bar shown ─────────────────────────────── */
@media (max-width: 767px) {
.sidebar { display: none; }
.mobile-header { display: flex; }
.app-body {
display: flex;
flex-direction: column;
flex: 1;
}
}
/* ── Sidebar components ────────────────────────────────────────────────── */
.sidebar-brand {
padding: 0 1rem 1rem;
font-size: 13px;
font-weight: 600;
letter-spacing: 0.02em;
}
.sidebar-brand a {
text-decoration: none;
color: var(--fg);
}
.sidebar-nav {
display: flex;
flex-direction: column;
flex: 1;
gap: 0.125rem;
padding: 0 0.5rem;
}
.nav-link {
padding: 0.5rem 0.75rem;
border-radius: var(--radius);
color: var(--fg);
text-decoration: none;
font-size: 14px;
transition: background 0.1s ease;
}
.nav-link:hover {
background: var(--bg-elevated);
}
.nav-link:focus-visible {
background: var(--bg-elevated);
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: var(--radius);
}
.nav-link.nav-active {
font-weight: 600;
background: var(--bg-elevated);
color: var(--fg);
}
.sidebar-footer {
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.25rem;
border-top: 1px solid var(--border-color); /* subtle divider, lighter than --border */
margin-top: auto;
}
.sidebar-user {
font-size: 11px;
color: var(--fg-muted);
}

View File

@@ -8,7 +8,9 @@
], ],
"exports": { "exports": {
"./tokens.css": "./dist/tokens.css", "./tokens.css": "./dist/tokens.css",
"./base.css": "./dist/base.css" "./base.css": "./dist/base.css",
"./sidebar.css": "./dist/sidebar.css",
"./breadcrumb.css": "./dist/breadcrumb.css"
}, },
"scripts": { "scripts": {
"test": "vitest run" "test": "vitest run"

13
tests/breadcrumb.test.ts Normal file
View File

@@ -0,0 +1,13 @@
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'fs';
const css = readFileSync(new URL('../dist/breadcrumb.css', import.meta.url), 'utf-8');
describe('breadcrumb.css', () => {
it('is non-empty', () => { expect(css.length).toBeGreaterThan(0); });
it('references --fg', () => { expect(css).toContain('var(--fg)'); });
it('references --fg-muted', () => { expect(css).toContain('var(--fg-muted)'); });
it('styles aria-current="page"', () => { expect(css).toContain('[aria-current="page"]'); });
it('styles .breadcrumb-sep', () => { expect(css).toContain('.breadcrumb-sep'); });
it('styles .breadcrumb links', () => { expect(css).toContain('.breadcrumb a'); });
});

17
tests/sidebar.test.ts Normal file
View File

@@ -0,0 +1,17 @@
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'fs';
const css = readFileSync(new URL('../dist/sidebar.css', import.meta.url), 'utf-8');
describe('sidebar.css', () => {
it('is non-empty', () => { expect(css.length).toBeGreaterThan(0); });
it('references --fg', () => { expect(css).toContain('var(--fg)'); });
it('references --bg', () => { expect(css).toContain('var(--bg)'); });
it('references --border', () => { expect(css).toMatch(/var\(--border/); });
it('references --radius', () => { expect(css).toContain('var(--radius)'); });
it('references --fg-muted', () => { expect(css).toContain('var(--fg-muted)'); });
it('includes responsive breakpoint at 768px', () => { expect(css).toContain('768px'); });
it('hides sidebar on mobile', () => { expect(css).toMatch(/\.sidebar\s*\{\s*display\s*:\s*none/); });
it('styles .nav-link', () => { expect(css).toContain('.nav-link'); });
it('styles .nav-active', () => { expect(css).toContain('nav-active'); });
});