Expandable Dialog
ericts-ui/expandable-dialogFreeComponent
A shared-layout dialog that expands list items into an accessible detail view.
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-dialog.jsonSource
1"use client";23import * as React from "react";4import {5 AnimatePresence,6 LayoutGroup,7 motion,8 useReducedMotion,9} from "motion/react";1011import { cn } from "@/lib/utils";1213export type ExpandableDialogItem = {14 id: string;15 title: string;16 description: string;17 content: React.ReactNode;18 image: string;19 imageAlt?: string;20 actionLabel?: string;21};2223export type ExpandableDialogProps = Omit<24 React.ComponentProps<"div">,25 "children" | "defaultValue" | "onChange" | "value"26> & {27 items: readonly ExpandableDialogItem[];28 /** Id of the expanded item, or null when collapsed. */29 value?: string | null;30 defaultValue?: string | null;31 /** Receives the expanded item's id, plus the item itself for convenience. */32 onValueChange?: (33 id: string | null,34 item: ExpandableDialogItem | null,35 ) => void;36 onAction?: (item: ExpandableDialogItem) => void;37 actionLabel?: string;38 modalLabel?: string;39 listClassName?: string;40 itemClassName?: string;41};424344/** How long a click waits for image decode before opening regardless. */45const DECODE_BUDGET_MS = 200;4647function wait(duration: number) {48 return new Promise<void>((resolve) => {49 window.setTimeout(resolve, duration);50 });51}5253const actionButtonClassName =54 "inline-flex h-7 shrink-0 items-center justify-center rounded-[min(var(--radius-md),12px)] border border-transparent bg-primary bg-clip-padding px-2.5 pt-px text-[0.8rem] leading-[1.4285714286] font-medium whitespace-nowrap text-primary-foreground outline-none transition-colors hover:bg-primary/80 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background";5556export function ExpandableDialog({57 items,58 value,59 defaultValue = null,60 onValueChange,61 onAction,62 actionLabel = "Open",63 modalLabel,64 className,65 listClassName,66 itemClassName,67 ...props68}: ExpandableDialogProps) {69 const shouldReduceMotion = useReducedMotion();70 const reactId = React.useId();71 const titleId = `${reactId}-title`;72 const descriptionId = `${reactId}-description`;73 const dialogRef = React.useRef<HTMLDivElement>(null);74 const previouslyFocusedElement = React.useRef<HTMLElement | null>(null);75 const imageDecodePromises = React.useRef(new Map<string, Promise<void>>());76 const decodedImages = React.useRef(new Set<string>());77 const openRequestId = React.useRef(0);78 const isControlled = value !== undefined;79 const [uncontrolledValue, setUncontrolledValue] = React.useState<80 string | null81// … 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 Panelexpandable-panel | ericts-ui | Components | Free | 2 deps | |
| Expandable Segmented Tabsexpandable-segmented-tabs | ericts-ui | Components | Free | 1 dep | |
| Expandable Sliderexpandable-slider | ericts-ui | Components | Free | no deps |
