(READ ONLY) Margin is an open annotation layer for the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
98
fork

Configure Feed

Select the types of activity you want to include in your feed.

enhance mobile nav

scanash00 d177110f ba77dfa9

+337 -65
+219 -61
web/src/components/MobileNav.jsx
··· 1 1 import { Link, useLocation } from "react-router-dom"; 2 2 import { useAuth } from "../context/AuthContext"; 3 - import { Home, Search, Folder, User, PenSquare, Bookmark } from "lucide-react"; 3 + import { useState, useEffect } from "react"; 4 + import { getUnreadNotificationCount } from "../api/client"; 5 + import { 6 + Home, 7 + Search, 8 + Folder, 9 + User, 10 + PenSquare, 11 + Bookmark, 12 + Settings, 13 + MoreHorizontal, 14 + LogOut, 15 + Bell, 16 + Highlighter, 17 + } from "lucide-react"; 4 18 5 19 export default function MobileNav() { 6 - const { user, isAuthenticated } = useAuth(); 20 + const { user, isAuthenticated, logout } = useAuth(); 7 21 const location = useLocation(); 22 + const [isMenuOpen, setIsMenuOpen] = useState(false); 23 + const [unreadCount, setUnreadCount] = useState(0); 8 24 9 25 const isActive = (path) => { 10 26 if (path === "/") return location.pathname === "/"; 11 27 return location.pathname.startsWith(path); 12 28 }; 13 29 14 - return ( 15 - <nav className="mobile-bottom-nav"> 16 - <Link 17 - to="/home" 18 - className={`mobile-bottom-nav-item ${isActive("/home") ? "active" : ""}`} 19 - > 20 - <Home size={22} /> 21 - <span>Home</span> 22 - </Link> 30 + useEffect(() => { 31 + if (isAuthenticated) { 32 + getUnreadNotificationCount() 33 + .then((data) => setUnreadCount(data.count || 0)) 34 + .catch(() => {}); 35 + } 36 + }, [isAuthenticated]); 23 37 24 - <Link 25 - to="/url" 26 - className={`mobile-bottom-nav-item ${isActive("/url") ? "active" : ""}`} 27 - > 28 - <Search size={22} /> 29 - <span>Browse</span> 30 - </Link> 38 + const closeMenu = () => setIsMenuOpen(false); 31 39 32 - {isAuthenticated ? ( 40 + return ( 41 + <> 42 + {isMenuOpen && ( 33 43 <> 34 - <Link 35 - to="/new" 36 - className="mobile-bottom-nav-item mobile-bottom-nav-new" 37 - > 38 - <div className="mobile-nav-new-btn"> 39 - <PenSquare size={20} /> 40 - </div> 41 - </Link> 44 + <div className="mobile-nav-overlay" onClick={closeMenu} /> 45 + <div className="mobile-nav-menu"> 46 + {isAuthenticated ? ( 47 + <> 48 + <Link 49 + to={`/profile/${user.did}`} 50 + className="mobile-menu-profile-card" 51 + onClick={closeMenu} 52 + > 53 + {user.avatar ? ( 54 + <img 55 + src={user.avatar} 56 + alt="" 57 + className="mobile-nav-avatar" 58 + /> 59 + ) : ( 60 + <div 61 + className="mobile-nav-avatar" 62 + style={{ 63 + background: "var(--bg-secondary)", 64 + display: "flex", 65 + alignItems: "center", 66 + justifyContent: "center", 67 + }} 68 + > 69 + <User size={14} /> 70 + </div> 71 + )} 72 + <div style={{ display: "flex", flexDirection: "column" }}> 73 + <span 74 + style={{ 75 + fontWeight: 600, 76 + fontSize: "0.9rem", 77 + color: "var(--text-primary)", 78 + }} 79 + > 80 + {user.displayName || user.handle} 81 + </span> 82 + <span 83 + style={{ 84 + fontSize: "0.8rem", 85 + color: "var(--text-tertiary)", 86 + }} 87 + > 88 + @{user.handle} 89 + </span> 90 + </div> 91 + </Link> 42 92 43 - <Link 44 - to="/bookmarks" 45 - className={`mobile-bottom-nav-item ${isActive("/bookmarks") || isActive("/collections") ? "active" : ""}`} 46 - > 47 - <Bookmark size={22} /> 48 - <span>Library</span> 49 - </Link> 93 + <Link 94 + to="/highlights" 95 + className="mobile-menu-item" 96 + onClick={closeMenu} 97 + > 98 + <Highlighter size={20} /> 99 + <span>Highlights</span> 100 + </Link> 101 + 102 + <Link 103 + to="/bookmarks" 104 + className="mobile-menu-item" 105 + onClick={closeMenu} 106 + > 107 + <Bookmark size={20} /> 108 + <span>Bookmarks</span> 109 + </Link> 50 110 51 - <Link 52 - to={user?.did ? `/profile/${user.did}` : "/profile"} 53 - className={`mobile-bottom-nav-item ${isActive("/profile") ? "active" : ""}`} 54 - > 55 - {user?.avatar ? ( 56 - <img src={user.avatar} alt="" className="mobile-nav-avatar" /> 111 + <Link 112 + to="/collections" 113 + className="mobile-menu-item" 114 + onClick={closeMenu} 115 + > 116 + <Folder size={20} /> 117 + <span>Collections</span> 118 + </Link> 119 + 120 + <Link 121 + to="/settings" 122 + className="mobile-menu-item" 123 + onClick={closeMenu} 124 + > 125 + <Settings size={20} /> 126 + <span>Settings</span> 127 + </Link> 128 + 129 + <div className="dropdown-divider" /> 130 + 131 + <button 132 + className="mobile-menu-item danger" 133 + onClick={() => { 134 + logout(); 135 + closeMenu(); 136 + }} 137 + > 138 + <LogOut size={20} /> 139 + <span>Log Out</span> 140 + </button> 141 + </> 57 142 ) : ( 58 - <User size={22} /> 143 + <> 144 + <Link 145 + to="/login" 146 + className="mobile-menu-item" 147 + onClick={closeMenu} 148 + > 149 + <User size={20} /> 150 + <span>Sign In</span> 151 + </Link> 152 + <Link 153 + to="/collections" 154 + className="mobile-menu-item" 155 + onClick={closeMenu} 156 + > 157 + <Folder size={20} /> 158 + <span>Collections</span> 159 + </Link> 160 + <Link 161 + to="/settings" 162 + className="mobile-menu-item" 163 + onClick={closeMenu} 164 + > 165 + <Settings size={20} /> 166 + <span>Settings</span> 167 + </Link> 168 + </> 59 169 )} 60 - <span>You</span> 61 - </Link> 170 + </div> 62 171 </> 63 - ) : ( 64 - <> 172 + )} 173 + 174 + <nav className="mobile-bottom-nav"> 175 + <Link 176 + to="/home" 177 + className={`mobile-bottom-nav-item ${isActive("/home") ? "active" : ""}`} 178 + onClick={closeMenu} 179 + > 180 + <Home size={24} strokeWidth={1.5} /> 181 + </Link> 182 + 183 + <Link 184 + to="/url" 185 + className={`mobile-bottom-nav-item ${isActive("/url") ? "active" : ""}`} 186 + onClick={closeMenu} 187 + > 188 + <Search size={24} strokeWidth={1.5} /> 189 + </Link> 190 + 191 + {isAuthenticated ? ( 192 + <> 193 + <Link 194 + to="/new" 195 + className="mobile-bottom-nav-item mobile-bottom-nav-new" 196 + onClick={closeMenu} 197 + > 198 + <div className="mobile-nav-new-btn"> 199 + <PenSquare size={20} strokeWidth={2} /> 200 + </div> 201 + </Link> 202 + 203 + <Link 204 + to="/notifications" 205 + className={`mobile-bottom-nav-item ${isActive("/notifications") ? "active" : ""}`} 206 + onClick={closeMenu} 207 + > 208 + <div style={{ position: "relative", display: "flex" }}> 209 + <Bell size={24} strokeWidth={1.5} /> 210 + {unreadCount > 0 && ( 211 + <span 212 + style={{ 213 + position: "absolute", 214 + top: -2, 215 + right: -2, 216 + width: 8, 217 + height: 8, 218 + background: "var(--accent)", 219 + borderRadius: "50%", 220 + border: "2px solid var(--nav-bg)", 221 + }} 222 + /> 223 + )} 224 + </div> 225 + </Link> 226 + </> 227 + ) : ( 65 228 <Link 66 229 to="/login" 67 230 className="mobile-bottom-nav-item mobile-bottom-nav-new" 231 + onClick={closeMenu} 68 232 > 69 233 <div className="mobile-nav-new-btn"> 70 - <User size={20} /> 234 + <User size={20} strokeWidth={2} /> 71 235 </div> 72 236 </Link> 237 + )} 73 238 74 - <Link 75 - to="/collections" 76 - className={`mobile-bottom-nav-item ${isActive("/collections") ? "active" : ""}`} 77 - > 78 - <Folder size={22} /> 79 - <span>Library</span> 80 - </Link> 81 - 82 - <Link to="/login" className={`mobile-bottom-nav-item`}> 83 - <User size={22} /> 84 - <span>Sign In</span> 85 - </Link> 86 - </> 87 - )} 88 - </nav> 239 + <button 240 + className={`mobile-bottom-nav-item ${isMenuOpen ? "active" : ""}`} 241 + onClick={() => setIsMenuOpen(!isMenuOpen)} 242 + > 243 + <MoreHorizontal size={24} strokeWidth={1.5} /> 244 + </button> 245 + </nav> 246 + </> 89 247 ); 90 248 }
+112 -3
web/src/css/layout.css
··· 435 435 436 436 .mobile-bottom-nav-item { 437 437 display: flex; 438 + flex: 1; 438 439 flex-direction: column; 439 440 align-items: center; 441 + justify-content: center; 440 442 gap: 4px; 441 - padding: 6px 12px; 443 + padding: 6px 0; 442 444 color: var(--text-tertiary); 443 445 text-decoration: none; 444 446 font-size: 0.65rem; 445 447 font-weight: 500; 446 448 transition: color 0.15s; 447 - min-width: 56px; 449 + min-width: 0; 448 450 } 449 451 450 452 .mobile-bottom-nav-item.active { ··· 456 458 } 457 459 458 460 .mobile-bottom-nav-new { 459 - padding: 6px 16px; 461 + padding: 6px 0; 460 462 } 461 463 462 464 .mobile-nav-new-btn { ··· 590 592 font-size: 0.85rem; 591 593 } 592 594 } 595 + 596 + .mobile-nav-overlay { 597 + position: fixed; 598 + inset: 0; 599 + background: rgba(0, 0, 0, 0.5); 600 + z-index: 150; 601 + backdrop-filter: blur(2px); 602 + -webkit-backdrop-filter: blur(2px); 603 + animation: fadeIn 0.15s ease-out; 604 + } 605 + 606 + .mobile-nav-menu { 607 + position: fixed; 608 + bottom: calc(70px + env(safe-area-inset-bottom)); 609 + right: 12px; 610 + width: 250px; 611 + background: var(--bg-elevated); 612 + border: 1px solid var(--border); 613 + border-radius: var(--radius-xl); 614 + padding: 6px; 615 + z-index: 151; 616 + box-shadow: var(--shadow-2xl); 617 + animation: mobileMenuSlide 0.2s cubic-bezier(0.16, 1, 0.3, 1); 618 + display: flex; 619 + flex-direction: column; 620 + gap: 2px; 621 + } 622 + 623 + @keyframes mobileMenuSlide { 624 + from { 625 + opacity: 0; 626 + transform: translateY(20px) scale(0.95); 627 + } 628 + 629 + to { 630 + opacity: 1; 631 + transform: translateY(0) scale(1); 632 + } 633 + } 634 + 635 + .mobile-menu-item { 636 + display: flex; 637 + align-items: center; 638 + gap: 12px; 639 + padding: 12px 14px; 640 + color: var(--text-secondary); 641 + text-decoration: none; 642 + font-size: 0.95rem; 643 + font-weight: 500; 644 + border-radius: var(--radius-md); 645 + transition: all 0.1s; 646 + background: transparent; 647 + border: none; 648 + width: 100%; 649 + text-align: left; 650 + cursor: pointer; 651 + } 652 + 653 + .mobile-menu-item:hover, 654 + .mobile-menu-item:active { 655 + background: var(--bg-hover); 656 + color: var(--text-primary); 657 + } 658 + 659 + .mobile-menu-item svg { 660 + opacity: 0.8; 661 + } 662 + 663 + .mobile-menu-item.active { 664 + background: var(--bg-tertiary); 665 + color: var(--accent); 666 + } 667 + 668 + .mobile-menu-item.danger { 669 + color: var(--error); 670 + } 671 + 672 + .mobile-menu-item.danger:hover { 673 + background: rgba(239, 68, 68, 0.1); 674 + } 675 + 676 + .mobile-menu-profile-card { 677 + display: flex; 678 + align-items: center; 679 + gap: 12px; 680 + padding: 12px; 681 + background: var(--bg-tertiary); 682 + border-radius: var(--radius-lg); 683 + margin-bottom: 6px; 684 + text-decoration: none; 685 + border: 1px solid transparent; 686 + } 687 + 688 + .mobile-menu-profile-card:active { 689 + background: var(--bg-hover); 690 + transform: scale(0.98); 691 + } 692 + 693 + .mobile-menu-badge { 694 + margin-left: auto; 695 + background: var(--accent); 696 + color: white; 697 + font-size: 0.75rem; 698 + font-weight: 700; 699 + padding: 2px 8px; 700 + border-radius: 99px; 701 + }
+6 -1
web/src/pages/Settings.jsx
··· 86 86 <h1 className="page-title">Settings</h1> 87 87 <p className="page-description">Manage your preferences and API keys.</p> 88 88 89 - <div className="settings-section"> 89 + <div className="settings-section layout-settings-section"> 90 90 <h2>Layout</h2> 91 91 <div className="layout-options"> 92 92 <button ··· 331 331 @media (max-width: 600px) { 332 332 .layout-options { 333 333 grid-template-columns: 1fr; 334 + } 335 + } 336 + @media (max-width: 768px) { 337 + .layout-settings-section { 338 + display: none; 334 339 } 335 340 } 336 341 `}</style>