This repository has no description
1<script module lang="ts">
2 import { tv, type VariantProps } from "tailwind-variants";
3
4 export const inputField = tv({
5 base: "flex min-h-9 items-center gap-2 rounded-sm border bg-background-default px-2 py-1 transition-colors",
6 variants: {
7 error: {
8 false:
9 "border-border-default focus-within:border-border-strong focus-within:ring-1 focus-within:ring-border-strong",
10 true: "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"
11 },
12 disabled: {
13 false: "",
14 true: "cursor-not-allowed bg-background-inset text-foreground-disabled"
15 }
16 },
17 defaultVariants: {
18 error: false,
19 disabled: false
20 }
21 });
22
23 export type InputFieldVariants = VariantProps<typeof inputField>;
24</script>
25
26<script lang="ts">
27 import type { Component } from "svelte";
28 import type { HTMLInputAttributes, SvelteHTMLElements } from "svelte/elements";
29 import Spinner from "./Spinner.svelte";
30
31 interface Props extends Omit<HTMLInputAttributes, "class" | "value"> {
32 value?: string;
33 error?: boolean;
34 disabled?: boolean;
35 loading?: boolean;
36 iconLeft?: Component<SvelteHTMLElements["svg"]>;
37 iconRight?: Component<SvelteHTMLElements["svg"]>;
38 /** static, non-editable trailing text shown inside the field, e.g. a domain suffix */
39 suffix?: string;
40 class?: string;
41 }
42
43 let {
44 value = $bindable(""),
45 error = false,
46 disabled = false,
47 loading = false,
48 iconLeft,
49 iconRight,
50 suffix,
51 class: className,
52 ...rest
53 }: Props = $props();
54
55 const classes = $derived(inputField({ error, disabled, class: className }));
56</script>
57
58<div class={classes}>
59 {#if iconLeft}
60 {@const IconLeft = iconLeft}
61 <IconLeft class="size-4 shrink-0 text-foreground-subtle" aria-hidden="true" />
62 {/if}
63 <input
64 bind:value
65 {disabled}
66 aria-invalid={error}
67 aria-busy={loading}
68 class="flex-1 bg-transparent text-sm outline-none placeholder:text-foreground-placeholder disabled:cursor-not-allowed"
69 {...rest}
70 />
71 {#if suffix}
72 <span class="shrink-0 text-sm text-foreground-muted select-none">{suffix}</span>
73 {/if}
74 {#if loading}
75 <Spinner class="size-4 shrink-0" />
76 {:else if iconRight}
77 {@const IconRight = iconRight}
78 <IconRight class="size-4 shrink-0 text-foreground-subtle" aria-hidden="true" />
79 {/if}
80</div>