Morphing Tabs
beui/morphing-tabsFreeBlock
Reorderable tabs whose selected item grows into a white content surface, with the active shape gliding as tabs move and a shared morph between rooms.
Install it
npx shadcn@latest add https://beui.dev/r/morphing-tabs.jsonSource
1"use client";2// beui.dev/components/blocks/morphing-tabs34import { X } from "lucide-react";5import {6 AnimatePresence,7 animate as animateValue,8 motion,9 useMotionValue,10 useReducedMotion,11 useSpring,12 useTransform,13 type MotionValue,14} from "motion/react";15import {16 useCallback,17 useEffect,18 useId,19 useLayoutEffect,20 useMemo,21 useRef,22 useState,23 type PointerEvent as ReactPointerEvent,24 type ReactNode,25} from "react";26import { createPortal } from "react-dom";27import { EASE_OUT, SPRING_GLIDE, SPRING_PRESS } from "@/lib/ease";28import { cn } from "@/lib/utils";2930export type MorphingTabsItem = {31 id: string;32 label: string;33 icon?: ReactNode;34 content: ReactNode;35 disabled?: boolean;36};3738export type MorphingTabsClassNames = {39 root?: string;40 rail?: string;41 tab?: string;42 activeTab?: string;43 icon?: string;44 label?: string;45 close?: string;46 content?: string;47};4849export interface MorphingTabsProps {50 items: MorphingTabsItem[];51 value?: string | null;52 defaultValue?: string | null;53 onValueChange?: (id: string | null) => void;54 /** Called once after a pointer drag or keyboard reorder completes. */55 onOrderChange?: (ids: string[]) => void;56 /** Enables the close affordance on every tab when provided. */57 onClose?: (id: string) => void;58 ariaLabel?: string;59 className?: string;60 classNames?: MorphingTabsClassNames;61}6263type DragSession = {64 id: string;65 pointerId: number;66 originX: number;67 startLeft: number;68 startIndex: number;69 targetIndex: number;70 moved: boolean;71 finishing: boolean;72 startOrder: string[];73 slotLefts: number[];74};7576type SpringTabProps = {77 id: string;78 targetLeft: number;79 dragging: boolean;80 dragLeft: MotionValue<number>;81 surfaceLeft: MotionValue<number>;82 reduce: boolean;83 active: boolean;84 anyDragging: boolean;85 surfaceHost: HTMLDivElement | null;86 surfaceWidth: number;87 surfaceClassName?: string;88 zIndex: number;89 className: string;90 children: ReactNode;91 registerPosition: (id: string, position: MotionValue<number> | null) => void;92 onPointerDown: (event: ReactPointerEvent<HTMLDivElement>) => void;93 onPointerMove: (event: ReactPointerEvent<HTMLDivElement>) => void;94 onPointerUp: (event: ReactPointerEvent<HTMLDivElement>) => void;95 onPointerCancel: (event: ReactPointerEvent<HTMLDivElement>) => void;96 onLostPointerCapture: (event: ReactPointerEvent<HTMLDivElement>) => void;97};9899const DRAG_THRESHOLD = 5;100const TAB_WIDTH = 176;101const TAB_HEIGHT = 56;102// … truncated
What it pulls in
npm packages
Files it writes
More from beui
All 104 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| File Upload Attachment Uploadattachment-upload | beui | Blocks | Free | 4 deps · 5 files | |
| Availability Scheduleravailability-scheduler | beui | Blocks | Free | 4 deps · 15 files | |
| Bloom Menubloom-menu | beui | Blocks | Free | 4 deps · 3 files | |
| Command Palettecommand-palette | beui | Blocks | Free | 4 deps · 3 files | |
| Dynamic Islanddynamic-island | beui | Blocks | Free | 3 deps · 3 files | |
| Expandable Action Barexpandable-action-bar | beui | Blocks | Free | 3 deps · 2 files | |
| Expandable Tabsexpandable-tabs | beui | Blocks | Free | 3 deps · 3 files | |
| Feedback Widgetfeedback-widget | beui | Blocks | Free | 4 deps · 9 files |
