Files
ss-tools/frontend/src/lib/ui/Button.svelte
2026-02-19 18:24:36 +03:00

68 lines
2.2 KiB
Svelte

<!-- [DEF:Button:Component] -->
<!--
@TIER: TRIVIAL
@SEMANTICS: button, ui-atom, interactive
@PURPOSE: Standardized button component with variants and loading states.
@LAYER: Atom
@INVARIANT: Always uses Tailwind for styling.
@INVARIANT: Supports accessible labels and keyboard navigation.
-->
<script>
// [SECTION: IMPORTS]
import { cn } from '$lib/utils.js';
// [/SECTION: IMPORTS]
// [SECTION: PROPS]
/**
* @purpose Define component interface and default values (Svelte 5 Runes).
*/
let {
variant = 'primary',
size = 'md',
isLoading = false,
disabled = false,
type = 'button',
class: className = '',
children,
onclick,
...rest
} = $props();
// [/SECTION: PROPS]
const baseStyles = 'inline-flex items-center justify-center font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 rounded-md';
const variants = {
primary: 'bg-primary text-white hover:bg-primary-hover focus-visible:ring-primary-ring',
secondary: 'bg-secondary text-secondary-text hover:bg-secondary-hover focus-visible:ring-secondary-ring',
danger: 'bg-destructive text-white hover:bg-destructive-hover focus-visible:ring-destructive-ring',
ghost: 'bg-transparent hover:bg-ghost-hover text-ghost-text focus-visible:ring-ghost-ring',
};
const sizes = {
sm: 'h-8 px-3 text-xs',
md: 'h-10 px-4 py-2 text-sm',
lg: 'h-12 px-6 text-base',
};
</script>
<!-- [SECTION: TEMPLATE] -->
<button
{type}
class={cn(baseStyles, variants[variant], sizes[size], className)}
disabled={disabled || isLoading}
{onclick}
{...rest}
>
{#if isLoading}
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-current" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
{/if}
{@render children?.()}
</button>
<!-- [/SECTION: TEMPLATE] -->
<!-- [/DEF:Button:Component] -->