Skip to content

Commit 4e1d6c3

Browse files
committed
feat: polish responsive application UI
1 parent 734981d commit 4e1d6c3

5 files changed

Lines changed: 87 additions & 49 deletions

File tree

‎frontend/src/components/layout.tsx‎

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {
1818
Keyboard,
1919
Scale,
2020
BarChart3,
21+
Menu,
22+
Sparkles,
2123
} from "lucide-react";
2224
import { Button } from "@/components/ui/button";
2325
import { Input } from "@/components/ui/input";
@@ -69,10 +71,10 @@ function SessionRow({
6971
<div
7072
onClick={onOpen}
7173
className={cn(
72-
"group relative flex cursor-pointer items-center gap-2 rounded-lg border-l-2 px-2 py-2 text-sm transition-colors",
74+
"group relative flex cursor-pointer items-center gap-2 rounded-xl border px-2.5 py-2.5 text-sm transition-all",
7375
active
74-
? "border-primary bg-muted text-foreground"
75-
: "border-transparent text-muted-foreground hover:border-border hover:bg-muted/60"
76+
? "border-primary/25 bg-primary/10 text-foreground shadow-sm"
77+
: "border-transparent text-muted-foreground hover:border-border/70 hover:bg-muted/60"
7678
)}
7779
>
7880
<MessageSquare className="size-3.5 shrink-0" />
@@ -532,6 +534,9 @@ export default function Layout() {
532534
const [shortcutsOpen, setShortcutsOpen] = useState(false);
533535
const [showOnboarding, setShowOnboarding] = useState(false);
534536
const [keybindings, setKeybindings] = useState<Record<KeybindingAction, string>>(DEFAULT_KEYBINDINGS);
537+
const [sidebarOpen, setSidebarOpen] = useState(false);
538+
539+
useEffect(() => setSidebarOpen(false), [sessionId]);
535540

536541
useEffect(() => {
537542
if (!hasApi) return;
@@ -628,19 +633,26 @@ export default function Layout() {
628633
}
629634

630635
return (
631-
<div className="flex h-svh">
632-
<aside className="flex w-64 shrink-0 flex-col border-r border-border bg-muted/40">
633-
<div className="flex items-center justify-between px-3 py-3">
634-
<span className="text-sm font-semibold tracking-tight">{t.appName}</span>
636+
<div className="flex h-svh overflow-hidden">
637+
{sidebarOpen && <button className="fixed inset-0 z-30 bg-black/35 backdrop-blur-sm md:hidden" onClick={() => setSidebarOpen(false)} aria-label="Close navigation" />}
638+
<aside className={cn(
639+
"surface-glass fixed inset-y-0 left-0 z-40 flex w-[18rem] shrink-0 flex-col border-r border-border/70 shadow-2xl transition-transform duration-200 md:static md:translate-x-0 md:shadow-none",
640+
sidebarOpen ? "translate-x-0" : "-translate-x-full"
641+
)}>
642+
<div className="flex items-center justify-between px-4 py-4">
643+
<div className="flex items-center gap-2.5">
644+
<span className="flex size-8 items-center justify-center rounded-xl bg-primary text-primary-foreground shadow-sm"><Sparkles className="size-4" /></span>
645+
<div><span className="block text-sm font-semibold tracking-tight">{t.appName}</span><span className="block text-[10px] font-medium uppercase tracking-[0.16em] text-muted-foreground">AI workspace</span></div>
646+
</div>
635647
<ThemeToggle />
636648
</div>
637649

638650
<div className="flex flex-col gap-1.5 px-3 pb-3">
639651
<Button
640652
onClick={() => handleNewChat()}
641653
size="sm"
642-
variant="outline"
643-
className="w-full justify-start gap-2"
654+
variant="default"
655+
className="h-9 w-full justify-start gap-2 rounded-xl shadow-sm"
644656
disabled={!hasApi}
645657
>
646658
<Plus className="size-4" />
@@ -694,14 +706,14 @@ export default function Layout() {
694706
</Button>
695707
</div>
696708

697-
<div className="relative px-3 pb-2">
709+
<div className="relative px-3 pb-3 pt-1">
698710
<Search className="pointer-events-none absolute top-1/2 left-5 size-3.5 -translate-y-1/2 text-muted-foreground" />
699711
<Input
700712
value={search}
701713
onChange={(e) => setSearch(e.target.value)}
702714
placeholder={t.searchChats}
703715
aria-label={t.searchChats}
704-
className="h-8 pl-7 text-xs"
716+
className="h-9 rounded-xl border-border/70 bg-background/70 pl-8 text-xs shadow-sm"
705717
/>
706718
</div>
707719

@@ -724,7 +736,7 @@ export default function Layout() {
724736
</div>
725737
)}
726738

727-
<ScrollArea className="flex-1 px-2">
739+
<ScrollArea className="flex-1 px-2.5">
728740
<div className="flex flex-col gap-0.5 pb-2">
729741
{projects.map((project) => (
730742
<ProjectGroup
@@ -757,7 +769,7 @@ export default function Layout() {
757769
</div>
758770
</ScrollArea>
759771

760-
<div className="flex border-t border-border p-2">
772+
<div className="flex border-t border-border/70 bg-background/30 p-2.5">
761773
<Button
762774
variant="ghost"
763775
size="sm"
@@ -780,7 +792,10 @@ export default function Layout() {
780792
</div>
781793
</aside>
782794

783-
<main className="flex-1 overflow-hidden">
795+
<main className="relative min-w-0 flex-1 overflow-hidden">
796+
<Button variant="outline" size="icon" className="surface-glass absolute left-3 top-3 z-30 shadow-md md:hidden" onClick={() => setSidebarOpen(true)} aria-label="Open navigation">
797+
<Menu className="size-4" />
798+
</Button>
784799
<Outlet />
785800
</main>
786801

‎frontend/src/components/settings-ui.tsx‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ export function SettingsSection({
1414
action?: React.ReactNode;
1515
}) {
1616
return (
17-
<section className={cn("mb-8 last:mb-0", className)}>
17+
<section className={cn("mb-10 last:mb-0", className)}>
1818
<div className="flex items-start justify-between gap-4">
1919
<div>
20-
<h2 className="text-sm font-semibold">{title}</h2>
21-
{description && <p className="mt-0.5 text-xs text-muted-foreground">{description}</p>}
20+
<h2 className="text-base font-semibold tracking-tight">{title}</h2>
21+
{description && <p className="mt-1 max-w-2xl text-xs leading-relaxed text-muted-foreground">{description}</p>}
2222
</div>
2323
{action}
2424
</div>
25-
<div className="mt-3 divide-y divide-border overflow-hidden rounded-lg border border-border bg-card">
25+
<div className="surface-glass shadow-soft mt-4 divide-y divide-border/70 overflow-hidden rounded-2xl border border-border/80">
2626
{children}
2727
</div>
2828
</section>
@@ -42,7 +42,7 @@ export function SettingsRow({
4242
}) {
4343
if (stacked) {
4444
return (
45-
<div className="flex flex-col gap-2 p-4">
45+
<div className="flex flex-col gap-2.5 p-5 transition-colors hover:bg-muted/20">
4646
{label && <p className="text-sm font-medium">{label}</p>}
4747
{description && <p className="-mt-1 text-xs text-muted-foreground">{description}</p>}
4848
{children}
@@ -51,14 +51,14 @@ export function SettingsRow({
5151
}
5252

5353
return (
54-
<div className="flex items-center justify-between gap-4 p-4">
54+
<div className="flex items-center justify-between gap-5 p-5 transition-colors hover:bg-muted/20 max-sm:flex-col max-sm:items-stretch">
5555
{label && (
5656
<div className="min-w-0">
5757
<p className="truncate text-sm font-medium">{label}</p>
58-
{description && <p className="mt-0.5 truncate text-xs text-muted-foreground">{description}</p>}
58+
{description && <p className="mt-1 text-xs leading-relaxed text-muted-foreground">{description}</p>}
5959
</div>
6060
)}
61-
<div className="flex shrink-0 items-center gap-2">{children}</div>
61+
<div className="flex shrink-0 items-center gap-2 max-sm:justify-start">{children}</div>
6262
</div>
6363
);
6464
}

‎frontend/src/index.css‎

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,39 @@
44
@custom-variant data-horizontal (&[data-orientation="horizontal"]);
55

66
:root {
7-
--radius: 0.625rem;
8-
--background: oklch(1 0 0);
7+
--radius: 0.75rem;
8+
--background: oklch(0.985 0.003 260);
99
--foreground: oklch(0.145 0 0);
10-
--card: oklch(1 0 0);
10+
--card: oklch(0.998 0.001 260);
1111
--card-foreground: oklch(0.145 0 0);
1212
--popover: oklch(1 0 0);
1313
--popover-foreground: oklch(0.145 0 0);
1414
--primary: oklch(0.205 0 0);
1515
--primary-foreground: oklch(0.985 0 0);
1616
--secondary: oklch(0.97 0 0);
1717
--secondary-foreground: oklch(0.205 0 0);
18-
--muted: oklch(0.97 0 0);
18+
--muted: oklch(0.965 0.006 260);
1919
--muted-foreground: oklch(0.556 0 0);
2020
--accent: oklch(0.97 0 0);
2121
--accent-foreground: oklch(0.205 0 0);
2222
--destructive: oklch(0.577 0.245 27.325);
23-
--border: oklch(0.922 0 0);
23+
--border: oklch(0.9 0.008 260);
2424
--input: oklch(0.922 0 0);
2525
--ring: oklch(0.708 0 0);
2626
}
2727

2828
.dark {
29-
--background: oklch(0.145 0 0);
29+
--background: oklch(0.125 0.012 260);
3030
--foreground: oklch(0.985 0 0);
31-
--card: oklch(0.205 0 0);
31+
--card: oklch(0.17 0.014 260);
3232
--card-foreground: oklch(0.985 0 0);
3333
--popover: oklch(0.205 0 0);
3434
--popover-foreground: oklch(0.985 0 0);
3535
--primary: oklch(0.922 0 0);
3636
--primary-foreground: oklch(0.205 0 0);
3737
--secondary: oklch(0.269 0 0);
3838
--secondary-foreground: oklch(0.985 0 0);
39-
--muted: oklch(0.269 0 0);
39+
--muted: oklch(0.225 0.014 260);
4040
--muted-foreground: oklch(0.708 0 0);
4141
--accent: oklch(0.269 0 0);
4242
--accent-foreground: oklch(0.985 0 0);
@@ -136,10 +136,28 @@
136136
@apply border-border outline-ring/50;
137137
}
138138
body {
139-
@apply bg-background text-foreground;
139+
@apply bg-background text-foreground antialiased;
140+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
141+
letter-spacing: -0.006em;
142+
background-image:
143+
radial-gradient(circle at 15% -10%, color-mix(in oklch, var(--primary) 8%, transparent), transparent 32rem),
144+
radial-gradient(circle at 100% 100%, color-mix(in oklch, var(--primary) 5%, transparent), transparent 36rem);
145+
background-attachment: fixed;
140146
}
147+
::selection { background: color-mix(in oklch, var(--primary) 25%, transparent); }
148+
button, a, input, textarea, select { -webkit-tap-highlight-color: transparent; }
141149
}
142150

151+
@layer utilities {
152+
.surface-glass {
153+
background: color-mix(in oklch, var(--card) 86%, transparent);
154+
backdrop-filter: blur(18px) saturate(1.15);
155+
}
156+
.shadow-soft { box-shadow: 0 1px 2px rgb(0 0 0 / 0.04), 0 12px 32px rgb(0 0 0 / 0.055); }
157+
}
158+
159+
* { scrollbar-width: thin; scrollbar-color: color-mix(in oklch, var(--muted-foreground) 35%, transparent) transparent; }
160+
143161
.prose-chat p {
144162
margin: 0 0 0.5em;
145163
}

‎frontend/src/pages/Chat.tsx‎

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,8 @@ const MessageBubble = memo(function MessageBubble({
270270
{(m.content || (isStreaming && isLastAssistant) || !m.toolCalls?.length) && (
271271
<div
272272
className={cn(
273-
"max-w-[75%] rounded-2xl px-4 py-2.5 text-sm leading-relaxed",
274-
m.role === "user" ? "bg-primary text-primary-foreground" : "bg-muted text-foreground"
273+
"max-w-[82%] rounded-2xl px-4 py-3 text-sm leading-relaxed shadow-sm sm:max-w-[75%]",
274+
m.role === "user" ? "rounded-br-md bg-primary text-primary-foreground" : "surface-glass rounded-bl-md border border-border/60 text-foreground"
275275
)}
276276
>
277277
{m.images && m.images.length > 0 && (
@@ -1278,8 +1278,8 @@ export default function Chat() {
12781278
: 0;
12791279

12801280
return (
1281-
<div className="flex h-full flex-col">
1282-
<div className="flex items-center gap-2 border-b border-border px-4 py-2.5">
1281+
<div className="flex h-full flex-col bg-background/35">
1282+
<div className="surface-glass flex min-h-14 flex-wrap items-center gap-2 border-b border-border/70 px-4 py-2.5 pl-14 shadow-sm md:pl-4">
12831283
{currentProject && (
12841284
<span className="rounded-md bg-muted px-2 py-1 text-xs font-medium text-muted-foreground">
12851285
{currentProject.name}
@@ -1805,11 +1805,16 @@ export default function Chat() {
18051805

18061806
<div className="relative flex-1 overflow-hidden">
18071807
<ScrollArea viewportRef={viewportRef} className="h-full">
1808-
<div className="mx-auto flex max-w-3xl flex-col gap-5 p-6 2xl:max-w-4xl">
1808+
<div className="mx-auto flex max-w-3xl flex-col gap-6 px-4 py-8 sm:px-6 2xl:max-w-4xl">
18091809
{messages.length === 0 && (
1810-
<div className="flex flex-col items-center gap-2 py-24 text-center text-muted-foreground">
1811-
<Sparkles className="size-6" />
1812-
<p className="text-sm">{t.startConversationWith(parsedModel?.modelId || "a model")}</p>
1810+
<div className="flex flex-col items-center gap-4 py-20 text-center">
1811+
<div className="flex size-14 items-center justify-center rounded-2xl bg-primary/10 text-primary shadow-sm"><Sparkles className="size-7" /></div>
1812+
<div><h2 className="text-xl font-semibold tracking-tight">What would you like to build?</h2><p className="mt-1 text-sm text-muted-foreground">{t.startConversationWith(parsedModel?.modelId || "a model")}</p></div>
1813+
<div className="mt-3 grid w-full max-w-xl gap-2 sm:grid-cols-3">
1814+
{["Analyze my GitHub repository", "Review and improve my code", "Plan a new application"].map((suggestion) => (
1815+
<button key={suggestion} onClick={() => setInput(suggestion)} className="surface-glass rounded-xl border border-border/70 p-3 text-left text-xs font-medium shadow-sm transition-all hover:-translate-y-0.5 hover:border-primary/30 hover:bg-primary/5 hover:shadow-md">{suggestion}</button>
1816+
))}
1817+
</div>
18131818
</div>
18141819
)}
18151820
{messages.map((m, i) => (
@@ -1958,7 +1963,7 @@ export default function Chat() {
19581963
)}
19591964
</div>
19601965

1961-
<div className="border-t border-border p-4">
1966+
<div className="surface-glass border-t border-border/70 p-3 shadow-[0_-12px_36px_rgb(0_0_0/0.04)] sm:p-4">
19621967
<div className="mx-auto max-w-3xl 2xl:max-w-4xl">
19631968
{(individualAttachments.length > 0 ||
19641969
folderGroups.length > 0 ||
@@ -2087,7 +2092,7 @@ export default function Chat() {
20872092
)}
20882093
{figmaError && <p className="mb-1.5 text-xs text-destructive">{figmaError}</p>}
20892094
{ocrError && <p className="mb-1.5 text-xs text-destructive">{ocrError}</p>}
2090-
<div className="flex items-end gap-2">
2095+
<div className="flex items-end gap-2 rounded-2xl border border-border/80 bg-background/75 p-2 shadow-soft focus-within:border-primary/35 focus-within:ring-3 focus-within:ring-primary/10">
20912096
<DropdownMenu>
20922097
<DropdownMenuTrigger
20932098
render={
@@ -2186,7 +2191,7 @@ export default function Chat() {
21862191
onChange={(e) => setInput(e.target.value)}
21872192
onKeyDown={handleKeyDown}
21882193
placeholder={t.sendMessage}
2189-
className="min-h-11 flex-1 resize-none rounded-xl"
2194+
className="min-h-11 flex-1 resize-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent"
21902195
disabled={isStreaming}
21912196
/>
21922197
{isStreaming ? (

‎frontend/src/pages/Settings.tsx‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -841,20 +841,20 @@ export default function Settings() {
841841
const exactMatchExists = searchResults.some((m) => m.name.toLowerCase() === search.trim().toLowerCase());
842842

843843
return (
844-
<ScrollArea className="h-full">
845-
<div className="mx-auto max-w-4xl p-6 pb-16 2xl:max-w-5xl">
846-
<div className="mb-6 flex items-center gap-2">
847-
<Settings2 className="size-5 text-muted-foreground" />
848-
<h1 className="text-lg font-semibold tracking-tight">{t.settings}</h1>
844+
<ScrollArea className="h-full bg-background/25">
845+
<div className="mx-auto max-w-5xl px-4 pb-20 pt-16 sm:px-6 md:pt-8 2xl:max-w-6xl">
846+
<div className="mb-8 flex items-center gap-3">
847+
<span className="flex size-10 items-center justify-center rounded-2xl bg-primary/10 text-primary shadow-sm"><Settings2 className="size-5" /></span>
848+
<div><h1 className="text-2xl font-semibold tracking-tight">{t.settings}</h1><p className="mt-0.5 text-xs text-muted-foreground">Configure models, integrations, automation, and your workspace.</p></div>
849849
</div>
850850

851851
<Tabs
852852
value={activeTab}
853853
onValueChange={(v) => setActiveTab(v as SettingsTab)}
854854
orientation="vertical"
855-
className="items-start gap-8"
855+
className="flex-col items-stretch gap-6 lg:flex-row lg:items-start lg:gap-10"
856856
>
857-
<TabsList variant="line" className="w-48 shrink-0">
857+
<TabsList variant="line" className="surface-glass sticky top-0 z-10 w-full shrink-0 flex-row overflow-x-auto rounded-xl border border-border/70 p-1 shadow-sm lg:top-6 lg:w-52 lg:flex-col lg:rounded-2xl lg:p-2">
858858
<TabsTrigger value="general" className="justify-start gap-2">
859859
<SlidersHorizontal className="size-4 shrink-0" /> {t.settingsTabGeneral}
860860
</TabsTrigger>

0 commit comments

Comments
 (0)