Skip to content

Commit e0db59e

Browse files
authored
Merge pull request #71 from CopilotKit/feat/mobile-responsive
feat: mobile responsive layout
2 parents df77f0a + 0d5433b commit e0db59e

File tree

5 files changed

+199
-24
lines changed

5 files changed

+199
-24
lines changed

apps/app/src/app/globals.css

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,12 @@ body, html {
308308

309309
@media (max-width: 768px) {
310310
.brand-shell { padding: 8px; }
311+
.brand-glass-container { border-radius: var(--radius-lg); }
312+
}
313+
314+
@media (max-width: 480px) {
315+
.brand-shell { padding: 4px; }
316+
.brand-glass-container { border-radius: var(--radius-md); }
311317
}
312318

313319
/* === Gradient Utilities === */
@@ -602,6 +608,35 @@ body, html {
602608
flex-shrink: 0;
603609
}
604610

611+
/* === Demo Gallery Drawer === */
612+
/* Mobile: full-screen, slides up from bottom */
613+
.demo-gallery-drawer {
614+
inset: 0;
615+
border-radius: 0;
616+
transform: translateY(100%);
617+
}
618+
.demo-gallery-drawer--open {
619+
transform: translateY(0);
620+
}
621+
622+
/* Desktop (sm+): right side panel, slides in from right */
623+
@media (min-width: 640px) {
624+
.demo-gallery-drawer {
625+
inset: auto;
626+
top: 0;
627+
right: 0;
628+
height: 100%;
629+
width: 480px;
630+
max-width: 90vw;
631+
border-left: 1px solid var(--color-border-glass, rgba(0,0,0,0.1));
632+
transform: translateX(100%);
633+
}
634+
.demo-gallery-drawer--open {
635+
transform: translateX(0);
636+
box-shadow: -8px 0 30px rgba(0,0,0,0.1);
637+
}
638+
}
639+
605640
/* === Flash Animation === */
606641
.content-flash {
607642
animation: content-flash 700ms ease-out;

apps/app/src/app/layout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export default function RootLayout({children}: Readonly<{ children: React.ReactN
1010
return (
1111
<html lang="en">
1212
<head>
13+
<meta name="viewport" content="width=device-width, initial-scale=1" />
1314
<title>Open Generative UI</title>
1415
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🪁</text></svg>" />
1516
<link rel="preconnect" href="https://fonts.googleapis.com" />

apps/app/src/app/page.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useGenerativeUIExamples, useExampleSuggestions } from "@/hooks";
66
import { ExplainerCardsPortal } from "@/components/explainer-cards";
77
import { DemoGallery, type DemoItem } from "@/components/demo-gallery";
88
import { GridIcon } from "@/components/demo-gallery/grid-icon";
9+
import { DesktopTipModal } from "@/components/desktop-tip-modal";
910
import { CopilotChat, useAgent, useCopilotKit } from "@copilotkit/react-core/v2";
1011

1112
export default function HomePage() {
@@ -51,25 +52,25 @@ export default function HomePage() {
5152
background: "linear-gradient(135deg, rgba(190,194,255,0.08) 0%, rgba(133,224,206,0.06) 100%)",
5253
}}
5354
>
54-
<div className="flex items-center justify-between gap-4 px-5 py-3">
55-
<div className="flex items-center gap-3 min-w-0 flex-1">
55+
<div className="flex items-center justify-between gap-2 sm:gap-4 px-3 sm:px-5 py-2.5 sm:py-3">
56+
<div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
5657
<div
57-
className="flex items-center justify-center shrink-0 w-9 h-9 rounded-lg"
58+
className="flex items-center justify-center shrink-0 w-8 h-8 sm:w-9 sm:h-9 rounded-lg"
5859
style={{
5960
background: "linear-gradient(135deg, rgba(190,194,255,0.15), rgba(133,224,206,0.12))",
6061
}}
6162
>
62-
<span className="text-xl leading-none" role="img" aria-label="CopilotKit">🪁</span>
63+
<span className="text-lg sm:text-xl leading-none" role="img" aria-label="CopilotKit">🪁</span>
6364
</div>
64-
<p className="text-base font-semibold m-0 leading-snug" style={{ color: "var(--text-primary)" }}>
65+
<p className="text-sm sm:text-base font-semibold m-0 leading-snug" style={{ color: "var(--text-primary)" }}>
6566
Open Generative UI
66-
<span className="font-normal" style={{ color: "var(--text-secondary)" }}> — powered by CopilotKit</span>
67+
<span className="hidden sm:inline font-normal" style={{ color: "var(--text-secondary)" }}> — powered by CopilotKit</span>
6768
</p>
6869
</div>
69-
<div className="flex items-center gap-2">
70+
<div className="flex items-center gap-1.5 sm:gap-2">
7071
<button
7172
onClick={() => setDemoDrawerOpen(true)}
72-
className="inline-flex items-center gap-1.5 px-3 py-2 rounded-full text-sm font-medium no-underline whitespace-nowrap transition-all duration-150 hover:-translate-y-px cursor-pointer"
73+
className="inline-flex items-center gap-1.5 px-2.5 sm:px-3 py-1.5 sm:py-2 rounded-full text-xs sm:text-sm font-medium no-underline whitespace-nowrap transition-all duration-150 hover:-translate-y-px cursor-pointer"
7374
style={{
7475
color: "var(--text-secondary)",
7576
border: "1px solid var(--color-border-glass, rgba(0,0,0,0.1))",
@@ -79,13 +80,13 @@ export default function HomePage() {
7980
title="Open Demo Gallery"
8081
>
8182
<GridIcon size={15} />
82-
Demos
83+
<span className="hidden sm:inline">Demos</span>
8384
</button>
8485
<a
8586
href="https://github.com/CopilotKit/OpenGenerativeUI"
8687
target="_blank"
8788
rel="noopener noreferrer"
88-
className="inline-flex items-center px-5 py-2 rounded-full text-sm font-semibold text-white no-underline whitespace-nowrap transition-all duration-150 hover:-translate-y-px"
89+
className="inline-flex items-center px-3 sm:px-5 py-1.5 sm:py-2 rounded-full text-xs sm:text-sm font-semibold text-white no-underline whitespace-nowrap transition-all duration-150 hover:-translate-y-px"
8990
style={{
9091
background: "linear-gradient(135deg, var(--color-lilac-dark), var(--color-mint-dark))",
9192
boxShadow: "0 1px 4px rgba(149,153,204,0.3)",
@@ -115,6 +116,8 @@ export default function HomePage() {
115116
onClose={() => setDemoDrawerOpen(false)}
116117
onTryDemo={handleTryDemo}
117118
/>
119+
120+
<DesktopTipModal />
118121
</>
119122
);
120123
}

apps/app/src/components/demo-gallery/index.tsx

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,14 @@ export function DemoGallery({ open, onClose, onTryDemo }: DemoGalleryProps) {
4242
/>
4343
)}
4444

45-
{/* Drawer panel */}
45+
{/* Drawer: full-screen bottom sheet on mobile, right side panel on sm+ */}
4646
<div
47-
className="fixed top-0 right-0 h-full z-50 flex flex-col transition-transform duration-300 ease-in-out"
48-
style={{
49-
width: 480,
50-
maxWidth: "90vw",
51-
transform: open ? "translateX(0)" : "translateX(100%)",
52-
background: "var(--surface-primary, #fff)",
53-
borderLeft: "1px solid var(--color-border-glass, rgba(0,0,0,0.1))",
54-
boxShadow: open ? "-8px 0 30px rgba(0,0,0,0.1)" : "none",
55-
}}
47+
className={`demo-gallery-drawer fixed z-50 flex flex-col transition-transform duration-300 ease-in-out ${open ? "demo-gallery-drawer--open" : ""}`}
48+
style={{ background: "var(--surface-primary, #fff)" }}
5649
>
5750
{/* Header */}
5851
<div
59-
className="flex items-center justify-between px-5 py-4 shrink-0"
52+
className="flex items-center justify-between px-4 sm:px-5 py-3 sm:py-4 shrink-0"
6053
style={{
6154
borderBottom:
6255
"1px solid var(--color-border-glass, rgba(0,0,0,0.1))",
@@ -82,7 +75,7 @@ export function DemoGallery({ open, onClose, onTryDemo }: DemoGalleryProps) {
8275
</div>
8376
<button
8477
onClick={onClose}
85-
className="p-1.5 rounded-lg transition-colors duration-150 cursor-pointer"
78+
className="p-2 -mr-1 rounded-lg transition-colors duration-150 cursor-pointer"
8679
style={{ color: "var(--text-secondary, #666)" }}
8780
>
8881
<svg
@@ -102,15 +95,15 @@ export function DemoGallery({ open, onClose, onTryDemo }: DemoGalleryProps) {
10295
</div>
10396

10497
{/* Category filter */}
105-
<div className="px-5 pt-4 pb-2 shrink-0">
98+
<div className="px-4 sm:px-5 pt-3 sm:pt-4 pb-2 shrink-0">
10699
<CategoryFilter
107100
selected={selectedCategory}
108101
onSelect={setSelectedCategory}
109102
/>
110103
</div>
111104

112105
{/* Card list */}
113-
<div className="flex-1 overflow-y-auto px-5 pb-5">
106+
<div className="flex-1 overflow-y-auto px-4 sm:px-5 pb-5 overscroll-contain">
114107
<div className="flex flex-col gap-3 pt-2">
115108
{filtered.map((demo) => (
116109
<DemoCard key={demo.id} demo={demo} onTry={onTryDemo} />
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
"use client";
2+
3+
import { useEffect, useSyncExternalStore } from "react";
4+
5+
const DISMISSED_KEY = "desktop-tip-dismissed";
6+
7+
let listeners: Array<() => void> = [];
8+
function emitChange() {
9+
listeners.forEach((l) => l());
10+
}
11+
12+
export function DesktopTipModal() {
13+
const notDismissed = useSyncExternalStore(
14+
(listener) => {
15+
listeners.push(listener);
16+
return () => {
17+
listeners = listeners.filter((l) => l !== listener);
18+
};
19+
},
20+
() => {
21+
try { return !sessionStorage.getItem(DISMISSED_KEY); }
22+
catch { return false; }
23+
},
24+
() => false,
25+
);
26+
27+
const dismiss = () => {
28+
try { sessionStorage.setItem(DISMISSED_KEY, "1"); }
29+
catch { /* privacy-restricted context */ }
30+
emitChange();
31+
};
32+
33+
useEffect(() => {
34+
if (!notDismissed) return;
35+
const handleKeyDown = (e: KeyboardEvent) => {
36+
if (e.key === "Escape") dismiss();
37+
};
38+
document.addEventListener("keydown", handleKeyDown);
39+
return () => document.removeEventListener("keydown", handleKeyDown);
40+
});
41+
42+
if (!notDismissed) return null;
43+
44+
return (
45+
<div
46+
onClick={dismiss}
47+
className="fixed inset-0 z-[9999] flex items-center justify-center p-6 sm:hidden"
48+
style={{
49+
background: "rgba(0,0,0,0.45)",
50+
backdropFilter: "blur(6px)",
51+
WebkitBackdropFilter: "blur(6px)",
52+
}}
53+
>
54+
<div
55+
onClick={(e) => e.stopPropagation()}
56+
style={{
57+
background: "var(--color-glass-dark, rgba(255,255,255,0.9))",
58+
backdropFilter: "blur(16px)",
59+
WebkitBackdropFilter: "blur(16px)",
60+
border: "1px solid var(--color-border-glass, rgba(255,255,255,0.3))",
61+
borderRadius: "var(--radius-xl, 16px)",
62+
boxShadow: "var(--shadow-glass, 0 4px 30px rgba(0,0,0,0.1))",
63+
padding: "32px 28px",
64+
maxWidth: 340,
65+
width: "100%",
66+
textAlign: "center",
67+
fontFamily: "var(--font-family)",
68+
}}
69+
>
70+
{/* Monitor icon */}
71+
<div
72+
style={{
73+
width: 56,
74+
height: 56,
75+
margin: "0 auto 20px",
76+
borderRadius: 14,
77+
background: "linear-gradient(135deg, rgba(190,194,255,0.15), rgba(133,224,206,0.12))",
78+
display: "flex",
79+
alignItems: "center",
80+
justifyContent: "center",
81+
}}
82+
>
83+
<svg
84+
width="28"
85+
height="28"
86+
viewBox="0 0 24 24"
87+
fill="none"
88+
stroke="var(--color-lilac-dark, #9599CC)"
89+
strokeWidth="1.8"
90+
strokeLinecap="round"
91+
strokeLinejoin="round"
92+
>
93+
<rect x="2" y="3" width="20" height="14" rx="2" />
94+
<path d="M8 21h8" />
95+
<path d="M12 17v4" />
96+
</svg>
97+
</div>
98+
99+
<h2
100+
style={{
101+
fontSize: 18,
102+
fontWeight: 700,
103+
color: "var(--text-primary, #374151)",
104+
margin: "0 0 8px",
105+
letterSpacing: "-0.01em",
106+
}}
107+
>
108+
Best viewed on desktop
109+
</h2>
110+
111+
<p
112+
style={{
113+
fontSize: 14,
114+
color: "var(--text-secondary, #6b7280)",
115+
margin: "0 0 24px",
116+
lineHeight: 1.5,
117+
}}
118+
>
119+
This experience includes interactive visualizations that work best on larger screens.
120+
</p>
121+
122+
<button
123+
onClick={dismiss}
124+
style={{
125+
width: "100%",
126+
padding: "10px 20px",
127+
borderRadius: 999,
128+
fontSize: 14,
129+
fontWeight: 600,
130+
fontFamily: "var(--font-family)",
131+
color: "#fff",
132+
background: "linear-gradient(135deg, var(--color-lilac-dark, #9599CC), var(--color-mint-dark, #1B936F))",
133+
border: "none",
134+
boxShadow: "0 1px 4px rgba(149,153,204,0.3)",
135+
cursor: "pointer",
136+
}}
137+
>
138+
Continue anyway
139+
</button>
140+
</div>
141+
</div>
142+
);
143+
}

0 commit comments

Comments
 (0)