fix: Vertical tabs visibility and add mobile horizontal tabs

Fixed two critical issues with VerticalTabForm:

Issue #1: All sections showing at once
- Problem: className override was removing original classes
- Fix: Preserve originalClassName and append 'hidden' when inactive
- Now only active section is visible
- Inactive sections get 'hidden' class added

Issue #2: No horizontal tabs on mobile
- Added mobile horizontal tabs (lg:hidden)
- Scrollable tab bar with overflow-x-auto
- Active tab highlighted with bg-primary
- Icons + labels for each tab
- Separate mobile content area

Changes to VerticalTabForm.tsx:
1. Fixed className merging logic
   - Get originalClassName from child.props
   - Active: use originalClassName as-is
   - Inactive: append ' hidden' to originalClassName
   - Prevents className override issue

2. Added mobile layout
   - Horizontal tabs at top (lg:hidden)
   - Flex with gap-2, overflow-x-auto
   - flex-shrink-0 prevents tab squishing
   - Active state: bg-primary text-primary-foreground
   - Inactive state: bg-muted text-muted-foreground

3. Desktop layout (hidden lg:flex)
   - Vertical sidebar (w-56)
   - Content area (flex-1)
   - Scroll spy for desktop only

4. Mobile content area (lg:hidden)
   - No scroll spy (simpler)
   - Direct tab switching
   - Same visibility logic (hidden class)

Result:
 Only active section visible (desktop + mobile)
 Mobile has horizontal tabs
 Desktop has vertical sidebar
 Proper responsive behavior
 Tab switching works correctly
This commit is contained in:
dwindown
2025-11-20 21:00:30 +07:00
parent c8bba9a91b
commit 7136b01be4

View File

@@ -66,9 +66,33 @@ export function VerticalTabForm({ tabs, children, className }: VerticalTabFormPr
}; };
return ( return (
<div className={cn('flex gap-6', className)}> <div className={cn('space-y-4', className)}>
{/* Mobile: Horizontal Tabs */}
<div className="lg:hidden">
<div className="flex gap-2 overflow-x-auto pb-2">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => scrollToSection(tab.id)}
className={cn(
'flex-shrink-0 px-4 py-2 rounded-md text-sm font-medium transition-colors',
'flex items-center gap-2',
activeTab === tab.id
? 'bg-primary text-primary-foreground'
: 'bg-muted text-muted-foreground'
)}
>
{tab.icon && <span className="w-4 h-4">{tab.icon}</span>}
{tab.label}
</button>
))}
</div>
</div>
{/* Desktop: Vertical Layout */}
<div className="hidden lg:flex gap-6">
{/* Vertical Tabs Sidebar */} {/* Vertical Tabs Sidebar */}
<div className="hidden lg:block w-56 flex-shrink-0"> <div className="w-56 flex-shrink-0">
<div className="sticky top-4 space-y-1"> <div className="sticky top-4 space-y-1">
{tabs.map((tab) => ( {tabs.map((tab) => (
<button <button
@@ -89,7 +113,7 @@ export function VerticalTabForm({ tabs, children, className }: VerticalTabFormPr
</div> </div>
</div> </div>
{/* Content Area */} {/* Content Area - Desktop */}
<div <div
ref={contentRef} ref={contentRef}
className="flex-1 overflow-y-auto pr-2" className="flex-1 overflow-y-auto pr-2"
@@ -98,9 +122,26 @@ export function VerticalTabForm({ tabs, children, className }: VerticalTabFormPr
if (React.isValidElement(child) && child.props['data-section-id']) { if (React.isValidElement(child) && child.props['data-section-id']) {
const sectionId = child.props['data-section-id']; const sectionId = child.props['data-section-id'];
const isActive = sectionId === activeTab; const isActive = sectionId === activeTab;
const originalClassName = child.props.className || '';
return React.cloneElement(child as React.ReactElement<any>, { return React.cloneElement(child as React.ReactElement<any>, {
ref: (el: HTMLElement) => registerSection(sectionId, el), ref: (el: HTMLElement) => registerSection(sectionId, el),
className: isActive ? '' : 'hidden', className: isActive ? originalClassName : `${originalClassName} hidden`.trim(),
});
}
return child;
})}
</div>
</div>
{/* Mobile: Content Area */}
<div className="lg:hidden">
{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;
const originalClassName = child.props.className || '';
return React.cloneElement(child as React.ReactElement<any>, {
className: isActive ? originalClassName : `${originalClassName} hidden`.trim(),
}); });
} }
return child; return child;