Added new Drawer Component
This commit is contained in:
@@ -64,7 +64,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
<input
|
||||
type="text"
|
||||
id={`${fieldId}-display`}
|
||||
class="wr:w-full wr:px-3 wr:py-2 wr:pr-10 wr:border wr:border-border wr:rounded-lg wr:bg-background wr:text-foreground focus:wr:ring-2 focus:wr:ring-primary focus:wr:border-primary disabled:wr:opacity-50"
|
||||
class="wr:w-full wr:px-3 wr:py-2 wr:pr-10 wr:border wr:border-border wr:rounded-lg wr:bg-background wr:text-foreground wr:focus:ring-2 wr:focus:ring-primary wr:focus:border-primary wr:disabled:opacity-50"
|
||||
placeholder={placeholder}
|
||||
readonly
|
||||
disabled={disabled}
|
||||
@@ -75,7 +75,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
<button
|
||||
type="button"
|
||||
id={`${fieldId}-trigger`}
|
||||
class="wr:absolute wr:right-0 wr:top-0 wr:h-full wr:px-3 wr:flex wr:items-center wr:text-muted-foreground hover:wr:text-foreground disabled:wr:opacity-50"
|
||||
class="wr:absolute wr:right-0 wr:top-0 wr:h-full wr:px-3 wr:flex wr:items-center wr:text-muted-foreground wr:hover:text-foreground wr:disabled:opacity-50"
|
||||
disabled={disabled}
|
||||
aria-label="Open calendar"
|
||||
>
|
||||
@@ -107,7 +107,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
<button
|
||||
type="button"
|
||||
id={`${fieldId}-prev-month`}
|
||||
class="wr:p-1 wr:rounded wr:hover:wr:bg-muted"
|
||||
class="wr:p-1 wr:rounded wr:hover:bg-muted"
|
||||
aria-label="Previous month"
|
||||
>
|
||||
<svg
|
||||
@@ -124,19 +124,19 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
<div class="wr:flex wr:gap-2">
|
||||
<select
|
||||
id={`${fieldId}-month-select`}
|
||||
class="wr:bg-card wr:border-none wr:text-sm wr:font-medium wr:text-foreground focus:wr:outline-none"
|
||||
class="wr:bg-card wr:border-none wr:text-sm wr:font-medium wr:text-foreground wr:focus:outline-none"
|
||||
></select>
|
||||
<!-- UPDATED: Year dropdown instead of input -->
|
||||
<select
|
||||
id={`${fieldId}-year-select`}
|
||||
class="wr:bg-card wr:border-none wr:text-sm wr:font-medium wr:text-foreground focus:wr:outline-none"
|
||||
class="wr:bg-card wr:border-none wr:text-sm wr:font-medium wr:text-foreground wr:focus:outline-none"
|
||||
></select>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id={`${fieldId}-next-month`}
|
||||
class="wr:p-1 wr:rounded wr:hover:wr:bg-muted"
|
||||
class="wr:p-1 wr:rounded wr:hover:bg-muted"
|
||||
aria-label="Next month"
|
||||
>
|
||||
<svg
|
||||
@@ -202,7 +202,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
<button
|
||||
type="button"
|
||||
id={`${fieldId}-today`}
|
||||
class="wr:px-3 wr:py-1 wr:text-sm wr:text-primary hover:wr:bg-primary/10 wr:rounded"
|
||||
class="wr:px-3 wr:py-1 wr:text-sm wr:text-primary wr:hover:bg-primary/10 wr:rounded"
|
||||
>
|
||||
Today
|
||||
</button>
|
||||
@@ -417,7 +417,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
dayButton.type = "button";
|
||||
dayButton.textContent = day;
|
||||
dayButton.className =
|
||||
"wr:w-8 wr:h-8 wr:text-sm wr:rounded wr:transition-colors wr:hover:wr:bg-muted focus:wr:outline-none focus:wr:ring-2 focus:wr:ring-primary";
|
||||
"wr:w-8 wr:h-8 wr:text-sm wr:rounded wr:transition-colors wr:hover:bg-muted wr:focus:outline-none wr:focus:ring-2 wr:focus:ring-primary";
|
||||
|
||||
const isDisabled = this.isDateDisabled(currentDate);
|
||||
if (isDisabled) {
|
||||
|
||||
@@ -76,7 +76,7 @@ if (value) {
|
||||
<input
|
||||
type="text"
|
||||
id={`${fieldId}-display`}
|
||||
class="wr:w-full wr:px-3 wr:py-2 wr:pr-10 wr:border wr:border-border wr:rounded-lg wr:bg-background wr:text-foreground focus:wr:ring-2 focus:wr:ring-primary focus:wr:border-primary disabled:wr:opacity-50"
|
||||
class="wr:w-full wr:px-3 wr:py-2 wr:pr-10 wr:border wr:border-border wr:rounded-lg wr:bg-background wr:text-foreground wr:focus:ring-2 wr:focus:ring-primary wr:focus:border-primary wr:disabled:opacity-50"
|
||||
placeholder={placeholder}
|
||||
readonly
|
||||
disabled={disabled}
|
||||
@@ -87,7 +87,7 @@ if (value) {
|
||||
<button
|
||||
type="button"
|
||||
id={`${fieldId}-trigger`}
|
||||
class="wr:absolute wr:right-0 wr:top-0 wr:h-full wr:px-3 wr:flex wr:items-center wr:text-muted-foreground hover:wr:text-foreground disabled:wr:opacity-50"
|
||||
class="wr:absolute wr:right-0 wr:top-0 wr:h-full wr:px-3 wr:flex wr:items-center wr:text-muted-foreground wr:hover:text-foreground wr:disabled:opacity-50"
|
||||
disabled={disabled}
|
||||
aria-label="Open datetime picker"
|
||||
>
|
||||
@@ -128,7 +128,7 @@ if (value) {
|
||||
<button
|
||||
type="button"
|
||||
id={`${fieldId}-time-tab`}
|
||||
class="wr:flex-1 wr:px-4 wr:py-2 wr:text-sm wr:font-medium wr:border-b-2 wr:border-transparent wr:text-muted-foreground hover:wr:text-foreground"
|
||||
class="wr:flex-1 wr:px-4 wr:py-2 wr:text-sm wr:font-medium wr:border-b-2 wr:border-transparent wr:text-muted-foreground wr:hover:text-foreground"
|
||||
data-tab="time"
|
||||
>
|
||||
Time
|
||||
@@ -144,7 +144,7 @@ if (value) {
|
||||
<button
|
||||
type="button"
|
||||
id={`${fieldId}-prev-month`}
|
||||
class="wr:p-1 wr:rounded wr:hover:wr:bg-muted"
|
||||
class="wr:p-1 wr:rounded wr:hover:bg-muted"
|
||||
>
|
||||
<svg
|
||||
class="wr:w-4 wr:h-4"
|
||||
@@ -171,7 +171,7 @@ if (value) {
|
||||
<button
|
||||
type="button"
|
||||
id={`${fieldId}-next-month`}
|
||||
class="wr:p-1 wr:rounded wr:hover:wr:bg-muted"
|
||||
class="wr:p-1 wr:rounded wr:hover:bg-muted"
|
||||
>
|
||||
<svg
|
||||
class="wr:w-4 wr:h-4"
|
||||
@@ -249,7 +249,7 @@ if (value) {
|
||||
>
|
||||
<select
|
||||
id={`${fieldId}-hour`}
|
||||
class="wr:w-full wr:px-2 wr:py-1 wr:text-center wr:border wr:border-border wr:rounded focus:wr:ring-1 focus:wr:ring-primary wr:bg-background"
|
||||
class="wr:w-full wr:px-2 wr:py-1 wr:text-center wr:border wr:border-border wr:rounded wr:focus:ring-1 wr:focus:ring-primary wr:bg-background"
|
||||
></select>
|
||||
</div>
|
||||
<div
|
||||
@@ -264,7 +264,7 @@ if (value) {
|
||||
>
|
||||
<select
|
||||
id={`${fieldId}-minute`}
|
||||
class="wr:w-full wr:px-2 wr:py-1 wr:text-center wr:border wr:border-border wr:rounded focus:wr:ring-1 focus:wr:ring-primary wr:bg-background"
|
||||
class="wr:w-full wr:px-2 wr:py-1 wr:text-center wr:border wr:border-border wr:rounded wr:focus:ring-1 wr:focus:ring-primary wr:bg-background"
|
||||
></select>
|
||||
</div>
|
||||
{
|
||||
@@ -275,7 +275,7 @@ if (value) {
|
||||
</label>
|
||||
<select
|
||||
id={`${fieldId}-period`}
|
||||
class="wr:w-full wr:px-2 wr:py-1 wr:border wr:border-border wr:rounded focus:wr:ring-1 focus:wr:ring-primary wr:bg-background"
|
||||
class="wr:w-full wr:px-2 wr:py-1 wr:border wr:border-border wr:rounded wr:focus:ring-1 wr:focus:ring-primary wr:bg-background"
|
||||
>
|
||||
<option value="AM">AM</option>
|
||||
<option value="PM">PM</option>
|
||||
@@ -302,14 +302,14 @@ if (value) {
|
||||
<button
|
||||
type="button"
|
||||
id={`${fieldId}-now`}
|
||||
class="wr:px-3 wr:py-1 wr:text-sm wr:text-primary hover:wr:bg-primary/10 wr:rounded"
|
||||
class="wr:px-3 wr:py-1 wr:text-sm wr:text-primary wr:hover:bg-primary/10 wr:rounded"
|
||||
>
|
||||
Now
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id={`${fieldId}-apply`}
|
||||
class="wr:px-3 wr:py-1 wr:text-sm wr:bg-primary wr:text-primary-foreground hover:wr:bg-primary/90 wr:rounded"
|
||||
class="wr:px-3 wr:py-1 wr:text-sm wr:bg-primary wr:text-primary-foreground wr:hover:bg-primary/90 wr:rounded"
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
@@ -556,7 +556,7 @@ if (value) {
|
||||
dayButton.type = "button";
|
||||
dayButton.textContent = day;
|
||||
dayButton.className =
|
||||
"wr:w-8 wr:h-8 wr:text-sm wr:rounded wr:transition-colors wr:hover:wr:bg-muted focus:wr:outline-none focus:wr:ring-2 focus:wr:ring-primary";
|
||||
"wr:w-8 wr:h-8 wr:text-sm wr:rounded wr:transition-colors wr:hover:bg-muted wr:focus:outline-none wr:focus:ring-2 wr:focus:ring-primary";
|
||||
|
||||
const dateStr = currentDate.toISOString().split("T")[0];
|
||||
if (this.selectedDate === dateStr) {
|
||||
@@ -639,14 +639,14 @@ if (value) {
|
||||
this.dateTab.className =
|
||||
"wr:flex-1 wr:px-4 wr:py-2 wr:text-sm wr:font-medium wr:border-b-2 wr:border-primary wr:text-primary";
|
||||
this.timeTab.className =
|
||||
"wr:flex-1 wr:px-4 wr:py-2 wr:text-sm wr:font-medium wr:border-b-2 wr:border-transparent wr:text-muted-foreground hover:wr:text-foreground";
|
||||
"wr:flex-1 wr:px-4 wr:py-2 wr:text-sm wr:font-medium wr:border-b-2 wr:border-transparent wr:text-muted-foreground wr:hover:text-foreground";
|
||||
this.datePanel.className = "wr:block";
|
||||
this.timePanel.className = "wr:hidden";
|
||||
} else {
|
||||
this.timeTab.className =
|
||||
"wr:flex-1 wr:px-4 wr:py-2 wr:text-sm wr:font-medium wr:border-b-2 wr:border-primary wr:text-primary";
|
||||
this.dateTab.className =
|
||||
"wr:flex-1 wr:px-4 wr:py-2 wr:text-sm wr:font-medium wr:border-b-2 wr:border-transparent wr:text-muted-foreground hover:wr:text-foreground";
|
||||
"wr:flex-1 wr:px-4 wr:py-2 wr:text-sm wr:font-medium wr:border-b-2 wr:border-transparent wr:text-muted-foreground wr:hover:text-foreground";
|
||||
this.timePanel.className = "wr:block";
|
||||
this.datePanel.className = "wr:hidden";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,531 @@
|
||||
---
|
||||
// Drawer.astro
|
||||
export interface Props {
|
||||
position?: "left" | "right" | "top" | "bottom";
|
||||
size?: string;
|
||||
overlay?: boolean;
|
||||
closeOnOverlayClick?: boolean;
|
||||
closeOnEsc?: boolean;
|
||||
showCloseButton?: boolean;
|
||||
animationDuration?: number;
|
||||
zIndex?: number;
|
||||
id?: string;
|
||||
class?: string;
|
||||
lockScroll?: boolean;
|
||||
persistent?: boolean;
|
||||
blurBackground?: boolean;
|
||||
}
|
||||
|
||||
const {
|
||||
position = "right",
|
||||
size = "320px",
|
||||
overlay = true,
|
||||
closeOnOverlayClick = true,
|
||||
closeOnEsc = true,
|
||||
showCloseButton = true,
|
||||
animationDuration = 300,
|
||||
zIndex = 1000,
|
||||
id = `drawer-${Math.random().toString(36).slice(2)}`,
|
||||
class: className = "",
|
||||
lockScroll = true,
|
||||
persistent = false,
|
||||
blurBackground = false,
|
||||
} = Astro.props;
|
||||
|
||||
const isHorizontal = position === "left" || position === "right";
|
||||
const sizeProperty = isHorizontal ? "width" : "height";
|
||||
---
|
||||
|
||||
<div class={`drawer-container ${className}`} id={id}>
|
||||
<!-- Trigger Slot -->
|
||||
<div class="drawer-trigger">
|
||||
<slot name="trigger" />
|
||||
</div>
|
||||
|
||||
<!-- Drawer Overlay & Panel -->
|
||||
<div
|
||||
class="drawer-overlay wr:fixed wr:inset-0 wr:z-[1000] wr:hidden wr:transition-all wr:duration-300"
|
||||
data-drawer-overlay
|
||||
style={`z-index: ${zIndex}; transition-duration: ${animationDuration}ms;`}
|
||||
>
|
||||
<!-- Background Overlay -->
|
||||
{
|
||||
overlay && (
|
||||
<div
|
||||
class={`drawer-backdrop wr:absolute wr:inset-0 wr:bg-black/50 wr:opacity-0 wr:transition-opacity ${blurBackground ? "wr:backdrop-blur-sm" : ""}`}
|
||||
data-drawer-backdrop
|
||||
style={`transition-duration: ${animationDuration}ms;`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
<!-- Drawer Panel -->
|
||||
<div
|
||||
class={`drawer-panel wr:fixed wr:bg-card wr:shadow-xl wr:transition-transform wr:flex wr:flex-col ${
|
||||
position === "left"
|
||||
? "wr:left-0 wr:top-0 wr:bottom-0 wr:border-r wr:border-border wr:-translate-x-full"
|
||||
: position === "right"
|
||||
? "wr:right-0 wr:top-0 wr:bottom-0 wr:border-l wr:border-border wr:translate-x-full"
|
||||
: position === "top"
|
||||
? "wr:top-0 wr:left-0 wr:right-0 wr:border-b wr:border-border wr:-translate-y-full"
|
||||
: "wr:bottom-0 wr:left-0 wr:right-0 wr:border-t wr:border-border wr:translate-y-full"
|
||||
}`}
|
||||
data-drawer-panel
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby={`${id}-title`}
|
||||
tabindex="-1"
|
||||
style={`${sizeProperty}: ${size}; transition-duration: ${animationDuration}ms; z-index: ${zIndex + 1};`}
|
||||
>
|
||||
<!-- Drawer Header -->
|
||||
<div
|
||||
class="drawer-header wr:flex wr:items-center wr:justify-between wr:p-4 wr:border-b wr:border-border wr:min-h-[60px]"
|
||||
>
|
||||
<div class="drawer-title" id={`${id}-title`}>
|
||||
<slot name="title">
|
||||
<h2
|
||||
class="wr:text-lg wr:font-semibold wr:text-foreground wr:m-0"
|
||||
>
|
||||
Drawer
|
||||
</h2>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
{
|
||||
showCloseButton && (
|
||||
<button
|
||||
type="button"
|
||||
class="drawer-close-btn wr:p-2 wr:rounded-lg wr:hover:bg-muted wr:transition-colors wr:text-muted-foreground wr:hover:text-foreground wr:focus:outline-none wr:focus:ring-2 wr:focus:ring-primary wr:focus:ring-offset-1"
|
||||
data-drawer-close
|
||||
aria-label="Close drawer"
|
||||
>
|
||||
<svg
|
||||
class="wr:w-5 wr:h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<line x1="18" y1="6" x2="6" y2="18" />
|
||||
<line x1="6" y1="6" x2="18" y2="18" />
|
||||
</svg>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Drawer Content -->
|
||||
<div class="drawer-content wr:flex-1 wr:overflow-y-auto wr:p-4">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<!-- Drawer Footer -->
|
||||
<div class="drawer-footer">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script
|
||||
is:inline
|
||||
define:vars={{
|
||||
id,
|
||||
position,
|
||||
closeOnOverlayClick,
|
||||
closeOnEsc,
|
||||
animationDuration,
|
||||
lockScroll,
|
||||
persistent,
|
||||
}}
|
||||
>
|
||||
class Drawer {
|
||||
constructor() {
|
||||
this.id = id;
|
||||
this.position = position;
|
||||
this.closeOnOverlayClick = closeOnOverlayClick;
|
||||
this.closeOnEsc = closeOnEsc;
|
||||
this.animationDuration = animationDuration;
|
||||
this.lockScroll = lockScroll;
|
||||
this.persistent = persistent;
|
||||
this.isOpen = false;
|
||||
this.isAnimating = false;
|
||||
|
||||
this.container = document.getElementById(this.id);
|
||||
this.trigger = this.container?.querySelector(".drawer-trigger");
|
||||
this.overlay = this.container?.querySelector(
|
||||
"[data-drawer-overlay]",
|
||||
);
|
||||
this.backdrop = this.container?.querySelector(
|
||||
"[data-drawer-backdrop]",
|
||||
);
|
||||
this.panel = this.container?.querySelector("[data-drawer-panel]");
|
||||
this.closeBtn = this.container?.querySelector(
|
||||
"[data-drawer-close]",
|
||||
);
|
||||
|
||||
this.focusableElements = [];
|
||||
this.firstFocusableElement = null;
|
||||
this.lastFocusableElement = null;
|
||||
this.previousActiveElement = null;
|
||||
|
||||
this.initializeEventListeners();
|
||||
|
||||
// FIXED: Register instance to window object immediately
|
||||
this.registerToWindow();
|
||||
}
|
||||
|
||||
// FIXED: Method to register instance to window
|
||||
registerToWindow() {
|
||||
const windowKey = `drawer_${this.id.replace(/-/g, "_")}`;
|
||||
if (!window.drawerInstances) {
|
||||
window.drawerInstances = {};
|
||||
}
|
||||
window.drawerInstances[this.id] = this;
|
||||
window[windowKey] = this;
|
||||
|
||||
// Also register with original ID for backward compatibility
|
||||
window[`drawer_${this.id}`] = this;
|
||||
}
|
||||
|
||||
initializeEventListeners() {
|
||||
if (!this.container) return;
|
||||
|
||||
// Trigger click events
|
||||
if (this.trigger) {
|
||||
const triggerElements = this.trigger.querySelectorAll(
|
||||
'button, a, [role="button"], [tabindex]',
|
||||
);
|
||||
if (triggerElements.length === 0) {
|
||||
// If no interactive elements, make the trigger itself clickable
|
||||
this.trigger.style.cursor = "pointer";
|
||||
this.trigger.addEventListener("click", () => this.open());
|
||||
} else {
|
||||
triggerElements.forEach((el) => {
|
||||
el.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.open();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Close button
|
||||
this.closeBtn?.addEventListener("click", () => this.close());
|
||||
|
||||
// Backdrop click
|
||||
if (this.closeOnOverlayClick && this.backdrop) {
|
||||
this.backdrop.addEventListener("click", () => {
|
||||
if (!this.persistent) this.close();
|
||||
});
|
||||
}
|
||||
|
||||
// Prevent panel click from closing
|
||||
this.panel?.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
// Escape key
|
||||
if (this.closeOnEsc) {
|
||||
document.addEventListener(
|
||||
"keydown",
|
||||
this.handleKeydown.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
// Focus trap
|
||||
this.panel?.addEventListener(
|
||||
"keydown",
|
||||
this.handleFocusTrap.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
handleKeydown(e) {
|
||||
if (e.key === "Escape" && this.isOpen && !this.persistent) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
handleFocusTrap(e) {
|
||||
if (!this.isOpen) return;
|
||||
|
||||
if (e.key === "Tab") {
|
||||
if (e.shiftKey) {
|
||||
// Shift + Tab
|
||||
if (document.activeElement === this.firstFocusableElement) {
|
||||
e.preventDefault();
|
||||
this.lastFocusableElement?.focus();
|
||||
}
|
||||
} else {
|
||||
// Tab
|
||||
if (document.activeElement === this.lastFocusableElement) {
|
||||
e.preventDefault();
|
||||
this.firstFocusableElement?.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getFocusableElements() {
|
||||
if (!this.panel) return;
|
||||
|
||||
const selectors = [
|
||||
"a[href]",
|
||||
"button:not([disabled])",
|
||||
"textarea:not([disabled])",
|
||||
"input:not([disabled])",
|
||||
"select:not([disabled])",
|
||||
'[tabindex]:not([tabindex="-1"])',
|
||||
"[contenteditable]",
|
||||
];
|
||||
|
||||
this.focusableElements = Array.from(
|
||||
this.panel.querySelectorAll(selectors.join(", ")),
|
||||
).filter((el) => {
|
||||
return (
|
||||
el.offsetWidth > 0 &&
|
||||
el.offsetHeight > 0 &&
|
||||
!el.hasAttribute("hidden")
|
||||
);
|
||||
});
|
||||
|
||||
this.firstFocusableElement = this.focusableElements[0] || null;
|
||||
this.lastFocusableElement =
|
||||
this.focusableElements[this.focusableElements.length - 1] ||
|
||||
null;
|
||||
}
|
||||
|
||||
async open() {
|
||||
if (this.isOpen || this.isAnimating) return;
|
||||
|
||||
this.isAnimating = true;
|
||||
this.previousActiveElement = document.activeElement;
|
||||
|
||||
// Emit opening event
|
||||
const openingEvent = new CustomEvent("drawer:opening", {
|
||||
detail: { drawerId: this.id },
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
this.container.dispatchEvent(openingEvent);
|
||||
|
||||
if (openingEvent.defaultPrevented) {
|
||||
this.isAnimating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Lock scroll
|
||||
if (this.lockScroll) {
|
||||
document.body.style.overflow = "hidden";
|
||||
document.body.style.paddingRight =
|
||||
this.getScrollbarWidth() + "px";
|
||||
}
|
||||
|
||||
// Show overlay
|
||||
this.overlay.classList.remove("wr:hidden");
|
||||
this.overlay.classList.add("wr:flex");
|
||||
|
||||
// Force reflow
|
||||
this.overlay.offsetHeight;
|
||||
|
||||
// Animate backdrop
|
||||
if (this.backdrop) {
|
||||
this.backdrop.classList.remove("wr:opacity-0");
|
||||
this.backdrop.classList.add("wr:opacity-100");
|
||||
}
|
||||
|
||||
// Animate panel
|
||||
this.panel.classList.remove(this.getTransformClass(false));
|
||||
this.panel.classList.add(this.getTransformClass(true));
|
||||
|
||||
// Wait for animation
|
||||
await this.delay(this.animationDuration);
|
||||
|
||||
this.isOpen = true;
|
||||
this.isAnimating = false;
|
||||
|
||||
// Focus management
|
||||
this.getFocusableElements();
|
||||
if (this.firstFocusableElement) {
|
||||
this.firstFocusableElement.focus();
|
||||
} else {
|
||||
this.panel.focus();
|
||||
}
|
||||
|
||||
// Emit opened event
|
||||
this.container.dispatchEvent(
|
||||
new CustomEvent("drawer:opened", {
|
||||
detail: { drawerId: this.id },
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (!this.isOpen || this.isAnimating) return;
|
||||
|
||||
this.isAnimating = true;
|
||||
|
||||
// Emit closing event
|
||||
const closingEvent = new CustomEvent("drawer:closing", {
|
||||
detail: { drawerId: this.id },
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
this.container.dispatchEvent(closingEvent);
|
||||
|
||||
if (closingEvent.defaultPrevented) {
|
||||
this.isAnimating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Animate panel
|
||||
this.panel.classList.remove(this.getTransformClass(true));
|
||||
this.panel.classList.add(this.getTransformClass(false));
|
||||
|
||||
// Animate backdrop
|
||||
if (this.backdrop) {
|
||||
this.backdrop.classList.remove("wr:opacity-100");
|
||||
this.backdrop.classList.add("wr:opacity-0");
|
||||
}
|
||||
|
||||
// Wait for animation
|
||||
await this.delay(this.animationDuration);
|
||||
|
||||
// Hide overlay
|
||||
this.overlay.classList.remove("wr:flex");
|
||||
this.overlay.classList.add("wr:hidden");
|
||||
|
||||
// Unlock scroll
|
||||
if (this.lockScroll) {
|
||||
document.body.style.overflow = "";
|
||||
document.body.style.paddingRight = "";
|
||||
}
|
||||
|
||||
this.isOpen = false;
|
||||
this.isAnimating = false;
|
||||
|
||||
// Restore focus
|
||||
if (this.previousActiveElement) {
|
||||
this.previousActiveElement.focus();
|
||||
this.previousActiveElement = null;
|
||||
}
|
||||
|
||||
// Emit closed event
|
||||
this.container.dispatchEvent(
|
||||
new CustomEvent("drawer:closed", {
|
||||
detail: { drawerId: this.id },
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
toggle() {
|
||||
return this.isOpen ? this.close() : this.open();
|
||||
}
|
||||
|
||||
getTransformClass(isOpen) {
|
||||
const transformMap = {
|
||||
left: isOpen ? "wr:translate-x-0" : "wr:-translate-x-full",
|
||||
right: isOpen ? "wr:translate-x-0" : "wr:translate-x-full",
|
||||
top: isOpen ? "wr:translate-y-0" : "wr:-translate-y-full",
|
||||
bottom: isOpen ? "wr:translate-y-0" : "wr:translate-y-full",
|
||||
};
|
||||
return transformMap[this.position];
|
||||
}
|
||||
|
||||
getScrollbarWidth() {
|
||||
const scrollDiv = document.createElement("div");
|
||||
scrollDiv.style.cssText =
|
||||
"width: 100px; height: 100px; overflow: scroll; position: absolute; top: -9999px;";
|
||||
document.body.appendChild(scrollDiv);
|
||||
const scrollbarWidth =
|
||||
scrollDiv.offsetWidth - scrollDiv.clientWidth;
|
||||
document.body.removeChild(scrollDiv);
|
||||
return scrollbarWidth;
|
||||
}
|
||||
|
||||
delay(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// Public API methods
|
||||
isOpened() {
|
||||
return this.isOpen;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// Remove all event listeners and cleanup
|
||||
if (this.lockScroll && this.isOpen) {
|
||||
document.body.style.overflow = "";
|
||||
document.body.style.paddingRight = "";
|
||||
}
|
||||
|
||||
this.container?.remove();
|
||||
}
|
||||
|
||||
openDrawer() {
|
||||
return this.open();
|
||||
}
|
||||
|
||||
closeDrawer() {
|
||||
return this.close();
|
||||
}
|
||||
|
||||
toggleDrawer() {
|
||||
return this.toggle();
|
||||
}
|
||||
|
||||
getState() {
|
||||
return {
|
||||
isOpen: this.isOpen,
|
||||
isAnimating: this.isAnimating,
|
||||
id: this.id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
const drawerElement = document.getElementById(id);
|
||||
if (!drawerElement) {
|
||||
console.warn(`Drawer element with id "${id}" not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid double initialization
|
||||
if (drawerElement.drawerInstance) {
|
||||
console.log(`Drawer "${id}" already initialized`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const drawer = new Drawer();
|
||||
drawerElement.drawerInstance = drawer;
|
||||
|
||||
// FIXED: Emit initialization event
|
||||
const initEvent = new CustomEvent("drawer:initialized", {
|
||||
detail: { drawerId: id, instance: drawer },
|
||||
bubbles: true,
|
||||
});
|
||||
drawerElement.dispatchEvent(initEvent);
|
||||
|
||||
console.log(`Drawer "${id}" initialized successfully`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to initialize drawer "${id}":`, error);
|
||||
}
|
||||
};
|
||||
|
||||
// FIXED: Multiple initialization strategies
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
} else {
|
||||
// Use setTimeout to ensure DOM is fully ready
|
||||
setTimeout(init, 0);
|
||||
}
|
||||
|
||||
// Astro page transitions
|
||||
document.addEventListener("astro:page-load", init);
|
||||
|
||||
// FIXED: Also try initialization after a small delay for complex components
|
||||
setTimeout(init, 100);
|
||||
</script>
|
||||
@@ -172,7 +172,7 @@ const controlWrap = "wr:relative";
|
||||
{p.kind === "password" && (
|
||||
<button
|
||||
type="button"
|
||||
class="wr:absolute wr:inset-y-0 wr:right-0 wr:pr-3 wr:flex wr:items-center wr:text-muted-foreground hover:wr:text-foreground"
|
||||
class="wr:absolute wr:inset-y-0 wr:right-0 wr:pr-3 wr:flex wr:items-center wr:text-muted-foreground wr:hover:text-foreground"
|
||||
onclick={`const i = this.previousElementSibling; i.type = i.type === 'password' ? 'text' : 'password';`}
|
||||
>
|
||||
<Icon name="mdi:eye" class={iconSize} />
|
||||
|
||||
@@ -93,7 +93,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
<!-- Drop Zone -->
|
||||
<div
|
||||
id="drop-zone"
|
||||
class="wr:border-2 wr:border-dashed wr:border-border wr:rounded-lg wr:p-6 wr:text-center wr:bg-background/50 wr:transition-all wr:duration-300 wr:cursor-pointer wr:min-h-[120px] wr:flex wr:items-center wr:justify-center hover:wr:border-primary hover:wr:bg-background focus:wr:outline-none focus:wr:ring-2 focus:wr:ring-primary focus:wr:ring-offset-2"
|
||||
class="wr:border-2 wr:border-dashed wr:border-border wr:rounded-lg wr:p-6 wr:text-center wr:bg-background/50 wr:transition-all wr:duration-300 wr:cursor-pointer wr:min-h-[120px] wr:flex wr:items-center wr:justify-center wr:hover:border-primary wr:hover:bg-background wr:focus:outline-none wr:focus:ring-2 wr:focus:ring-primary wr:focus:ring-offset-2"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
aria-label="Click to select files or drag and drop files here"
|
||||
@@ -113,7 +113,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
Drop files here or <button
|
||||
type="button"
|
||||
id="browse-btn"
|
||||
class="wr:text-primary wr:underline wr:font-medium wr:pointer-events-auto wr:bg-none wr:border-none wr:cursor-pointer hover:wr:text-primary/80"
|
||||
class="wr:text-primary wr:underline wr:font-medium wr:pointer-events-auto wr:bg-none wr:border-none wr:cursor-pointer wr:hover:text-primary/80"
|
||||
>browse</button
|
||||
>
|
||||
</p>
|
||||
@@ -163,7 +163,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
<button
|
||||
type="button"
|
||||
id="upload-btn"
|
||||
class="wr:flex wr:items-center wr:gap-2 wr:px-4 wr:py-2 wr:rounded-lg wr:text-sm wr:font-medium wr:bg-primary wr:text-primary-foreground hover:wr:bg-primary/90 disabled:wr:opacity-50 disabled:wr:cursor-not-allowed focus:wr:outline-none focus:wr:ring-2 focus:wr:ring-primary focus:wr:ring-offset-1"
|
||||
class="wr:flex wr:items-center wr:gap-2 wr:px-4 wr:py-2 wr:rounded-lg wr:text-sm wr:font-medium wr:bg-primary wr:text-primary-foreground wr:hover:bg-primary/90 wr:disabled:opacity-50 wr:disabled:cursor-not-allowed wr:focus:outline-none wr:focus:ring-2 wr:focus:ring-primary wr:focus:ring-offset-1"
|
||||
>
|
||||
<svg
|
||||
class="wr:w-4 wr:h-4"
|
||||
@@ -178,7 +178,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
<button
|
||||
type="button"
|
||||
id="clear-btn"
|
||||
class="wr:flex wr:items-center wr:gap-2 wr:px-4 wr:py-2 wr:rounded-lg wr:text-sm wr:font-medium wr:bg-secondary wr:text-secondary-foreground wr:border wr:border-border hover:wr:bg-secondary/80 focus:wr:outline-none focus:wr:ring-2 focus:wr:ring-secondary focus:wr:ring-offset-1"
|
||||
class="wr:flex wr:items-center wr:gap-2 wr:px-4 wr:py-2 wr:rounded-lg wr:text-sm wr:font-medium wr:bg-secondary wr:text-secondary-foreground wr:border wr:border-border wr:hover:bg-secondary/80 wr:focus:outline-none wr:focus:ring-2 wr:focus:ring-secondary wr:focus:ring-offset-1"
|
||||
>
|
||||
<svg
|
||||
class="wr:w-4 wr:h-4"
|
||||
@@ -480,7 +480,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
|
||||
const fileItem = document.createElement("div");
|
||||
fileItem.className =
|
||||
"wr:flex wr:items-center wr:gap-3 wr:p-3 wr:border wr:border-border wr:rounded-lg wr:bg-background wr:transition-all wr:duration-200 hover:wr:shadow-sm";
|
||||
"wr:flex wr:items-center wr:gap-3 wr:p-3 wr:border wr:border-border wr:rounded-lg wr:bg-background wr:transition-all wr:duration-200 wr:hover:shadow-sm";
|
||||
|
||||
// Create preview
|
||||
const previewUrl = file.type.startsWith("image/")
|
||||
@@ -524,7 +524,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
${
|
||||
status === "pending" && !this.autoUpload && this.uploadFunction
|
||||
? `
|
||||
<button class="wr:p-1 wr:rounded wr:bg-primary/10 wr:text-primary wr:border wr:border-primary/20 hover:wr:bg-primary/20"
|
||||
<button class="wr:p-1 wr:rounded wr:bg-primary/10 wr:text-primary wr:border wr:border-primary/20 wr:hover:bg-primary/20"
|
||||
data-action="process"
|
||||
data-index="${index}"
|
||||
title="Process">
|
||||
@@ -536,7 +536,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
: ""
|
||||
}
|
||||
|
||||
<button class="wr:p-1 wr:rounded wr:bg-danger/10 wr:text-danger wr:border wr:border-danger/20 hover:wr:bg-danger/20 ${status === "processing" ? "wr:opacity-50 wr:cursor-not-allowed" : ""}"
|
||||
<button class="wr:p-1 wr:rounded wr:bg-danger/10 wr:text-danger wr:border wr:border-danger/20 wr:hover:bg-danger/20 ${status === "processing" ? "wr:opacity-50 wr:cursor-not-allowed" : ""}"
|
||||
data-action="delete"
|
||||
data-index="${index}"
|
||||
title="Remove"
|
||||
|
||||
@@ -46,7 +46,7 @@ const inputCls = cn(
|
||||
"wr:py-2.5 wr:sm:py-3 wr:px-4 wr:block wr:w-full wr:border-border wr:rounded-lg wr:sm:text-sm wr:focus:border-border wr:focus:ring-blue-500 wr:disabled:opacity-50 wr:disabled:pointer-events-none wr:dark:bg-input wr:dark:border-border wr:dark:text-neutral-400 wr:dark:placeholder-neutral-500 wr:dark:focus:ring-neutral-600",
|
||||
sizeKls,
|
||||
"wr:pr-10", // space for eye button
|
||||
p.error && "wr:border-danger wr:focus-visible:wr:ring-danger/40",
|
||||
p.error && "wr:border-danger wr:focus-visible:ring-danger/40",
|
||||
);
|
||||
|
||||
const containerId = `pf-${Math.random().toString(36).slice(2)}`;
|
||||
@@ -104,7 +104,7 @@ const showChecklist = p.showChecklist ?? true;
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Toggle password"
|
||||
class="wr:absolute wr:inset-y-0 wr:right-0 wr:pr-3 wr:flex wr:items-center wr:text-muted-foreground hover:wr:text-foreground wr:z-20"
|
||||
class="wr:absolute wr:inset-y-0 wr:right-0 wr:pr-3 wr:flex wr:items-center wr:text-muted-foreground wr:hover:text-foreground wr:z-20"
|
||||
onclick={`const i = this.previousElementSibling; i.type = i.type === 'password' ? 'text' : 'password';`}
|
||||
tabindex="-1"
|
||||
>
|
||||
@@ -202,7 +202,7 @@ const showChecklist = p.showChecklist ?? true;
|
||||
p.description && (
|
||||
<p
|
||||
id={descId}
|
||||
class="wr:text-sm wr:text-gray-500 dark:wr:text-neutral-500"
|
||||
class="wr:text-sm wr:text-gray-500 wr:dark:text-neutral-500"
|
||||
>
|
||||
{p.description}
|
||||
</p>
|
||||
|
||||
@@ -64,7 +64,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
<input
|
||||
type="text"
|
||||
id={`${fieldId}-display`}
|
||||
class="wr:w-full wr:px-3 wr:py-2 wr:pr-10 wr:border wr:border-border wr:rounded-lg wr:bg-background wr:text-foreground focus:wr:ring-2 focus:wr:ring-primary focus:wr:border-primary disabled:wr:opacity-50"
|
||||
class="wr:w-full wr:px-3 wr:py-2 wr:pr-10 wr:border wr:border-border wr:rounded-lg wr:bg-background wr:text-foreground wr:focus:ring-2 wr:focus:ring-primary wr:focus:border-primary wr:disabled:opacity-50"
|
||||
placeholder={placeholder}
|
||||
readonly
|
||||
disabled={disabled}
|
||||
@@ -75,7 +75,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
<button
|
||||
type="button"
|
||||
id={`${fieldId}-trigger`}
|
||||
class="wr:absolute wr:right-0 wr:top-0 wr:h-full wr:px-3 wr:flex wr:items-center wr:text-muted-foreground hover:wr:text-foreground disabled:wr:opacity-50"
|
||||
class="wr:absolute wr:right-0 wr:top-0 wr:h-full wr:px-3 wr:flex wr:items-center wr:text-muted-foreground wr:hover:text-foreground wr:disabled:opacity-50"
|
||||
disabled={disabled}
|
||||
aria-label="Open time picker"
|
||||
>
|
||||
@@ -107,7 +107,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
>
|
||||
<select
|
||||
id={`${fieldId}-hour`}
|
||||
class="wr:w-full wr:px-2 wr:py-1 wr:text-center wr:border wr:border-border wr:rounded focus:wr:ring-1 focus:wr:ring-primary focus:wr:border-primary wr:bg-background"
|
||||
class="wr:w-full wr:px-2 wr:py-1 wr:text-center wr:border wr:border-border wr:rounded wr:focus:ring-1 wr:focus:ring-primary wr:focus:border-primary wr:bg-background"
|
||||
></select>
|
||||
</div>
|
||||
|
||||
@@ -124,7 +124,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
>
|
||||
<select
|
||||
id={`${fieldId}-minute`}
|
||||
class="wr:w-full wr:px-2 wr:py-1 wr:text-center wr:border wr:border-border wr:rounded focus:wr:ring-1 focus:wr:ring-primary focus:wr:border-primary wr:bg-background"
|
||||
class="wr:w-full wr:px-2 wr:py-1 wr:text-center wr:border wr:border-border wr:rounded wr:focus:ring-1 wr:focus:ring-primary wr:focus:border-primary wr:bg-background"
|
||||
></select>
|
||||
</div>
|
||||
|
||||
@@ -136,7 +136,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
</label>
|
||||
<select
|
||||
id={`${fieldId}-period`}
|
||||
class="wr:w-full wr:px-2 wr:py-1 wr:border wr:border-border wr:rounded focus:wr:ring-1 focus:wr:ring-primary focus:wr:border-primary wr:bg-background"
|
||||
class="wr:w-full wr:px-2 wr:py-1 wr:border wr:border-border wr:rounded wr:focus:ring-1 wr:focus:ring-primary wr:focus:border-primary wr:bg-background"
|
||||
>
|
||||
<option value="AM">AM</option>
|
||||
<option value="PM">PM</option>
|
||||
@@ -149,17 +149,17 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
<div class="wr:grid wr:grid-cols-3 wr:gap-1 wr:mb-4">
|
||||
<button
|
||||
type="button"
|
||||
class="wr:px-2 wr:py-1 wr:text-xs wr:bg-muted wr:rounded hover:wr:bg-muted/80"
|
||||
class="wr:px-2 wr:py-1 wr:text-xs wr:bg-muted wr:rounded wr:hover:bg-muted/80"
|
||||
data-time="09:00">9:00</button
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="wr:px-2 wr:py-1 wr:text-xs wr:bg-muted wr:rounded hover:wr:bg-muted/80"
|
||||
class="wr:px-2 wr:py-1 wr:text-xs wr:bg-muted wr:rounded wr:hover:bg-muted/80"
|
||||
data-time="12:00">12:00</button
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="wr:px-2 wr:py-1 wr:text-xs wr:bg-muted wr:rounded hover:wr:bg-muted/80"
|
||||
class="wr:px-2 wr:py-1 wr:text-xs wr:bg-muted wr:rounded wr:hover:bg-muted/80"
|
||||
data-time="17:00">17:00</button
|
||||
>
|
||||
</div>
|
||||
@@ -168,7 +168,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
<button
|
||||
type="button"
|
||||
id={`${fieldId}-now`}
|
||||
class="wr:px-3 wr:py-1 wr:text-sm wr:text-primary hover:wr:bg-primary/10 wr:rounded"
|
||||
class="wr:px-3 wr:py-1 wr:text-sm wr:text-primary wr:hover:bg-primary/10 wr:rounded"
|
||||
>
|
||||
Now
|
||||
</button>
|
||||
@@ -176,14 +176,14 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
||||
<button
|
||||
type="button"
|
||||
id={`${fieldId}-cancel`}
|
||||
class="wr:px-3 wr:py-1 wr:text-sm wr:text-muted-foreground hover:wr:bg-muted wr:rounded"
|
||||
class="wr:px-3 wr:py-1 wr:text-sm wr:text-muted-foreground wr:hover:bg-muted wr:rounded"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id={`${fieldId}-apply`}
|
||||
class="wr:px-3 wr:py-1 wr:text-sm wr:bg-primary wr:text-primary-foreground hover:wr:bg-primary/90 wr:rounded"
|
||||
class="wr:px-3 wr:py-1 wr:text-sm wr:bg-primary wr:text-primary-foreground wr:hover:bg-primary/90 wr:rounded"
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
|
||||
@@ -53,6 +53,9 @@ import Base from "./Base.astro";
|
||||
<li class="list-item">
|
||||
<a href="/dropdown">Dropdown</a>
|
||||
</li>
|
||||
<li class="list-item">
|
||||
<a href="/drawer">Drawer</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
---
|
||||
import Drawer from "../components/Drawer.astro";
|
||||
import ComponentLayout from "../layouts/ComponentLayout.astro";
|
||||
---
|
||||
|
||||
<ComponentLayout>
|
||||
<Drawer
|
||||
position="right"
|
||||
size="400px"
|
||||
overlay={true}
|
||||
closeOnOverlayClick={false}
|
||||
id="first-drawer"
|
||||
>
|
||||
<button
|
||||
slot="trigger"
|
||||
class="wr:px-4 wr:py-2 wr:bg-primary wr:text-primary-foreground wr:rounded-lg"
|
||||
>
|
||||
Open Settings
|
||||
</button>
|
||||
|
||||
<div slot="title">
|
||||
<h2 class="wr:text-xl wr:font-bold">Settings</h2>
|
||||
</div>
|
||||
|
||||
<div class="wr:space-y-4">
|
||||
<div>
|
||||
<label class="wr:block wr:text-sm wr:font-medium wr:mb-2"
|
||||
>Theme</label
|
||||
>
|
||||
<select
|
||||
class="wr:w-full wr:px-3 wr:py-2 wr:border wr:border-border wr:rounded-lg"
|
||||
>
|
||||
<option>Light</option>
|
||||
<option>Dark</option>
|
||||
<option>System</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="wr:block wr:text-sm wr:font-medium wr:mb-2"
|
||||
>Language</label
|
||||
>
|
||||
<select
|
||||
class="wr:w-full wr:px-3 wr:py-2 wr:border wr:border-border wr:rounded-lg"
|
||||
>
|
||||
<option>English</option>
|
||||
<option>Spanish</option>
|
||||
<option>French</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
slot="footer"
|
||||
class="wr:p-4 wr:border-t wr:border-border wr:flex wr:gap-2 wr:justify-end"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
id="cancle-button"
|
||||
class="wr:px-4 wr:py-2 wr:text-muted-foreground wr:rounded-lg hover:wr:bg-muted"
|
||||
>Cancel</button
|
||||
>
|
||||
<button
|
||||
class="wr:px-4 wr:py-2 wr:bg-primary wr:text-primary-foreground wr:rounded-lg"
|
||||
>Save</button
|
||||
>
|
||||
</div>
|
||||
</Drawer>
|
||||
|
||||
<Drawer position="left" size="280px" id="nav-drawer">
|
||||
<div slot="trigger">
|
||||
<button class="wr:p-2 wr:rounded-lg wr:hover:wr:bg-muted">
|
||||
<svg
|
||||
class="wr:w-6 wr:h-6"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<line x1="3" y1="6" x2="21" y2="6"></line>
|
||||
<line x1="3" y1="12" x2="21" y2="12"></line>
|
||||
<line x1="3" y1="18" x2="21" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div slot="title">
|
||||
<h2 class="wr:text-lg wr:font-bold">Navigation</h2>
|
||||
</div>
|
||||
|
||||
<nav class="wr:space-y-1">
|
||||
<a
|
||||
href="/"
|
||||
class="wr:flex wr:items-center wr:gap-3 wr:px-3 wr:py-2 wr:rounded-lg wr:hover:wr:bg-muted"
|
||||
>
|
||||
<svg
|
||||
class="wr:w-5 wr:h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
|
||||
></path>
|
||||
</svg>
|
||||
Home
|
||||
</a>
|
||||
<a
|
||||
href="/about"
|
||||
class="wr:flex wr:items-center wr:gap-3 wr:px-3 wr:py-2 wr:rounded-lg wr:hover:wr:bg-muted"
|
||||
>
|
||||
<svg
|
||||
class="wr:w-5 wr:h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
||||
<circle cx="12" cy="7" r="4"></circle>
|
||||
</svg>
|
||||
About
|
||||
</a>
|
||||
<a
|
||||
href="/contact"
|
||||
class="wr:flex wr:items-center wr:gap-3 wr:px-3 wr:py-2 wr:rounded-lg wr:hover:wr:bg-muted"
|
||||
>
|
||||
<svg
|
||||
class="wr:w-5 wr:h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2-2z"
|
||||
></path>
|
||||
<polyline points="22,6 12,13 2,6"></polyline>
|
||||
</svg>
|
||||
Contact
|
||||
</a>
|
||||
</nav>
|
||||
</Drawer>
|
||||
|
||||
<Drawer
|
||||
position="top"
|
||||
size="200px"
|
||||
overlay={false}
|
||||
animationDuration={400}
|
||||
blurBackground={true}
|
||||
>
|
||||
<div slot="trigger">
|
||||
<button
|
||||
class="wr:relative wr:p-2 wr:rounded-lg wr:hover:wr:bg-muted"
|
||||
>
|
||||
<svg
|
||||
class="wr:w-6 wr:h-6"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"></path>
|
||||
<path d="m13.73 21a2 2 0 0 1-3.46 0"></path>
|
||||
</svg>
|
||||
<span
|
||||
class="wr:absolute wr:-top-1 wr:-right-1 wr:w-3 wr:h-3 wr:bg-danger wr:rounded-full"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div slot="title">
|
||||
<h3 class="wr:text-lg wr:font-semibold">Notifications</h3>
|
||||
</div>
|
||||
|
||||
<div class="wr:space-y-3">
|
||||
<div class="wr:p-3 wr:border wr:border-border wr:rounded-lg">
|
||||
<h4 class="wr:font-medium wr:text-sm">New message from John</h4>
|
||||
<p class="wr:text-xs wr:text-muted-foreground wr:mt-1">
|
||||
5 minutes ago
|
||||
</p>
|
||||
</div>
|
||||
<div class="wr:p-3 wr:border wr:border-border wr:rounded-lg">
|
||||
<h4 class="wr:font-medium wr:text-sm">
|
||||
System update available
|
||||
</h4>
|
||||
<p class="wr:text-xs wr:text-muted-foreground wr:mt-1">
|
||||
1 hour ago
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
|
||||
<Drawer
|
||||
position="right"
|
||||
size="50vw"
|
||||
persistent={false}
|
||||
lockScroll={true}
|
||||
showCloseButton={false}
|
||||
id="advanced-drawer"
|
||||
>
|
||||
<div slot="trigger">
|
||||
<button
|
||||
class="wr:px-6 wr:py-3 wr:bg-secondary wr:text-secondary-foreground wr:rounded-lg wr:font-medium"
|
||||
>
|
||||
Open Advanced Drawer
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div slot="title">
|
||||
<div class="wr:flex wr:items-center wr:gap-2">
|
||||
<svg
|
||||
class="wr:w-5 wr:h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M12 2L2 7v10c0 5.55 3.84 9.74 9 11 5.16-1.26 9-5.45 9-11V7l-10-5z"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="wr:text-lg wr:font-semibold"
|
||||
>Advanced Settings</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wr:space-y-6">
|
||||
<section>
|
||||
<h3 class="wr:text-base wr:font-semibold wr:mb-3">Security</h3>
|
||||
<div class="wr:space-y-3">
|
||||
<label class="wr:flex wr:items-center wr:gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="wr:rounded wr:border-border"
|
||||
/>
|
||||
<span class="wr:text-sm">Two-factor authentication</span
|
||||
>
|
||||
</label>
|
||||
<label class="wr:flex wr:items-center wr:gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="wr:rounded wr:border-border"
|
||||
/>
|
||||
<span class="wr:text-sm">Login notifications</span>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="wr:text-base wr:font-semibold wr:mb-3">Privacy</h3>
|
||||
<div class="wr:space-y-3">
|
||||
<label class="wr:flex wr:items-center wr:gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="wr:rounded wr:border-border"
|
||||
/>
|
||||
<span class="wr:text-sm">Allow analytics</span>
|
||||
</label>
|
||||
<label class="wr:flex wr:items-center wr:gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="wr:rounded wr:border-border"
|
||||
/>
|
||||
<span class="wr:text-sm">Share usage data</span>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div
|
||||
slot="footer"
|
||||
class="wr:p-4 wr:border-t wr:border-border wr:flex wr:gap-3 wr:justify-end"
|
||||
>
|
||||
<button
|
||||
class="wr:px-4 wr:py-2 wr:text-muted-foreground wr:rounded-lg wr:border wr:border-border hover:wr:bg-muted"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
class="wr:px-4 wr:py-2 wr:bg-primary wr:text-primary-foreground wr:rounded-lg wr:font-medium"
|
||||
>
|
||||
Apply Changes
|
||||
</button>
|
||||
</div>
|
||||
</Drawer>
|
||||
</ComponentLayout>
|
||||
|
||||
<script>
|
||||
// Listen to drawer events
|
||||
document.addEventListener("drawer:opened", (e) => {
|
||||
console.log("Drawer opened:", e.detail.drawerId);
|
||||
});
|
||||
|
||||
document.addEventListener("drawer:closed", (e) => {
|
||||
console.log("Drawer closed:", e.detail.drawerId);
|
||||
});
|
||||
|
||||
// Programmatic control
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const advancedDrawer = window.drawer_advanced_drawer;
|
||||
|
||||
// You can control the drawer programmatically
|
||||
advancedDrawer.open();
|
||||
// advancedDrawer.close();
|
||||
// advancedDrawer.toggle();
|
||||
});
|
||||
|
||||
const cancelButton = document.getElementById("cancle-button");
|
||||
cancelButton?.addEventListener("click", () => {
|
||||
const advancedDrawer = window.drawer_first_drawer;
|
||||
advancedDrawer.close();
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user