Expandable Panel
ericts-ui/expandable-panelFreeComponent
A floating panel that grows from a compact icon trigger and holds arbitrary content.
Live preview
live · rendered by the registry
Rendered by ericts-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.ericts.com/r/expandable-panel.jsonSource
1"use client";23import { ChevronsUpDown } from "lucide-react";4import { AnimatePresence, motion, useReducedMotion } from "motion/react";5import * as React from "react";67import { cn } from "@/lib/utils";89const PANEL_TRANSITION = {10 type: "spring",11 duration: 0.24,12 bounce: 0,13} as const;14const CONTENT_TRANSITION = {15 duration: 0.2,16 ease: [0.16, 1, 0.3, 1],17} as const;18const REDUCED_TRANSITION = { duration: 0 } as const;19// Single source of truth for the panel's height cap. Applied to the content20// region so consumers can add a scroll area / pinned footer without knowing it.21const CONTENT_MAX_HEIGHT =22 "var(--expandable-panel-max-height, min(15.5rem, calc(100dvh - 8rem)))";2324type ExpandablePanelContextValue = {25 open: boolean;26 setOpen: (open: boolean) => void;27};2829const ExpandablePanelContext =30 React.createContext<ExpandablePanelContextValue | null>(null);3132/**33 * Access the expanding panel's open state from within its `children`, e.g. to34 * collapse the panel after an action. Throws when used outside the component.35 */36export function useExpandablePanel() {37 const context = React.useContext(ExpandablePanelContext);3839 if (!context) {40 throw new Error(41 "useExpandablePanel must be used within an <ExpandablePanel>.",42 );43 }4445 return context;46}4748export type ExpandablePanelClassNames = {49 panel?: string;50 content?: string;51 trigger?: string;52};5354export type ExpandablePanelProps = Omit<55 React.ComponentProps<"aside">,56 "children"57> & {58 open?: boolean;59 defaultOpen?: boolean;60 onOpenChange?: (open: boolean) => void;61 triggerIcon?: React.ReactNode;62 openLabel?: string;63 closeLabel?: string;64 closeOnEscape?: boolean;65 /**66 * Collapse when a pointer goes down outside the panel. Content that portals67 * outside the panel's subtree (Select, Popover, Dialog) registers as68 * "outside"; set this to false and close manually in that case.69 */70 closeOnOutsideClick?: boolean;71 classNames?: ExpandablePanelClassNames;72 children?: React.ReactNode;73};7475/**76 * A floating panel that grows in width and height from a compact icon trigger77 * in its top-right corner. It is intentionally content-agnostic.78 *79 * The panel is bounded by `--expandable-panel-width` and80 * `--expandable-panel-max-height` and clips overflow, so the morph never runs81 * off-screen. The content region is a flex column capped at that height, so82 * tall content can add a `flex-1` scroll area (and, e.g., a `shrink-0` pinned83// … truncated
What it pulls in
npm packages
Files it writes
More from ericts-ui
All 43 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Adaptive Draweradaptive-drawer | ericts-ui | Components | Free | 2 deps | |
| Adaptive Switchadaptive-switch | ericts-ui | Components | Free | 2 deps | |
| Check Markcheck-mark | ericts-ui | Components | Free | no deps · 2 files | |
| Context Cursorcontext-cursor | ericts-ui | Components | Free | 1 dep | |
| Copy Buttoncopy-button | ericts-ui | Components | Free | 2 deps | |
| Expandable Dialogexpandable-dialog | ericts-ui | Components | Free | 1 dep | |
| Expandable Segmented Tabsexpandable-segmented-tabs | ericts-ui | Components | Free | 1 dep | |
| Expandable Sliderexpandable-slider | ericts-ui | Components | Free | no deps |
