feat: Add scroll-hide header and contextual FAB system

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 <div>...</div>;
}

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
This commit is contained in:
dwindown
2025-11-06 20:27:19 +07:00
parent 76624bb473
commit 4d2469f826
3 changed files with 106 additions and 2 deletions

View File

@@ -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 (
<header className={`h-16 border-b border-border flex items-center px-4 justify-between sticky ${fullscreen ? `top-0` : `top-[32px]`} z-40 bg-background md:bg-background/95 md:backdrop-blur md:supports-[backdrop-filter]:bg-background/60`}>
<header className={`h-16 border-b border-border flex items-center px-4 justify-between sticky ${fullscreen ? `top-0` : `top-[32px]`} z-40 bg-background md:bg-background/95 md:backdrop-blur md:supports-[backdrop-filter]:bg-background/60 transition-transform duration-300 ${!isVisible ? '-translate-y-full md:translate-y-0' : 'translate-y-0'}`}>
<div className="font-semibold">{siteTitle}</div>
<div className="flex items-center gap-3">
<div className="text-sm opacity-70 hidden sm:block">{window.WNW_API?.isDev ? 'Dev Server' : 'Production'}</div>