Added new Drawer Component
This commit is contained in:
@@ -64,7 +64,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id={`${fieldId}-display`}
|
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}
|
placeholder={placeholder}
|
||||||
readonly
|
readonly
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -75,7 +75,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id={`${fieldId}-trigger`}
|
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}
|
disabled={disabled}
|
||||||
aria-label="Open calendar"
|
aria-label="Open calendar"
|
||||||
>
|
>
|
||||||
@@ -107,7 +107,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id={`${fieldId}-prev-month`}
|
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"
|
aria-label="Previous month"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -124,19 +124,19 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
<div class="wr:flex wr:gap-2">
|
<div class="wr:flex wr:gap-2">
|
||||||
<select
|
<select
|
||||||
id={`${fieldId}-month-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>
|
></select>
|
||||||
<!-- UPDATED: Year dropdown instead of input -->
|
<!-- UPDATED: Year dropdown instead of input -->
|
||||||
<select
|
<select
|
||||||
id={`${fieldId}-year-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>
|
></select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id={`${fieldId}-next-month`}
|
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"
|
aria-label="Next month"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -202,7 +202,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id={`${fieldId}-today`}
|
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
|
Today
|
||||||
</button>
|
</button>
|
||||||
@@ -417,7 +417,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
dayButton.type = "button";
|
dayButton.type = "button";
|
||||||
dayButton.textContent = day;
|
dayButton.textContent = day;
|
||||||
dayButton.className =
|
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);
|
const isDisabled = this.isDateDisabled(currentDate);
|
||||||
if (isDisabled) {
|
if (isDisabled) {
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ if (value) {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id={`${fieldId}-display`}
|
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}
|
placeholder={placeholder}
|
||||||
readonly
|
readonly
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -87,7 +87,7 @@ if (value) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id={`${fieldId}-trigger`}
|
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}
|
disabled={disabled}
|
||||||
aria-label="Open datetime picker"
|
aria-label="Open datetime picker"
|
||||||
>
|
>
|
||||||
@@ -128,7 +128,7 @@ if (value) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id={`${fieldId}-time-tab`}
|
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"
|
data-tab="time"
|
||||||
>
|
>
|
||||||
Time
|
Time
|
||||||
@@ -144,7 +144,7 @@ if (value) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id={`${fieldId}-prev-month`}
|
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
|
<svg
|
||||||
class="wr:w-4 wr:h-4"
|
class="wr:w-4 wr:h-4"
|
||||||
@@ -171,7 +171,7 @@ if (value) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id={`${fieldId}-next-month`}
|
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
|
<svg
|
||||||
class="wr:w-4 wr:h-4"
|
class="wr:w-4 wr:h-4"
|
||||||
@@ -249,7 +249,7 @@ if (value) {
|
|||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
id={`${fieldId}-hour`}
|
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>
|
></select>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -264,7 +264,7 @@ if (value) {
|
|||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
id={`${fieldId}-minute`}
|
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>
|
></select>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
@@ -275,7 +275,7 @@ if (value) {
|
|||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id={`${fieldId}-period`}
|
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="AM">AM</option>
|
||||||
<option value="PM">PM</option>
|
<option value="PM">PM</option>
|
||||||
@@ -302,14 +302,14 @@ if (value) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id={`${fieldId}-now`}
|
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
|
Now
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id={`${fieldId}-apply`}
|
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
|
Apply
|
||||||
</button>
|
</button>
|
||||||
@@ -556,7 +556,7 @@ if (value) {
|
|||||||
dayButton.type = "button";
|
dayButton.type = "button";
|
||||||
dayButton.textContent = day;
|
dayButton.textContent = day;
|
||||||
dayButton.className =
|
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];
|
const dateStr = currentDate.toISOString().split("T")[0];
|
||||||
if (this.selectedDate === dateStr) {
|
if (this.selectedDate === dateStr) {
|
||||||
@@ -639,14 +639,14 @@ if (value) {
|
|||||||
this.dateTab.className =
|
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";
|
"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 =
|
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.datePanel.className = "wr:block";
|
||||||
this.timePanel.className = "wr:hidden";
|
this.timePanel.className = "wr:hidden";
|
||||||
} else {
|
} else {
|
||||||
this.timeTab.className =
|
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";
|
"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 =
|
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.timePanel.className = "wr:block";
|
||||||
this.datePanel.className = "wr:hidden";
|
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" && (
|
{p.kind === "password" && (
|
||||||
<button
|
<button
|
||||||
type="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';`}
|
onclick={`const i = this.previousElementSibling; i.type = i.type === 'password' ? 'text' : 'password';`}
|
||||||
>
|
>
|
||||||
<Icon name="mdi:eye" class={iconSize} />
|
<Icon name="mdi:eye" class={iconSize} />
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
<!-- Drop Zone -->
|
<!-- Drop Zone -->
|
||||||
<div
|
<div
|
||||||
id="drop-zone"
|
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"
|
tabindex="0"
|
||||||
role="button"
|
role="button"
|
||||||
aria-label="Click to select files or drag and drop files here"
|
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
|
Drop files here or <button
|
||||||
type="button"
|
type="button"
|
||||||
id="browse-btn"
|
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
|
>browse</button
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
@@ -163,7 +163,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id="upload-btn"
|
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
|
<svg
|
||||||
class="wr:w-4 wr:h-4"
|
class="wr:w-4 wr:h-4"
|
||||||
@@ -178,7 +178,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id="clear-btn"
|
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
|
<svg
|
||||||
class="wr:w-4 wr:h-4"
|
class="wr:w-4 wr:h-4"
|
||||||
@@ -480,7 +480,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
|
|
||||||
const fileItem = document.createElement("div");
|
const fileItem = document.createElement("div");
|
||||||
fileItem.className =
|
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
|
// Create preview
|
||||||
const previewUrl = file.type.startsWith("image/")
|
const previewUrl = file.type.startsWith("image/")
|
||||||
@@ -524,7 +524,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
${
|
${
|
||||||
status === "pending" && !this.autoUpload && this.uploadFunction
|
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-action="process"
|
||||||
data-index="${index}"
|
data-index="${index}"
|
||||||
title="Process">
|
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-action="delete"
|
||||||
data-index="${index}"
|
data-index="${index}"
|
||||||
title="Remove"
|
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",
|
"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,
|
sizeKls,
|
||||||
"wr:pr-10", // space for eye button
|
"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)}`;
|
const containerId = `pf-${Math.random().toString(36).slice(2)}`;
|
||||||
@@ -104,7 +104,7 @@ const showChecklist = p.showChecklist ?? true;
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label="Toggle password"
|
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';`}
|
onclick={`const i = this.previousElementSibling; i.type = i.type === 'password' ? 'text' : 'password';`}
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
@@ -202,7 +202,7 @@ const showChecklist = p.showChecklist ?? true;
|
|||||||
p.description && (
|
p.description && (
|
||||||
<p
|
<p
|
||||||
id={descId}
|
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.description}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id={`${fieldId}-display`}
|
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}
|
placeholder={placeholder}
|
||||||
readonly
|
readonly
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -75,7 +75,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id={`${fieldId}-trigger`}
|
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}
|
disabled={disabled}
|
||||||
aria-label="Open time picker"
|
aria-label="Open time picker"
|
||||||
>
|
>
|
||||||
@@ -107,7 +107,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
id={`${fieldId}-hour`}
|
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>
|
></select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
id={`${fieldId}-minute`}
|
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>
|
></select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id={`${fieldId}-period`}
|
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="AM">AM</option>
|
||||||
<option value="PM">PM</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">
|
<div class="wr:grid wr:grid-cols-3 wr:gap-1 wr:mb-4">
|
||||||
<button
|
<button
|
||||||
type="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
|
data-time="09:00">9:00</button
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="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
|
data-time="12:00">12:00</button
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="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
|
data-time="17:00">17:00</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@@ -168,7 +168,7 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id={`${fieldId}-now`}
|
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
|
Now
|
||||||
</button>
|
</button>
|
||||||
@@ -176,14 +176,14 @@ const fieldId = `field-${name.replace(/[[\]]/g, "-")}`;
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id={`${fieldId}-cancel`}
|
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
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id={`${fieldId}-apply`}
|
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
|
Apply
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ import Base from "./Base.astro";
|
|||||||
<li class="list-item">
|
<li class="list-item">
|
||||||
<a href="/dropdown">Dropdown</a>
|
<a href="/dropdown">Dropdown</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="list-item">
|
||||||
|
<a href="/drawer">Drawer</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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