diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-09-12 22:22:33 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-09-12 22:22:33 +0200 |
| commit | 1cef356be95a67f95d40f5b0b131f98d11f13c13 (patch) | |
| tree | a0d93976998b0395e373a7a9f0127cc35e77a6f2 /src/lib | |
| parent | b11a40840cfe0e9978f78cacd68d68764eabec58 (diff) | |
Extract Button primitive, migrate all call sites
Variants primary/accent/ghost/done with scoped styles.
Disabled handling unified; block/size/style props cover
login stretch, done-lg, save-check positioning.
Delete btn-* globals and dead page overrides.
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/components/Button.svelte | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/src/lib/components/Button.svelte b/src/lib/components/Button.svelte new file mode 100644 index 0000000..30e9fc2 --- /dev/null +++ b/src/lib/components/Button.svelte @@ -0,0 +1,150 @@ +<script lang="ts"> + import type { Snippet } from 'svelte'; + + let { + variant = 'primary', + type = 'submit', + disabled = false, + name, + value, + ariaLabel, + block = false, + size = 'md', + style, + class: klass = '', + onclick, + children, + }: { + variant?: 'primary' | 'accent' | 'ghost' | 'done'; + type?: 'button' | 'submit' | 'reset'; + disabled?: boolean; + name?: string; + value?: string | number; + ariaLabel?: string; + block?: boolean; + size?: 'md' | 'lg'; + style?: string; + class?: string; + onclick?: (e: MouseEvent) => void; + children: Snippet; + } = $props(); + + const cls = $derived( + [variant, size === 'lg' ? 'lg' : '', block ? 'block' : '', klass] + .filter(Boolean) + .join(' '), + ); +</script> + +<button + {type} + {disabled} + {name} + {value} + aria-label={ariaLabel} + class={cls} + {style} + {onclick} +> + {@render children()} +</button> + +<style> + button { + cursor: pointer; + font-family: inherit; + transition: all 0.2s ease; + border: none; + } + + button:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + box-shadow: none; + } + + .primary { + padding: 0.75rem 1.5rem; + border-radius: 0.75rem; + background: var(--accent); + color: var(--white); + font-size: 0.9375rem; + font-weight: 500; + align-self: flex-start; + box-shadow: 0 1px 2px var(--shadow); + } + + .primary:hover:not(:disabled) { + background: var(--accent-hover); + transform: translateY(-1px); + box-shadow: 0 4px 8px var(--shadow-accent-lg); + } + + .accent { + width: 3rem; + height: 3rem; + flex-shrink: 0; + padding: 0; + border-radius: 0.75rem; + background: var(--accent); + color: var(--white); + font-size: 1.25rem; + font-weight: 500; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 1px 2px var(--shadow); + } + + .accent:hover:not(:disabled) { + background: var(--accent-hover); + transform: translateY(-1px); + box-shadow: 0 4px 8px var(--shadow-accent-lg); + } + + .ghost { + flex-shrink: 0; + width: 2rem; + aspect-ratio: 1; + padding: 0; + border-radius: 0.625rem; + background: transparent; + color: var(--text-secondary); + font-size: 1.25rem; + display: flex; + align-items: center; + justify-content: center; + } + + .ghost:hover:not(:disabled) { + background: var(--danger-hover-bg); + color: var(--danger); + } + + .done { + flex-shrink: 0; + padding: 0.5rem 0.875rem; + border-radius: 0.625rem; + background: var(--success); + color: var(--white); + font-size: 0.875rem; + font-weight: 500; + } + + .done:hover:not(:disabled) { + filter: brightness(1.05); + transform: translateY(-1px); + } + + .done.lg { + padding: 0.75rem 1.25rem; + border-radius: 0.75rem; + font-size: 0.9375rem; + } + + .block { + align-self: stretch; + width: 100%; + } +</style> |
