import { DeesElement, property, html, customElement, type TemplateResult, cssManager, css, unsafeCSS, state, } from '@design.estate/dees-element'; import * as domtools from '@design.estate/dees-domtools'; import { SioCombox } from './sio-combox.js'; import { SioIcon } from './sio-icon.js'; SioCombox; SioIcon; // Import design tokens import { colors, bdTheme } from './00colors.js'; import { spacing, radius, shadows, transitions, sizes } from './00tokens.js'; import { fontFamilies, typography } from './00fonts.js'; declare global { interface HTMLElementTagNameMap { 'sio-fab': SioFab; } } @customElement('sio-fab') export class SioFab extends DeesElement { @property({ type: Boolean }) public accessor showCombox = false; @state() private accessor shouldPulse = false; public static demo = () => html` `; constructor() { super(); domtools.DomTools.setupDomTools(); } public static styles = [ cssManager.defaultStyles, css` :host { will-change: transform; position: absolute; display: block; bottom: 20px; right: 20px; z-index: 10000; color: #fff; --fab-gradient-start: #6366f1; --fab-gradient-mid: #8b5cf6; --fab-gradient-end: #a855f7; --fab-gradient-hover-end: #c026d3; --fab-shadow-color: rgba(139, 92, 246, 0.25); --fab-size: 60px; } #mainbox { transition: ${unsafeCSS(transitions.all)}; position: absolute; bottom: 0px; right: 0px; height: var(--fab-size); width: var(--fab-size); box-shadow: 0 4px 16px -2px rgba(0, 0, 0, 0.1), 0 2px 8px -2px rgba(0, 0, 0, 0.06); line-height: var(--fab-size); text-align: center; cursor: pointer; background: linear-gradient(135deg, var(--fab-gradient-start) 0%, var(--fab-gradient-mid) 50%, var(--fab-gradient-end) 100%); color: white; border-radius: ${unsafeCSS(radius.full)}; user-select: none; border: none; animation: fabEntrance 300ms cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); } #mainbox::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 50%); opacity: 0; transition: opacity 200ms ease; } #mainbox::after { content: ''; position: absolute; top: -4px; left: -4px; right: -4px; bottom: -4px; background: linear-gradient(135deg, var(--fab-gradient-start), var(--fab-gradient-end)); border-radius: inherit; z-index: -1; opacity: 0; filter: blur(12px); transition: opacity 300ms ease; } #mainbox:hover::before { opacity: 1; } #mainbox:hover::after { opacity: 0.3; } @keyframes fabEntrance { from { transform: scale(0.8); opacity: 0; } to { transform: scale(1); opacity: 1; } } #mainbox:hover { transform: scale(1.02); background: linear-gradient(135deg, var(--fab-gradient-start) 0%, var(--fab-gradient-mid) 50%, var(--fab-gradient-hover-end) 100%); box-shadow: 0 8px 20px -4px var(--fab-shadow-color); } #mainbox:active { transform: scale(0.98); box-shadow: 0 4px 12px -2px var(--fab-shadow-color); } #mainbox.pulse::after { animation: fabPulse 0.6s ease-out forwards; } @keyframes fabPulse { 0% { box-shadow: 0 0 0 0 rgba(139, 92, 246, 0.4); opacity: 1; } 100% { box-shadow: 0 0 0 12px rgba(139, 92, 246, 0); opacity: 0; } } #mainbox .icon { position: absolute; top: 0px; left: 0px; will-change: transform, opacity; transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1); height: 100%; width: 100%; display: flex; align-items: center; justify-content: center; } #mainbox .icon.open { opacity: 1; transform: rotate(0deg) scale(1); } #mainbox .icon.close { opacity: 0; transform: rotate(-45deg) scale(0.9); } /* When combox is open */ :host(.combox-open) #mainbox .icon.open { opacity: 0; transform: rotate(45deg) scale(0.9); } :host(.combox-open) #mainbox .icon.close { opacity: 1; transform: rotate(0deg) scale(1); } #mainbox .icon sio-icon { color: white; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1)); } #mainbox .icon.close sio-icon { transform: scale(1); } `, // Mobile responsive styles - smaller FAB cssManager.cssForPhablet(css` :host { --fab-size: 48px; bottom: 16px; right: 16px; } `), ]; public render(): TemplateResult { return html`
{ this.shouldPulse = false; }} >
`; } /** * toggles the combox */ public async toggleCombox() { const combox = SioCombox.getInstance(); if (combox) { const wasOpen = combox.getIsOpen(); combox.toggle(); this.showCombox = combox.getIsOpen(); if (this.showCombox && !wasOpen) { this.shouldPulse = true; } } } public async firstUpdated(args: any) { super.firstUpdated(args); const domtools = await this.domtoolsPromise; // Create the singleton combox on body const combox = SioCombox.createOnBody(); // Listen for close events combox.addEventListener('close', () => { this.showCombox = false; }); // Set up keyboard shortcut domtools.keyboard .on([domtools.keyboard.keyEnum.Ctrl, domtools.keyboard.keyEnum.S]) .subscribe(() => { this.toggleCombox(); }); } public async updated(changedProperties: Map) { super.updated(changedProperties); // Update host class based on combox state if (changedProperties.has('showCombox')) { if (this.showCombox) { this.classList.add('combox-open'); } else { this.classList.remove('combox-open'); } } } }