# @mks2508/sidebar-headless

Headless sidebar component for React with advanced animations, keyboard navigation, and full WAI-ARIA accessibility.

[![npm version](https://img.shields.io/npm/v/@mks2508/sidebar-headless.svg)](https://www.npmjs.com/package/@mks2508/sidebar-headless)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

## Features

- 🎨 **Headless Architecture** - Complete control over styling and behavior
- ⌨️ **Keyboard Navigation** - Full arrow key, Home/End support
- ♿ **WAI-ARIA Compliant** - Full accessibility out of the box
- 🎭 **Advanced Animations** - Fluid hover indicators, glassmorphism effects, 3D transforms
- 📱 **Mobile-First Bottom Navigation** - iOS 26 Safari compatible, safe area support
- 🎯 **TypeScript First** - Full type safety with comprehensive declarations
- 🪶 **Lightweight** - Minimal dependencies (only clsx + tailwind-merge)
- 🎨 **Dark/Light Mode** - Automatic theme support via CSS tokens
- 🔧 **Highly Configurable** - Extensive customization options

## Installation

```bash
npm install @mks2508/sidebar-headless
# or
yarn add @mks2508/sidebar-headless
# or
pnpm add @mks2508/sidebar-headless
# or
bun add @mks2508/sidebar-headless
```

### Peer Dependencies

This package requires React 18+ and Framer Motion:

```bash
npm install react react-dom motion
```

### Optional Dependencies

For advanced liquid glass effects (optional):

```bash
npm install @liquid-svg-glass/core @liquid-svg-glass/react
```

## Quick Start

```tsx
import {
  Sidebar,
  SidebarNav,
  SidebarToggle,
  SidebarContent,
  SidebarItem,
  SidebarIconLibrary,
  MobileBottomNav,
  NavVariant,
  NavItemState
} from '@mks2508/sidebar-headless'
import '@mks2508/sidebar-headless/styles.css'

function App() {
  return (
    <>
      {/* Desktop Sidebar */}
      <Sidebar defaultOpen={true}>
        <SidebarNav>
          <SidebarToggle iconLibrary={SidebarIconLibrary.LUCIDE} />
          <SidebarContent>
            <SidebarItem href="/" icon={<HomeIcon />} label="Home" />
            <SidebarItem href="/settings" icon={<SettingsIcon />} label="Settings" />
          </SidebarContent>
        </SidebarNav>
      </Sidebar>

      {/* Mobile Bottom Navigation */}
      <MobileBottomNav.Root variant={NavVariant.GLASS}>
        <MobileBottomNav.NavList>
          <MobileBottomNav.NavItem
            icon={<HomeIcon />}
            label="Home"
            state={NavItemState.ACTIVE}
          />
          <MobileBottomNav.NavItem
            icon={<SearchIcon />}
            label="Search"
          />
        </MobileBottomNav.NavList>
      </MobileBottomNav.Root>
    </>
  )
}
```

## Mobile Bottom Navigation

### MobileBottomNav (Mobile Navigation)

A mobile-optimized bottom navigation component with slot architecture and iOS 26 compatibility.

```tsx
<MobileBottomNav.Root
  variant={NavVariant.GLASS}
  size={NavSize.MEDIUM}
  visible={true}
>
  <MobileBottomNav.NavList>
    <MobileBottomNav.NavItem
      icon={<HomeIcon />}
      label="Home"
      state={NavItemState.ACTIVE}
      onClick={() => navigate('/home')}
    />
    <MobileBottomNav.NavItem
      icon={<SearchIcon />}
      label="Search"
      badge={5}
    />
  </MobileBottomNav.NavList>
</MobileBottomNav.Root>
```

### Navigation Variants

#### Glass Variant (iOS 26 Compatible)
```tsx
<MobileBottomNav.Root
  variant={NavVariant.GLASS}
  glassConfig={{
    blur: BlurIntensity.MEDIUM,
    opacity: 0.72,
    borderOpacity: 0.18
  }}
/>
```

#### Solid Variant
```tsx
<MobileBottomNav.Root
  variant={NavVariant.SOLID}
/>
```

#### Headless Variant (Custom Styling)
```tsx
<MobileBottomNav.Root
  variant={NavVariant.HEADLESS}
  className="custom-nav-style"
/>
```

### Mobile Bottom Nav Features

- **iOS 26 Safari Compatible**: Fixes viewport bugs with address bar
- **Safe Area Support**: Handles notched devices (iPhone X+)
- **Glassmorphism Effects**: Beautiful blur and transparency
- **Motion Animations**: Spring and tween animations with Framer Motion
- **Accessibility**: Full WAI-ARIA support with keyboard navigation
- **Badge Support**: Show notifications and counts
- **Size Variants**: SMALL (56px), MEDIUM (72px), LARGE (88px)
- **Label Positions**: BELOW, BESIDE, HIDDEN
- **Touch Optimized**: WCAG 2.2 compliant touch targets (44px+)

### Mobile Navigation APIs

#### Root Props
```tsx
interface RootProps {
  variant?: NavVariant
  size?: NavSize
  visible?: boolean
  glassConfig?: GlassConfig
  animationConfig?: AnimationConfig
  rounded?: NavBorderRadius | number
  full?: boolean
  gap?: boolean
  zIndex?: ZIndexLevel | number
}
```

#### Nav Item Props
```tsx
interface NavItemProps {
  icon: ReactNode
  label: string
  state?: NavItemState
  onClick?: () => void
  href?: string
  badge?: number | boolean
  disabled?: boolean
  labelPosition?: LabelPosition
  iconSize?: IconSize | number
}
```

#### Mobile Utilities
```tsx
// Spacer to prevent content overlap
<MobileBottomNav.Spacer size={NavSize.MEDIUM} />

// iOS Safari fix utilities
import { 
  useIOSSafariFix, 
  useIOSFixedReset, 
  detectIOSVersion 
} from '@mks2508/sidebar-headless'
```

### iOS Safari Compatibility Hooks

#### `useIOSSafariFix()` - iOS Detection
```tsx
const { iosInfo, hasViewportBugs } = useIOSSafariFix({
  enableScrollFix: true,
  enableKeyboardFix: true,
})

interface IOSInfo {
  isIOS: boolean
  majorVersion: number | null
  hasViewportBugs: boolean
  device: 'iphone' | 'ipad' | 'unknown'
  isStandalone: boolean
}
```

#### `useIOSFixedReset()` - Position Reset
```tsx
const ref = useRef<HTMLElement>(null)
const { forceReset } = useIOSFixedReset(ref)

// Call when keyboard dismisses or viewport changes
useEffect(() => {
  if (keyboardDismissed) {
    forceReset()
  }
}, [keyboardDismissed])
```

#### `detectIOSVersion()` - Version Detection
```tsx
const version = detectIOSVersion()
// Returns: { major: 26, minor: 0, patch: 1, full: "26.0.1" }
```

### iOS 26 Safari Known Issues & Fixes

#### Issues Fixed:
- **Address bar shifting**: Fixed elements drift when URL bar hides
- **Keyboard dismissal**: VisualViewport doesn't reset after keyboard
- **100dvh gaps**: Dynamic viewport units don't account for floating tab bar

#### Automatic Fixes Applied:
- Hardware acceleration (`translateZ(0)`)
- Scroll containment (`contain: layout style`)
- Proper stacking context creation
- CSS custom properties for safe areas
- Responsive design with fallbacks

#### Manual Fixes Available:
```tsx
// Enable debug mode to see iOS detection
<MobileBottomNav.Root 
  data-ios-debug="true"
  variant={NavVariant.GLASS}
>
```

### Advanced Configuration

#### Animation Configuration
```tsx
<MobileBottomNav.Root
  animationConfig={{
    type: AnimationType.SPRING,
    pressScale: 0.95,
    hoverScale: 1.05,
    spring: {
      stiffness: 400,
      damping: 30,
    },
  }}
/>
```

#### Glass Configuration
```tsx
<MobileBottomNav.Root
  variant={NavVariant.GLASS}
  glassConfig={{
    blur: BlurIntensity.HEAVY, // or number (px)
    opacity: 0.8,            // 0-1
    saturation: 180,         // 100-200%
    borderOpacity: 0.15,     // 0-1
  }}
/>
```

#### Responsive Design
```tsx
// Size variants affect height and touch targets
<MobileBottomNav.Root size={NavSize.SMALL}>  {/* 56px */}
<MobileBottomNav.Root size={NavSize.MEDIUM}> {/* 72px */}
<MobileBottomNav.Root size={NavSize.LARGE}> {/* 88px */}

// Custom border radius
<MobileBottomNav.Root rounded={1.5} /> {/* rem */}
<MobileBottomNav.Root rounded={NavBorderRadius.ROUNDED} />
```

## Core Components

### Sidebar (Root Provider)

The main container that provides context to all child components.

```tsx
<Sidebar
  defaultOpen={true}
  collapseMode="hide"
  layoutBehaviour="floating"
>
  {/* ... */}
</Sidebar>
```

### SidebarNav

Navigation container with event management.

```tsx
<SidebarNav>
  <SidebarToggle />
  <SidebarContent>
    {/* items */}
  </SidebarContent>
</SidebarNav>
```

### SidebarItem

Individual navigation item with optional sub-content.

```tsx
<SidebarItem
  href="/dashboard"
  icon={<DashboardIcon />}
  label="Dashboard"
>
  <SidebarSubContent title="Dashboard">
    <SidebarSubLink href="/analytics">Analytics</SidebarSubLink>
    <SidebarSubLink href="/reports">Reports</SidebarSubLink>
  </SidebarSubContent>
</SidebarItem>
```

## Configuration Options

### Collapse Modes

```tsx
import { SidebarCollapseMode } from '@mks2508/sidebar-headless'

// Collapse to icon-only width
<Sidebar collapseMode={SidebarCollapseMode.COLLAPSE} />

// Hide completely
<Sidebar collapseMode={SidebarCollapseMode.HIDE} />
```

### Layout Behaviours

```tsx
import { SidebarLayoutBehaviour } from '@mks2508/sidebar-headless'

// Floating sidebar (doesn't push content)
<Sidebar layoutBehaviour={SidebarLayoutBehaviour.FLOATING} />

// Inline sidebar (pushes content)
<Sidebar layoutBehaviour={SidebarLayoutBehaviour.INLINE} />
```

### Custom Dimensions

```tsx
<Sidebar
  dimensions={{
    collapsedWidth: "4rem",
    expandedWidth: "18rem",
    indicatorHeight: "3rem",
    tooltipDistance: "16px"
  }}
/>
```

## Advanced Features

### Fluid Indicators

Enable glassmorphism-style hover indicators:

```tsx
<Sidebar
  enableFluidIndicator={true}
  liquidGlass={{
    enableLiquidGlassV2: true,
    enableChromaticAberration: true
  }}
/>
```

### Keyboard Navigation

Built-in keyboard shortcuts:

- **Arrow Up/Down**: Navigate between items
- **Home**: Jump to first item
- **End**: Jump to last item
- **Enter**: Activate focused item

### Render Props

Access sidebar state via render props:

```tsx
<Sidebar>
  {({ open, collapsed }) => (
    <div>
      <SidebarNav />
      <p>Sidebar is {open ? 'open' : 'closed'}</p>
    </div>
  )}
</Sidebar>
```

## Styling

The sidebar uses shadcn-style CSS tokens for theming:

```css
:root {
  --sidebar: oklch(98% 0 0);
  --sidebar-foreground: oklch(20% 0 0);
  --sidebar-primary: oklch(55% 0.2 250);
  --sidebar-accent: oklch(95% 0.01 250);
  --sidebar-border: oklch(90% 0.01 250);
  --sidebar-ring: oklch(55% 0.2 250);
}

.dark {
  --sidebar: oklch(17% 0 0);
  --sidebar-foreground: oklch(90% 0 0);
  /* ... */
}
```

## TypeScript Support

Full TypeScript support with comprehensive type definitions:

```tsx
import type {
  SidebarProps,
  SidebarItemProps,
  SidebarContextValue
} from '@mks2508/sidebar-headless'
```

## Performance & Troubleshooting

### Performance Tips

#### Glass Variant Performance
```tsx
// Reduce blur for better performance on older devices
<MobileBottomNav.Root
  variant={NavVariant.GLASS}
  glassConfig={{
    blur: BlurIntensity.LIGHT,  // 10px vs 20px
    saturation: 150,             // Less saturation
  }}
/>
```

#### Animation Performance
```tsx
// Disable animations for reduced motion
const prefersReduced = useMediaQuery('(prefers-reduced-motion: reduce)')

<MobileBottomNav.Root
  animationConfig={{
    type: prefersReduced ? AnimationType.NONE : AnimationType.SPRING,
  }}
/>
```

#### Bundle Size Optimization
```tsx
// Import only what you need
import { 
  MobileBottomNav, 
  NavVariant, 
  NavItemState 
} from '@mks2508/sidebar-headless'

// Tree-shake CSS imports
import '@mks2508/sidebar-headless/mobile-optimizations.css'
```

### Common Issues & Solutions

#### iOS Safari Issues
```tsx
// Problem: Navigation shifts when scrolling
// Solution: Enable scroll fix
const { hasViewportBugs } = useIOSSafariFix({ 
  enableScrollFix: true 
})

// Problem: Navigation offset after keyboard
// Solution: Force reset
const { forceReset } = useIOSFixedReset(navRef)
useEffect(() => {
  if (keyboardDismissed) forceReset()
}, [keyboardDismissed])
```

#### Touch Target Issues
```tsx
// Problem: Touch targets too small
// Solution: Use larger size or custom styling
<MobileBottomNav.Root size={NavSize.LARGE} /> // 88px

// Or custom minimum touch targets
<MobileBottomNav.NavItem
  className="min-touch-target" // CSS: min-width: 48px; min-height: 48px;
/>
```

#### Animation Conflicts
```tsx
// Problem: Conflicts with parent animations
// Solution: Use NONE animation type
<MobileBottomNav.Root
  animationConfig={{
    type: AnimationType.NONE,
  }}
/>
```

### Debug Mode

Enable debug mode to see internal state and iOS detection:
```tsx
<MobileBottomNav.Root
  data-ios-debug="true"
  data-mobile-nav-debug="true"
  variant={NavVariant.GLASS}
>
```

## Browser Support

- Chrome/Edge (latest)
- Firefox (latest)
- Safari (latest) - Full iOS 26 Safari compatibility
- Modern browsers with ES2020 support

## Mobile Compatibility

- ✅ iOS 15+ (including iOS 26 Safari viewport fixes)
- ✅ Android 8+
- ✅ Safe area support (iPhone X+ notched devices)
- ✅ Foldable devices support
- ✅ Touch gesture navigation
- ✅ Keyboard dismissal handling

### Device-Specific Notes

#### iPhone X and newer (with notch)
- Safe area padding automatically applied via CSS `env()`
- No additional configuration needed
- Works with both portrait and landscape orientations

#### iPad with Home Indicator
- Bottom safe area respected automatically
- Optimal touch targets for finger navigation

#### Foldable Devices (Samsung Galaxy Fold, etc.)
- Uses `horizontal-viewport-segments` CSS media query
- Automatic layout adjustment for fold position

#### Android Devices
- Material Design navigation patterns compatible
- Back gesture handling respected
- Dynamic viewport units supported

## API Reference

### MobileBottomNav Components

#### `MobileBottomNav.Root`
Main container for mobile bottom navigation.

**Props:** `RootProps`
```tsx
<MobileBottomNav.Root
  variant={NavVariant.GLASS}
  size={NavSize.MEDIUM}
  visible={true}
  glassConfig={glassConfig}
  animationConfig={animationConfig}
  rounded={NavBorderRadius.ROUNDED}
  full={false}
  gap={true}
  zIndex={50}
  aria-label="Main navigation"
  data-testid="mobile-nav"
  className="custom-nav"
>
  {children}
</MobileBottomNav.Root>
```

#### `MobileBottomNav.NavList`
Container for navigation items with flexbox layout.

**Props:** `NavListProps`
```tsx
<MobileBottomNav.NavList
  justify="around"  // "start" | "center" | "end" | "around" | "between"
  gap={1}         // number (rem)
  className="nav-list"
>
  {children}
</MobileBottomNav.NavList>
```

#### `MobileBottomNav.NavItem`
Individual navigation item with icon, label, and optional badge.

**Props:** `NavItemProps`
```tsx
<MobileBottomNav.NavItem
  icon={<HomeIcon />}
  label="Home"
  state={NavItemState.ACTIVE}
  onClick={() => navigate('/home')}
  href="/home"                    // Optional: renders as <a>
  badge={5}                       // number | boolean
  disabled={false}
  labelPosition={LabelPosition.BELOW}
  iconSize={IconSize.MEDIUM}
  aria-label="Go to home"
  data-testid="nav-item-home"
  className="nav-item"
/>
```

#### `MobileBottomNav.Spacer`
Invisible spacer to prevent content from being hidden under navigation.

**Props:** `SpacerProps`
```tsx
<MobileBottomNav.Spacer
  size={NavSize.MEDIUM}
  className="content-spacer"
/>
```

### Enums & Types

#### `NavVariant`
```tsx
enum NavVariant {
  GLASS = "glass",      // Glassmorphism with blur
  SOLID = "solid",      // Solid background
  HEADLESS = "headless" // No default styles
}
```

#### `NavSize`
```tsx
enum NavSize {
  SMALL = "sm",  // 56px height
  MEDIUM = "md", // 72px height
  LARGE = "lg"   // 88px height
}
```

#### `NavItemState`
```tsx
enum NavItemState {
  INACTIVE = "inactive",
  ACTIVE = "active",
  DISABLED = "disabled"
}
```

#### `LabelPosition`
```tsx
enum LabelPosition {
  BELOW = "below",   // Icon above text
  BESIDE = "beside",  // Icon beside text
  HIDDEN = "hidden"   // Icon only
}
```

#### `IconSize`
```tsx
enum IconSize {
  SMALL = 18,   // 18px
  MEDIUM = 24,  // 24px
  LARGE = 28    // 28px
}
```

#### `BlurIntensity`
```tsx
enum BlurIntensity {
  LIGHT = 10,   // 10px blur
  MEDIUM = 20,  // 20px blur
  HEAVY = 30    // 30px blur
}
```

### Configuration Interfaces

#### `GlassConfig`
```tsx
interface GlassConfig {
  blur: BlurIntensity | number     // Blur amount in pixels
  saturation: number                // 100-200%
  opacity: number                  // 0-1
  borderOpacity: number             // 0-1
}
```

#### `AnimationConfig`
```tsx
interface AnimationConfig {
  type: AnimationType              // SPRING | TWEEN | NONE
  pressScale?: number              // Scale when pressed (0-1)
  hoverScale?: number              // Scale when hovered (>1)
  spring?: SpringConfig            // Spring physics
  tween?: TweenConfig              // Tween animation
}

interface SpringConfig {
  stiffness?: number               // Spring stiffness (100-1000)
  damping?: number                 // Spring damping (10-100)
}

interface TweenConfig {
  duration?: number                // Duration in seconds (0.1-2)
  ease?: string                   // CSS easing function
}
```

#### `IOSSafariFixConfig`
```tsx
interface IOSSafariFixConfig {
  enableScrollFix?: boolean        // Fix scroll-induced drift
  enableKeyboardFix?: boolean     // Fix keyboard dismissal issues
  debug?: boolean                // Enable debug logging
}
```

## License

MIT © MKS2508

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## Links

- [GitHub Repository](https://github.com/mks2508/sidebar-headless)
- [Report Issues](https://github.com/mks2508/sidebar-headless/issues)
- [npm Package](https://npmjs.com/package/@mks2508/sidebar-headless)
