Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
167
admin-ui/js/components/ds-toast.js
Normal file
167
admin-ui/js/components/ds-toast.js
Normal file
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* admin-ui/js/components/ds-toast.js
|
||||
* A single toast notification component with swipe-to-dismiss support.
|
||||
*/
|
||||
class DsToast extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ['type', 'duration'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
this._duration = 5000;
|
||||
this._dismissTimer = null;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
this.setupAutoDismiss();
|
||||
this.setupSwipeToDismiss();
|
||||
this.shadowRoot.querySelector('.close-button')?.addEventListener('click', () => this.dismiss());
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this._dismissTimer) {
|
||||
clearTimeout(this._dismissTimer);
|
||||
}
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
if (name === 'duration') {
|
||||
this._duration = parseInt(newValue, 10);
|
||||
}
|
||||
}
|
||||
|
||||
setupAutoDismiss() {
|
||||
if (this._duration > 0 && !this.hasAttribute('progress')) {
|
||||
this._dismissTimer = setTimeout(() => this.dismiss(), this._duration);
|
||||
}
|
||||
}
|
||||
|
||||
dismiss() {
|
||||
if (this._dismissTimer) {
|
||||
clearTimeout(this._dismissTimer);
|
||||
}
|
||||
this.classList.add('dismissing');
|
||||
this.addEventListener('animationend', () => {
|
||||
this.dispatchEvent(new CustomEvent('dismiss', { bubbles: true, composed: true }));
|
||||
this.remove();
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
setupSwipeToDismiss() {
|
||||
let startX = 0;
|
||||
let currentX = 0;
|
||||
let isDragging = false;
|
||||
|
||||
this.addEventListener('pointerdown', (e) => {
|
||||
isDragging = true;
|
||||
startX = e.clientX;
|
||||
currentX = startX;
|
||||
this.style.transition = 'none';
|
||||
this.setPointerCapture(e.pointerId);
|
||||
});
|
||||
|
||||
this.addEventListener('pointermove', (e) => {
|
||||
if (!isDragging) return;
|
||||
currentX = e.clientX;
|
||||
const diff = currentX - startX;
|
||||
this.style.transform = `translateX(${diff}px)`;
|
||||
});
|
||||
|
||||
const onPointerUp = (e) => {
|
||||
if (!isDragging) return;
|
||||
isDragging = false;
|
||||
this.style.transition = 'transform 0.2s ease';
|
||||
const diff = currentX - startX;
|
||||
const threshold = this.offsetWidth * 0.3;
|
||||
|
||||
if (Math.abs(diff) > threshold) {
|
||||
this.style.transform = `translateX(${diff > 0 ? '100%' : '-100%'})`;
|
||||
this.dismiss();
|
||||
} else {
|
||||
this.style.transform = 'translateX(0)';
|
||||
}
|
||||
};
|
||||
|
||||
this.addEventListener('pointerup', onPointerUp);
|
||||
this.addEventListener('pointercancel', onPointerUp);
|
||||
}
|
||||
|
||||
render() {
|
||||
const type = this.getAttribute('type') || 'info';
|
||||
const dismissible = this.hasAttribute('dismissible');
|
||||
|
||||
this.shadowRoot.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
background: var(--card);
|
||||
color: var(--card-foreground);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 4px solid var(--primary);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform-origin: top center;
|
||||
animation: slide-in 0.3s ease forwards;
|
||||
will-change: transform, opacity;
|
||||
cursor: grab;
|
||||
touch-action: pan-y;
|
||||
}
|
||||
:host([type="success"]) { border-left-color: var(--success); }
|
||||
:host([type="warning"]) { border-left-color: var(--warning); }
|
||||
:host([type="error"]) { border-left-color: var(--destructive); }
|
||||
:host(.dismissing) {
|
||||
animation: slide-out 0.3s ease forwards;
|
||||
}
|
||||
.content {
|
||||
flex: 1;
|
||||
font-size: var(--text-sm);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.close-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--muted-foreground);
|
||||
padding: var(--space-1);
|
||||
cursor: pointer;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius);
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
.close-button:hover {
|
||||
background: var(--accent);
|
||||
color: var(--foreground);
|
||||
}
|
||||
@keyframes slide-in {
|
||||
from { opacity: 0; transform: translateY(-20px) scale(0.95); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
}
|
||||
@keyframes slide-out {
|
||||
from { opacity: 1; transform: translateY(0) scale(1); }
|
||||
to { opacity: 0; transform: translateY(-20px) scale(0.95); }
|
||||
}
|
||||
</style>
|
||||
<div class="icon"><slot name="icon"></slot></div>
|
||||
<div class="content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
${dismissible ? `<button class="close-button" aria-label="Dismiss">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>` : ''}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ds-toast', DsToast);
|
||||
Reference in New Issue
Block a user