Components
Loading preview...
This SmartPopover gives you a mobile-first popover/drawer combo that auto-adapts based on screen size. It wraps Radix Popover and Vaul Drawer with a shared context, so you write it once and it just works—perfect for menus, filters, or actions on both mobile and desktop.
npx shadcn@latest add https://21st.dev/r/sshahaider/smart-popover'use client';
import * as React from 'react';
import { Button } from '@/components/ui/button';
import { useIsMobile } from '@/hooks/use-media-query';
import {
SmartPopover,
SmartPopoverBody,
SmartPopoverContent,
SmartPopoverDescription,
SmartPopoverFooter,
SmartPopoverHeader,
SmartPopoverTitle,
SmartPopoverTrigger,
} from '@/components/ui/smart-popover';
import { User, Settings } from 'lucide-react';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
function Preview() {
const [open, setOpen] = React.useState(false);
return (
<SmartPopover open={open} onOpenChange={setOpen}>
<SmartPopoverTrigger asChild>
<Button variant="outline">Open Popover</Button>
</SmartPopoverTrigger>
<SmartPopoverContent className='md:w-62'>
<SmartPopoverHeader>
<div className="flex items-center space-x-3">
<Avatar className="h-10 w-10">
<AvatarImage src="https://avatar.vercel.sh/128" />
<AvatarFallback>JD</AvatarFallback>
</Avatar>
<div>
<SmartPopoverTitle>John Doe</SmartPopoverTitle>
<SmartPopoverDescription className='text-xs'>john.doe@example.com</SmartPopoverDescription>
</div>
</div>
</SmartPopoverHeader>
<SmartPopoverBody className="space-y-1 px-2 py-4 md:py-2">
<Button variant="ghost" className="w-full justify-start" size="sm">
<User className="mr-2 h-4 w-4" />
View Profile
</Button>
<Button variant="ghost" className="w-full justify-start" size="sm">
<Settings className="mr-2 h-4 w-4" />
Settings
</Button>
</SmartPopoverBody>
<SmartPopoverFooter>
<Button variant="outline" className="w-full bg-transparent" size="sm">
Sign Out
</Button>
</SmartPopoverFooter>
</SmartPopoverContent>
</SmartPopover>
);
}
export default function Default() {
return (
<div className="flex min-h-screen flex-col items-center space-y-12 py-24">
<DeviceIndicator />
<div className="space-y-3 text-center">
<h1 className="text-6xl font-extrabold tracking-tight">
Responsive Popover
</h1>
<p className="text-xl font-medium">
This popover automatically adapts to your device:
</p>
<div className="flex justify-center gap-8 text-sm">
<div className="flex items-center gap-2 rounded-md border p-1">
<div className="h-3 w-3 rounded-full bg-blue-500" />
<span className="flex items-center gap-2">
<strong className="font-semibold">Desktop:</strong>{' '}
<code>
<pre>{`<Popover />`}</pre>
</code>
</span>
</div>
<div className="flex items-center gap-2 rounded-md border p-1">
<div className="h-3 w-3 rounded-full bg-green-500" />
<span className="flex items-center gap-2">
<strong className="font-semibold">Mobile:</strong>{' '}
<code>
<pre>{`<Drawer />`}</pre>
</code>
</span>
</div>
</div>
</div>
<Preview />
<p className="text-muted-foreground text-sm">
*Try resizing your browser window or opening this on different devices!
</p>
</div>
);
}
function DeviceIndicator() {
const isMobile = useIsMobile();
return (
<div className="fixed top-4 left-4 z-50">
<div
className={`bg-muted rounded-full border px-3 py-2 text-sm font-medium`}
>
<div className="flex items-center gap-2">
<div
className={`h-2 w-2 rounded-full ${isMobile ? 'bg-green-500' : 'bg-blue-500'}`}
/>
{isMobile ? 'Mobile' : 'Desktop'}
</div>
</div>
</div>
);
}