266 lines
6.7 KiB
TypeScript
266 lines
6.7 KiB
TypeScript
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`
|
|
<div style="display: flex; gap: 16px; flex-wrap: wrap; align-items: center;">
|
|
<sio-button>Default</sio-button>
|
|
<sio-button type="primary">Primary</sio-button>
|
|
<sio-button type="destructive">Delete</sio-button>
|
|
<sio-button type="outline">Outline</sio-button>
|
|
<sio-button type="ghost">Ghost</sio-button>
|
|
<sio-button size="sm">Small</sio-button>
|
|
<sio-button size="lg">Large</sio-button>
|
|
<sio-button disabled>Disabled</sio-button>
|
|
</div>
|
|
`;
|
|
|
|
@property({ type: String })
|
|
public text: string = '';
|
|
|
|
@property({ type: String })
|
|
public type: 'default' | 'primary' | 'destructive' | 'outline' | 'ghost' = 'default';
|
|
|
|
@property({ type: String })
|
|
public size: 'sm' | 'default' | 'lg' = 'default';
|
|
|
|
@property({ type: Boolean, reflect: true })
|
|
public disabled: boolean = false;
|
|
|
|
@property({ type: String })
|
|
public 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: ${unsafeCSS(radius.md)};
|
|
font-weight: 500;
|
|
transition: ${unsafeCSS(transitions.all)};
|
|
cursor: pointer;
|
|
user-select: none;
|
|
outline: none;
|
|
border: 1px solid transparent;
|
|
gap: ${unsafeCSS(spacing["2"])};
|
|
font-size: 0.875rem;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* Size variants */
|
|
.button.size-sm {
|
|
height: 32px;
|
|
padding: 0 ${unsafeCSS(spacing["3"])};
|
|
font-size: 13px;
|
|
}
|
|
|
|
.button.size-default {
|
|
height: 36px;
|
|
padding: 0 ${unsafeCSS(spacing["4"])};
|
|
font-size: 14px;
|
|
}
|
|
|
|
.button.size-lg {
|
|
height: 44px;
|
|
padding: 0 ${unsafeCSS(spacing["6"])};
|
|
font-size: 16px;
|
|
}
|
|
|
|
/* Type variants */
|
|
.button.default {
|
|
background: ${bdTheme('background')};
|
|
color: ${bdTheme('foreground')};
|
|
border-color: ${bdTheme('border')};
|
|
box-shadow: ${unsafeCSS(shadows.sm)};
|
|
}
|
|
|
|
.button.default:hover:not(.disabled) {
|
|
background: ${bdTheme('accent')};
|
|
border-color: ${bdTheme('accent')};
|
|
transform: translateY(-1px);
|
|
box-shadow: ${unsafeCSS(shadows.md)};
|
|
}
|
|
|
|
.button.default:active:not(.disabled) {
|
|
transform: translateY(0);
|
|
box-shadow: ${unsafeCSS(shadows.sm)};
|
|
}
|
|
|
|
.button.primary {
|
|
background: ${bdTheme('primary')};
|
|
color: ${bdTheme('primaryForeground')};
|
|
box-shadow: ${unsafeCSS(shadows.sm)};
|
|
}
|
|
|
|
.button.primary:hover:not(.disabled) {
|
|
opacity: 0.9;
|
|
transform: translateY(-1px);
|
|
box-shadow: ${unsafeCSS(shadows.md)};
|
|
}
|
|
|
|
.button.primary:active:not(.disabled) {
|
|
transform: translateY(0);
|
|
box-shadow: ${unsafeCSS(shadows.sm)};
|
|
}
|
|
|
|
.button.destructive {
|
|
background: ${bdTheme('destructive')};
|
|
color: ${bdTheme('destructiveForeground')};
|
|
}
|
|
|
|
.button.destructive:hover:not(.disabled) {
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.button.destructive:active:not(.disabled) {
|
|
transform: translateY(1px);
|
|
}
|
|
|
|
.button.outline {
|
|
background: transparent;
|
|
color: ${bdTheme('foreground')};
|
|
border-color: ${bdTheme('border')};
|
|
}
|
|
|
|
.button.outline:hover:not(.disabled) {
|
|
background: ${bdTheme('accent')};
|
|
color: ${bdTheme('accentForeground')};
|
|
}
|
|
|
|
.button.outline:active:not(.disabled) {
|
|
transform: translateY(1px);
|
|
}
|
|
|
|
.button.ghost {
|
|
background: transparent;
|
|
color: ${bdTheme('foreground')};
|
|
border-color: transparent;
|
|
}
|
|
|
|
.button.ghost:hover:not(.disabled) {
|
|
background: ${bdTheme('accent')};
|
|
color: ${bdTheme('accentForeground')};
|
|
border-color: transparent;
|
|
}
|
|
|
|
.button.ghost:active:not(.disabled) {
|
|
background: ${bdTheme('accent')};
|
|
opacity: 0.8;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* Focus state */
|
|
.button:focus-visible {
|
|
outline: 2px solid ${bdTheme('ring')};
|
|
outline-offset: 2px;
|
|
}
|
|
`,
|
|
];
|
|
|
|
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`
|
|
<button
|
|
class="${buttonClasses}"
|
|
?disabled=${this.disabled}
|
|
@click=${this.handleClick}
|
|
>
|
|
${this.status === 'pending' ? html`
|
|
<sio-icon class="spinner" icon="loader" size="16"></sio-icon>
|
|
` : ''}
|
|
${this.status === 'success' ? html`
|
|
<sio-icon icon="check" size="16"></sio-icon>
|
|
` : ''}
|
|
${this.status === 'error' ? html`
|
|
<sio-icon icon="x" size="16"></sio-icon>
|
|
` : ''}
|
|
<slot>${this.text}</slot>
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
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
|
|
}
|
|
} |