Monorepo for Tangled tangled.org
3

Configure Feed

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

core / web / src / lib / components / ui / Textarea.svelte
2.9 kB 99 lines
1<script module lang="ts"> 2 import { tv, type VariantProps } from "tailwind-variants"; 3 4 export const textareaField = tv({ 5 slots: { 6 root: "relative flex max-h-64 items-start gap-1 rounded border bg-background-default px-2.5 py-2 transition-colors", 7 textarea: 8 "min-h-24 flex-1 bg-transparent text-sm leading-6 outline-none placeholder:text-foreground-placeholder read-only:resize-none disabled:cursor-not-allowed disabled:resize-none" 9 }, 10 variants: { 11 error: { 12 false: { 13 root: "border-border-default focus-within:border-border-strong focus-within:ring-1 focus-within:ring-border-strong" 14 }, 15 true: { 16 root: "border-border-danger bg-background-danger-subtle text-foreground-danger focus-within:border-border-focus-danger focus-within:ring-1 focus-within:ring-border-focus-danger", 17 textarea: "placeholder:text-foreground-danger" 18 } 19 }, 20 disabled: { 21 false: "", 22 true: { 23 root: "cursor-not-allowed border-border-disabled bg-background-subtle text-foreground-muted" 24 } 25 }, 26 readonly: { 27 false: "", 28 true: { 29 root: "cursor-default border-border-disabled bg-background-subtle text-foreground-muted" 30 } 31 }, 32 resizeable: { 33 false: { textarea: "resize-none" }, 34 true: { textarea: "resize-y" } 35 } 36 }, 37 defaultVariants: { 38 error: false, 39 disabled: false, 40 readonly: false 41 } 42 }); 43 44 export type TextareaFieldVariants = VariantProps<typeof textareaField>; 45</script> 46 47<script lang="ts"> 48 import type { Component } from "svelte"; 49 import type { HTMLTextareaAttributes, SvelteHTMLElements } from "svelte/elements"; 50 import Spinner from "./Spinner.svelte"; 51 52 interface Props extends Omit<HTMLTextareaAttributes, "class" | "value"> { 53 value?: string; 54 error?: boolean; 55 disabled?: boolean; 56 loading?: boolean; 57 readonly?: boolean; 58 resizeable?: boolean; 59 iconLeft?: Component<SvelteHTMLElements["svg"]>; 60 iconRight?: Component<SvelteHTMLElements["svg"]>; 61 class?: string; 62 } 63 64 let { 65 value = $bindable(""), 66 error = false, 67 disabled = false, 68 loading = false, 69 readonly = false, 70 resizeable = false, 71 iconLeft, 72 iconRight, 73 class: className, 74 ...rest 75 }: Props = $props(); 76 77 const { root, textarea } = $derived(textareaField({ error, disabled, readonly, resizeable })); 78</script> 79 80<div class={root({ class: className })}> 81 {#if iconLeft} 82 {@const IconLeft = iconLeft} 83 <IconLeft class="size-4 mt-0.5 mr-1 shrink-0 text-foreground-subtle" aria-hidden="true" /> 84 {/if} 85 <textarea 86 bind:value 87 {disabled} 88 {readonly} 89 aria-invalid={error} 90 aria-busy={loading} 91 class={textarea({ class: loading ? "pr-5" : undefined })} 92 {...rest}></textarea> 93 {#if loading} 94 <Spinner class="absolute right-2.5 top-2 size-4" /> 95 {:else if iconRight} 96 {@const IconRight = iconRight} 97 <IconRight class="size-4 mt-0.5 ml-1 shrink-0 text-foreground-subtle" aria-hidden="true" /> 98 {/if} 99</div>