1<script lang="ts" module>
2 import { cn, type WithElementRef } from "$lib/utils.js";
3 import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
4 import { type VariantProps, tv } from "tailwind-variants";
5
6 export const buttonVariants = tv({
7 base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
8 variants: {
9 variant: {
10 default: "bg-primary text-primary-foreground hover:bg-primary/90 shadow-xs",
11 destructive:
12 "bg-destructive hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white shadow-xs",
13 outline:
14 "bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border border-border shadow-xs",
15 secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-xs",
16 ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
17 link: "text-primary underline-offset-4 hover:underline",
18 },
19 size: {
20 default: "h-9 px-4 py-2 has-[>svg]:px-3",
21 sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5",
22 lg: "h-10 px-6 has-[>svg]:px-4",
23 icon: "size-9",
24 "icon-sm": "size-8",
25 "icon-lg": "size-10",
26 },
27 },
28 defaultVariants: {
29 variant: "default",
30 size: "default",
31 },
32 });
33
34 export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
35 export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
36
37 export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
38 WithElementRef<HTMLAnchorAttributes> & {
39 variant?: ButtonVariant;
40 size?: ButtonSize;
41 };
42</script>
43
44<script lang="ts">
45 let {
46 class: className,
47 variant = "default",
48 size = "default",
49 ref = $bindable(null),
50 href = undefined,
51 type = "button",
52 disabled,
53 children,
54 ...restProps
55 }: ButtonProps = $props();
56</script>
57
58{#if href}
59 <a
60 bind:this={ref}
61 data-slot="button"
62 class={cn(buttonVariants({ variant, size }), className)}
63 href={disabled ? undefined : href}
64 aria-disabled={disabled}
65 role={disabled ? "link" : undefined}
66 tabindex={disabled ? -1 : undefined}
67 {...restProps}
68 >
69 {@render children?.()}
70 </a>
71{:else}
72 <button
73 bind:this={ref}
74 data-slot="button"
75 class={cn(buttonVariants({ variant, size }), className)}
76 {type}
77 {disabled}
78 {...restProps}
79 >
80 {@render children?.()}
81 </button>
82{/if}