import { LitElement, html, css } from 'lit';
import { property, state } from 'lit/decorators.js';
export class Drawer extends LitElement {
@property({ type: Boolean, reflect: true }) open = false;
@property({ type: String }) anchor: 'left' | 'right' | 'top' | 'bottom' = 'left';
@property({ type: String }) variant: 'temporary' | 'persistent' | 'permanent' = 'temporary';
@property({ type: Number }) width = 240;
@property({ type: Number }) height = 240;
@property({ type: Boolean }) hideBackdrop = false;
@property({ type: String }) elevation = '16';
@state() private isAnimating = false;
static styles = css`
:host {
position: fixed;
z-index: 1200;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
}
:host([open]) {
pointer-events: auto;
}
/* Backdrop */
.backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
opacity: 0;
transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
pointer-events: none;
z-index: -1;
}
:host([open]) .backdrop {
opacity: 1;
pointer-events: auto;
}
:host([hideBackdrop]) .backdrop {
display: none;
}
/* Drawer paper */
.drawer {
position: fixed;
background-color: #fff;
color: rgba(0, 0, 0, 0.87);
transition: transform 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
box-shadow: 0px 8px 10px -5px rgba(0,0,0,0.2), 0px 16px 24px 2px rgba(0,0,0,0.14), 0px 6px 30px 5px rgba(0,0,0,0.12);
overflow-y: auto;
display: flex;
flex-direction: column;
}
/* Anchor positions */
:host([anchor="left"]) .drawer {
top: 0;
bottom: 0;
left: 0;
width: var(--drawer-width, 240px);
transform: translateX(-100%);
}
:host([anchor="right"]) .drawer {
top: 0;
bottom: 0;
right: 0;
width: var(--drawer-width, 240px);
transform: translateX(100%);
}
:host([anchor="top"]) .drawer {
top: 0;
left: 0;
right: 0;
height: var(--drawer-height, 240px);
transform: translateY(-100%);
}
:host([anchor="bottom"]) .drawer {
bottom: 0;
left: 0;
right: 0;
height: var(--drawer-height, 240px);
transform: translateY(100%);
}
/* Open state */
:host([open][anchor="left"]) .drawer,
:host([open][anchor="right"]) .drawer {
transform: translateX(0);
}
:host([open][anchor="top"]) .drawer,
:host([open][anchor="bottom"]) .drawer {
transform: translateY(0);
}
/* Variant styles */
:host([variant="permanent"]) {
position: relative;
z-index: auto;
}
:host([variant="permanent"]) .backdrop {
display: none;
}
:host([variant="permanent"]) .drawer {
position: relative;
transform: none !important;
box-shadow: none;
border-right: 1px solid rgba(0, 0, 0, 0.12);
}
:host([variant="permanent"][anchor="right"]) .drawer {
border-right: none;
border-left: 1px solid rgba(0, 0, 0, 0.12);
}
:host([variant="permanent"][anchor="top"]) .drawer {
border-right: none;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}
:host([variant="permanent"][anchor="bottom"]) .drawer {
border-right: none;
border-top: 1px solid rgba(0, 0, 0, 0.12);
}
:host([variant="persistent"]) {
position: relative;
z-index: auto;
}
:host([variant="persistent"]) .backdrop {
display: none;
}
:host([variant="persistent"]) .drawer {
position: relative;
box-shadow: none;
border-right: 1px solid rgba(0, 0, 0, 0.12);
}
:host([variant="persistent"][anchor="right"]) .drawer {
border-right: none;
border-left: 1px solid rgba(0, 0, 0, 0.12);
}
:host([variant="persistent"][anchor="top"]) .drawer {
border-right: none;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}
:host([variant="persistent"][anchor="bottom"]) .drawer {
border-right: none;
border-top: 1px solid rgba(0, 0, 0, 0.12);
}
/* Content */
.drawer-content {
flex: 1;
overflow-y: auto;
}
/* Responsive behavior */
@media (max-width: 600px) {
:host([anchor="left"]) .drawer,
:host([anchor="right"]) .drawer {
width: calc(100vw - 56px);
max-width: 280px;
}
}
`;
connectedCallback() {
super.connectedCallback();
this.updateDrawerSize();
}
updated(changedProperties: Map) {
super.updated(changedProperties);
if (changedProperties.has('width') || changedProperties.has('height')) {
this.updateDrawerSize();
}
if (changedProperties.has('open')) {
this.handleOpenChange();
}
}
private updateDrawerSize() {
if (this.anchor === 'left' || this.anchor === 'right') {
this.style.setProperty('--drawer-width', `${this.width}px`);
} else {
this.style.setProperty('--drawer-height', `${this.height}px`);
}
}
private handleOpenChange() {
if (this.open) {
this.dispatchEvent(new CustomEvent('drawer-opening', {
bubbles: true,
composed: true
}));
} else {
this.dispatchEvent(new CustomEvent('drawer-closing', {
bubbles: true,
composed: true
}));
}
// Handle animation state
this.isAnimating = true;
setTimeout(() => {
this.isAnimating = false;
if (this.open) {
this.dispatchEvent(new CustomEvent('drawer-opened', {
bubbles: true,
composed: true
}));
} else {
this.dispatchEvent(new CustomEvent('drawer-closed', {
bubbles: true,
composed: true
}));
}
}, 225);
}
private handleBackdropClick(e: Event) {
if (this.variant === 'temporary' && !this.hideBackdrop) {
this.closeDrawer();
}
}
private handleKeyDown(e: KeyboardEvent) {
if (e.key === 'Escape' && this.variant === 'temporary') {
this.closeDrawer();
}
}
public openDrawer() {
this.open = true;
}
public closeDrawer() {
this.open = false;
}
public toggleDrawer() {
this.open = !this.open;
}
render() {
return html`
${this.variant === 'temporary' && !this.hideBackdrop ? html`
` : ''}
`;
}
}
export class DrawerHeader extends LitElement {
static styles = css`
:host {
display: flex;
align-items: center;
padding: 0 16px;
min-height: 64px;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
background-color: inherit;
}
.header-content {
display: flex;
align-items: center;
width: 100%;
gap: 16px;
}
::slotted([slot="icon"]) {
flex-shrink: 0;
}
::slotted([slot="title"]) {
flex: 1;
font-size: 1.25rem;
font-weight: 500;
line-height: 1.6;
letter-spacing: 0.0075em;
margin: 0;
}
::slotted([slot="action"]) {
flex-shrink: 0;
margin-left: auto;
}
/* Dense variant */
:host([dense]) {
min-height: 48px;
padding: 0 12px;
}
:host([dense]) .header-content {
gap: 12px;
}
`;
render() {
return html`
`;
}
}