@epigie/react-wheel-nav
v0.1.2
Published
A generic animated wheel navigation component for React.
Maintainers
Readme
@epigie/react-wheel-nav
Animated wheel navigation for React apps: an infinite-feeling vertical scroll list with center highlight, snap-to-center behavior, active route styling, per-item color theming, reduced-motion support, and an optional locked overlay.
Install
pnpm add @epigie/react-wheel-nav framer-motion lucide-reactreact and react-dom are peer dependencies. The package supports React 18 and React 19.
Tailwind Setup
The component is styled with Tailwind utility classes. Add the package output to your Tailwind content sources so the classes are included in your app CSS.
Tailwind v4 (CSS-first config)
/* app.css */
@import "tailwindcss";
@source "../node_modules/@epigie/react-wheel-nav/dist";Tailwind v3 (JS config)
// tailwind.config.ts
import type { Config } from 'tailwindcss'
export default {
content: [
'./src/**/*.{ts,tsx}',
'./node_modules/@epigie/react-wheel-nav/dist/**/*.{mjs,cjs}',
],
} satisfies ConfigOptional CSS variables
The component reads these CSS variables, with sensible fallbacks if you don't set them:
:root {
--wheel-nav-active-color: var(--primary); /* color of centered/active item; per-item `color` prop overrides */
--wheel-nav-muted-color: var(--text-secondary); /* color of non-centered items */
--wheel-nav-lock-bg: color-mix(in srgb, var(--background) 40%, transparent); /* background for the locked overlay */
}API
import type { ComponentType } from 'react'
type WheelNavItem = {
href: string
label: string
icon: ComponentType<{ className?: string; strokeWidth?: number }>
color?: string // overrides --wheel-nav-active-color for this item's centered/hover state
}
type WheelNavProps = {
items: WheelNavItem[]
activeHref?: string
onNavigate: (href: string) => void
locked?: boolean
onLockedNavigate?: () => void
className?: string
orientation?: 'vertical' | 'horizontal' // default: 'vertical'
itemHeight?: number
visibleItems?: number
repeatCount?: number
}Next App Router
'use client'
import { BookOpen, LayoutDashboard, MessageCircle, User, Users } from 'lucide-react'
import { usePathname, useRouter } from 'next/navigation'
import { WheelNav, type WheelNavItem } from '@epigie/react-wheel-nav'
const items: WheelNavItem[] = [
{ href: '/home', label: 'Home', icon: LayoutDashboard },
{ href: '/companions', label: 'Companions', icon: Users },
{ href: '/bookings', label: 'Bookings', icon: BookOpen },
{ href: '/chats', label: 'Chats', icon: MessageCircle },
{ href: '/me', label: 'Me', icon: User },
]
export function AppWheelNav({ authenticated }: { authenticated: boolean }) {
const pathname = usePathname()
const router = useRouter()
return (
<WheelNav
items={items}
activeHref={pathname}
locked={!authenticated}
onNavigate={(href) => router.push(href)}
onLockedNavigate={() => router.push('/auth/login')}
/>
)
}Vite React
import { BookOpen, LayoutDashboard, MessageCircle, User, Users } from 'lucide-react'
import { useLocation, useNavigate } from 'react-router-dom'
import { WheelNav, type WheelNavItem } from '@epigie/react-wheel-nav'
const items: WheelNavItem[] = [
{ href: '/home', label: 'Home', icon: LayoutDashboard },
{ href: '/companions', label: 'Companions', icon: Users },
{ href: '/bookings', label: 'Bookings', icon: BookOpen },
{ href: '/chats', label: 'Chats', icon: MessageCircle },
{ href: '/me', label: 'Me', icon: User },
]
export function AppWheelNav() {
const location = useLocation()
const navigate = useNavigate()
return (
<WheelNav
items={items}
activeHref={location.pathname}
onNavigate={navigate}
/>
)
}Build
pnpm typecheck
pnpm lint
pnpm build
pnpm pack:dryStorybook
Run Storybook to preview the wheel nav in isolation:
pnpm storybookThen open:
http://localhost:6006The included stories cover the default navigation, a different active route, the locked overlay, and compact sizing.
Publish with:
pnpm publishPublic access is set via publishConfig in package.json, so no --access flag is needed.
