This repository has no description
1<script module lang="ts">
2 import { tv, type VariantProps } from "tailwind-variants";
3
4 export const buttonCard = tv({
5 base: "flex cursor-pointer select-none items-start gap-3 rounded p-3 text-left no-underline transition-colors duration-150 ease-[cubic-bezier(0.4,0,0.2,1)] hover:no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-border-focus disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50",
6 variants: {
7 variant: {
8 default: "border border-border-default bg-background-default hover:bg-background-subtle",
9 ghost: "bg-transparent hover:bg-background-inset"
10 }
11 },
12 defaultVariants: {
13 variant: "default"
14 }
15 });
16
17 export type ButtonCardVariants = VariantProps<typeof buttonCard>;
18</script>
19
20<script lang="ts">
21 import { resolve } from "$app/paths";
22 import type { Component, Snippet } from "svelte";
23 import type {
24 HTMLAnchorAttributes,
25 HTMLButtonAttributes,
26 SvelteHTMLElements
27 } from "svelte/elements";
28
29 interface Props extends Omit<HTMLAnchorAttributes & HTMLButtonAttributes, "class" | "children"> {
30 variant?: ButtonCardVariants["variant"];
31 href?: string;
32 type?: "button" | "submit" | "reset";
33 disabled?: boolean;
34 title?: string;
35 description?: string;
36 icon?: Component<SvelteHTMLElements["svg"]>;
37 class?: string;
38 children?: Snippet;
39 }
40
41 let {
42 variant = "default",
43 href,
44 type = "button",
45 disabled = false,
46 title,
47 description,
48 icon,
49 class: className,
50 children,
51 ...rest
52 }: Props = $props();
53
54 const classes = $derived(buttonCard({ variant, class: className }));
55 const Icon = $derived(icon);
56</script>
57
58{#if href}
59 <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -- internal hrefs go through resolve(), external ones must pass through untouched -->
60 <a
61 href={href?.startsWith("/") ? resolve(href as "/") : href}
62 class={classes}
63 aria-disabled={disabled ? "true" : undefined}
64 tabindex={disabled ? -1 : undefined}
65 {...rest}
66 >
67 {#if Icon}
68 <Icon class="size-5 shrink-0" aria-hidden="true" />
69 {/if}
70 {#if children}
71 {@render children()}
72 {:else}
73 <div class="flex flex-col gap-0.5">
74 {#if title}
75 <span class="text-sm font-medium text-foreground-default">{title}</span>
76 {/if}
77 {#if description}
78 <span class="text-xs text-foreground-muted">{description}</span>
79 {/if}
80 </div>
81 {/if}
82 </a>
83{:else}
84 <button {type} {disabled} class={classes} {...rest}>
85 {#if Icon}
86 <Icon class="size-5 shrink-0" aria-hidden="true" />
87 {/if}
88 {#if children}
89 {@render children()}
90 {:else}
91 <div class="flex flex-col gap-0.5">
92 {#if title}
93 <span class="text-sm font-medium text-foreground-default">{title}</span>
94 {/if}
95 {#if description}
96 <span class="text-xs text-foreground-muted">{description}</span>
97 {/if}
98 </div>
99 {/if}
100 </button>
101{/if}