This repository has no description
1<script module lang="ts">
2 import { tv, type VariantProps } from 'tailwind-variants';
3
4 export const button = tv({
5 base: 'inline-flex cursor-pointer select-none items-center justify-center overflow-hidden rounded no-underline transition-[background-color,border-color,color] duration-150 ease-[cubic-bezier(0.4,0,0.2,1)] hover:no-underline focus-visible:outline-2 focus-visible:outline-offset-2 disabled:pointer-events-none disabled:opacity-50',
6 variants: {
7 variant: {
8 default:
9 'border border-border bg-surface text-fg hover:bg-surface-hover focus-visible:outline-ring',
10 primary:
11 'border border-primary-border bg-primary text-primary-fg hover:bg-primary-hover focus-visible:outline-primary',
12 ghost: 'bg-transparent text-fg-muted hover:bg-surface-muted focus-visible:outline-ring',
13 danger:
14 'border border-danger-border bg-danger text-danger-fg hover:bg-danger-hover focus-visible:outline-danger',
15 success:
16 'border border-success-border bg-success text-success-fg hover:bg-success-hover focus-visible:outline-success'
17 },
18 size: {
19 sm: 'min-h-8 gap-1.5 px-2 py-1.5 text-sm',
20 md: 'min-h-9 gap-2 px-4 py-2 text-sm',
21 lg: 'min-h-10 gap-2.5 px-6 py-2 text-base'
22 }
23 },
24 compoundVariants: [
25 // raised variants draw inset depth via a ::before layer.
26 {
27 variant: 'default',
28 class:
29 "relative z-0 before:absolute before:inset-0 before:-z-10 before:rounded-[inherit] before:content-[''] before:shadow-[inset_0_-2px_0_0_var(--color-raised-depth)] before:transition-all before:duration-150 hover:before:shadow-[inset_0_-2px_0_0_var(--color-raised-depth-hover)] active:translate-y-0.5 active:before:shadow-[inset_0_2px_2px_0_var(--color-raised-depth)] disabled:active:translate-y-0"
30 },
31 {
32 variant: ['primary', 'danger', 'success'],
33 class:
34 "relative z-0 before:absolute before:inset-0 before:-z-10 before:rounded-[inherit] before:content-[''] before:shadow-[inset_0_-2px_0_0_var(--color-raised-accent-depth)] before:transition-all before:duration-150 active:translate-y-0.5 active:before:shadow-[inset_0_2px_2px_0_var(--color-raised-accent-depth-active)] disabled:active:translate-y-0"
35 }
36 ],
37 defaultVariants: {
38 variant: 'default',
39 size: 'md'
40 }
41 });
42
43 export type ButtonVariants = VariantProps<typeof button>;
44</script>
45
46<script lang="ts">
47 import { resolve } from '$app/paths';
48 import type { Snippet } from 'svelte';
49 import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
50 import Spinner from './Spinner.svelte';
51
52 interface Props extends Omit<HTMLAnchorAttributes & HTMLButtonAttributes, 'class' | 'children'> {
53 variant?: ButtonVariants['variant'];
54 size?: ButtonVariants['size'];
55 href?: string;
56 type?: 'button' | 'submit' | 'reset';
57 disabled?: boolean;
58 class?: string;
59 loading?: boolean;
60 spinnerClass?: string;
61 children?: Snippet;
62 }
63
64 let {
65 variant = 'default',
66 size = 'md',
67 href,
68 type = 'button',
69 disabled = false,
70 class: className,
71 loading = false,
72 spinnerClass,
73 children,
74 ...rest
75 }: Props = $props();
76
77 const classes = $derived(button({ variant, size, class: className }));
78 const spinnerSize = $derived(spinnerClass ?? (size === 'lg' ? 'size-5' : 'size-4'));
79</script>
80
81{#if href}
82 <a
83 href={resolve(href as '/')}
84 class={classes}
85 aria-disabled={disabled || loading ? 'true' : undefined}
86 tabindex={disabled ? -1 : undefined}
87 {...rest}
88 >
89 {#if loading}
90 <Spinner class={spinnerSize} />
91 {:else if children}
92 {@render children()}
93 {/if}
94 </a>
95{:else}
96 <button {type} disabled={disabled || loading} class={classes} aria-busy={loading} {...rest}>
97 {#if loading}
98 <Spinner class={spinnerSize} />
99 {:else if children}
100 {@render children()}
101 {/if}
102 </button>
103{/if}