Icon Bar
nexvyn/icon-barFreeComponent
A horizontal toolbar of icon buttons with bloom-open label reveal on hover/selection, accent dot indicator, and roving tabindex.
Live preview
live · rendered by the registry
Rendered by nexvyn on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.nexvyn.dev/r/icon-bar.jsonSource
1'use client'23import {4 createContext,5 forwardRef,6 useCallback,7 useContext,8 useEffect,9 useMemo,10 useRef,11 useState,12 type HTMLAttributes,13 type ReactNode,14} from 'react'15import { useReducedMotion } from 'motion/react'16import { cn } from '@/lib/utils'17import { durations } from '@/lib/motion-tokens'18import { WeightShiftText } from '@/components/ui/weight-shift-text'1920interface IconBarContextValue {21 value: string | null22 setValue: (v: string | null) => void2324 rovingIndex: number25 setRovingIndex: (i: number) => void26}2728const IconBarContext = createContext<IconBarContextValue | null>(null)2930function useIconBarCtx(componentName: string) {31 const ctx = useContext(IconBarContext)32 if (!ctx) throw new Error(`${componentName} must be used within IconBar`)33 return ctx34}3536export interface IconBarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'defaultValue'> {37 children: ReactNode38 value?: string | null39 defaultValue?: string | null40 onValueChange?: (v: string | null) => void41 'aria-label'?: string42}4344export const IconBar = forwardRef<HTMLDivElement, IconBarProps>(45 (46 {47 children,48 value: valueProp,49 defaultValue = null,50 onValueChange,51 'aria-label': ariaLabel = 'Toolbar',52 className,53 ...props54 },55 ref,56 ) => {57 const isControlled = valueProp !== undefined58 const [internalValue, setInternalValue] = useState(defaultValue)59 const value = isControlled ? valueProp : internalValue6061 const setValue = useCallback(62 (v: string | null) => {63 if (!isControlled) setInternalValue(v)64 onValueChange?.(v)65 },66 [isControlled, onValueChange],67 )6869 const [rovingIndex, setRovingIndex] = useState(0)70 const barRef = useRef<HTMLDivElement>(null)7172 const handleKeyDown = useCallback((e: React.KeyboardEvent<HTMLDivElement>) => {73 const bar = barRef.current74 if (!bar) return75 const buttons = Array.from(76 bar.querySelectorAll<HTMLElement>('[role="toolbar"] > button:not([disabled])'),77 )78 if (!buttons.length) return7980 const isRTL = bar.ownerDocument.defaultView?.getComputedStyle(bar).direction === 'rtl'81 const count = buttons.length82 const currentIdx = buttons.indexOf(document.activeElement as HTMLElement)8384 let nextIdx = currentIdx8586 if (e.key === 'ArrowRight') {87 e.preventDefault()88 nextIdx = isRTL ? (currentIdx <= 0 ? count - 1 : currentIdx - 1) : (currentIdx + 1) % count89// … truncated
What it pulls in
npm packages
Files it writes
More from nexvyn
All 40 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | nexvyn | Components | Free | 1 dep | |
| Action Buttonaction-button | nexvyn | Components | Free | 3 deps · 4 files | |
| Adaptive Actionsadaptive-actions | nexvyn | Components | Free | 1 dep | |
| AI Inputai-input | nexvyn | Components | Free | 1 dep · 2 files | |
| Badgebadge | nexvyn | Components | Free | 2 deps · 2 files | |
| Bars Themebars-theme | nexvyn | Components | Free | 1 dep · 3 files | |
| Bounce Sidebarbounce-sidebar | nexvyn | Components | Free | 1 dep · 2 files | |
| Breadcrumbsbreadcrumbs | nexvyn | Components | Free | 1 dep · 2 files |
