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 flat: 'bg-gray-50 text-fg hover:bg-gray-100 focus-visible:outline-ring dark:bg-gray-900 dark:hover:bg-gray-700',
14 danger:
15 'border border-danger-border bg-danger text-danger-fg hover:bg-danger-hover focus-visible:outline-danger',
16 success:
17 'border border-success-border bg-success text-success-fg hover:bg-success-hover focus-visible:outline-success'
18 },
19 size: {
20 sm: 'min-h-8 gap-1.5 px-2 py-1.5 text-sm',
21 md: 'min-h-9 gap-2 px-4 py-2 text-sm',
22 lg: 'min-h-10 gap-2.5 px-6 py-2 text-base'
23 }
24 },
25 compoundVariants: [
26 // raised variants draw inset depth via a ::before layer.
27 {
28 variant: 'default',
29 class:
30 "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"
31 },
32 {
33 variant: ['primary', 'danger', 'success'],
34 class:
35 "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"
36 }
37 ],
38 defaultVariants: {
39 variant: 'default',
40 size: 'md'
41 }
42 });
43
44 export type ButtonVariants = VariantProps<typeof button>;
45</script>
46
47<script lang="ts">
48 import { resolve } from '$app/paths';
49 import type { Snippet } from 'svelte';
50 import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
51 import Spinner from './Spinner.svelte';
52
53 interface Props extends Omit<HTMLAnchorAttributes & HTMLButtonAttributes, 'class' | 'children'> {
54 variant?: ButtonVariants['variant'];
55 size?: ButtonVariants['size'];
56 href?: string;
57 type?: 'button' | 'submit' | 'reset';
58 disabled?: boolean;
59 class?: string;
60 loading?: boolean;
61 spinnerClass?: string;
62 children?: Snippet;
63 }
64
65 let {
66 variant = 'default',
67 size = 'md',
68 href,
69 type = 'button',
70 disabled = false,
71 class: className,
72 loading = false,
73 spinnerClass,
74 children,
75 ...rest
76 }: Props = $props();
77
78 const classes = $derived(button({ variant, size, class: className }));
79 const spinnerSize = $derived(spinnerClass ?? (size === 'lg' ? 'size-5' : 'size-4'));
80</script>
81
82{#if href}
83 <a
84 href={resolve(href as '/')}
85 class={classes}
86 aria-disabled={disabled || loading ? 'true' : undefined}
87 tabindex={disabled ? -1 : undefined}
88 {...rest}
89 >
90 {#if loading}
91 <Spinner class={spinnerSize} />
92 {:else if children}
93 {@render children()}
94 {/if}
95 </a>
96{:else}
97 <button {type} disabled={disabled || loading} class={classes} aria-busy={loading} {...rest}>
98 {#if loading}
99 <Spinner class={spinnerSize} />
100 {:else if children}
101 {@render children()}
102 {/if}
103 </button>
104{/if}