initial
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import { html } from '@design.estate/dees-element';
|
||||
import { injectCssVariables } from '../../00variables.js';
|
||||
|
||||
export const demoFunc = () => {
|
||||
injectCssVariables();
|
||||
return html`
|
||||
<style>
|
||||
.demo-section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.demo-section h3 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 0.875rem;
|
||||
color: var(--dees-muted-foreground);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.demo-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Toast Types</h3>
|
||||
<div class="demo-row">
|
||||
<dees-mobile-button
|
||||
variant="outline"
|
||||
@click=${() => {
|
||||
const toast = document.createElement('dees-mobile-toast');
|
||||
(toast as any).type = 'success';
|
||||
(toast as any).message = 'Item saved successfully!';
|
||||
toast.addEventListener('close', () => toast.remove());
|
||||
document.body.appendChild(toast);
|
||||
}}
|
||||
>Success Toast</dees-mobile-button>
|
||||
|
||||
<dees-mobile-button
|
||||
variant="outline"
|
||||
@click=${() => {
|
||||
const toast = document.createElement('dees-mobile-toast');
|
||||
(toast as any).type = 'error';
|
||||
(toast as any).message = 'Failed to save item. Please try again.';
|
||||
toast.addEventListener('close', () => toast.remove());
|
||||
document.body.appendChild(toast);
|
||||
}}
|
||||
>Error Toast</dees-mobile-button>
|
||||
|
||||
<dees-mobile-button
|
||||
variant="outline"
|
||||
@click=${() => {
|
||||
const toast = document.createElement('dees-mobile-toast');
|
||||
(toast as any).type = 'warning';
|
||||
(toast as any).message = 'Your session will expire in 5 minutes.';
|
||||
toast.addEventListener('close', () => toast.remove());
|
||||
document.body.appendChild(toast);
|
||||
}}
|
||||
>Warning Toast</dees-mobile-button>
|
||||
|
||||
<dees-mobile-button
|
||||
variant="outline"
|
||||
@click=${() => {
|
||||
const toast = document.createElement('dees-mobile-toast');
|
||||
(toast as any).type = 'info';
|
||||
(toast as any).message = 'New updates are available.';
|
||||
toast.addEventListener('close', () => toast.remove());
|
||||
document.body.appendChild(toast);
|
||||
}}
|
||||
>Info Toast</dees-mobile-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Custom Duration</h3>
|
||||
<div class="demo-row">
|
||||
<dees-mobile-button
|
||||
variant="secondary"
|
||||
@click=${() => {
|
||||
const toast = document.createElement('dees-mobile-toast');
|
||||
(toast as any).type = 'info';
|
||||
(toast as any).message = 'This toast stays for 10 seconds.';
|
||||
(toast as any).duration = 10000;
|
||||
toast.addEventListener('close', () => toast.remove());
|
||||
document.body.appendChild(toast);
|
||||
}}
|
||||
>Long Duration (10s)</dees-mobile-button>
|
||||
|
||||
<dees-mobile-button
|
||||
variant="secondary"
|
||||
@click=${() => {
|
||||
const toast = document.createElement('dees-mobile-toast');
|
||||
(toast as any).type = 'success';
|
||||
(toast as any).message = 'Quick notification!';
|
||||
(toast as any).duration = 1500;
|
||||
toast.addEventListener('close', () => toast.remove());
|
||||
document.body.appendChild(toast);
|
||||
}}
|
||||
>Short Duration (1.5s)</dees-mobile-button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
@@ -0,0 +1,339 @@
|
||||
import {
|
||||
DeesElement,
|
||||
css,
|
||||
cssManager,
|
||||
customElement,
|
||||
html,
|
||||
property,
|
||||
type TemplateResult,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import { mobileComponentStyles } from '../../00componentstyles.js';
|
||||
import '../dees-mobile-icon/dees-mobile-icon.js';
|
||||
import { demoFunc } from './dees-mobile-toast.demo.js';
|
||||
|
||||
export type ToastType = 'success' | 'error' | 'info' | 'warning';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-mobile-toast': DeesMobileToast;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-mobile-toast')
|
||||
export class DeesMobileToast extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
|
||||
@property({ type: String })
|
||||
accessor message: string = '';
|
||||
|
||||
@property({ type: String })
|
||||
accessor type: ToastType = 'info';
|
||||
|
||||
@property({ type: Number })
|
||||
accessor duration: number = 0; // 0 means use default
|
||||
|
||||
private timeoutId?: number;
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
mobileComponentStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
position: fixed;
|
||||
/* Mobile-first defaults */
|
||||
bottom: 1rem;
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
transform: none;
|
||||
z-index: var(--dees-z-notification, 900);
|
||||
animation: slideUp 200ms var(--dees-spring);
|
||||
}
|
||||
|
||||
/* Desktop enhancements */
|
||||
@media (min-width: 641px) {
|
||||
:host {
|
||||
bottom: 2rem;
|
||||
left: 50%;
|
||||
right: auto;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile-first animations */
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(100%) scale(0.95);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
transform: translateY(0) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: translateY(100%) scale(0.95);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop-specific animations that include X translation */
|
||||
@media (min-width: 641px) {
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translate(-50%, 100%) scale(0.95);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translate(-50%, 0) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
transform: translate(-50%, 0) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: translate(-50%, 100%) scale(0.95);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:host(.closing) {
|
||||
animation: slideDown 200ms var(--dees-ease-in);
|
||||
}
|
||||
|
||||
.toast {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: var(--dees-radius);
|
||||
box-shadow: var(--dees-shadow-lg);
|
||||
/* Mobile-first defaults */
|
||||
width: 100%;
|
||||
min-width: auto;
|
||||
max-width: none;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Desktop enhancements */
|
||||
@media (min-width: 641px) {
|
||||
.toast {
|
||||
width: auto;
|
||||
min-width: 300px;
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Type-specific styles */
|
||||
.toast.success {
|
||||
background: var(--dees-card);
|
||||
color: var(--dees-foreground);
|
||||
border: 1px solid var(--dees-border);
|
||||
}
|
||||
|
||||
.toast.error {
|
||||
background: var(--dees-danger);
|
||||
color: white;
|
||||
border: 1px solid var(--dees-danger);
|
||||
}
|
||||
|
||||
.toast.warning {
|
||||
background: var(--dees-warning);
|
||||
color: white;
|
||||
border: 1px solid var(--dees-warning);
|
||||
}
|
||||
|
||||
.toast.info {
|
||||
background: var(--dees-primary);
|
||||
color: white;
|
||||
border: 1px solid var(--dees-primary);
|
||||
}
|
||||
|
||||
.icon {
|
||||
flex-shrink: 0;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.icon.success {
|
||||
color: var(--dees-success);
|
||||
}
|
||||
|
||||
.icon.error,
|
||||
.icon.warning,
|
||||
.icon.info {
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.message {
|
||||
flex: 1;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.close {
|
||||
flex-shrink: 0;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0.375rem;
|
||||
margin: -0.375rem;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all var(--dees-transition-fast);
|
||||
opacity: 0.8;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
pointer-events: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.close.success {
|
||||
color: var(--dees-muted-foreground);
|
||||
}
|
||||
|
||||
.close.error,
|
||||
.close.warning,
|
||||
.close.info {
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.close:hover {
|
||||
opacity: 1;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* Progress bar */
|
||||
.progress-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: currentColor;
|
||||
opacity: 0.3;
|
||||
transform-origin: left;
|
||||
animation: progress linear forwards;
|
||||
pointer-events: none;
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.toast.success .progress-bar {
|
||||
background: var(--dees-success);
|
||||
}
|
||||
|
||||
.toast.error .progress-bar,
|
||||
.toast.warning .progress-bar,
|
||||
.toast.info .progress-bar {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
@keyframes progress {
|
||||
from {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
to {
|
||||
transform: scaleX(0);
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
private get defaultDuration(): number {
|
||||
switch (this.type) {
|
||||
case 'success': return 3000;
|
||||
case 'error': return 5000;
|
||||
case 'warning': return 4000;
|
||||
case 'info': return 4000;
|
||||
}
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
await super.connectedCallback();
|
||||
// Auto-dismiss after duration
|
||||
const duration = this.duration || this.defaultDuration;
|
||||
this.timeoutId = window.setTimeout(() => {
|
||||
this.handleClose();
|
||||
}, duration);
|
||||
}
|
||||
|
||||
async disconnectedCallback() {
|
||||
await super.disconnectedCallback();
|
||||
// Clear the timeout when the element is removed
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
private handleClose() {
|
||||
// Cancel the auto-dismiss timer
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId);
|
||||
this.timeoutId = undefined;
|
||||
}
|
||||
|
||||
// Prevent double-triggering
|
||||
if (this.classList.contains('closing')) return;
|
||||
|
||||
// Add closing animation
|
||||
this.classList.add('closing');
|
||||
|
||||
// Wait for closing animation to complete
|
||||
setTimeout(() => {
|
||||
this.dispatchEvent(new CustomEvent('close', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}));
|
||||
}, 200);
|
||||
}
|
||||
|
||||
private getIcon(): string {
|
||||
switch (this.type) {
|
||||
case 'success': return 'check-circle';
|
||||
case 'error': return 'alert-circle';
|
||||
case 'warning': return 'alert-triangle';
|
||||
case 'info': return 'info';
|
||||
}
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
const duration = this.duration || this.defaultDuration;
|
||||
|
||||
return html`
|
||||
<div class="toast ${this.type}">
|
||||
<div class="icon ${this.type}">
|
||||
<dees-mobile-icon icon=${this.getIcon()} size="20"></dees-mobile-icon>
|
||||
</div>
|
||||
<span class="message">${this.message}</span>
|
||||
<button
|
||||
class="close ${this.type}"
|
||||
@click=${() => this.handleClose()}
|
||||
aria-label="Close"
|
||||
type="button"
|
||||
>
|
||||
<dees-mobile-icon icon="x" size="20"></dees-mobile-icon>
|
||||
</button>
|
||||
<div class="progress-bar" style="animation-duration: ${duration}ms"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
1
ts_web/elements/00group-ui/dees-mobile-toast/index.ts
Normal file
1
ts_web/elements/00group-ui/dees-mobile-toast/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './dees-mobile-toast.js';
|
||||
Reference in New Issue
Block a user