Files
sigpro-ui/docs/components/drawer.md
2026-04-06 03:19:15 +02:00

493 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
## 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'),
Tag('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');
```