Basic Drawer
The most basic drawer usage.
Basic Drawer
Some contents...
Some contents...
Some contents...
Code
<%= render HakumiComponents::Button::Component.new(type: :primary, id: "basic-drawer-trigger") do %>
Open Drawer
<% end %>
<%= render(HakumiComponents::Drawer::Component.new(
id: "basic-drawer",
open: false
)) do |drawer| %>
<% drawer.with_header { "Basic Drawer" } %>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<% end %>
<script>
(() => {
const button = document.getElementById("basic-drawer-trigger")
const drawer = document.getElementById("basic-drawer")
if (!button || !drawer) return
const wire = () => {
const api = drawer.hakumiComponent?.api
if (!api) return false
button.addEventListener("click", () => api.open())
return true
}
if (wire()) return
const onRegister = ({ detail }) => {
if (detail.id !== drawer.id) return
if (wire()) window.removeEventListener("hakumi-component:registered", onRegister)
}
window.addEventListener("hakumi-component:registered", onRegister)
})()
</script>
Placements
Open drawers from different edges.
Left Drawer
Drawer content for left placement.
Right Drawer
Drawer content for right placement.
Top Drawer
Drawer content for top placement.
Bottom Drawer
Drawer content for bottom placement.
Code
<%= render HakumiComponents::Space::Component.new(wrap: true) do %>
<%= render(HakumiComponents::Button::Component.new(id: "drawer-left-trigger")) { "Left" } %>
<%= render(HakumiComponents::Button::Component.new(id: "drawer-right-trigger")) { "Right" } %>
<%= render(HakumiComponents::Button::Component.new(id: "drawer-top-trigger")) { "Top" } %>
<%= render(HakumiComponents::Button::Component.new(id: "drawer-bottom-trigger")) { "Bottom" } %>
<% end %>
<%= render(HakumiComponents::Drawer::Component.new(
id: "drawer-left",
placement: :left
)) do |drawer| %>
<% drawer.with_header { "Left Drawer" } %>
<p>Drawer content for left placement.</p>
<% end %>
<%= render(HakumiComponents::Drawer::Component.new(
id: "drawer-right",
placement: :right
)) do |drawer| %>
<% drawer.with_header { "Right Drawer" } %>
<p>Drawer content for right placement.</p>
<% end %>
<%= render(HakumiComponents::Drawer::Component.new(
id: "drawer-top",
placement: :top,
height: 240
)) do |drawer| %>
<% drawer.with_header { "Top Drawer" } %>
<p>Drawer content for top placement.</p>
<% end %>
<%= render(HakumiComponents::Drawer::Component.new(
id: "drawer-bottom",
placement: :bottom,
height: 240
)) do |drawer| %>
<% drawer.with_header { "Bottom Drawer" } %>
<p>Drawer content for bottom placement.</p>
<% end %>
<script>
(() => {
const mapping = [
["drawer-left-trigger", "drawer-left"],
["drawer-right-trigger", "drawer-right"],
["drawer-top-trigger", "drawer-top"],
["drawer-bottom-trigger", "drawer-bottom"]
]
const wire = () => {
let wired = true
mapping.forEach(([buttonId, drawerId]) => {
const button = document.getElementById(buttonId)
const drawer = document.getElementById(drawerId)
const api = drawer?.hakumiComponent?.api
if (!button || !drawer || !api) {
wired = false
return
}
button.addEventListener("click", () => api.open())
})
return wired
}
if (wire()) return
const onRegister = ({ detail }) => {
if (!mapping.some(([, drawerId]) => drawerId === detail.id)) return
if (wire()) window.removeEventListener("hakumi-component:registered", onRegister)
}
window.addEventListener("hakumi-component:registered", onRegister)
})()
</script>
Custom Footer
Drawer with extra header actions and custom footer.
Code
<%= render HakumiComponents::Button::Component.new(type: :primary, id: "drawer-footer-trigger") do %>
Open Drawer
<% end %>
<%= render(HakumiComponents::Drawer::Component.new(
id: "drawer-footer"
)) do |drawer| %>
<% drawer.with_header { "Drawer with Footer" } %>
<% drawer.with_extra do %>
<%= render(HakumiComponents::Button::Component.new(type: :text)) { "Extra Action" } %>
<% end %>
<% drawer.with_footer do %>
<%= render(HakumiComponents::Space::Component.new) do %>
<%= render(HakumiComponents::Button::Component.new) { "Cancel" } %>
<%= render(HakumiComponents::Button::Component.new(type: :primary)) { "Submit" } %>
<% end %>
<% end %>
<p>Drawer content with custom footer.</p>
<% end %>
<script>
(() => {
const button = document.getElementById("drawer-footer-trigger")
const drawer = document.getElementById("drawer-footer")
if (!button || !drawer) return
const wire = () => {
const api = drawer.hakumiComponent?.api
if (!api) return false
button.addEventListener("click", () => api.open())
return true
}
if (wire()) return
const onRegister = ({ detail }) => {
if (detail.id !== drawer.id) return
if (wire()) window.removeEventListener("hakumi-component:registered", onRegister)
}
window.addEventListener("hakumi-component:registered", onRegister)
})()
</script>
No Mask
Disable the backdrop mask.
Drawer without Mask
Drawer without a backdrop mask.
Code
<%= render HakumiComponents::Button::Component.new(type: :primary, id: "drawer-no-mask-trigger") do %>
Open Drawer
<% end %>
<%= render(HakumiComponents::Drawer::Component.new(
id: "drawer-no-mask",
mask: false
)) do |drawer| %>
<% drawer.with_header { "Drawer without Mask" } %>
<p>Drawer without a backdrop mask.</p>
<% end %>
<script>
(() => {
const button = document.getElementById("drawer-no-mask-trigger")
const drawer = document.getElementById("drawer-no-mask")
if (!button || !drawer) return
const wire = () => {
const api = drawer.hakumiComponent?.api
if (!api) return false
button.addEventListener("click", () => api.open())
return true
}
if (wire()) return
const onRegister = ({ detail }) => {
if (detail.id !== drawer.id) return
if (wire()) window.removeEventListener("hakumi-component:registered", onRegister)
}
window.addEventListener("hakumi-component:registered", onRegister)
})()
</script>
Programmatic Drawer
Render a drawer via the component API endpoint and inject it into the page.
Code
<div class="hakumi-space hakumi-space-horizontal" style="gap: 12px;">
<%= render(HakumiComponents::Button::Component.new(type: :primary, id: "programmatic-drawer-btn")) { "Render Drawer via API" } %>
<div id="programmatic-drawer-target"></div>
</div>
<script>
(() => {
const button = document.getElementById("programmatic-drawer-btn")
if (!button) return
const wire = () => {
if (!window.HakumiComponents?.renderComponent) return false
button.addEventListener("click", async () => {
const result = await window.HakumiComponents.renderComponent("drawer", {
params: { title: "Programmatic Drawer", message: "Loaded from /hakumi/components/drawer", open: true },
target: "#programmatic-drawer-target",
mode: "destroy_on_close"
})
result?.element?.hakumiComponent.api?.open()
})
return true
}
if (wire()) return
const onReady = () => {
if (wire()) {
document.removeEventListener("turbo:load", onReady)
window.removeEventListener("load", onReady)
}
}
document.addEventListener("turbo:load", onReady)
window.addEventListener("load", onReady)
})()
</script>
Drawer API
| Prop | Type | Default | Description |
|---|---|---|---|
open |
Boolean |
false |
Controls visibility. |
title |
String or ViewComponent |
- |
Drawer header title. |
placement |
Symbol |
:right |
Drawer placement (left/right/top/bottom). |
size |
Symbol |
:default |
Width/height preset (:default or :large). |
width |
Integer or String |
- |
Custom width when placement is left/right. |
height |
Integer or String |
- |
Custom height when placement is top/bottom. |
closable |
Boolean |
true |
Show close button. |
mask |
Boolean |
true |
Render backdrop. |
mask_closable |
Boolean |
true |
Allow closing by clicking the mask. |
keyboard |
Boolean |
true |
Close on ESC key. |
footer |
ViewComponent |
nil |
Custom footer content. |
extra |
ViewComponent |
nil |
Extra header content (actions). |
destroy_on_close |
Boolean |
false |
Unmount body content after closing. |
content slot |
Slot |
- |
Drawer body content. |
**html_attributes |
Keyword args |
- |
Attributes merged into the root `.hakumi-drawer` (pass as kwargs such as `class:`, `style:`). |
Drawer JavaScript API
| Prop | Type | Default | Description |
|---|---|---|---|
open() |
Function |
- |
Open the drawer. |
close() |
Function |
- |
Close the drawer. |
toggle() |
Function |
- |
Toggle visibility. |
isOpen() |
Function |
- |
Return current open state. |
getState() |
Function |
- |
Return the current state and configuration. |