~james/core

core/web/src/lib/components/ui/Link.svelte -rw-r--r-- 3.0 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<script module lang="ts">
	import { tv, type VariantProps } from "tailwind-variants";

	const link = tv({
		base: "inline-flex cursor-pointer items-center gap-2xs no-underline select-none hover:underline focus-visible:outline-2 focus-visible:outline-offset-2",
		variants: {
			variant: {
				default:
					"text-foreground-link-default hover:text-foreground-link-hover focus-visible:outline-border-focus",
				success:
					"text-foreground-link-success hover:text-foreground-link-success-hover focus-visible:outline-foreground-link-success",
				warning:
					"text-foreground-link-warning hover:text-foreground-link-warning-hover focus-visible:outline-foreground-link-warning",
				danger: "text-foreground-link-danger hover:text-foreground-link-danger-hover focus-visible:outline-foreground-link-danger",
				inherit: "text-inherit focus-visible:outline-border-focus"
			},
			size: {
				regular: "",
				small: "typography-paragraph-small"
			},
			disabled: {
				true: "pointer-events-none opacity-50",
				false: ""
			}
		},
		defaultVariants: {
			variant: "default",
			size: "regular",
			disabled: false
		}
	});

	export type LinkVariants = VariantProps<typeof link>;
</script>

<script lang="ts">
	import type { ResolvedPathname } from "$app/types";
	import type { Component, Snippet } from "svelte";
	import type {
		HTMLAnchorAttributes,
		HTMLButtonAttributes,
		SvelteHTMLElements
	} from "svelte/elements";

	interface Props extends Omit<
		HTMLAnchorAttributes & HTMLButtonAttributes,
		"class" | "children"
	> {
		variant?: LinkVariants["variant"];
		size?: LinkVariants["size"];
		href?: ResolvedPathname;
		fragment?: string;
		type?: "button" | "submit" | "reset";
		disabled?: boolean;
		iconLeft?: Component<SvelteHTMLElements["svg"]>;
		iconRight?: Component<SvelteHTMLElements["svg"]>;
		class?: string;
		children?: Snippet;
	}

	let {
		variant = "default",
		size = "regular",
		href,
		fragment,
		type = "button",
		disabled = false,
		iconLeft,
		iconRight,
		class: className,
		children,
		...rest
	}: Props = $props();

	const classes = $derived(link({ variant, size, disabled, class: className }));
	const iconSize = "size-3.5";
</script>

{#if href || fragment !== undefined}
	<a
		href={fragment === undefined ? href : `#${fragment}`}
		class={classes}
		aria-disabled={disabled ? "true" : undefined}
		tabindex={disabled ? -1 : undefined}
		{...rest}
	>
		{#if iconLeft}
			{@const IconLeft = iconLeft}
			<IconLeft class={iconSize} aria-hidden="true" />
		{/if}
		{#if children}
			{@render children()}
		{/if}
		{#if iconRight}
			{@const IconRight = iconRight}
			<IconRight class={iconSize} aria-hidden="true" />
		{/if}
	</a>
{:else}
	<button {type} {disabled} class={classes} {...rest}>
		{#if iconLeft}
			{@const IconLeft = iconLeft}
			<IconLeft class={iconSize} aria-hidden="true" />
		{/if}
		{#if children}
			{@render children()}
		{/if}
		{#if iconRight}
			{@const IconRight = iconRight}
			<IconRight class={iconSize} aria-hidden="true" />
		{/if}
	</button>
{/if}