update
This commit is contained in:
259
ts_web/elements/sio-button.ts
Normal file
259
ts_web/elements/sio-button.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
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('secondary')};
|
||||
color: ${bdTheme('secondaryForeground')};
|
||||
border-color: ${bdTheme('border')};
|
||||
}
|
||||
|
||||
.button.default:hover:not(.disabled) {
|
||||
background: ${bdTheme('secondary')};
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.button.default:active:not(.disabled) {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.button.primary {
|
||||
background: ${bdTheme('primary')};
|
||||
color: ${bdTheme('primaryForeground')};
|
||||
}
|
||||
|
||||
.button.primary:hover:not(.disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.button.primary:active:not(.disabled) {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.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')};
|
||||
}
|
||||
|
||||
.button.ghost:hover:not(.disabled) {
|
||||
background: ${bdTheme('accent')};
|
||||
color: ${bdTheme('accentForeground')};
|
||||
}
|
||||
|
||||
.button.ghost:active:not(.disabled) {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
// Dispatch a custom event with any data
|
||||
this.dispatchEvent(new CustomEvent('click', {
|
||||
detail: { originalEvent: event },
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user