aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
789 lines (786 loc) • 30.1 kB
JavaScript
'use client';
import { jsx, jsxs } from 'react/jsx-runtime';
import React, { forwardRef, useMemo, useRef, useState, useImperativeHandle, useCallback, useEffect } from 'react';
import { useAccessibilitySettings } from '../../hooks/useAccessibilitySettings.js';
import { TabBarContainer, TabSelector } from './styled.js';
import TabItemComponent from './components/TabItem.js';
import CollapsedMenu from './components/CollapsedMenu.js';
import { ScrollButtons } from './components/ScrollButtons.js';
import useResponsive from './hooks/useResponsive.js';
import useTabAnimations from './hooks/useTabAnimations.js';
import { scrollTabIntoView, calculateVisibleTabs } from './utils/tabUtils.js';
import { useAnimationContext } from '../../contexts/AnimationContext.js';
import { cn } from '../../lib/utilsComprehensive.js';
/**
* GlassTabBar Component
*/
const GlassTabBar = /*#__PURE__*/forwardRef((props, ref) => {
const {
tabs,
activeTab,
onChange,
orientation = "horizontal",
variant = "default",
glassVariant = "frosted",
blurStrength = "standard",
animationStyle = "spring",
physics = {
tension: 280,
friction: 26,
mass: 1
},
alignment = "center",
color = "primary",
fullWidth = false,
scrollable = true,
showLabels = true,
elevated = false,
background = true,
width,
height,
borderRadius,
className,
style,
onContextMenu,
renderTab,
iconPosition = "left",
verticalDisplayMode = "expanded",
placement = "top",
responsiveOrientation,
responsiveConfig,
collapseTabs = false,
renderCollapsedMenu,
keyboardNavigation = true,
tabIndex = 0,
ariaLabel = "Tab Navigation",
tabStyle,
tabClassName,
activeTabClassName,
defaultBadgeAnimation,
badgeStyle,
animationConfig: propAnimationConfig,
disableAnimation: propDisableAnimation,
// Capture remaining props
...restProps
} = props;
const responsiveState = useResponsive();
// Calculate effective values based on responsive state
const effectiveOrientation = responsiveState.isMobile && responsiveOrientation === "vertical" ? "vertical" : orientation;
responsiveState.isMobile ? false : showLabels;
responsiveState.isMobile ? true : fullWidth;
const effectiveWidth = width || (responsiveState.isMobile ? "100%" : "auto");
const effectiveHeight = height;
const maxVisibleTabs = responsiveState.isMobile ? 3 : 8;
// Hooks
const {
settings: accessibilitySettings
} = useAccessibilitySettings();
const isReducedMotion = accessibilitySettings?.reducedMotion || false;
const contextValue = useAnimationContext();
// Determine final animation settings
const finalDisableAnimation = !!(propDisableAnimation ?? contextValue?.disableAnimation ?? isReducedMotion);
useMemo(() => {
return propAnimationConfig ?? contextValue?.animationConfig ?? (physics ? {
tension: physics.tension,
friction: physics.friction,
mass: physics.mass
} : undefined) ??
// Convert old prop
undefined // Or a default config if needed
;
}, [propAnimationConfig, contextValue, physics]);
// Refs
const tabsRef = useRef(null);
const tabRefs = useRef([]);
// State
const [selectorStyle, setSelectorStyle] = useState({
width: 0,
height: 0,
left: 0,
top: 0
});
const [showLeftScroll, setShowLeftScroll] = useState(false);
const [showRightScroll, setShowRightScroll] = useState(false);
const [isScrolling, setIsScrolling] = useState(false);
// State for collapsed tabs
const [visibleTabs, setVisibleTabs] = useState(tabs);
const [collapsedTabs, setCollapsedTabs] = useState([]);
const [showCollapsedMenu, setShowCollapsedMenu] = useState(false);
// Ref for tracking tab widths to determine when to collapse
const tabWidthsRef = useRef([]);
// Set up scroll animation
const [scrollPosition, setScrollPosition] = useState({
x: 0,
y: 0,
left: 0,
top: 0
});
// Initialize scroll animation ref
const scrollAnimationRef = useRef({
rafId: null,
velocity: {
x: 0,
y: 0
},
timestamp: 0,
active: false
});
// Set up tab animations
useTabAnimations(activeTab || 0);
// Magnetic effect state
const [tabMagneticData, setTabMagneticData] = useState({
closestTabIndex: null,
selectionProgress: 0
});
// Physics animation state
const [usePhysicsAnimation, setUsePhysicsAnimation] = useState(false);
// Spring props for animations
const [springProps, setSpringProps] = useState({
left: 0,
top: 0,
width: 0,
height: 0
});
// Transform state for magnetic effects
const [transform, setTransform] = useState({
translateX: 0,
translateY: 0
});
// Expose imperative methods via the forwarded ref
useImperativeHandle(ref, () => ({
getContainerElement: () => tabsRef.current,
getTabElements: () => tabRefs.current,
getTabElement: index => {
if (index >= 0 && index < tabRefs.current.length) {
return tabRefs.current[index];
}
return null;
},
getActiveTab: () => activeTab || 0,
setActiveTab: index => {
if (index >= 0 && index < tabs.length && !tabs[index].disabled) {
// Create a synthetic mouse event
const syntheticEvent = {
currentTarget: tabRefs.current[index] || document.createElement("button"),
preventDefault: () => {},
stopPropagation: () => {}
};
// Call the onChange handler with the synthetic event
onChange(syntheticEvent, index);
}
},
selectTab: index => {
if (index >= 0 && index < tabs.length && !tabs[index].disabled) {
// Create a synthetic mouse event
const syntheticEvent = {
currentTarget: tabRefs.current[index] || document.createElement("button"),
preventDefault: () => {},
stopPropagation: () => {}
};
// Call the onChange handler with the synthetic event
onChange(syntheticEvent, index);
}
},
scrollToTab: (index, smooth = true) => {
if (index >= 0 && index < tabRefs.current.length && tabRefs.current[index] && tabsRef.current) {
scrollTabIntoView(tabRefs.current[index], tabsRef.current, smooth ? "smooth" : "auto");
}
},
toggleBadge: (index, show) => {
// Badge toggle functionality - implement based on your application needs
// This provides the interface for dynamic badge show/hide functionality
},
updateBadge: (index, value) => {
// Badge update functionality - implement based on your application needs
// This provides the interface for dynamic badge value updates
},
isScrolling: () => isScrolling
}), [tabsRef, tabRefs, tabs, onChange, effectiveOrientation, isScrolling, activeTab]);
// Handle tab click
const handleTabClick = (event, index) => {
if (collapseTabs && index === visibleTabs.length - 1) {
// This is the "More" menu tab, toggle the menu
setShowCollapsedMenu(!showCollapsedMenu);
return;
}
// If this is a normal tab click, handle it normally
if (visibleTabs[index].disabled) return;
onChange(event, index);
};
// Handle collapsed tab selection
const handleCollapsedTabSelect = collapsedIndex => {
// Find the original index of this tab in the full tabs array
const realIndex = collapsedTabs[collapsedIndex] ? tabs.findIndex(tab => tab.value === collapsedTabs[collapsedIndex].value) : -1;
if (realIndex !== -1) {
// Create a synthetic event that matches the expected type
const syntheticEvent = {
currentTarget: tabRefs.current[realIndex] || document.createElement("button"),
preventDefault: () => {},
stopPropagation: () => {}
};
onChange(syntheticEvent, realIndex);
}
// Close the menu
setShowCollapsedMenu(false);
};
// Handle scroll visibility check
const checkScrollVisibility = useCallback(() => {
if (!tabsRef.current || !scrollable) return;
const container = tabsRef.current;
if (effectiveOrientation === "horizontal") {
setShowLeftScroll(container.scrollLeft > 0);
setShowRightScroll(container.scrollLeft < container.scrollWidth - container.clientWidth - 2);
} else {
setShowLeftScroll(container.scrollTop > 0);
setShowRightScroll(container.scrollTop < container.scrollHeight - container.clientHeight - 2);
}
}, [effectiveOrientation, scrollable]);
// Set scroll target with animation
const setScrollTarget = (position, animate = true) => {
const scrollPos = "left" in position ? position : {
...position,
left: position.x,
top: position.y
};
setScrollPosition(scrollPos);
if (tabsRef.current) {
if (animate) {
// Apply smooth scrolling if animate is true
tabsRef.current.scrollTo({
left: scrollPos.left,
top: scrollPos.top,
behavior: "smooth"
});
} else {
// Instant scroll
tabsRef.current.scrollLeft = scrollPos.left;
tabsRef.current.scrollTop = scrollPos.top;
}
}
};
// Handle scroll button click
const handleScroll = direction => {
if (!tabsRef.current) return;
const container = tabsRef.current;
const scrollAmount = effectiveOrientation === "horizontal" ? container.clientWidth / 2 : container.clientHeight / 2;
const targetPosition = {
x: scrollPosition.left,
y: scrollPosition.top,
left: scrollPosition.left,
top: scrollPosition.top
};
if (effectiveOrientation === "horizontal") {
if (direction === "left") {
targetPosition.x = Math.max(0, scrollPosition.left - scrollAmount);
targetPosition.left = targetPosition.x;
} else {
targetPosition.x = Math.min(container.scrollWidth - container.clientWidth, scrollPosition.left + scrollAmount);
targetPosition.left = targetPosition.x;
}
targetPosition.y = scrollPosition.top;
targetPosition.top = scrollPosition.top;
} else {
if (direction === "up") {
targetPosition.y = Math.max(0, scrollPosition.top - scrollAmount);
targetPosition.top = targetPosition.y;
} else {
targetPosition.y = Math.min(container.scrollHeight - container.clientHeight, scrollPosition.top + scrollAmount);
targetPosition.top = targetPosition.y;
}
targetPosition.x = scrollPosition.left;
targetPosition.left = scrollPosition.left;
}
setScrollTarget(targetPosition);
};
// Setup momentum tracking
const momentumRef = useRef({
startX: 0,
startY: 0,
lastX: 0,
lastY: 0,
lastMoveTime: 0,
velocityX: 0,
velocityY: 0
});
// Set up scroll animation configuration
const scrollPhysicsConfig = useMemo(() => {
return {
// Use physics parameters that create a fluid scrolling experience
tension: 180,
friction: 24,
mass: 1,
// Adjust physics based on animation style preference for consistency
...(animationStyle === "spring" ? {
friction: 18
} : {}),
...(animationStyle === "keyframes" ? {
tension: 220,
friction: 26
} : {}),
...(isReducedMotion ? {
tension: 280,
friction: 36
} : {})
};
}, [animationStyle, isReducedMotion]);
// Handle scroll gesture events
const handleScrollGestureStart = e => {
if (!scrollable || !tabsRef.current) return;
// Cancel any ongoing scroll animation
if (scrollAnimationRef.current.rafId !== null) {
cancelAnimationFrame(scrollAnimationRef.current.rafId);
scrollAnimationRef.current.rafId = null;
scrollAnimationRef.current.active = false;
}
let clientX = 0;
let clientY = 0;
if ("touches" in e) {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else {
clientX = e.clientX;
clientY = e.clientY;
}
// Initialize momentum tracking
momentumRef.current = {
startX: clientX,
startY: clientY,
lastX: clientX,
lastY: clientY,
lastMoveTime: Date.now(),
velocityX: 0,
velocityY: 0
};
setIsScrolling(true);
};
const handleScrollGestureMove = e => {
if (!isScrolling || !tabsRef.current) return;
// Prevent default to avoid browser handling
e.preventDefault();
const container = tabsRef.current;
let clientX = 0;
let clientY = 0;
if ("touches" in e) {
// Touch event
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else {
// Mouse event
clientX = e.clientX;
clientY = e.clientY;
}
// Calculate delta
const deltaX = clientX - momentumRef.current.lastX;
const deltaY = clientY - momentumRef.current.lastY;
// Track velocity for momentum
const now = Date.now();
const elapsed = now - momentumRef.current.lastMoveTime;
if (elapsed > 0) {
momentumRef.current.velocityX = deltaX / elapsed * 15; // Scale for better feel
momentumRef.current.velocityY = deltaY / elapsed * 15;
}
// Update last position and time
momentumRef.current.lastX = clientX;
momentumRef.current.lastY = clientY;
momentumRef.current.lastMoveTime = now;
// Apply scroll delta
if (effectiveOrientation === "horizontal") {
container.scrollLeft -= deltaX;
setScrollTarget({
left: container.scrollLeft,
top: scrollPosition.top,
x: container.scrollLeft,
y: scrollPosition.y
}, false);
} else {
container.scrollTop -= deltaY;
setScrollTarget({
left: scrollPosition.left,
top: container.scrollTop,
x: scrollPosition.x,
y: container.scrollTop
}, false);
}
checkScrollVisibility();
};
const handleScrollGestureEnd = () => {
if (!isScrolling || !tabsRef.current) return;
setIsScrolling(false);
const container = tabsRef.current;
const velocityX = momentumRef.current.velocityX;
const velocityY = momentumRef.current.velocityY;
// Only apply momentum if velocity is significant
if (Math.abs(velocityX) > 0.5 || Math.abs(velocityY) > 0.5) {
// Get boundaries for scrolling
const maxScrollX = container.scrollWidth - container.clientWidth;
const maxScrollY = container.scrollHeight - container.clientHeight;
// Store velocity for physics-based animation
scrollAnimationRef.current.velocity = {
x: velocityX,
y: velocityY
};
scrollAnimationRef.current.timestamp = Date.now();
scrollAnimationRef.current.active = true;
// Use a more sophisticated physics-based animation
const animateScroll = () => {
if (!scrollAnimationRef.current.active || !tabsRef.current) {
scrollAnimationRef.current.rafId = null;
return;
}
const now = Date.now();
const elapsed = now - scrollAnimationRef.current.timestamp;
scrollAnimationRef.current.timestamp = now;
// Convert to seconds for physics calculations
const dt = Math.min(elapsed, 64) / 1000; // Cap at 64ms to avoid big jumps
// Apply physics (spring-driven deceleration)
const friction = scrollPhysicsConfig.friction;
const frictionFactor = Math.pow(0.95, friction * dt * 60); // Scale by dt
// Update velocity with friction
scrollAnimationRef.current.velocity.x *= frictionFactor;
scrollAnimationRef.current.velocity.y *= frictionFactor;
// Calculate new scroll position
let newScrollX = tabsRef.current.scrollLeft - scrollAnimationRef.current.velocity.x * dt * 60;
let newScrollY = tabsRef.current.scrollTop - scrollAnimationRef.current.velocity.y * dt * 60;
// Apply boundaries
newScrollX = Math.max(0, Math.min(maxScrollX, newScrollX));
newScrollY = Math.max(0, Math.min(maxScrollY, newScrollY));
// Apply scroll position
if (effectiveOrientation === "horizontal") {
tabsRef.current.scrollLeft = newScrollX;
setScrollTarget({
left: newScrollX,
top: scrollPosition.top,
x: newScrollX,
y: scrollPosition.y
}, false);
} else {
tabsRef.current.scrollTop = newScrollY;
setScrollTarget({
left: scrollPosition.left,
top: newScrollY,
x: scrollPosition.x,
y: newScrollY
}, false);
}
// Check if we should stop animating
const stopThreshold = 0.1;
const isMoving = Math.abs(scrollAnimationRef.current.velocity.x) > stopThreshold || Math.abs(scrollAnimationRef.current.velocity.y) > stopThreshold;
// Apply boundary bounce effect if we hit an edge
if (effectiveOrientation === "horizontal") {
if (newScrollX === 0 || newScrollX === maxScrollX) {
// Bounce effect: reverse velocity with damping
scrollAnimationRef.current.velocity.x *= -0.3;
}
} else {
if (newScrollY === 0 || newScrollY === maxScrollY) {
// Bounce effect: reverse velocity with damping
scrollAnimationRef.current.velocity.y *= -0.3;
}
}
// Continue animation or stop
if (isMoving) {
checkScrollVisibility();
scrollAnimationRef.current.rafId = requestAnimationFrame(animateScroll);
} else {
scrollAnimationRef.current.active = false;
scrollAnimationRef.current.rafId = null;
}
};
// Start animation
scrollAnimationRef.current.rafId = requestAnimationFrame(animateScroll);
}
};
// Update selector position function
const updateSelectorPosition = useCallback(() => {
if (!tabsRef.current || !activeTab || activeTab < 0 || activeTab >= tabs.length) return;
const activeTabElement = tabRefs.current[activeTab];
if (!activeTabElement) return;
const tabRect = activeTabElement.getBoundingClientRect();
const containerRect = tabsRef.current.getBoundingClientRect();
// Update selector style
const newStyle = {
width: tabRect.width,
height: tabRect.height,
left: activeTabElement.offsetLeft,
top: activeTabElement.offsetTop
};
// Adjust for underlined variant
if (variant === "underline") {
if (effectiveOrientation === "horizontal") {
newStyle.height = 2;
newStyle.top = containerRect.height - 2;
} else {
newStyle.width = 2;
newStyle.left = containerRect.width - 2;
}
}
setSelectorStyle(newStyle);
setSpringProps(newStyle);
}, [activeTab, effectiveOrientation, variant, tabs.length]);
// Magnetic selector function
const magneticSelector = useCallback(e => {
if (animationStyle !== "spring" || !tabsRef.current) return;
const container = tabsRef.current;
const rect = container.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
// Find closest tab
let closestIndex = 0;
let closestDistance = Infinity;
tabRefs.current.forEach((tabRef, index) => {
if (!tabRef || visibleTabs[index]?.disabled) return;
const tabRect = tabRef.getBoundingClientRect();
const tabCenterX = tabRect.left + tabRect.width / 2 - rect.left;
const tabCenterY = tabRect.top + tabRect.height / 2 - rect.top;
const distance = Math.sqrt(Math.pow(mouseX - tabCenterX, 2) + Math.pow(mouseY - tabCenterY, 2));
if (distance < closestDistance) {
closestDistance = distance;
closestIndex = index;
}
});
// Update magnetic data
const progress = Math.max(0, Math.min(1, 1 - closestDistance / 100));
setTabMagneticData({
closestTabIndex: closestIndex,
selectionProgress: progress
});
// Update transform based on magnetic effect
if (progress > 0.1) {
const closestTab = tabRefs.current[closestIndex];
if (closestTab) {
const tabRect = closestTab.getBoundingClientRect();
const tabCenterX = tabRect.left + tabRect.width / 2 - rect.left;
const tabCenterY = tabRect.top + tabRect.height / 2 - rect.top;
setTransform({
translateX: (tabCenterX - mouseX) * progress * 0.3,
translateY: (tabCenterY - mouseY) * progress * 0.3
});
}
} else {
setTransform({
translateX: 0,
translateY: 0
});
}
}, [animationStyle, visibleTabs]);
// Handle mouse leave
const handleMouseLeave = useCallback(() => {
if (animationStyle === "spring" && usePhysicsAnimation) {
// Reset selector position
updateSelectorPosition();
setTabMagneticData({
closestTabIndex: null,
selectionProgress: 0
});
setTransform({
translateX: 0,
translateY: 0
});
}
}, [animationStyle, usePhysicsAnimation, updateSelectorPosition]);
// Update selector position when active tab changes
useEffect(() => {
if (!tabsRef.current || !activeTab || activeTab < 0 || activeTab >= tabs.length) return;
const activeTabElement = tabRefs.current[activeTab];
if (!activeTabElement) return;
const tabRect = activeTabElement.getBoundingClientRect();
const containerRect = tabsRef.current.getBoundingClientRect();
// Update selector style - this code needs to be in sync with updateSelectorPosition
const newStyle = {
width: tabRect.width,
height: tabRect.height,
left: activeTabElement.offsetLeft,
top: activeTabElement.offsetTop
};
// Adjust for underlined variant
if (variant === "underline") {
if (effectiveOrientation === "horizontal") {
newStyle.height = 2;
newStyle.top = containerRect.height - 2;
} else {
newStyle.width = 2;
newStyle.left = containerRect.width - 2;
}
}
setSelectorStyle(newStyle); // Update local state for non-animated rendering
setSpringProps(newStyle); // Update spring props for animations
// Apply any physics-based animations
if (animationStyle === "spring") {
updateSelectorPosition();
}
// Also scroll active tab into view
scrollTabIntoView(activeTabElement, tabsRef.current, "smooth");
}, [activeTab, effectiveOrientation, scrollable, tabs.length, variant, tabsRef, tabRefs, animationStyle, updateSelectorPosition]);
// Setup scroll event listener
useEffect(() => {
const container = tabsRef.current;
if (!container || !scrollable) return;
const handleScroll = () => {
checkScrollVisibility();
// Update inertial scroll position
if (orientation === "horizontal") {
setScrollTarget({
x: container.scrollLeft,
y: scrollPosition.y
}, false);
} else {
setScrollTarget({
x: scrollPosition.x,
y: container.scrollTop
}, false);
}
};
container.addEventListener("scroll", handleScroll);
return () => container.removeEventListener("scroll", handleScroll);
}, [scrollable, orientation, checkScrollVisibility]);
// Check scroll visibility on mount and when tabs change
useEffect(() => {
checkScrollVisibility();
// Reset tab state when tabs change
// Initialize with all tabs visible temporarily
setVisibleTabs(tabs);
setCollapsedTabs([]);
// Re-calculate visible tabs after a short delay to allow DOM updates
if (collapseTabs) {
const timerId = setTimeout(() => {
if (!tabsRef.current) return;
// Ensure tabRefs array is aligned with tabs array
if (tabRefs.current.length !== tabs.length) {
if (process.env.NODE_ENV === "development") {
console.warn("GlassTabBar: tabRefs length mismatch, delaying recalculation.");
}
// Optionally, could reschedule or handle this state
return;
}
// Update tab width measurements more robustly
const currentTabWidths = [];
tabs.forEach((_, index) => {
const tabRef = tabRefs.current[index];
const width = tabRef ? tabRef.getBoundingClientRect().width : 50; // Use fallback if ref missing
currentTabWidths[index] = width > 0 ? width : 50; // Use fallback if width is 0
});
tabWidthsRef.current = currentTabWidths;
const {
visibleTabs: newVisible,
hiddenTabs: newCollapsed
} = calculateVisibleTabs(tabs, tabsRef.current.clientWidth);
setVisibleTabs(newVisible);
setCollapsedTabs(newCollapsed);
}, 50); // Short delay for DOM updates
return () => clearTimeout(timerId); // Cleanup timeout on effect re-run
}
// Only include dependencies that trigger recalculation
}, [tabs, checkScrollVisibility, collapseTabs, maxVisibleTabs]);
// Create magnetic trail effect component
useCallback(() => {
if (animationStyle !== "spring" || !tabMagneticData.closestTabIndex || tabMagneticData.closestTabIndex === activeTab || tabMagneticData.selectionProgress < 0.2) {
return null;
}
const trailOpacity = Math.min(0.4, tabMagneticData.selectionProgress * 0.4);
const trailSize = 20 + tabMagneticData.selectionProgress * 30;
return jsx("div", {
style: {
position: "absolute",
pointerEvents: "none",
zIndex: 0,
width: `${trailSize}px`,
height: `${trailSize}px`,
borderRadius: "50%",
background: color === "default" ? `rgba(255, 255, 255, ${trailOpacity})` : `var(--color-${color}-transparent)`,
filter: `blur(${trailSize / 4}px)`,
opacity: trailOpacity,
transform: `translate3d(${springProps.left + springProps.width / 2 - trailSize / 2}px, ${springProps.top + springProps.height / 2 - trailSize / 2}px, 0)`,
transition: "transform 0.1s ease-out, width 0.2s ease, height 0.2s ease, opacity 0.2s ease"
}
});
}, [animationStyle, tabMagneticData, activeTab, color, springProps]);
// Filter out props that shouldn't be passed to the styled component
const filteredRestProps = Object.fromEntries(Object.entries(restProps).filter(([key]) => !["onDrag", "onDragStart", "onDragEnd", "onDragOver", "onDragEnter", "onDragLeave", "onDrop", "draggable"].includes(key)));
const containerClassName = cn("glass-tab-bar", className);
const selectorOffset = !finalDisableAnimation ? {
x: transform.translateX,
y: transform.translateY
} : undefined;
return jsxs(TabBarContainer, {
ref: tabsRef,
"$orientation": effectiveOrientation,
"$variant": variant,
"$glassVariant": glassVariant,
"$blurStrength": blurStrength,
"$elevated": elevated,
"$background": background,
"$width": effectiveWidth,
"$height": effectiveHeight,
"$borderRadius": borderRadius,
className: containerClassName,
style: style,
onMouseMove: isScrolling ? handleScrollGestureMove : magneticSelector,
onMouseLeave: isScrolling ? handleScrollGestureEnd : handleMouseLeave,
onMouseDown: handleScrollGestureStart,
onTouchStart: handleScrollGestureStart,
onTouchMove: isScrolling ? handleScrollGestureMove : undefined,
onMouseUp: isScrolling ? handleScrollGestureEnd : undefined,
onTouchEnd: isScrolling ? handleScrollGestureEnd : undefined,
role: "tablist",
"aria-label": ariaLabel,
"aria-orientation": effectiveOrientation,
...filteredRestProps,
children: [variant !== "default" && jsx(TabSelector, {
left: selectorStyle.left,
top: selectorStyle.top,
width: selectorStyle.width,
height: selectorStyle.height,
variant: variant,
orientation: effectiveOrientation,
disableAnimation: finalDisableAnimation,
offset: selectorOffset
}), animationStyle === "spring" && !finalDisableAnimation && jsx("div", {
className: 'absolute inset-0 pointer-events-none glass-radius-lg opacity-30',
children: jsx("div", {
className: 'absolute glass-radius-full glass-surface-blue/20 transition-all duration-500 ease-out',
style: {
left: `${tabMagneticData.closestTabIndex !== null ? tabMagneticData.closestTabIndex * 60 : 0}px`,
width: "60px",
height: "100%",
transform: `scale(${tabMagneticData.selectionProgress})`
}
})
}), visibleTabs.map((tab, index) => {
const isActive = index === activeTab;
// Check if this specific tab represents the "More" menu trigger
collapseTabs && collapsedTabs.length > 0 && index === visibleTabs.length - 1;
// Conditionally render using renderTab prop or default TabItemComponent
return renderTab ? jsx(React.Fragment, {
children: renderTab(tab, index, isActive)
}, `tab-${tab.value}`) : jsx(TabItemComponent, {
id: tab.id || `tab-${index}`,
label: tab.label,
icon: tab.icon,
badge: tab.badge,
disabled: tab.disabled,
active: isActive,
onClick: e => handleTabClick(e, index)
});
}), scrollable && effectiveOrientation === "horizontal" && jsx(ScrollButtons, {
onScrollLeft: () => handleScroll("left"),
onScrollRight: () => handleScroll("right"),
showLeft: showLeftScroll,
showRight: showRightScroll
}), scrollable && effectiveOrientation === "vertical" && jsx(ScrollButtons, {
onScrollLeft: () => handleScroll("up"),
onScrollRight: () => handleScroll("down"),
showLeft: showLeftScroll,
showRight: showRightScroll
}), collapseTabs && collapsedTabs.length > 0 && (renderCollapsedMenu ? renderCollapsedMenu(collapsedTabs, activeTab || 0, handleCollapsedTabSelect) : jsx(CollapsedMenu, {
items: collapsedTabs,
onItemClick: item => {
const index = collapsedTabs.indexOf(item);
handleCollapsedTabSelect(index);
}
}))]
});
});
// Add displayName for better debugging
GlassTabBar.displayName = "GlassTabBar";
export { GlassTabBar, GlassTabBar as default };
//# sourceMappingURL=GlassTabBar.js.map