506 lines
15 KiB
Markdown
506 lines
15 KiB
Markdown
# Drawer
|
||
|
||
Drawer component for creating off-canvas side panels with overlay and toggle functionality.
|
||
|
||
## Tag
|
||
|
||
`Drawer`
|
||
|
||
## Props
|
||
|
||
| Prop | Type | Default | Description |
|
||
| :--- | :--- | :--- | :--- |
|
||
| `id` | `string` | Required | Unique identifier for the drawer |
|
||
| `open` | `boolean \| Signal<boolean>` | `false` | Drawer open state |
|
||
| `side` | `VNode` | Required | Content to display in the drawer panel |
|
||
| `content` | `VNode` | Required | Main page content |
|
||
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
|
||
|
||
## Styling
|
||
|
||
Drawer supports all **daisyUI Drawer classes**:
|
||
|
||
| Category | Keywords | Description |
|
||
| :--- | :--- | :--- |
|
||
| Position | `drawer-end` | Drawer opens from the right side |
|
||
| Width | `w-64`, `w-80`, `w-96` | Custom drawer width (Tailwind) |
|
||
|
||
> For further details, check the [daisyUI Drawer Documentation](https://daisyui.com/components/drawer) – Full reference for CSS classes.
|
||
|
||
### Example
|
||
|
||
```javascript
|
||
Drawer({
|
||
id: "my-drawer",
|
||
open: isOpen,
|
||
class: "drawer-end",
|
||
side: SidebarContent,
|
||
content: MainContent
|
||
});
|
||
// Applies: drawer opens from right side
|
||
```
|
||
|
||
## Live Examples
|
||
|
||
### Basic Drawer
|
||
|
||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||
<div class="card-body">
|
||
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||
<div id="demo-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
const BasicDemo = () => {
|
||
const isOpen = $(false);
|
||
|
||
return Drawer({
|
||
id: 'basic-drawer',
|
||
open: isOpen,
|
||
side: Div({ class: 'p-4' }, [
|
||
Div({ class: 'text-lg font-bold mb-4' }, 'Menu'),
|
||
Div({ class: 'flex flex-col gap-2' }, [
|
||
Button({ class: 'btn btn-ghost justify-start' }, 'Home'),
|
||
Button({ class: 'btn btn-ghost justify-start' }, 'About'),
|
||
Button({ class: 'btn btn-ghost justify-start' }, 'Contact')
|
||
])
|
||
]),
|
||
content: Div({ class: 'p-4 text-center' }, [
|
||
Button({
|
||
class: 'btn btn-primary',
|
||
onclick: () => isOpen(true)
|
||
}, 'Open Drawer')
|
||
])
|
||
});
|
||
};
|
||
$mount(BasicDemo, '#demo-basic');
|
||
```
|
||
|
||
### Navigation Drawer
|
||
|
||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||
<div class="card-body">
|
||
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||
<div id="demo-nav" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
const NavDrawer = () => {
|
||
const isOpen = $(false);
|
||
const activePage = $('home');
|
||
|
||
const pages = {
|
||
home: 'Welcome to the Home Page!',
|
||
about: 'About Us - Learn more about our company',
|
||
services: 'Our Services - What we offer',
|
||
contact: 'Contact Us - Get in touch'
|
||
};
|
||
|
||
return Drawer({
|
||
id: 'nav-drawer',
|
||
open: isOpen,
|
||
side: Div({ class: 'p-4 w-64' }, [
|
||
Div({ class: 'text-xl font-bold mb-6' }, 'MyApp'),
|
||
Div({ class: 'flex flex-col gap-1' }, [
|
||
Button({
|
||
class: `btn btn-ghost justify-start ${activePage() === 'home' ? 'btn-active' : ''}`,
|
||
onclick: () => {
|
||
activePage('home');
|
||
isOpen(false);
|
||
}
|
||
}, '🏠 Home'),
|
||
Button({
|
||
class: `btn btn-ghost justify-start ${activePage() === 'about' ? 'btn-active' : ''}`,
|
||
onclick: () => {
|
||
activePage('about');
|
||
isOpen(false);
|
||
}
|
||
}, 'ℹ️ About'),
|
||
Button({
|
||
class: `btn btn-ghost justify-start ${activePage() === 'services' ? 'btn-active' : ''}`,
|
||
onclick: () => {
|
||
activePage('services');
|
||
isOpen(false);
|
||
}
|
||
}, '⚙️ Services'),
|
||
Button({
|
||
class: `btn btn-ghost justify-start ${activePage() === 'contact' ? 'btn-active' : ''}`,
|
||
onclick: () => {
|
||
activePage('contact');
|
||
isOpen(false);
|
||
}
|
||
}, '📧 Contact')
|
||
])
|
||
]),
|
||
content: Div({ class: 'p-4' }, [
|
||
Div({ class: 'flex justify-between items-center mb-4' }, [
|
||
Button({
|
||
class: 'btn btn-ghost btn-circle',
|
||
onclick: () => isOpen(true)
|
||
}, '☰'),
|
||
Span({ class: 'text-lg font-bold' }, 'MyApp')
|
||
]),
|
||
Div({ class: 'card bg-base-200 shadow-lg' }, [
|
||
Div({ class: 'card-body' }, [
|
||
Div({ class: 'text-2xl font-bold mb-2' }, () => activePage().charAt(0).toUpperCase() + activePage().slice(1)),
|
||
Div({ class: 'text-lg' }, () => pages[activePage()])
|
||
])
|
||
])
|
||
])
|
||
});
|
||
};
|
||
$mount(NavDrawer, '#demo-nav');
|
||
```
|
||
|
||
### Settings Drawer
|
||
|
||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||
<div class="card-body">
|
||
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||
<div id="demo-settings" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
const SettingsDrawer = () => {
|
||
const isOpen = $(false);
|
||
const darkMode = $(false);
|
||
const notifications = $(true);
|
||
const autoSave = $(false);
|
||
|
||
return Drawer({
|
||
id: 'settings-drawer',
|
||
open: isOpen,
|
||
side: Div({ class: 'p-4 w-80' }, [
|
||
Div({ class: 'flex justify-between items-center mb-6' }, [
|
||
Span({ class: 'text-xl font-bold' }, 'Settings'),
|
||
Button({
|
||
class: 'btn btn-ghost btn-circle btn-sm',
|
||
onclick: () => isOpen(false)
|
||
}, '✕')
|
||
]),
|
||
Div({ class: 'flex flex-col gap-4' }, [
|
||
Div({ class: 'flex justify-between items-center' }, [
|
||
Span({}, 'Dark Mode'),
|
||
Swap({
|
||
value: darkMode,
|
||
on: "🌙",
|
||
off: "☀️",
|
||
onclick: () => darkMode(!darkMode())
|
||
})
|
||
]),
|
||
Div({ class: 'flex justify-between items-center' }, [
|
||
Span({}, 'Notifications'),
|
||
Swap({
|
||
value: notifications,
|
||
on: "🔔",
|
||
off: "🔕",
|
||
onclick: () => notifications(!notifications())
|
||
})
|
||
]),
|
||
Div({ class: 'flex justify-between items-center' }, [
|
||
Span({}, 'Auto Save'),
|
||
Swap({
|
||
value: autoSave,
|
||
on: "✅",
|
||
off: "⭕",
|
||
onclick: () => autoSave(!autoSave())
|
||
})
|
||
])
|
||
]),
|
||
Div({ class: 'divider my-4' }),
|
||
Div({ class: 'flex gap-2' }, [
|
||
Button({
|
||
class: 'btn btn-primary flex-1',
|
||
onclick: () => {
|
||
isOpen(false);
|
||
Toast('Settings saved!', 'alert-success', 2000);
|
||
}
|
||
}, 'Save'),
|
||
Button({
|
||
class: 'btn btn-ghost flex-1',
|
||
onclick: () => isOpen(false)
|
||
}, 'Cancel')
|
||
])
|
||
]),
|
||
content: Div({ class: 'p-4' }, [
|
||
Div({ class: 'flex justify-between items-center' }, [
|
||
Span({ class: 'text-lg font-bold' }, 'Dashboard'),
|
||
Button({
|
||
class: 'btn btn-ghost btn-circle',
|
||
onclick: () => isOpen(true)
|
||
}, '⚙️')
|
||
]),
|
||
Div({ class: 'mt-4 grid grid-cols-2 gap-4' }, [
|
||
Div({ class: 'stat bg-base-200 rounded-lg p-4' }, [
|
||
Div({ class: 'stat-title' }, 'Users'),
|
||
Div({ class: 'stat-value' }, '1,234')
|
||
]),
|
||
Div({ class: 'stat bg-base-200 rounded-lg p-4' }, [
|
||
Div({ class: 'stat-title' }, 'Revenue'),
|
||
Div({ class: 'stat-value' }, '$45K')
|
||
])
|
||
])
|
||
])
|
||
});
|
||
};
|
||
$mount(SettingsDrawer, '#demo-settings');
|
||
```
|
||
|
||
### Cart Drawer
|
||
|
||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||
<div class="card-body">
|
||
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||
<div id="demo-cart" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
const CartDrawer = () => {
|
||
const isOpen = $(false);
|
||
const cart = $([
|
||
{ id: 1, name: 'Product 1', price: 29, quantity: 2 },
|
||
{ id: 2, name: 'Product 2', price: 49, quantity: 1 }
|
||
]);
|
||
|
||
const updateQuantity = (id, delta) => {
|
||
cart(cart().map(item => {
|
||
if (item.id === id) {
|
||
const newQty = Math.max(0, item.quantity + delta);
|
||
return newQty === 0 ? null : { ...item, quantity: newQty };
|
||
}
|
||
return item;
|
||
}).filter(Boolean));
|
||
};
|
||
|
||
const total = () => cart().reduce((sum, item) => sum + (item.price * item.quantity), 0);
|
||
|
||
return Drawer({
|
||
id: 'cart-drawer',
|
||
open: isOpen,
|
||
side: Div({ class: 'flex flex-col h-full' }, [
|
||
Div({ class: 'p-4 border-b border-base-300' }, [
|
||
Div({ class: 'flex justify-between items-center' }, [
|
||
Span({ class: 'text-xl font-bold' }, `Cart (${cart().length} items)`),
|
||
Button({
|
||
class: 'btn btn-ghost btn-circle btn-sm',
|
||
onclick: () => isOpen(false)
|
||
}, '✕')
|
||
])
|
||
]),
|
||
Div({ class: 'flex-1 overflow-y-auto p-4' }, cart().length === 0
|
||
? Div({ class: 'text-center text-gray-500 mt-8' }, 'Your cart is empty')
|
||
: Div({ class: 'flex flex-col gap-3' }, cart().map(item =>
|
||
Div({ class: 'flex gap-3 items-center p-2 bg-base-200 rounded-lg' }, [
|
||
Div({ class: 'flex-1' }, [
|
||
Div({ class: 'font-medium' }, item.name),
|
||
Div({ class: 'text-sm' }, `$${item.price} each`)
|
||
]),
|
||
Div({ class: 'flex items-center gap-2' }, [
|
||
Button({
|
||
class: 'btn btn-xs btn-circle',
|
||
onclick: () => updateQuantity(item.id, -1)
|
||
}, '-'),
|
||
Span({ class: 'w-8 text-center' }, item.quantity),
|
||
Button({
|
||
class: 'btn btn-xs btn-circle',
|
||
onclick: () => updateQuantity(item.id, 1)
|
||
}, '+')
|
||
]),
|
||
Span({ class: 'font-bold w-16 text-right' }, `$${item.price * item.quantity}`)
|
||
])
|
||
))
|
||
),
|
||
Div({ class: 'p-4 border-t border-base-300' }, [
|
||
Div({ class: 'flex justify-between items-center mb-4' }, [
|
||
Span({ class: 'font-bold' }, 'Total'),
|
||
Span({ class: 'text-xl font-bold' }, () => `$${total()}`)
|
||
]),
|
||
Button({
|
||
class: 'btn btn-primary w-full',
|
||
onclick: () => {
|
||
isOpen(false);
|
||
Toast('Checkout initiated!', 'alert-success', 2000);
|
||
},
|
||
disabled: () => cart().length === 0
|
||
}, 'Checkout')
|
||
])
|
||
]),
|
||
content: Div({ class: 'p-4' }, [
|
||
Div({ class: 'flex justify-between items-center' }, [
|
||
Span({ class: 'text-lg font-bold' }, 'Store'),
|
||
Button({
|
||
class: 'btn btn-primary',
|
||
onclick: () => isOpen(true)
|
||
}, () => `🛒 Cart (${cart().length})`)
|
||
]),
|
||
Div({ class: 'mt-4 grid grid-cols-2 gap-4' }, [
|
||
Button({
|
||
class: 'btn btn-outline h-32 flex flex-col',
|
||
onclick: () => {
|
||
cart([...cart(), { id: Date.now(), name: 'New Product', price: 39, quantity: 1 }]);
|
||
Toast('Added to cart!', 'alert-success', 1500);
|
||
}
|
||
}, ['📦', 'Add to Cart'])
|
||
])
|
||
])
|
||
});
|
||
};
|
||
$mount(CartDrawer, '#demo-cart');
|
||
```
|
||
|
||
### Responsive Drawer
|
||
|
||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||
<div class="card-body">
|
||
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||
<div id="demo-responsive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
const ResponsiveDrawer = () => {
|
||
const isOpen = $(false);
|
||
const activePage = $('home');
|
||
|
||
const MenuItems = () => Div({ class: 'flex flex-col gap-1 p-4' }, [
|
||
Button({
|
||
class: `btn btn-ghost justify-start ${activePage() === 'home' ? 'btn-active' : ''}`,
|
||
onclick: () => {
|
||
activePage('home');
|
||
if (window.innerWidth < 1024) isOpen(false);
|
||
}
|
||
}, '🏠 Home'),
|
||
Button({
|
||
class: `btn btn-ghost justify-start ${activePage() === 'analytics' ? 'btn-active' : ''}`,
|
||
onclick: () => {
|
||
activePage('analytics');
|
||
if (window.innerWidth < 1024) isOpen(false);
|
||
}
|
||
}, '📊 Analytics'),
|
||
Button({
|
||
class: `btn btn-ghost justify-start ${activePage() === 'settings' ? 'btn-active' : ''}`,
|
||
onclick: () => {
|
||
activePage('settings');
|
||
if (window.innerWidth < 1024) isOpen(false);
|
||
}
|
||
}, '⚙️ Settings')
|
||
]);
|
||
|
||
return Drawer({
|
||
id: 'responsive-drawer',
|
||
open: isOpen,
|
||
side: Div({ class: 'w-64' }, [
|
||
Div({ class: 'text-xl font-bold p-4 border-b border-base-300' }, 'Menu'),
|
||
MenuItems()
|
||
]),
|
||
content: Div({ class: 'flex' }, [
|
||
Div({ class: 'hidden lg:block w-64 border-r border-base-300' }, [MenuItems()]),
|
||
Div({ class: 'flex-1 p-4' }, [
|
||
Div({ class: 'flex justify-between items-center lg:hidden mb-4' }, [
|
||
Button({
|
||
class: 'btn btn-ghost btn-circle',
|
||
onclick: () => isOpen(true)
|
||
}, '☰'),
|
||
Span({ class: 'text-lg font-bold' }, 'MyApp')
|
||
]),
|
||
Div({ class: 'card bg-base-200' }, [
|
||
Div({ class: 'card-body' }, [
|
||
Div({ class: 'text-2xl font-bold' }, () => activePage().charAt(0).toUpperCase() + activePage().slice(1)),
|
||
Div({}, 'Content area. On desktop, the menu is always visible on the left.')
|
||
])
|
||
])
|
||
])
|
||
])
|
||
});
|
||
};
|
||
$mount(ResponsiveDrawer, '#demo-responsive');
|
||
```
|
||
|
||
### Form Drawer
|
||
|
||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||
<div class="card-body">
|
||
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||
<div id="demo-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
const FormDrawer = () => {
|
||
const isOpen = $(false);
|
||
const name = $('');
|
||
const email = $('');
|
||
const message = $('');
|
||
|
||
const handleSubmit = () => {
|
||
if (name() && email() && message()) {
|
||
Toast('Message sent!', 'alert-success', 2000);
|
||
isOpen(false);
|
||
name('');
|
||
email('');
|
||
message('');
|
||
} else {
|
||
Toast('Please fill all fields', 'alert-warning', 2000);
|
||
}
|
||
};
|
||
|
||
return Drawer({
|
||
id: 'form-drawer',
|
||
open: isOpen,
|
||
side: Div({ class: 'p-4 w-96' }, [
|
||
Div({ class: 'flex justify-between items-center mb-4' }, [
|
||
Span({ class: 'text-xl font-bold' }, 'Contact Us'),
|
||
Button({
|
||
class: 'btn btn-ghost btn-circle btn-sm',
|
||
onclick: () => isOpen(false)
|
||
}, '✕')
|
||
]),
|
||
Div({ class: 'flex flex-col gap-4' }, [
|
||
Input({
|
||
label: 'Name',
|
||
value: name,
|
||
placeholder: 'Your name',
|
||
oninput: (e) => name(e.target.value)
|
||
}),
|
||
Input({
|
||
label: 'Email',
|
||
type: 'email',
|
||
value: email,
|
||
placeholder: 'your@email.com',
|
||
oninput: (e) => email(e.target.value)
|
||
}),
|
||
Div({ class: 'form-control' }, [
|
||
Span({ class: 'label-text mb-1' }, 'Message'),
|
||
$html('textarea', {
|
||
class: 'textarea textarea-bordered h-24',
|
||
placeholder: 'Your message',
|
||
value: message,
|
||
oninput: (e) => message(e.target.value)
|
||
})
|
||
]),
|
||
Div({ class: 'flex gap-2 mt-2' }, [
|
||
Button({
|
||
class: 'btn btn-primary flex-1',
|
||
onclick: handleSubmit
|
||
}, 'Send'),
|
||
Button({
|
||
class: 'btn btn-ghost flex-1',
|
||
onclick: () => isOpen(false)
|
||
}, 'Cancel')
|
||
])
|
||
])
|
||
]),
|
||
content: Div({ class: 'p-4 text-center' }, [
|
||
Button({
|
||
class: 'btn btn-primary',
|
||
onclick: () => isOpen(true)
|
||
}, 'Contact Us')
|
||
])
|
||
});
|
||
};
|
||
$mount(FormDrawer, '#demo-form');
|
||
``` |