add multi-line TextareaInput component

This commit is contained in:
Elijah Duffy
2025-09-03 18:29:30 -07:00
parent e0654dd20c
commit fd8357382a
2 changed files with 109 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
<script lang="ts">
import {
validate,
type InputValidatorEvent,
type ValidatorOptions
} from '@svelte-toolkit/validate';
import type { HTMLTextareaAttributes } from 'svelte/elements';
import { generateIdentifier } from './util';
import { onMount } from 'svelte';
import Label from './Label.svelte';
type Props = Omit<HTMLTextareaAttributes, 'id'> & {
id?: string;
label?: string;
invalidMessage?: string | null;
validate?: ValidatorOptions;
focus?: boolean;
compact?: boolean;
allowResize?: boolean;
use?: (node: HTMLTextAreaElement) => void;
ref?: HTMLTextAreaElement | null;
onvalidate?: (e: InputValidatorEvent) => void;
};
let {
label,
invalidMessage,
id = generateIdentifier('textarea-input'),
name,
value = $bindable(''),
placeholder,
validate: validateOpts,
disabled = false,
focus: focusOnMount = false,
compact = false,
rows = 4,
allowResize = false,
use,
ref = $bindable<HTMLTextAreaElement | null>(null),
onvalidate,
class: classValue,
...others
}: Props = $props();
let valid: boolean = $state(true);
export const focus = () => {
if (ref) ref.focus();
};
const conditionalUse = $derived(use ? use : () => {});
onMount(() => {
if (focusOnMount && ref) {
ref.focus();
}
});
</script>
<div class={['w-full', classValue]}>
{#if label}
<Label for={id}>{label}</Label>
{/if}
<textarea
{id}
{name}
{placeholder}
aria-label={placeholder}
{...others}
bind:value
{rows}
class={[
'w-full',
!allowResize && 'resize-none',
'border-sui-accent w-full rounded-sm border bg-white font-normal transition-colors',
'text-sui-text placeholder:text-sui-text/60 dark:border-sui-accent/50 dark:bg-sui-text-800 placeholder:font-normal',
'dark:text-sui-background dark:placeholder:text-sui-background/60 dark:sm:bg-slate-800',
'ring-sui-primary ring-offset-1 placeholder-shown:text-ellipsis focus:ring-2',
!compact ? 'px-[1.125rem] py-3.5' : 'px-[0.75rem] py-2',
!valid && 'border-red-500!',
disabled &&
'border-sui-accent/20 text-sui-text/60 dark:text-sui-background/60 cursor-not-allowed'
]}
{disabled}
use:validate={validateOpts}
use:conditionalUse
onvalidate={(e) => {
valid = e.detail.valid;
onvalidate?.(e);
}}
bind:this={ref}
></textarea>
{#if validateOpts && invalidMessage !== null}
<div class={['opacity-0 transition-opacity', !valid && 'opacity-100']}>
<Label for={id} error>
{invalidMessage}
</Label>
</div>
{/if}
</div>

View File

@@ -32,6 +32,7 @@
import { Time } from '@internationalized/date';
import { onMount, type Component } from 'svelte';
import ErrorBox from '$lib/ErrorBox.svelte';
import TextareaInput from '$lib/TextareaInput.svelte';
// Lazy-load heavy components
let PhoneInput = createLazyComponent(() => import('$lib/PhoneInput.svelte'));
@@ -276,6 +277,12 @@
</InputGroup>
</div>
<div class="component">
<p class="title">Multi-line input (textarea)</p>
<TextareaInput label="Write your message here" />
</div>
<div class="component">
<p class="title">Time Input</p>