Expandable Card
navdeep-singh/expandable-cardFreeComponent
Composable card-dialog primitives with accessible modal behavior, controlled state, shared media motion, and custom actions.
Install it
npx shadcn@latest add https://ui.navdeepsingh.dev/r/expandable-card.jsonSource
1'use client'23import * as React from 'react'4import { XIcon } from 'lucide-react'5import { Dialog as DialogPrimitive } from 'radix-ui'6import {7 AnimatePresence,8 LayoutGroup,9 motion,10 useReducedMotion,11 type Transition,12} from 'motion/react'13import { useControllableState } from '@radix-ui/react-use-controllable-state'1415import { Button } from '@/components/ui/button'16import { cn } from '@/lib/utils'1718type ExpandableCardContextValue = {19 open: boolean20 cardLayoutId: string21 mediaLayoutId: string22}2324const ExpandableCardContext = React.createContext<ExpandableCardContextValue | null>(null)2526const expandableCardTransition: Transition = {27 type: 'spring',28 stiffness: 220,29 damping: 30,30 mass: 0.8,31}3233function useExpandableCard() {34 const context = React.useContext(ExpandableCardContext)3536 if (!context) {37 throw new Error('ExpandableCard components must be used within <ExpandableCard>.')38 }3940 return context41}4243function ExpandableCard({44 open: openProp,45 defaultOpen = false,46 onOpenChange,47 children,48 ...props49}: React.ComponentProps<typeof DialogPrimitive.Root>) {50 const layoutGroupId = React.useId()51 const [open = false, setOpen] = useControllableState({52 prop: openProp,53 defaultProp: defaultOpen,54 onChange: onOpenChange,55 })5657 return (58 <ExpandableCardContext.Provider59 value={{60 open,61 cardLayoutId: `${layoutGroupId}-card`,62 mediaLayoutId: `${layoutGroupId}-media`,63 }}64 >65 <LayoutGroup id={layoutGroupId}>66 <DialogPrimitive.Root open={open} onOpenChange={setOpen} {...props}>67 {children}68 </DialogPrimitive.Root>69 </LayoutGroup>70 </ExpandableCardContext.Provider>71 )72}7374type ExpandableCardTriggerProps = Omit<React.ComponentProps<typeof motion.button>, 'layoutId'>7576function ExpandableCardTrigger({77 className,78 transition = expandableCardTransition,79 type = 'button',80 ...props81}: ExpandableCardTriggerProps) {82 const context = useExpandableCard()83 const shouldReduceMotion = useReducedMotion()8485 return (86 <DialogPrimitive.Trigger asChild>87 <motion.button88 data-slot="expandable-card-trigger"89 data-state={context.open ? 'open' : 'closed'}90 type={type}91 layoutId={context.cardLayoutId}92 transition={shouldReduceMotion ? { duration: 0 } : transition}93 className={cn(94// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from navdeep-singh
All 64 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Animated Groupanimated-group | navdeep-singh | Components | Free | 1 dep | |
| Animated Numbersanimated-numbers | navdeep-singh | Components | Free | 1 dep | |
| Animated Tabsanimated-tabs | navdeep-singh | Components | Free | 2 deps | |
| Approval Panel Illustrationapproval-panel | navdeep-singh | Components | Free | 2 deps · 2 files | |
| Brand Logobrand-logo | navdeep-singh | Components | Free | 1 dep | |
| Contribution Graphcontribution-graph | navdeep-singh | Components | Free | no deps | |
| Designed for Focus Illustrationdesigned-for-focus-illustration | navdeep-singh | Components | Free | 1 dep | |
| Fast by Default Illustrationfast-by-default-illustration | navdeep-singh | Components | Free | 1 dep |
