Skip to content

Commit 95997bc

Browse files
committed
feat: enhance UI components and theming support
- Updated CSS to include base styles and improved theme variables for better customization. - Enhanced AppShell and Sidebar components with new state management for reduced transparency and OS detection. - Refined NavItem and Button components for improved accessibility and visual feedback. - Updated various UI components (e.g., Badge, Card, Dialog) with consistent styling and responsive design. - Integrated new props and state handling in Switch and Tabs components for better user interaction. - Improved Toaster component with dynamic theming based on HTML class changes.
1 parent 9a14d70 commit 95997bc

42 files changed

Lines changed: 3340 additions & 367 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/ui-design-report.md

Lines changed: 2542 additions & 0 deletions
Large diffs are not rendered by default.

src-tauri/src/config.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,26 @@ use anyhow::{Context, Result};
22
use serde::{Deserialize, Serialize};
33
use std::path::PathBuf;
44

5+
pub fn default_ui_font_size() -> u8 {
6+
14
7+
}
8+
59
/// Application configuration, persisted to ~/.misakax/config.yaml
610
#[derive(Debug, Clone, Serialize, Deserialize)]
711
#[serde(default)]
812
pub struct AppConfig {
913
/// UI language (en, zh_CN)
1014
pub language: String,
11-
/// Theme: "light", "dark", "system"
15+
/// Theme: "light", "dark", "dim", "system"
1216
pub theme: String,
1317
/// Accent color (hex)
1418
pub accent_color: String,
19+
/// Reduce translucent surfaces and backdrop blur for readability
20+
#[serde(default)]
21+
pub reduced_transparency: bool,
22+
/// Base UI font size in pixels (e.g. 14)
23+
#[serde(default = "default_ui_font_size")]
24+
pub ui_font_size: u8,
1525
/// Default LLM model
1626
pub default_model: String,
1727
/// Log level
@@ -28,6 +38,8 @@ impl Default for AppConfig {
2838
language: "en".to_string(),
2939
theme: "system".to_string(),
3040
accent_color: "#6366f1".to_string(),
41+
reduced_transparency: false,
42+
ui_font_size: 14,
3143
default_model: "claude-sonnet-4-20250514".to_string(),
3244
log_level: "info".to_string(),
3345
sidecar_port: 9527,

src/__tests__/settings-store.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ const mockConfig = {
2323
language: "en",
2424
theme: "system" as const,
2525
accent_color: "#6366f1",
26+
reduced_transparency: false,
27+
ui_font_size: 14,
2628
default_model: "claude-sonnet-4-20250514",
2729
log_level: "info",
2830
sidecar_port: 9527,

src/components/layout/AppShell.tsx

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,36 @@
1-
import { useEffect } from "react";
1+
import { useEffect, useState } from "react";
22
import { TooltipProvider } from "@/components/ui/tooltip";
33
import { Sidebar } from "./Sidebar";
44
import { TopBar } from "./TopBar";
55
import { ContentArea } from "./ContentArea";
66
import { useAppStore } from "@/stores";
77
import { useThemeStore } from "@/stores/theme-store";
8+
import { settingsIpc } from "@/lib/ipc";
9+
import { cn } from "@/lib/utils";
810

9-
const TABLET_BREAKPOINT = 1280;
11+
const NARROW_BREAKPOINT = 960;
1012

1113
export function AppShell() {
1214
const setSidebarCollapsed = useAppStore((s) => s.setSidebarCollapsed);
13-
const initialize = useThemeStore((s) => s.initialize);
14-
const cleanup = useThemeStore((s) => s.cleanup);
15+
const initializeTheme = useThemeStore((s) => s.initialize);
16+
const cleanupTheme = useThemeStore((s) => s.cleanup);
17+
const reducedTransparency = useThemeStore((s) => s.reducedTransparency);
18+
const [isWindows, setIsWindows] = useState(false);
1519

1620
useEffect(() => {
17-
initialize();
18-
return cleanup;
19-
}, [initialize, cleanup]);
21+
initializeTheme();
22+
return cleanupTheme;
23+
}, [initializeTheme, cleanupTheme]);
24+
25+
useEffect(() => {
26+
void settingsIpc.getSystemInfo().then((info) => {
27+
setIsWindows(info.os.toLowerCase() === "windows");
28+
});
29+
}, []);
2030

2131
useEffect(() => {
2232
const handleResize = () => {
23-
if (window.innerWidth < TABLET_BREAKPOINT) {
33+
if (window.innerWidth < NARROW_BREAKPOINT) {
2434
setSidebarCollapsed(true);
2535
}
2636
};
@@ -32,11 +42,17 @@ export function AppShell() {
3242

3343
return (
3444
<TooltipProvider delayDuration={300}>
35-
<div className="flex h-screen w-screen overflow-hidden bg-background">
45+
<div
46+
className={cn(
47+
"misaka-app flex h-screen w-screen overflow-hidden bg-background",
48+
reducedTransparency && "reduced-transparency",
49+
isWindows && "is-windows"
50+
)}
51+
>
3652
<Sidebar />
37-
<div className="flex flex-1 flex-col overflow-hidden">
53+
<div className="relative flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
3854
<TopBar />
39-
<main className="flex-1 overflow-auto">
55+
<main className="min-h-0 flex-1 overflow-auto bg-[color:var(--surface-messages)]">
4056
<ContentArea />
4157
</main>
4258
</div>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { LucideIcon } from "lucide-react";
2+
import { cn } from "@/lib/utils";
3+
4+
interface EmptyStateProps {
5+
icon: LucideIcon;
6+
title: string;
7+
description?: string;
8+
className?: string;
9+
}
10+
11+
export function EmptyState({
12+
icon: Icon,
13+
title,
14+
description,
15+
className,
16+
}: EmptyStateProps) {
17+
return (
18+
<div
19+
className={cn(
20+
"flex h-full min-h-[min(420px,70vh)] flex-col items-center justify-center gap-4 px-8 py-12 text-center",
21+
className
22+
)}
23+
>
24+
<div className="flex size-16 items-center justify-center rounded-[var(--radius-ui-xl)] border border-[color:var(--border-subtle)] bg-[color:var(--surface-card)] shadow-[inset_0_1px_0_rgba(255,255,255,0.05)]">
25+
<Icon className="size-8 text-muted-foreground/60" strokeWidth={1.15} />
26+
</div>
27+
<div className="max-w-md space-y-2">
28+
<h2 className="text-[1rem] font-semibold tracking-tight text-foreground">
29+
{title}
30+
</h2>
31+
{description ? (
32+
<p className="text-[0.8125rem] leading-relaxed text-muted-foreground">
33+
{description}
34+
</p>
35+
) : null}
36+
</div>
37+
</div>
38+
);
39+
}

src/components/layout/NavItem.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,24 @@ export function NavItem({ icon: Icon, label, route, badge }: NavItemProps) {
2424

2525
const button = (
2626
<button
27+
type="button"
2728
onClick={() => navigate(route)}
2829
aria-current={isActive ? "page" : undefined}
2930
className={cn(
30-
"relative flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors duration-150 ease-out",
31+
"relative flex w-full items-center gap-3 px-3 py-2 text-[0.8125rem] font-semibold leading-snug outline-none transition-[color,background-color,box-shadow,transform] duration-[var(--ds-dur-fast)] [transition-timing-function:var(--ds-ease-out)] focus-visible:ring-2 focus-visible:ring-[color:var(--border-accent)] focus-visible:ring-offset-2 focus-visible:ring-offset-transparent active:scale-[var(--ds-active-scale-sm)]",
32+
"rounded-[var(--radius-button)] border border-transparent",
3133
collapsed && "justify-center px-0",
3234
isActive
33-
? "bg-primary/10 text-primary"
34-
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
35+
? "border-[color:var(--border-accent-soft)] bg-[color:var(--surface-active)] text-foreground shadow-[inset_0_1px_0_rgba(255,255,255,0.06)]"
36+
: "text-muted-foreground hover:bg-[color:var(--surface-hover)] hover:text-foreground"
3537
)}
3638
>
37-
{isActive && (
38-
<span className="absolute left-0 top-1/2 h-5 w-[3px] -translate-y-1/2 rounded-r-full bg-primary" />
39-
)}
40-
<Icon className="h-5 w-5 shrink-0" />
39+
<Icon
40+
className={cn(
41+
"h-5 w-5 shrink-0",
42+
isActive && "text-[color:var(--text-accent-soft)]"
43+
)}
44+
/>
4145
{!collapsed && <span className="truncate">{label}</span>}
4246
{badge !== undefined && badge > 0 && (
4347
<BadgeIndicator count={badge} collapsed={collapsed} />
@@ -73,7 +77,7 @@ function BadgeIndicator({
7377
}
7478

7579
return (
76-
<span className="ml-auto flex h-5 min-w-5 items-center justify-center rounded-full bg-destructive px-1.5 text-[10px] font-semibold text-destructive-foreground">
80+
<span className="bg-destructive text-destructive-foreground ml-auto flex h-5 min-w-5 items-center justify-center rounded-full px-1.5 text-meta">
7781
{count > 99 ? "99+" : count}
7882
</span>
7983
);

src/components/layout/Sidebar.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ export function Sidebar() {
1313
return (
1414
<aside
1515
className={cn(
16-
"flex flex-col border-r border-border bg-sidebar transition-all duration-300 ease-out",
17-
collapsed ? "w-14" : "w-60"
16+
"relative flex min-h-0 shrink-0 flex-col border-r border-sidebar-border",
17+
"border-[color:var(--border-muted)] bg-sidebar",
18+
"backdrop-blur-xl backdrop-saturate-150 [transition:width_var(--ds-dur-slow)_var(--ds-ease-out)]",
19+
collapsed ? "w-14" : "w-[280px]"
1820
)}
1921
>
2022
<SidebarHeader />
2123

22-
<nav className="flex-1 space-y-1 px-2 py-2">
24+
<nav className="min-h-0 flex-1 space-y-1 overflow-y-auto px-2 py-2">
2325
<NavItem icon={MessageSquare} label={t("chat")} route={{ page: "chat" }} />
2426
<NavItem icon={Sparkles} label={t("skills")} route={{ page: "skills" }} />
2527
<NavItem icon={BookOpen} label={t("knowledge")} route={{ page: "knowledge" }} />

src/components/layout/SidebarFooter.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ export function SidebarFooter() {
1818

1919
const collapseButton = (
2020
<button
21+
type="button"
2122
onClick={toggleSidebar}
2223
className={cn(
23-
"flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-muted-foreground transition-colors duration-150 ease-out hover:bg-accent hover:text-accent-foreground",
24+
"flex w-full items-center gap-3 rounded-[var(--radius-button)] px-3 py-2 text-sm font-medium text-muted-foreground transition-[background,color] duration-[var(--ds-dur-fast)] ease-out hover:bg-[color:var(--surface-hover)] hover:text-foreground",
2425
collapsed && "justify-center px-0"
2526
)}
2627
>

src/components/layout/SidebarHeader.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ export function SidebarHeader() {
66
const collapsed = useAppStore((s) => s.sidebarCollapsed);
77

88
return (
9-
<div className="flex h-12 items-center gap-2 border-b border-border px-3">
10-
<Sparkles className="h-6 w-6 shrink-0 text-primary" />
9+
<div
10+
className={cn(
11+
"flex h-12 shrink-0 items-center gap-2 border-b border-[color:var(--border-muted)] px-3"
12+
)}
13+
>
14+
<Sparkles className="h-6 w-6 shrink-0 text-[color:var(--text-accent-soft)]" />
1115
<span
1216
className={cn(
13-
"text-base font-bold text-foreground transition-opacity duration-200",
17+
"text-sidebar-heading text-foreground transition-opacity duration-[var(--ds-dur-normal)] ease-out",
1418
collapsed ? "w-0 opacity-0" : "opacity-100"
1519
)}
1620
>

src/components/layout/TopBar.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@ export function TopBar() {
1717
const titleKey = PAGE_TITLES[route.page] || "nav:chat";
1818

1919
return (
20-
<header className="sticky top-0 z-10 flex h-12 items-center border-b border-border bg-background/80 px-6 backdrop-blur-md">
21-
<h1 className="text-sm font-semibold text-foreground">
22-
{t(titleKey)}
23-
</h1>
20+
<header className="sticky top-0 z-[3] flex h-12 shrink-0 items-center border-b border-border px-6 bg-[color:var(--surface-topbar)] backdrop-blur-xl backdrop-saturate-150">
21+
<h1 className="text-settings-section-title text-foreground">{t(titleKey)}</h1>
2422
<div className="flex-1" />
2523
</header>
2624
);

0 commit comments

Comments
 (0)