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 / Button.svelte
5.0 kB 134 lines
1<script module lang="ts"> 2 import { tv, type VariantProps } from "tailwind-variants"; 3 4 export const button = tv({ 5 base: "inline-flex cursor-pointer select-none items-center justify-center overflow-hidden rounded-l-(--btn-radius-l) rounded-r-(--btn-radius-r) [--btn-radius:var(--radius-sm)] [--btn-radius-l:var(--btn-radius)] [--btn-radius-r:var(--btn-radius)] no-underline transition-colors duration-150 ease-in-out \ 6 hover:no-underline \ 7 focus-visible:outline-2 focus-visible:outline-offset-2 \ 8 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50", 9 variants: { 10 variant: { 11 default: 12 "border border-border-default bg-background-default text-foreground-default \ 13 hover:bg-background-subtle focus-visible:outline-border-focus", 14 primary: 15 "border border-border-success bg-background-success-emphasis text-foreground-on-emphasis \ 16 hover:bg-background-success-emphasis-hover focus-visible:outline-background-success-emphasis", 17 ghost: 18 "bg-transparent text-foreground-muted \ 19 hover:bg-background-inset focus-visible:outline-border-focus", 20 // danger / success / warning are outline buttons in Figma: default surface + 21 // border, colored text only (bound to the fg-* tokens), no accent fill. 22 danger: 23 "border border-border-default bg-background-default text-foreground-danger \ 24 hover:bg-background-subtle focus-visible:outline-foreground-danger", 25 success: 26 "border border-border-default bg-background-default text-foreground-success \ 27 hover:bg-background-subtle focus-visible:outline-foreground-success", 28 warning: 29 "border border-border-default bg-background-default text-foreground-warning \ 30 hover:bg-background-subtle focus-visible:outline-foreground-warning" 31 }, 32 size: { 33 sm: "min-h-8 gap-1.5 px-2 py-1.5 text-sm", 34 md: "min-h-9 gap-2 px-3 py-1.5 text-sm", 35 lg: "min-h-10 gap-2.5 px-4 py-2 text-base" 36 } 37 }, 38 compoundVariants: [ 39 // raised variants draw inset depth via a ::before layer. 40 { 41 variant: "default", 42 class: 43 "relative z-0 before:absolute before:inset-0 before:-z-10 before:rounded-l-[max(calc(var(--btn-radius-l)-2px),0px)] before:rounded-r-[max(calc(var(--btn-radius-r)-2px),0px)] before:content-[''] before:shadow-[inset_0_-2px_0_0_var(--color-shadow-inner-subtle)] before:transition-all before:duration-150 hover:before:shadow-[inset_0_-2px_0_0_var(--color-shadow-inner-strong-hover)] \ 44 active:translate-y-0.5 active:before:shadow-[inset_0_2px_2px_0_var(--color-shadow-inner-subtle)] \ 45 disabled:active:translate-y-0" 46 }, 47 { 48 variant: "primary", 49 class: 50 "relative z-0 before:absolute before:inset-0 before:-z-10 before:rounded-l-[max(calc(var(--btn-radius-l)-2px),0px)] before:rounded-r-[max(calc(var(--btn-radius-r)-2px),0px)] before:content-[''] before:shadow-[inset_0_-2px_0_0_var(--color-alpha-dark-25)] before:transition-all before:duration-150 \ 51 active:translate-y-0.5 active:before:shadow-[inset_0_2px_2px_0_var(--color-alpha-dark-50)] \ 52 disabled:active:translate-y-0" 53 } 54 ], 55 defaultVariants: { 56 variant: "default", 57 size: "md" 58 } 59 }); 60 61 export type ButtonVariants = VariantProps<typeof button>; 62</script> 63 64<script lang="ts"> 65 import type { ResolvedPathname } from "$app/types"; 66 import type { Component, Snippet } from "svelte"; 67 import type { 68 HTMLAnchorAttributes, 69 HTMLButtonAttributes, 70 SvelteHTMLElements 71 } from "svelte/elements"; 72 import Spinner from "./Spinner.svelte"; 73 74 interface Props extends Omit<HTMLAnchorAttributes & HTMLButtonAttributes, "class" | "children"> { 75 variant?: ButtonVariants["variant"]; 76 size?: ButtonVariants["size"]; 77 href?: ResolvedPathname; 78 type?: "button" | "submit" | "reset"; 79 disabled?: boolean; 80 icon?: Component<SvelteHTMLElements["svg"]>; 81 class?: string; 82 loading?: boolean; 83 spinnerClass?: string; 84 children?: Snippet; 85 } 86 87 let { 88 variant = "default", 89 size = "md", 90 href, 91 type = "button", 92 disabled = false, 93 icon, 94 class: className, 95 loading = false, 96 spinnerClass, 97 children, 98 ...rest 99 }: Props = $props(); 100 101 const classes = $derived(button({ variant, size, class: className })); 102 const spinnerSize = $derived(spinnerClass ?? (size === "lg" ? "size-5" : "size-4")); 103 const iconSize = $derived(size === "lg" ? "size-5" : "size-4"); 104 const Icon = $derived(icon); 105</script> 106 107{#snippet content()} 108 {#if loading} 109 <Spinner class={spinnerSize} /> 110 {:else} 111 {#if Icon} 112 <Icon class="{iconSize} shrink-0" aria-hidden="true" /> 113 {/if} 114 {#if children} 115 {@render children()} 116 {/if} 117 {/if} 118{/snippet} 119 120{#if href} 121 <a 122 {href} 123 class={classes} 124 aria-disabled={disabled || loading ? "true" : undefined} 125 tabindex={disabled ? -1 : undefined} 126 {...rest} 127 > 128 {@render content()} 129 </a> 130{:else} 131 <button {type} disabled={disabled || loading} class={classes} aria-busy={loading} {...rest}> 132 {@render content()} 133 </button> 134{/if}