import { DeesElement, html, property, customElement, cssManager, css, unsafeCSS, type TemplateResult, } from '@design.estate/dees-element'; // Import design tokens import { colors, bdTheme } from './00colors.js'; import { spacing, radius, shadows, transitions } from './00tokens.js'; import { fontFamilies, typography } from './00fonts.js'; declare global { interface HTMLElementTagNameMap { 'sio-button': SioButton; } } @customElement('sio-button') export class SioButton extends DeesElement { public static demo = () => html`
Default Primary Delete Outline Ghost Small Large Disabled
`; @property({ type: String }) public accessor text: string = ''; @property({ type: String }) public accessor type: 'default' | 'primary' | 'secondary' | 'destructive' | 'outline' | 'ghost' = 'default'; @property({ type: String }) public accessor size: 'sm' | 'default' | 'lg' = 'default'; @property({ type: Boolean, reflect: true }) public accessor disabled: boolean = false; @property({ type: String }) public accessor status: 'normal' | 'pending' | 'success' | 'error' = 'normal'; public static styles = [ cssManager.defaultStyles, css` :host { display: inline-block; font-family: ${unsafeCSS(fontFamilies.sans)}; } :host([disabled]) { pointer-events: none; } .button { position: relative; display: inline-flex; align-items: center; justify-content: center; white-space: nowrap; border-radius: 6px; font-weight: 500; font-size: 14px; line-height: 1; letter-spacing: -0.01em; transition: all 120ms ease; cursor: pointer; user-select: none; outline: none; border: none; gap: 6px; } /* Size variants */ .button.size-sm { height: 32px; padding: 0 12px; font-size: 13px; } .button.size-default { height: 36px; padding: 0 16px; font-size: 14px; } .button.size-lg { height: 42px; padding: 0 24px; font-size: 15px; } /* Type variants */ .button.default { background: ${bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 15%)')}; color: ${bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 95%)')}; } .button.default:hover:not(.disabled) { background: ${bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 20%)')}; } .button.default:active:not(.disabled) { background: ${bdTheme('hsl(0 0% 87%)', 'hsl(0 0% 18%)')}; } .button.primary { background: ${bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 95%)')}; color: ${bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 0%)')}; } .button.primary:hover:not(.disabled) { background: ${bdTheme('hsl(0 0% 25%)', 'hsl(0 0% 100%)')}; } .button.primary:active:not(.disabled) { background: ${bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')}; } /* Secondary variant */ .button.secondary { background: transparent; color: ${bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 95%)')}; box-shadow: inset 0 0 0 1px ${bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 25%)')}; } .button.secondary:hover:not(.disabled) { background: ${bdTheme('hsl(0 0% 96%)', 'hsl(0 0% 15%)')}; } .button.secondary:active:not(.disabled) { background: ${bdTheme('hsl(0 0% 92%)', 'hsl(0 0% 12%)')}; } /* Destructive variant */ .button.destructive { background: ${bdTheme('hsl(0 100% 95%)', 'hsl(0 50% 20%)')}; color: ${bdTheme('hsl(0 100% 45%)', 'hsl(0 100% 75%)')}; } .button.destructive:hover:not(.disabled) { background: ${bdTheme('hsl(0 100% 45%)', 'hsl(0 100% 50%)')}; color: white; } .button.destructive:active:not(.disabled) { background: ${bdTheme('hsl(0 100% 40%)', 'hsl(0 100% 45%)')}; color: white; } .button.outline { background: transparent; color: ${bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 70%)')}; box-shadow: inset 0 0 0 1.5px ${bdTheme('hsl(0 0% 90%)', 'hsl(0 0% 30%)')}; } .button.outline:hover:not(.disabled) { color: ${bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 95%)')}; box-shadow: inset 0 0 0 1.5px ${bdTheme('hsl(0 0% 70%)', 'hsl(0 0% 50%)')}; } .button.outline:active:not(.disabled) { background: ${bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 15%)')}; } .button.ghost { background: transparent; color: ${bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 70%)')}; } .button.ghost:hover:not(.disabled) { color: ${bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 95%)')}; background: ${bdTheme('hsl(0 0% 0% / 0.05)', 'hsl(0 0% 100% / 0.05)')}; } .button.ghost:active:not(.disabled) { background: ${bdTheme('hsl(0 0% 0% / 0.1)', 'hsl(0 0% 100% / 0.1)')}; } /* Status states */ .button.pending { pointer-events: none; opacity: 0.7; } .spinner { position: absolute; left: ${unsafeCSS(spacing["3"])}; animation: spin 1s linear infinite; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .button.success { background: ${bdTheme('success')}; color: ${bdTheme('successForeground')}; pointer-events: none; } .button.error { background: ${bdTheme('destructive')}; color: ${bdTheme('destructiveForeground')}; pointer-events: none; } /* Disabled state */ .button.disabled { opacity: 0.5; cursor: not-allowed; transform: none !important; } /* Focus state */ .button:focus-visible { outline: 2px solid ${bdTheme('hsl(0 0% 15% / 0.2)', 'hsl(0 0% 95% / 0.2)')}; outline-offset: 2px; } /* Icon sizing within buttons */ .button sio-icon { width: 16px; height: 16px; flex-shrink: 0; } .button.size-sm sio-icon { width: 14px; height: 14px; } .button.size-lg sio-icon { width: 18px; height: 18px; } `, ]; public render(): TemplateResult { const buttonClasses = [ 'button', this.type === 'primary' ? 'primary' : this.type, `size-${this.size}`, this.disabled ? 'disabled' : '', this.status, ].filter(Boolean).join(' '); return html` `; } private handleClick(event: MouseEvent) { if (this.disabled || this.status !== 'normal') { event.preventDefault(); event.stopPropagation(); return; } // Let the native click bubble normally // Don't dispatch a custom event to avoid double-triggering } }