Files
heicode/cc-haha/desktop/src/components/layout/TitleBar.tsx
T
chenchenandClaude Opus 4.7 463f9be2db feat(client): premium UI overhaul — Iris design system + single-instance lock
Production-grade visual upgrade. Pulls the violet→blue gradient from
the H glass app icon up into a coherent design system: brand color,
focus rings, button gradients, sidebar accents, selection highlight,
shadow tints all derive from the same palette. Result: login surface
no longer clashes with the app icon, post-login UI feels like a
single product instead of a Tauri shell.

Changes
-------

src/theme/globals.css — Iris design tokens:
  - --color-primary #C5A572 (gold) → #7B6BE3 (violet, from logo)
  - --color-secondary slate → #6B7CE0 (blue from logo)
  - --color-tertiary gold → #9A8DEC (lighter violet)
  - Surface stack rebased to slight cool/blue undertone (#13131A,
    #181820, …) so violet accents pop without clashing
  - Borders softened: --color-border 0.08 → 0.06, separator 0.06 → 0.04
  - Radii loosened: sm 4 → 6, md 8 → 10, lg 12 → 14, xl 16 → 18
  - --gradient-btn-primary: 3-stop violet→primary→blue (replaces flat
    gold gradient); CTA glow shadow tinted violet
  - --gradient-brand-wordmark exposed as a token so wordmark / avatar
    gradient stays consistent across surfaces
  - --shadow-dropdown: 3-stop layered, dark stop tinted violet so
    floating menus feel of-a-piece with brand
  - --shadow-focus-ring: violet 55% so input focus is visible
  - --color-selection-bg: violet 32%
  - Sidebar item hover/active: rgba violet base instead of plain white
  - Sidebar panel backdrop: 2-stop radial (violet top-left + blue
    bottom-right) replacing plain gold radial
  - Type scale tokens: --text-xxs … --text-display
  - Body font-feature-settings: 'kern','liga','cv11' +
    text-rendering: optimizeLegibility for crisp Inter / Manrope
  - .tabular-nums utility for status counters

src/components/login/HeicodeLoginPage.tsx — login redesign:
  - Embeds /app-icon.png (the H glass logo) above wordmark
  - Drop-shadow violet-tinted so icon feels continuous with backdrop
  - Layered backdrop: violet halo top, blue glow bottom, faint SVG
    grain texture for tactile premium feel
  - Wordmark + tagline use --gradient-brand-wordmark token
  - Card max-width tightened to sm (was md) for taller, more focused
    composition (Linear / Vercel / Raycast desktop login style)
  - Chrome strip: blurred translucent surface

src/components/login/ProviderLoginCard.tsx:
  - Card gets faint violet→neutral surface gradient (lifts off backdrop
    without heavy 1px border)
  - Top hairline accent (centered violet line) at card top edge
  - CTA button uses --gradient-btn-primary; hover glow stronger;
    active scale 0.99 microinteraction
  - Removed hardcoded rgba(197,165,114,…) gold artifact

src/components/layout/TitleBar.tsx:
  - UserPill avatar gradient now uses --gradient-brand-wordmark token
    (was inline hardcoded violet) so future palette tweaks propagate

src-tauri (Cargo.toml + src/lib.rs):
  - Add tauri-plugin-single-instance and register it as the FIRST
    plugin in the Builder chain. Subsequent Heicode launches focus
    the existing window via show_main_window() instead of spawning
    a new sidecar / new server port. Fixes "every shortcut click
    starts a new instance" behavior.

Verification
------------
  - bunx tsc -b --noEmit (desktop)         clean
  - cargo check (src-tauri)                 clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:38:31 +08:00

140 lines
5.4 KiB
TypeScript

import { useUIStore } from '../../stores/uiStore'
import { useTranslation } from '../../i18n'
import { useHeicodeAuthStore } from '../../stores/heicodeAuthStore'
import { WindowControls } from './WindowControls'
const isMacHost = typeof navigator !== 'undefined'
&& /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent || '')
export function TitleBar() {
const { activeView, setActiveView } = useUIStore()
const t = useTranslation()
return (
<div
className="h-[var(--titlebar-height)] flex items-center border-b border-[var(--color-border)] bg-[var(--color-surface)] select-none"
data-tauri-drag-region
>
{/* macOS traffic-light spacer (native overlay buttons appear here) */}
{isMacHost ? <div className="w-[78px] flex-shrink-0" data-tauri-drag-region /> : <div className="w-3" data-tauri-drag-region />}
{/* Logo */}
<div className="flex items-center gap-2 mr-4" data-tauri-drag-region>
<span className="text-xs font-bold tracking-[0.22em] text-[var(--color-brand)] uppercase">Heicode</span>
</div>
{/* Navigation arrows */}
<div className="flex items-center gap-1 mr-4">
<button className="p-1 rounded-[var(--radius-md)] text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] transition-colors">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M15 18l-6-6 6-6" />
</svg>
</button>
<button className="p-1 rounded-[var(--radius-md)] text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] transition-colors">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M9 18l6-6-6-6" />
</svg>
</button>
</div>
{/* Center tabs */}
<div className="flex-1 flex items-center justify-center gap-1" data-tauri-drag-region>
<TabButton
active={activeView === 'code'}
onClick={() => setActiveView('code')}
icon="code"
>
{t('titlebar.code')}
</TabButton>
<TabButton
active={activeView === 'terminal'}
onClick={() => setActiveView('terminal')}
icon="terminal"
>
{t('titlebar.terminal')}
</TabButton>
<TabButton
active={activeView === 'history'}
onClick={() => setActiveView('history')}
icon="history"
>
{t('titlebar.history')}
</TabButton>
</div>
{/* Right: User pill + Settings + Window controls */}
<div className="flex items-center h-full">
<UserPill />
<button className="p-1.5 mr-2 rounded-[var(--radius-md)] text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] transition-colors">
<span className="material-symbols-outlined text-[18px]">settings</span>
</button>
<WindowControls />
</div>
</div>
)
}
// Tiny avatar + display name + role badge. Sourced from
// useHeicodeAuthStore.status.user, populated by /api/heicode-auth/status.
// Hidden until auth is loaded so the bar doesn't flicker on app boot.
function UserPill() {
const user = useHeicodeAuthStore((s) => s.status?.user ?? null)
if (!user) return null
const name = user.displayName?.trim() || user.email?.trim() || 'You'
const initial = (name.slice(0, 1) || '?').toUpperCase()
// Show role badge only when meaningful (admin/root); plain "user" is noise.
const roleBadge =
user.role && user.role !== 'user' && user.role !== '1'
? user.role
: null
return (
<div
className="mr-2 flex items-center gap-2 rounded-full border border-[var(--color-border)] bg-[var(--color-surface-container)] py-0.5 pl-1 pr-3 text-xs text-[var(--color-text-secondary)] hover:border-[var(--color-border-hi,rgba(255,255,255,0.14))]"
title={user.email || user.userId || ''}
>
<span
className="flex h-5 w-5 items-center justify-center rounded-full text-[10px] font-bold text-white"
style={{ backgroundImage: 'var(--gradient-brand-wordmark)' }}
>
{initial}
</span>
<span className="max-w-[120px] truncate font-medium text-[var(--color-text-primary)]">
{name}
</span>
{roleBadge ? (
<span className="rounded-[var(--radius-sm)] border border-[var(--color-border)] px-1 text-[9px] uppercase tracking-wider text-[var(--color-text-tertiary)]">
{roleBadge}
</span>
) : null}
</div>
)
}
function TabButton({
active,
onClick,
icon,
children,
}: {
active: boolean
onClick: () => void
icon: string
children: React.ReactNode
}) {
return (
<button
onClick={onClick}
className={`
flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium rounded-[var(--radius-md)] transition-colors duration-200
${active
? 'bg-[var(--color-surface-selected)] text-[var(--color-text-primary)]'
: 'text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]'
}
`}
>
<span className="material-symbols-outlined text-[16px]">{icon}</span>
{children}
</button>
)
}