~james/core

core/web/src/lib/components/ui/ButtonCard.svelte -rw-r--r-- 3.2 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
114
115
116
117
118
<script module lang="ts">
	import { tv, type VariantProps } from "tailwind-variants";

	export const buttonCard = tv({
		base: [
			"flex h-40 w-48 cursor-pointer gap-sm rounded-(--btn-radius) px-md py-sm no-underline select-none [--btn-radius:var(--radius-md)]",
			"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"
		],
		variants: {
			variant: {
				default: ["bg-background-default edge-ring", "hover:bg-background-subtle"],
				ghost: ["bg-transparent", "hover:bg-background-inset"]
			},
			insetShadow: {
				true: "",
				false: ""
			}
		},
		compoundVariants: [
			{
				variant: "default",
				insetShadow: true,
				class: [
					"relative z-0 before:absolute before:inset-0 before:-z-10 before:rounded-(--btn-radius) before:shadow-[inset_0_-2px_0_0_var(--color-shadow-inner-subtle)] before:content-['']",
					"transition-[translate,background-color] duration-150 ease-out-strong before:transition-shadow before:duration-150 before:ease-out-strong",
					"hover:before:shadow-[inset_0_-2px_0_0_var(--color-shadow-inner-strong-hover)]",
					"active:translate-y-0.5 active:before:shadow-[inset_0_2px_2px_0_var(--color-shadow-inner-subtle)]",
					"disabled:active:translate-y-0 motion-reduce:active:translate-y-0"
				]
			}
		],
		defaultVariants: {
			variant: "default",
			insetShadow: false
		}
	});

	export type ButtonCardVariants = VariantProps<typeof buttonCard>;
</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?: ButtonCardVariants["variant"];
		insetShadow?: boolean;
		href?: ResolvedPathname;
		type?: "button" | "submit" | "reset";
		disabled?: boolean;
		title?: string;
		description?: string;
		icon?: Component<SvelteHTMLElements["svg"]>;
		class?: string;
		children?: Snippet;
	}

	let {
		variant = "default",
		insetShadow = false,
		href,
		type = "button",
		disabled = false,
		title,
		description,
		icon,
		class: className,
		children,
		...rest
	}: Props = $props();

	const classes = $derived(buttonCard({ variant, insetShadow, class: className }));
	const Icon = $derived(icon);
</script>

{#snippet content()}
	<div class="flex flex-col items-start gap-1.5">
		{#if title}
			<div class="flex flex-row items-center gap-xs">
				{#if Icon}
					<Icon class="size-3.5 shrink-0" aria-hidden="true" />
				{/if}
				<span class="typography-paragraph-small font-medium text-foreground-default"
					>{title}</span
				>
			</div>
		{/if}
		{#if description}
			<span class="typography-paragraph-small text-foreground-muted">{description}</span>
		{/if}
	</div>
{/snippet}

{#if href}
	<a
		{href}
		class={classes}
		aria-disabled={disabled ? "true" : undefined}
		tabindex={disabled ? -1 : undefined}
		{...rest}
	>
		{@render (children ?? content)()}
	</a>
{:else}
	<button {type} {disabled} class={classes} {...rest}>
		{@render (children ?? content)()}
	</button>
{/if}