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