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:
@@ -66,20 +66,20 @@ export function VerticalTabForm({ tabs, children, className }: VerticalTabFormPr
|
||||
};
|
||||
|
||||
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">
|
||||
<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(
|
||||
'w-full text-left px-4 py-2.5 rounded-md text-sm font-medium transition-colors',
|
||||
'flex items-center gap-3',
|
||||
'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'
|
||||
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
||||
: 'bg-muted text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{tab.icon && <span className="w-4 h-4">{tab.icon}</span>}
|
||||
@@ -89,18 +89,59 @@ export function VerticalTabForm({ tabs, children, className }: VerticalTabFormPr
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content Area */}
|
||||
<div
|
||||
ref={contentRef}
|
||||
className="flex-1 overflow-y-auto pr-2"
|
||||
>
|
||||
{/* Desktop: Vertical Layout */}
|
||||
<div className="hidden lg:flex gap-6">
|
||||
{/* Vertical Tabs Sidebar */}
|
||||
<div className="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 - Desktop */}
|
||||
<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;
|
||||
const originalClassName = child.props.className || '';
|
||||
return React.cloneElement(child as React.ReactElement<any>, {
|
||||
ref: (el: HTMLElement) => registerSection(sectionId, el),
|
||||
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>, {
|
||||
ref: (el: HTMLElement) => registerSection(sectionId, el),
|
||||
className: isActive ? '' : 'hidden',
|
||||
className: isActive ? originalClassName : `${originalClassName} hidden`.trim(),
|
||||
});
|
||||
}
|
||||
return child;
|
||||
|
||||
Reference in New Issue
Block a user