This repository has no description
0

Configure Feed

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

core / web / src / lib / components / ui / ButtonCard.svelte
2.9 kB 101 lines
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}