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
}
}