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,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;
|
||||||
|
|||||||
Reference in New Issue
Block a user