Skip to content

Commit f2a91e6

Browse files
committed
xyz
1 parent 525ef2f commit f2a91e6

12 files changed

Lines changed: 515 additions & 223 deletions

File tree

src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,13 @@ function AppContent() {
8686
</div>
8787
}>
8888
<Routes location={location} key={location.pathname}>
89+
<Route path="/auth" element={<Auth />} />
90+
<Route path="/fonts/:id" element={<FontDetails />} />
8991
<Route path="/" element={<Layout />}>
9092
<Route index element={<Home />} />
9193
<Route path="fonts" element={<FontsCatalog />} />
9294
<Route path="fonts/:id" element={<FontDetails />} />
9395
<Route path="pairing" element={<FontPairing />} />
94-
<Route path="auth" element={<Auth />} />
9596
<Route path="upload" element={<Upload />} />
9697
<Route path="profile" element={<Profile />} />
9798
<Route path="members" element={<Members />} />

src/components/layout/Footer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const Footer: React.FC = memo(() => {
1111
}, []);
1212

1313
return (
14-
<footer className="hidden sm:block bg-[rgb(var(--color-background))] text-[rgb(var(--color-muted-foreground))] pb-20 font-sans selection:bg-[rgb(var(--color-foreground))] selection:text-[rgb(var(--color-background))] z-50">
14+
<footer className="hidden sm:block bg-[rgb(var(--color-background))] text-[rgb(var(--color-muted-foreground))] pb-20 font-sans selection:bg-[rgb(var(--color-foreground))] selection:text-[rgb(var(--color-background))]">
1515
<div className="max-w-480 mx-auto px-3">
1616

1717
<div className=''>

src/components/layout/Layout.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@ const Layout = memo(function Layout() {
1111
<div className="app-container flex flex-col min-h-screen px-4 md:px-8 bg-[rgb(var(--color-background))] text-[rgb(var(--color-foreground))] antialiased selection:bg-[rgb(var(--color-foreground))] selection:text-[rgb(var(--color-background))] overflow-x-hidden">
1212

1313
{/* Progressive Blur Effects */}
14-
<ProgressiveBlur position="top" height="100px" zIndex={45} />
14+
<ProgressiveBlur position="top" height="150px" zIndex={45} />
1515
<ProgressiveBlur position="bottom" height="120px" zIndex={45} />
1616

1717
{/* Global background noise or effects can go here */}
1818
<div className="fixed inset-0 xbg-[url('/noise.png')] opacity-[0.03] pointer-events-none mix-blend-overlay z-[60]" />
1919

2020
<Navbar />
21-
<main className="grow pt-24 pb-34 max-w-480 mx-auto w-full relative">
21+
<main className="grow pt-24 pb-34 max-w-480 mx-auto w-full">
2222
<AnimatePresence mode="wait">
2323
<motion.div
2424
key={location.pathname}
25-
initial={{ opacity: 0, y: 20 }}
26-
animate={{ opacity: 1, y: 0 }}
25+
initial={{ opacity: 1, y: 20 }}
26+
animate={{ opacity: 1, y: 20 }}
2727
exit={{ opacity: 0, y: -20 }}
2828
transition={{ duration: 0.4, ease: [0.22, 1, 0.36, 1] }}
2929
className="w-full"

src/components/layout/Navbar.tsx

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
"use client";
22

3-
import { Link, useLocation } from 'react-router-dom';
3+
import { Link, useLocation, useNavigate } from 'react-router-dom';
44
import { useAuth } from '../../contexts/AuthContext';
55
import { useState, useEffect, useMemo } from 'react';
66
import { cn } from '../../lib/utils';
7-
import { Type, Combine, Terminal, Users, Upload, User, Shield, Plus, Sun, Moon, Coffee, Sparkles, Cherry, Gem, Leaf, Flame, Cloud, SunDim, Waves, Anchor, Building, Joystick, ShoppingBag, Droplets } from 'lucide-react';
8-
import { motion } from 'framer-motion';
7+
import { Type, Combine, Terminal, Users, User, Shield, Plus, Sun, Moon, Coffee, Sparkles, Cherry, Gem, Leaf, Flame, Cloud, SunDim, Waves, Anchor, Building, Joystick, ShoppingBag, Droplets } from 'lucide-react';
8+
import { motion, AnimatePresence } from 'framer-motion';
99
import { useTheme } from '../../contexts/ThemeContext';
1010
import { Haptics, ImpactStyle } from '@capacitor/haptics';
1111
import { useUpdate } from '../../contexts/UpdateContext';
1212
import { Download } from 'lucide-react';
1313
import Logo from '/logo/logo.png'; // Assuming white version exists or we filter it
14+
import { CustomSerifA, CustomTerminal, CustomUpload, CustomUsers } from '../ui/CustomIcons';
1415

1516
export default function Navbar() {
1617
const { user, profile } = useAuth();
1718
const { theme, toggleTheme } = useTheme();
1819
const { hasUpdate, isDownloading, progress, openModal, closeModal, isModalOpen } = useUpdate();
1920
const [isScrolled, setIsScrolled] = useState(false);
2021
const location = useLocation();
22+
const navigate = useNavigate();
23+
24+
// Local state for instantaneous UI updates so animations don't lag
25+
const [activeTab, setActiveTab] = useState(location.pathname);
26+
27+
useEffect(() => {
28+
setActiveTab(location.pathname);
29+
}, [location.pathname]);
2130

2231
useEffect(() => {
2332
const handleScroll = () => {
@@ -37,13 +46,11 @@ export default function Navbar() {
3746
], [profile?.role]);
3847

3948
const mobileNavLinks = useMemo(() => [
40-
{ name: 'Fonts', path: '/fonts', icon: Type },
49+
{ name: 'Fonts', path: '/fonts', icon: CustomSerifA },
4150
{ name: 'Pairing', path: '/pairing', icon: Combine },
42-
{ name: 'CLI', path: '/cli', icon: Terminal, badge: 'NEW' },
43-
...(profile?.role === 'member' || profile?.role === 'admin'
44-
? [{ name: 'Members', path: '/members', icon: Users },
45-
{ name: 'Upload', path: '/upload', icon: Upload },]
46-
: []),
51+
{ name: 'CLI', path: '/cli', icon: CustomTerminal, badge: 'NEW' },
52+
{ name: 'Members', path: '/members', icon: CustomUsers },
53+
{ name: 'Upload', path: '/upload', icon: CustomUpload },
4754
], [profile?.role]);
4855

4956
return (
@@ -234,57 +241,65 @@ export default function Navbar() {
234241

235242
{/* Mobile Bottom Dock */}
236243
<div className="md:hidden fixed bottom-0 left-1/2 -translate-x-1/2 z-100">
237-
<nav className="flex items-center justify-evenly w-screen px-2 py-2.5 bg-[rgb(var(--color-background)/0.6)] backdrop-blur-3xl border-t border-[rgb(var(--color-border)/0.3)] shadow-2xl">
244+
<nav className="flex items-center justify-evenly w-screen px-2 py-2.5 bg-[rgb(var(--color-background)/0.6)] backdrop-blur-3xl border-t border-[rgb(var(--color-border)/0.3)]">
238245
{mobileNavLinks.map((link) => {
239246
const Icon = link.icon;
240-
const isActive = location.pathname === link.path;
247+
const isActive = activeTab === link.path;
248+
241249
return (
242-
<Link
250+
<motion.button
243251
key={link.path}
244-
to={link.path}
245-
onClick={async () => {
246-
await Haptics.selectionChanged()
252+
layout // Smoothly adjusts the surrounding buttons when width changes
253+
onClick={async (e) => {
254+
e.preventDefault();
255+
if (activeTab === link.path) return;
256+
257+
setActiveTab(link.path); // Instant UI feedback
258+
await Haptics.selectionChanged();
259+
260+
// Let the button layout animation finish (300ms) before hitting the React Router tree
261+
setTimeout(() => {
262+
navigate(link.path);
263+
}, 300);
247264
}}
248265
className={cn(
249-
"relative flex flex-col items-center justify-center h-12 px-4 rounded-full transition-all duration-300",
266+
"relative flex items-center justify-center max-h-12 py-2 rounded-full transition-all duration-300 ease-in-out",
250267
isActive
251-
? "text-[rgb(var(--color-foreground))]"
252-
: "text-[rgb(var(--color-muted-foreground))] hover:text-[rgb(var(--color-foreground))] hover:bg-[rgb(var(--color-foreground)/0.1)]"
268+
? "bg-[rgb(var(--color-foreground)/0.8)] text-[rgb(var(--color-background))] px-4 shadow-sm"
269+
: "bg-transparent text-[rgb(var(--color-foreground))] hover:text-[rgb(var(--color-foreground))] hover:bg-[rgb(var(--color-foreground)/0.1)] px-3"
253270
)}
254271
style={{ WebkitTapHighlightColor: 'transparent' }}
255272
>
256-
{/* Animated Background Pill */}
257-
{isActive && (
258-
<motion.div
259-
layoutId="active-nav-pill"
260-
className="absolute inset-0 bg-white/10 rounded-full"
261-
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
262-
/>
263-
)}
264-
265-
{/* Icon & Label */}
266-
<span className="relative z-10 flex items-center gap-2">
267-
<Icon size={20} strokeWidth={isActive ? 3.5 : 2} />
273+
{/* Icon */}
274+
<Icon className={cn(
275+
"shrink-0 w-4.5 h-4.5",
276+
isActive
277+
? "fill-[rgb(var(--color-background))]"
278+
: "text-[rgb(var(--color-foreground))]"
279+
)}/>
268280

281+
{/* Animating Text */}
282+
<AnimatePresence initial={false}>
269283
{isActive && (
270284
<motion.span
271-
initial={{ opacity: 0, width: 0 }}
272-
animate={{ opacity: 1, width: 'auto' }}
273-
exit={{ opacity: 0, width: 0 }}
274-
className="font-black text-md overflow-hidden whitespace-nowrap"
285+
initial={{ width: 0, opacity: 0, marginLeft: 0 }}
286+
animate={{ width: 'auto', opacity: 1, marginLeft: 8 }}
287+
exit={{ width: 0, opacity: 0, marginLeft: 0 }}
288+
transition={{ type: 'spring', bounce: 0, duration: 0.35 }}
289+
className="font-boldx font-rayton-brink text-[28px] mt-1 overflow-hidden whitespace-nowrap"
275290
>
276291
{link.name}
277292
</motion.span>
278293
)}
279-
</span>
294+
</AnimatePresence>
280295

281296
{link.badge && (
282-
<span className="absolute top-1 right-1 flex h-2 w-2">
297+
<span className="absolute top-0 right-0 flex h-2.5 w-2.5 transform translate-x-1/2 -translate-y-1/2">
283298
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[rgb(var(--color-success)/0.75)]"></span>
284-
<span className="relative inline-flex rounded-full h-2 w-2 bg-[rgb(var(--color-success))]"></span>
299+
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-[rgb(var(--color-success))]"></span>
285300
</span>
286301
)}
287-
</Link>
302+
</motion.button>
288303
);
289304
})}
290305
</nav>

src/components/layout/Tabs.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { useState } from 'react';
2+
import { motion, AnimatePresence } from 'framer-motion';
3+
import { CustomBell, CustomBook, CustomHome, CustomSettings, CustomShield } from '../ui/CustomIcons';
4+
5+
// --- Main Tabs Component ---
6+
7+
const TABS = [
8+
{ id: 'dashboard', label: 'Dashboard', icon: CustomHome },
9+
{ id: 'notifications', label: 'Notifications', icon: CustomBell },
10+
{ id: 'settings', label: 'Settings', icon: CustomSettings },
11+
{ id: 'changelog', label: 'Changelog', icon: CustomBook },
12+
{ id: 'security', label: 'Security', icon: CustomShield },
13+
];
14+
15+
export default function Tabs() {
16+
const [activeTab, setActiveTab] = useState(TABS[0].id);
17+
18+
return (
19+
<div className="absolute top-0 bottom-0 left-0 w-screen bg-black flex items-center justify-center font-sans z-90">
20+
21+
{/* Main Widget Container */}
22+
<div className="w-screen bg-[#0c0c0c] border border-zinc-800/50 rounded-3xl p-4 flex flex-col shadow-2xl relative overflow-hidden">
23+
24+
{/* Bottom Navigation Bar */}
25+
<div className="flex items-center justify-between mt-auto pt-2">
26+
{TABS.map((tab) => {
27+
const isActive = activeTab === tab.id;
28+
29+
return (
30+
<motion.button
31+
key={tab.id}
32+
layout // Smoothly adjusts the surrounding buttons when width changes
33+
onClick={() => setActiveTab(tab.id)}
34+
className={`flex items-center justify-center h-10 rounded-full transition-all duration-300 ease-in-out ${isActive
35+
? 'bg-white/10 text-white px-4' // Active tab gets background and wider padding
36+
: 'bg-transparent text-zinc-500 hover:text-zinc-300 px-3'
37+
}`}
38+
style={{ WebkitTapHighlightColor: 'transparent' }}
39+
>
40+
{/* Fixed Icon */}
41+
<tab.icon className="shrink-0" />
42+
43+
{/* Animating Text */}
44+
<AnimatePresence initial={false}>
45+
{isActive && (
46+
<motion.span
47+
initial={{ width: 0, opacity: 0, marginLeft: 0 }}
48+
animate={{ width: 'auto', opacity: 1, marginLeft: 8 }}
49+
exit={{ width: 0, opacity: 0, marginLeft: 0 }}
50+
transition={{ type: 'spring', bounce: 0, duration: 0.35 }}
51+
className="font-medium text-sm overflow-hidden whitespace-nowrap"
52+
>
53+
{tab.label}
54+
</motion.span>
55+
)}
56+
</AnimatePresence>
57+
</motion.button>
58+
);
59+
})}
60+
</div>
61+
</div>
62+
63+
</div>
64+
);
65+
}

0 commit comments

Comments
 (0)