Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / lib / components / ui / ButtonCard.svelte
3.3 kB 117 lines
1<script module lang="ts"> 2 import { tv, type VariantProps } from "tailwind-variants"; 3 4 export const buttonCard = tv({ 5 base: [ 6 "flex h-40 w-48 cursor-pointer gap-3 rounded-(--btn-radius) px-4 py-3 no-underline select-none [--btn-radius:var(--radius-sm)]", 7 "transition-colors duration-150 ease-in-out", 8 "hover:no-underline", 9 "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-border-focus", 10 "disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50" 11 ], 12 variants: { 13 variant: { 14 default: [ 15 "border border-border-default bg-background-default", 16 "hover:bg-background-subtle" 17 ], 18 ghost: ["border border-transparent bg-transparent", "hover:bg-background-inset"] 19 }, 20 insetShadow: { 21 true: "", 22 false: "" 23 } 24 }, 25 compoundVariants: [ 26 // raised variants draw inset depth via a ::before layer. 27 { 28 variant: "default", 29 insetShadow: true, 30 class: [ 31 "relative z-0 before:absolute before:inset-0 before:-z-10 before:rounded-[max(calc(var(--btn-radius)-2px),0px)] before:shadow-[inset_0_-2px_0_0_var(--color-shadow-inner-subtle)] before:transition-all before:duration-150 before:content-['']", 32 "hover:before:shadow-[inset_0_-2px_0_0_var(--color-shadow-inner-strong-hover)]", 33 "active:translate-y-0.5 active:before:shadow-[inset_0_2px_2px_0_var(--color-shadow-inner-subtle)]", 34 "disabled:active:translate-y-0" 35 ] 36 } 37 ], 38 defaultVariants: { 39 variant: "default", 40 insetShadow: false 41 } 42 }); 43 44 export type ButtonCardVariants = VariantProps<typeof buttonCard>; 45</script> 46 47<script lang="ts"> 48 import type { ResolvedPathname } from "$app/types"; 49 import type { Component, Snippet } from "svelte"; 50 import type { 51 HTMLAnchorAttributes, 52 HTMLButtonAttributes, 53 SvelteHTMLElements 54 } from "svelte/elements"; 55 56 interface Props extends Omit<HTMLAnchorAttributes & HTMLButtonAttributes, "class" | "children"> { 57 variant?: ButtonCardVariants["variant"]; 58 insetShadow?: boolean; 59 href?: ResolvedPathname; 60 type?: "button" | "submit" | "reset"; 61 disabled?: boolean; 62 title?: string; 63 description?: string; 64 icon?: Component<SvelteHTMLElements["svg"]>; 65 class?: string; 66 children?: Snippet; 67 } 68 69 let { 70 variant = "default", 71 insetShadow = false, 72 href, 73 type = "button", 74 disabled = false, 75 title, 76 description, 77 icon, 78 class: className, 79 children, 80 ...rest 81 }: Props = $props(); 82 83 const classes = $derived(buttonCard({ variant, insetShadow, class: className })); 84 const Icon = $derived(icon); 85</script> 86 87{#snippet content()} 88 <div class="flex flex-col items-start gap-1.5"> 89 {#if title} 90 <div class="flex flex-row items-center gap-2"> 91 {#if Icon} 92 <Icon class="size-4 shrink-0" aria-hidden="true" /> 93 {/if} 94 <span class="text-sm font-medium text-foreground-default">{title}</span> 95 </div> 96 {/if} 97 {#if description} 98 <span class="text-xs text-foreground-muted">{description}</span> 99 {/if} 100 </div> 101{/snippet} 102 103{#if href} 104 <a 105 {href} 106 class={classes} 107 aria-disabled={disabled ? "true" : undefined} 108 tabindex={disabled ? -1 : undefined} 109 {...rest} 110 > 111 {@render (children ?? content)()} 112 </a> 113{:else} 114 <button {type} {disabled} class={classes} {...rest}> 115 {@render (children ?? content)()} 116 </button> 117{/if}