From 4d2469f826782bf46b1d6d8baeeddc07021b8a6f Mon Sep 17 00:00:00 2001 From: dwindown Date: Thu, 6 Nov 2025 20:27:19 +0700 Subject: [PATCH] feat: Add scroll-hide header and contextual FAB system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented: 1. Scroll-Hide App Bar (Mobile) - Hides on scroll down (past 50px) - Shows on scroll up - Chrome URL bar behavior - Smooth slide animation (300ms) - Desktop always visible (md:translate-y-0) 2. Contextual FAB Hook - useFABConfig() hook for pages - Pre-configured for: orders, products, customers, coupons, dashboard - Automatic cleanup on unmount - Easy to use: useFABConfig('orders') 3. Removed Focus Styles - Bottom nav links: focus:outline-none - Cleaner mobile UX Header Scroll Behavior: - Scroll down > 50px: Header slides up (-translate-y-full) - Scroll up: Header slides down (translate-y-0) - Desktop: Always visible (md:translate-y-0) - Smooth transition (duration-300) FAB Configuration: const configs = { orders: 'Create Order' → /orders/new products: 'Add Product' → /products/new customers: 'Add Customer' → /customers/new coupons: 'Create Coupon' → /coupons/new dashboard: 'Quick Actions' → (future speed dial) none: Hide FAB } Usage in Pages: import { useFABConfig } from '@/hooks/useFABConfig'; function OrdersPage() { useFABConfig('orders'); // Sets up FAB automatically return
...
; } Next Steps: - Add useFABConfig to actual pages - Test scroll behavior on devices - Implement speed dial for dashboard Files Created: - useFABConfig.tsx: Contextual FAB configuration hook Files Modified: - App.tsx: Scroll detection and header animation - BottomNav.tsx: Removed focus outline styles --- admin-spa/src/App.tsx | 33 +++++++++- admin-spa/src/components/nav/BottomNav.tsx | 2 +- admin-spa/src/hooks/useFABConfig.tsx | 73 ++++++++++++++++++++++ 3 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 admin-spa/src/hooks/useFABConfig.tsx diff --git a/admin-spa/src/App.tsx b/admin-spa/src/App.tsx index ba02008..7c0f42f 100644 --- a/admin-spa/src/App.tsx +++ b/admin-spa/src/App.tsx @@ -264,6 +264,8 @@ function AddonRoute({ config }: { config: any }) { function Header({ onFullscreen, fullscreen, showToggle = true }: { onFullscreen: () => void; fullscreen: boolean; showToggle?: boolean }) { const [siteTitle, setSiteTitle] = React.useState((window as any).wnw?.siteTitle || 'WooNooW'); + const [isVisible, setIsVisible] = React.useState(true); + const [lastScrollY, setLastScrollY] = React.useState(0); const isStandalone = window.WNW_CONFIG?.standaloneMode ?? false; // Listen for store settings updates @@ -278,6 +280,35 @@ function Header({ onFullscreen, fullscreen, showToggle = true }: { onFullscreen: return () => window.removeEventListener('woonoow:store:updated' as any, handleStoreUpdate); }, []); + // Hide/show header on scroll (mobile only) + React.useEffect(() => { + const handleScroll = () => { + const currentScrollY = window.scrollY; + + // Only apply on mobile (check window width) + if (window.innerWidth >= 768) { + setIsVisible(true); + return; + } + + if (currentScrollY > lastScrollY && currentScrollY > 50) { + // Scrolling down & past threshold + setIsVisible(false); + } else if (currentScrollY < lastScrollY) { + // Scrolling up + setIsVisible(true); + } + + setLastScrollY(currentScrollY); + }; + + window.addEventListener('scroll', handleScroll, { passive: true }); + + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, [lastScrollY]); + const handleLogout = async () => { try { await fetch((window.WNW_CONFIG?.restUrl || '') + '/auth/logout', { @@ -291,7 +322,7 @@ function Header({ onFullscreen, fullscreen, showToggle = true }: { onFullscreen: }; return ( -
+
{siteTitle}
{window.WNW_API?.isDev ? 'Dev Server' : 'Production'}
diff --git a/admin-spa/src/components/nav/BottomNav.tsx b/admin-spa/src/components/nav/BottomNav.tsx index ebf31bd..d6978b8 100644 --- a/admin-spa/src/components/nav/BottomNav.tsx +++ b/admin-spa/src/components/nav/BottomNav.tsx @@ -63,7 +63,7 @@ export function BottomNav() { { + switch (page) { + case 'orders': + setFAB({ + icon: , + label: 'Create Order', + onClick: () => navigate('/orders/new'), + visible: true + }); + break; + + case 'products': + setFAB({ + icon: , + label: 'Add Product', + onClick: () => navigate('/products/new'), + visible: true + }); + break; + + case 'customers': + setFAB({ + icon: , + label: 'Add Customer', + onClick: () => navigate('/customers/new'), + visible: true + }); + break; + + case 'coupons': + setFAB({ + icon: , + label: 'Create Coupon', + onClick: () => navigate('/coupons/new'), + visible: true + }); + break; + + case 'dashboard': + // Dashboard could have a speed dial menu in the future + setFAB({ + icon: , + label: 'Quick Actions', + onClick: () => { + // TODO: Implement speed dial menu + console.log('Quick actions menu'); + }, + visible: true + }); + break; + + case 'none': + default: + clearFAB(); + break; + } + + return () => clearFAB(); + }, [page, navigate, setFAB, clearFAB]); +}