add multi-line TextareaInput component
This commit is contained in:
102
src/lib/TextareaInput.svelte
Normal file
102
src/lib/TextareaInput.svelte
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user