From 8e1af41cd3f69ac7d7729efeadd362ff1af5a069 Mon Sep 17 00:00:00 2001 From: Developer Date: Tue, 16 Jun 2026 12:23:53 +0000 Subject: [PATCH] feat(web/ui): expand design tokens and add primitive component styles --- apps/web/src/styles/global.css | 363 ++++++++++++++++++++++++++++++++- apps/web/src/styles/tokens.css | 41 ++++ 2 files changed, 402 insertions(+), 2 deletions(-) diff --git a/apps/web/src/styles/global.css b/apps/web/src/styles/global.css index f59da17..3d52af8 100644 --- a/apps/web/src/styles/global.css +++ b/apps/web/src/styles/global.css @@ -1198,9 +1198,368 @@ a .icon:last-child { @media (max-width: 640px) { .start-tool-fab { - bottom: 5rem; /* above mobile tab bar */ - right: 1rem; + bottom: calc(var(--mobile-nav-height) + var(--space-3)); + right: var(--space-4); width: 3rem; height: 3rem; } } +/* ─── Primitive Buttons ─── */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + min-height: var(--touch-target); + padding: var(--space-2) var(--space-4); + border: 1px solid transparent; + border-radius: var(--radius-md); + font-size: var(--font-size-sm); + font-weight: 500; + line-height: var(--line-height-tight); + background: var(--panel); + color: var(--ink); + cursor: pointer; + font-family: inherit; + transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease; +} + +.btn:focus-visible { + outline: 2px solid var(--brand); + outline-offset: 2px; +} + +.btn-primary { + background: var(--brand); + color: var(--primary-fg); + border-color: var(--brand); +} + +.btn-primary:hover { + background: var(--brand-strong); + border-color: var(--brand-strong); +} + +.btn-secondary { + background: var(--panel); + color: var(--ink); + border-color: var(--border); +} + +.btn-secondary:hover { + background: var(--bg); +} + +.btn-danger { + background: var(--danger); + color: white; + border-color: var(--danger); +} + +.btn-danger:hover { + background: color-mix(in srgb, var(--danger) 85%, black); + border-color: color-mix(in srgb, var(--danger) 85%, black); +} + +.btn-sm { + min-height: calc(var(--touch-target) - 8px); + padding: var(--space-1) var(--space-3); + font-size: var(--font-size-xs); +} + +.btn-icon { + padding: var(--space-2); + min-width: var(--touch-target); + min-height: var(--touch-target); +} + +.btn:disabled, +.btn[aria-disabled="true"] { + opacity: 0.55; + cursor: not-allowed; +} + +/* ─── Link Button ─── */ +.link-button { + display: inline-flex; + align-items: center; + gap: var(--space-1); + padding: 0; + background: transparent; + border: none; + color: var(--brand); + font-size: inherit; + font-weight: 500; + cursor: pointer; + text-decoration: none; +} + +.link-button:hover { + text-decoration: underline; +} + +.link-button:focus-visible { + outline: 2px solid var(--brand); + outline-offset: 2px; + border-radius: var(--radius-sm); +} + +/* ─── Form Inputs ─── */ +.form-input, +.form-textarea { + width: 100%; + min-height: var(--touch-target); + padding: var(--space-2) var(--space-3); + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--panel); + color: var(--ink); + font-size: var(--font-size-base); + line-height: var(--line-height-normal); + font-family: inherit; +} + +.form-input:focus, +.form-textarea:focus { + outline: none; + border-color: var(--brand); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--brand) 20%, transparent); +} + +.form-input::placeholder, +.form-textarea::placeholder { + color: var(--muted); +} + +.form-textarea { + min-height: 6rem; + resize: vertical; +} + +/* ─── Checkbox Label ─── */ +.checkbox-label { + display: inline-flex; + align-items: center; + gap: var(--space-2); + min-height: var(--touch-target); + font-size: var(--font-size-sm); + color: var(--ink); + cursor: pointer; +} + +.checkbox-label input[type="checkbox"] { + width: 1.125rem; + height: 1.125rem; + accent-color: var(--brand); +} + +.checkbox-label-compact { + min-height: auto; + font-size: var(--font-size-xs); +} + +/* ─── Hint ─── */ +.hint { + font-size: var(--font-size-sm); + color: var(--muted); + line-height: var(--line-height-normal); +} + +/* ─── Error Message ─── */ +.form-error, +.error-message { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-md); + background: var(--danger-light); + color: var(--danger); + font-size: var(--font-size-sm); + line-height: var(--line-height-normal); +} + +/* ─── Alert Banners ─── */ +.alert { + display: flex; + align-items: flex-start; + gap: var(--space-3); + padding: var(--space-3) var(--space-4); + border-radius: var(--radius-md); + border: 1px solid var(--border); + background: var(--panel); + color: var(--ink); + font-size: var(--font-size-sm); + line-height: var(--line-height-normal); +} + +.alert-error { + background: var(--danger-light); + border-color: color-mix(in srgb, var(--danger) 30%, transparent); + color: var(--danger); +} + +.alert-warning { + background: var(--warning-light); + border-color: color-mix(in srgb, var(--warning) 30%, transparent); + color: var(--warning); +} + +.alert-info { + background: var(--info-light); + border-color: color-mix(in srgb, var(--info) 30%, transparent); + color: var(--info); +} + +/* ─── Loading State ─── */ +.loading-state { + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + padding: var(--space-4); + color: var(--muted); + font-size: var(--font-size-sm); +} + +/* ─── Badges ─── */ +.badge { + display: inline-flex; + align-items: center; + gap: var(--space-1); + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-full); + font-size: var(--font-size-xs); + font-weight: 600; + line-height: var(--line-height-tight); + background: var(--bg); + color: var(--muted); + border: 1px solid var(--border); +} + +.badge-success { + background: var(--success-light); + color: var(--success); + border-color: color-mix(in srgb, var(--success) 30%, transparent); +} + +.badge-secondary { + background: var(--info-light); + color: var(--info); + border-color: color-mix(in srgb, var(--info) 30%, transparent); +} + +/* ─── Text Utilities ─── */ +.text-muted { + color: var(--muted); +} + +/* ─── Focus States ─── */ +.nav-item:focus-visible, +.mobile-nav-item:focus-visible, +.tab:focus-visible, +.tree-entry:focus-visible, +.btn:focus-visible, +.form-input:focus-visible, +.form-textarea:focus-visible, +.link-button:focus-visible, +.checkbox-label input:focus-visible { + outline: 2px solid var(--brand); + outline-offset: 2px; +} + +/* ─── Profile Avatar Section ─── */ +.profile-avatar-section { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-3); + padding: var(--space-4); + background: var(--panel); + border: 1px solid var(--border); + border-radius: var(--radius-md); +} + +.avatar-preview, +.avatar-placeholder { + width: 6rem; + height: 6rem; + border-radius: var(--radius-full); + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg); + border: 2px solid var(--border); +} + +.avatar-image { + width: 100%; + height: 100%; + object-fit: cover; +} + +.avatar-placeholder { + font-size: var(--font-size-2xl); + font-weight: 600; + color: var(--muted); +} + +/* ─── Mobile Back Button (detail views) ─── */ +.mobile-detail-back { + display: flex; + align-items: center; + justify-content: center; + width: var(--touch-target); + height: var(--touch-target); + padding: 0; + background: transparent; + border: none; + color: var(--ink); + cursor: pointer; + border-radius: var(--radius-md); +} + +.mobile-detail-back:hover { + background: var(--bg); +} + +.mobile-detail-back:focus-visible { + outline: 2px solid var(--brand); + outline-offset: 2px; +} + +/* Legacy text-error alias used in manifest editor and tool type editor */ +.text-error { + color: var(--danger); + font-size: var(--font-size-sm); +} + +/* Legacy button-secondary alias used in manifest editor and config profile editor */ +.button-secondary { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + min-height: var(--touch-target); + padding: var(--space-2) var(--space-4); + border: 1px solid var(--border); + border-radius: var(--radius-md); + font-size: var(--font-size-sm); + font-weight: 500; + line-height: var(--line-height-tight); + background: var(--panel); + color: var(--ink); + cursor: pointer; + font-family: inherit; + transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease; +} + +.button-secondary:hover { + background: var(--bg); +} + +.button-secondary:focus-visible { + outline: 2px solid var(--brand); + outline-offset: 2px; +} diff --git a/apps/web/src/styles/tokens.css b/apps/web/src/styles/tokens.css index 7eaf335..430e924 100644 --- a/apps/web/src/styles/tokens.css +++ b/apps/web/src/styles/tokens.css @@ -1,3 +1,18 @@ +/* + * Headquarter Design Tokens + * + * Naming convention: + * --space-* : 4px-based spacing scale (1 = 4px, 2 = 8px, ...) + * --font-size-* : fluid type scale from xs to 2xl + * --line-height-* : text leading scale + * --radius-* : corner radius scale + * --shadow-* : elevation shadows + * --bp-* : responsive breakpoints + * --touch-target : minimum interactive control size + * --sidebar-width : desktop sidebar width + * --mobile-nav-height : bottom nav including safe-area inset + */ + :root { color-scheme: light; font-family: "Inter", "IBM Plex Sans", "Segoe UI", sans-serif; @@ -27,8 +42,34 @@ --space-4: 1rem; --space-5: 1.5rem; --space-6: 2rem; + --space-7: 2.5rem; --space-8: 3rem; + --space-9: 5rem; --space-10: 4rem; + --space-12: 6rem; + + /* Border Radius */ + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 14px; + --radius-xl: 16px; + --radius-full: 999px; + + /* Line Height */ + --line-height-tight: 1.25; + --line-height-normal: 1.5; + --line-height-relaxed: 1.75; + + /* Shadow / Elevation */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08); + --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12); + --shadow-xl: 0 12px 40px rgba(0, 0, 0, 0.18); + + /* Layout & Touch */ + --touch-target: 44px; + --sidebar-width: 280px; + --mobile-nav-height: calc(64px + env(safe-area-inset-bottom, 0px)); /* Breakpoints */ --bp-sm: 480px;