Added new Drawer Component

This commit is contained in:
2025-08-22 15:23:39 +05:30
parent 589d6fd7d1
commit c854d0733e
9 changed files with 885 additions and 43 deletions
+8 -8
View File
@@ -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) {
+13 -13
View File
@@ -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";
}
+531
View File
@@ -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>
+1 -1
View File
@@ -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} />
+7 -7
View File
@@ -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"
+3 -3
View File
@@ -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>
+11 -11
View File
@@ -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>