Files
WooNooW/admin-spa/src/components/VerticalTabForm.tsx
dwindown e8ca3ceeb2 fix: Vertical tabs visibility and add mobile search/filter
Fixed 3 critical issues:

1. Fixed Vertical Tabs - Cards All Showing
   - Updated VerticalTabForm to hide inactive sections
   - Only active section visible (className: hidden for others)
   - Proper tab switching now works

2. Added Mobile Search/Filter to Coupons
   - Created CouponFilterSheet component
   - Added mobile search bar with icon
   - Filter button with active count badge
   - Matches Products pattern exactly
   - Sheet with Apply/Reset buttons

3. Removed max-height from VerticalTabForm
   - User removed max-h-[calc(100vh-200px)]
   - Content now flows naturally
   - Better for forms with varying heights

Components Created:
- CouponFilterSheet.tsx - Mobile filter bottom sheet
  - Discount type filter
  - Apply/Reset actions
  - Active filter count

Changes to Coupons/index.tsx:
- Added mobile search bar (md:hidden)
- Added filter sheet state
- Added activeFiltersCount
- Search icon + SlidersHorizontal icon
- Filter badge indicator

Changes to VerticalTabForm:
- Hide inactive sections (className: hidden)
- Only show section matching activeTab
- Proper visibility control

Result:
 Vertical tabs work correctly (only one section visible)
 Mobile search/filter on Coupons (like Products)
 Filter count badge
 Professional mobile UX

Next: Move customer site member checkbox to settings
2025-11-20 20:32:46 +07:00

135 lines
3.9 KiB
TypeScript

import React, { useState, useEffect, useRef } from 'react';
import { cn } from '@/lib/utils';
export interface VerticalTab {
id: string;
label: string;
icon?: React.ReactNode;
}
interface VerticalTabFormProps {
tabs: VerticalTab[];
children: React.ReactNode;
className?: string;
}
export function VerticalTabForm({ tabs, children, className }: VerticalTabFormProps) {
const [activeTab, setActiveTab] = useState(tabs[0]?.id || '');
const contentRef = useRef<HTMLDivElement>(null);
const sectionRefs = useRef<{ [key: string]: HTMLElement }>({});
// Scroll spy - update active tab based on scroll position
useEffect(() => {
const handleScroll = () => {
if (!contentRef.current) return;
const scrollPosition = contentRef.current.scrollTop + 100; // Offset for better UX
// Find which section is currently in view
for (const tab of tabs) {
const section = sectionRefs.current[tab.id];
if (section) {
const { offsetTop, offsetHeight } = section;
if (scrollPosition >= offsetTop && scrollPosition < offsetTop + offsetHeight) {
setActiveTab(tab.id);
break;
}
}
}
};
const content = contentRef.current;
if (content) {
content.addEventListener('scroll', handleScroll);
return () => content.removeEventListener('scroll', handleScroll);
}
}, [tabs]);
// Register section refs
const registerSection = (id: string, element: HTMLElement | null) => {
if (element) {
sectionRefs.current[id] = element;
}
};
// Scroll to section
const scrollToSection = (id: string) => {
const section = sectionRefs.current[id];
if (section && contentRef.current) {
const offsetTop = section.offsetTop - 20; // Small offset from top
contentRef.current.scrollTo({
top: offsetTop,
behavior: 'smooth',
});
setActiveTab(id);
}
};
return (
<div className={cn('flex gap-6', className)}>
{/* Vertical Tabs Sidebar */}
<div className="hidden lg:block w-56 flex-shrink-0">
<div className="sticky top-4 space-y-1">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => scrollToSection(tab.id)}
className={cn(
'w-full text-left px-4 py-2.5 rounded-md text-sm font-medium transition-colors',
'flex items-center gap-3',
activeTab === tab.id
? 'bg-primary text-primary-foreground'
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
)}
>
{tab.icon && <span className="w-4 h-4">{tab.icon}</span>}
{tab.label}
</button>
))}
</div>
</div>
{/* Content Area */}
<div
ref={contentRef}
className="flex-1 overflow-y-auto pr-2"
>
{React.Children.map(children, (child) => {
if (React.isValidElement(child) && child.props['data-section-id']) {
const sectionId = child.props['data-section-id'];
const isActive = sectionId === activeTab;
return React.cloneElement(child as React.ReactElement<any>, {
ref: (el: HTMLElement) => registerSection(sectionId, el),
className: isActive ? '' : 'hidden',
});
}
return child;
})}
</div>
</div>
);
}
// Section wrapper component for easier usage
interface SectionProps {
id: string;
children: React.ReactNode;
className?: string;
}
export const FormSection = React.forwardRef<HTMLDivElement, SectionProps>(
({ id, children, className }, ref) => {
return (
<div
ref={ref}
data-section-id={id}
className={cn('mb-6 scroll-mt-4', className)}
>
{children}
</div>
);
}
);
FormSection.displayName = 'FormSection';