Docs
Go back
Component by
OV
OblivionTSX
glass-drawer
"use client"
import React from "react";
import { motion, AnimatePresence } from "framer-motion";
import { X } from "lucide-react";
// Replaced with Tailwind classes for seamless dark/light mode support.
interface GlassDrawerProps {
open: boolean;
onClose: () => void;
side?: "right" | "left" | "top" | "bottom";
title: string;
children: React.ReactNode;
}
export function GlassDrawer({ open, onClose, side = "right", title, children }: GlassDrawerProps) {
const variants = {
right: { hidden: { x: "100%" }, visible: { x: 0 } },
left: { hidden: { x: "-100%" }, visible: { x: 0 } },
bottom: { hidden: { y: "100%" }, visible: { y: 0 } },
top: { hidden: { y: "-100%" }, visible: { y: 0 } },
};
const placement = {
right: { top: 0, right: 0, bottom: 0, width: 360 },
left: { top: 0, left: 0, bottom: 0, width: 360 },
bottom: { left: 0, right: 0, bottom: 0, height: 320 },
top: { left: 0, right: 0, top: 0, height: 320 },
};
const borderRadius = {
right: "24px 0 0 24px",
left: "0 24px 24px 0",
bottom: "24px 24px 0 0",
top: "0 0 24px 24px",
};
return (
<AnimatePresence>
{open && (
<>
<motion.div
key="drawer-bg"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
className="fixed inset-0 z-50 bg-black/10 dark:bg-black/60 backdrop-blur-[2px]"
/>
<motion.div
key="drawer"
variants={variants[side]}
initial="hidden"
animate="visible"
exit="hidden"
transition={{ type: "spring", stiffness: 340, damping: 34 }}
className="fixed z-[51] flex flex-col bg-white/70 dark:bg-zinc-900/70 backdrop-blur-2xl border-black/5 dark:border-white/10 shadow-[0_24px_80px_rgba(0,0,0,0.1)] dark:shadow-[0_24px_80px_rgba(0,0,0,0.5)]"
style={{
...placement[side],
borderRadius: borderRadius[side],
borderWidth: "1px",
}}
>
<div className="flex items-center justify-between px-6 py-[22px] border-b border-black/5 dark:border-white/10">
<span className="text-[15px] font-semibold text-neutral-900 dark:text-neutral-100">{title}</span>
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
onClick={onClose}
className="flex items-center justify-center w-7 h-7 bg-black/5 dark:bg-white/10 hover:bg-black/10 dark:hover:bg-white/20 border border-black/5 dark:border-white/10 rounded-lg cursor-pointer text-neutral-600 dark:text-neutral-300 transition-colors"
>
<X size={14} />
</motion.button>
</div>
<div className="flex-1 overflow-y-auto px-6 py-5">
{children}
</div>
</motion.div>
</>
)}
</AnimatePresence>
);
}
export const componentMeta = {
name: "Glass Drawer",
description: "A sleek, side-anchored glassmorphism drawer for settings and navigation.",
props: {
open: { type: "boolean", description: "Whether the drawer is visible", default: "false" },
onClose: { type: "function", description: "Callback when the drawer should close" },
side: { type: "enum", description: "Which side the drawer appears from", default: '"right"' },
title: { type: "string", description: "Drawer title", default: '"Settings"' },
children: { type: "ReactNode", description: "Drawer content" }
}
};
export default function GlassDrawerPreview() {
const [open, setOpen] = React.useState(false);
const [side, setSide] = React.useState<"right" | "left" | "top" | "bottom" | any>("right");
const openDrawer = (s: typeof side) => {
setSide(s);
setOpen(true);
};
return (
<div className="flex flex-col items-center justify-center min-h-[500px] w-full relative gap-4">
<div className="flex gap-2 relative z-10">
{(["left", "right", "top", "bottom"] as const).map((s) => (
<motion.button
key={s}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => openDrawer(s)}
className="px-4 py-2 bg-white/70 dark:bg-white/10 border border-black/10 dark:border-white/20 rounded-xl text-neutral-900 dark:text-white text-xs font-semibold backdrop-blur-xl shadow-sm hover:bg-white dark:hover:bg-white/20 transition-all cursor-pointer"
>
{s.charAt(0).toUpperCase() + s.slice(1)}
</motion.button>
))}
</div>
<GlassDrawer open={open} onClose={() => setOpen(false)} side={side} title="Component Settings">
<div className="space-y-6">
{["Layout", "Styling", "Animations", "Advanced"].map((label) => (
<div key={label}>
<div className="text-[10px] uppercase tracking-wider text-neutral-500 dark:text-zinc-500 font-bold mb-3">{label}</div>
<div className="space-y-3">
<div className="p-3 rounded-xl bg-black/5 dark:bg-white/5 border border-black/5 dark:border-white/10 flex items-center justify-between">
<span className="text-xs font-medium text-neutral-800 dark:text-zinc-300">Enable physics</span>
<div className="w-8 h-4 bg-black dark:bg-[#c8ff3e] rounded-full shadow-inner" />
</div>
<div className="p-3 rounded-xl bg-black/5 dark:bg-white/5 border border-black/5 dark:border-white/10 flex items-center justify-between">
<span className="text-xs font-medium text-neutral-800 dark:text-zinc-300">Blur intensity</span>
<span className="text-[10px] text-neutral-500 font-mono font-semibold">16px</span>
</div>
</div>
</div>
))}
</div>
</GlassDrawer>
</div>
);
}"use client"
import React from "react";
import { motion, AnimatePresence } from "framer-motion";
import { X } from "lucide-react";
// Replaced with Tailwind classes for seamless dark/light mode support.
interface GlassDrawerProps {
open: boolean;
onClose: () => void;
side?: "right" | "left" | "top" | "bottom";
title: string;
children: React.ReactNode;
}
export function GlassDrawer({ open, onClose, side = "right", title, children }: GlassDrawerProps) {
const variants = {
right: { hidden: { x: "100%" }, visible: { x: 0 } },
left: { hidden: { x: "-100%" }, visible: { x: 0 } },
bottom: { hidden: { y: "100%" }, visible: { y: 0 } },
top: { hidden: { y: "-100%" }, visible: { y: 0 } },
};
const placement = {
right: { top: 0, right: 0, bottom: 0, width: 360 },
left: { top: 0, left: 0, bottom: 0, width: 360 },
bottom: { left: 0, right: 0, bottom: 0, height: 320 },
top: { left: 0, right: 0, top: 0, height: 320 },
};
const borderRadius = {
right: "24px 0 0 24px",
left: "0 24px 24px 0",
bottom: "24px 24px 0 0",
top: "0 0 24px 24px",
};
return (
<AnimatePresence>
{open && (
<>
<motion.div
key="drawer-bg"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
className="fixed inset-0 z-50 bg-black/10 dark:bg-black/60 backdrop-blur-[2px]"
/>
<motion.div
key="drawer"
variants={variants[side]}
initial="hidden"
animate="visible"
exit="hidden"
transition={{ type: "spring", stiffness: 340, damping: 34 }}
className="fixed z-[51] flex flex-col bg-white/70 dark:bg-zinc-900/70 backdrop-blur-2xl border-black/5 dark:border-white/10 shadow-[0_24px_80px_rgba(0,0,0,0.1)] dark:shadow-[0_24px_80px_rgba(0,0,0,0.5)]"
style={{
...placement[side],
borderRadius: borderRadius[side],
borderWidth: "1px",
}}
>
<div className="flex items-center justify-between px-6 py-[22px] border-b border-black/5 dark:border-white/10">
<span className="text-[15px] font-semibold text-neutral-900 dark:text-neutral-100">{title}</span>
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
onClick={onClose}
className="flex items-center justify-center w-7 h-7 bg-black/5 dark:bg-white/10 hover:bg-black/10 dark:hover:bg-white/20 border border-black/5 dark:border-white/10 rounded-lg cursor-pointer text-neutral-600 dark:text-neutral-300 transition-colors"
>
<X size={14} />
</motion.button>
</div>
<div className="flex-1 overflow-y-auto px-6 py-5">
{children}
</div>
</motion.div>
</>
)}
</AnimatePresence>
);
}
export const componentMeta = {
name: "Glass Drawer",
description: "A sleek, side-anchored glassmorphism drawer for settings and navigation.",
props: {
open: { type: "boolean", description: "Whether the drawer is visible", default: "false" },
onClose: { type: "function", description: "Callback when the drawer should close" },
side: { type: "enum", description: "Which side the drawer appears from", default: '"right"' },
title: { type: "string", description: "Drawer title", default: '"Settings"' },
children: { type: "ReactNode", description: "Drawer content" }
}
};
export default function GlassDrawerPreview() {
const [open, setOpen] = React.useState(false);
const [side, setSide] = React.useState<"right" | "left" | "top" | "bottom" | any>("right");
const openDrawer = (s: typeof side) => {
setSide(s);
setOpen(true);
};
return (
<div className="flex flex-col items-center justify-center min-h-[500px] w-full relative gap-4">
<div className="flex gap-2 relative z-10">
{(["left", "right", "top", "bottom"] as const).map((s) => (
<motion.button
key={s}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => openDrawer(s)}
className="px-4 py-2 bg-white/70 dark:bg-white/10 border border-black/10 dark:border-white/20 rounded-xl text-neutral-900 dark:text-white text-xs font-semibold backdrop-blur-xl shadow-sm hover:bg-white dark:hover:bg-white/20 transition-all cursor-pointer"
>
{s.charAt(0).toUpperCase() + s.slice(1)}
</motion.button>
))}
</div>
<GlassDrawer open={open} onClose={() => setOpen(false)} side={side} title="Component Settings">
<div className="space-y-6">
{["Layout", "Styling", "Animations", "Advanced"].map((label) => (
<div key={label}>
<div className="text-[10px] uppercase tracking-wider text-neutral-500 dark:text-zinc-500 font-bold mb-3">{label}</div>
<div className="space-y-3">
<div className="p-3 rounded-xl bg-black/5 dark:bg-white/5 border border-black/5 dark:border-white/10 flex items-center justify-between">
<span className="text-xs font-medium text-neutral-800 dark:text-zinc-300">Enable physics</span>
<div className="w-8 h-4 bg-black dark:bg-[#c8ff3e] rounded-full shadow-inner" />
</div>
<div className="p-3 rounded-xl bg-black/5 dark:bg-white/5 border border-black/5 dark:border-white/10 flex items-center justify-between">
<span className="text-xs font-medium text-neutral-800 dark:text-zinc-300">Blur intensity</span>
<span className="text-[10px] text-neutral-500 font-mono font-semibold">16px</span>
</div>
</div>
</div>
))}
</div>
</GlassDrawer>
</div>
);
}Save to favoritesCopy to Figma
React
Glass Drawer
A sleek, side-anchored glassmorphism drawer for settings and navigation.
Installation
$ npx @cosmoo/oblivion add glass-drawerDependencies
npm install framer-motion lucide-reactUsage Example
tsx
import GlassDrawer from "@/components/oblivion/glass-drawer"
export default function Demo() {
return (
<div className="p-10 flex justify-center">
<GlassDrawer />
</div>
)
}import GlassDrawer from "@/components/oblivion/glass-drawer"
export default function Demo() {
return (
<div className="p-10 flex justify-center">
<GlassDrawer />
</div>
)
}