Coves frontend - a photon fork
1<script module lang="ts">
2 import { Label } from 'mono-svelte'
3 import type { HTMLInputAttributes } from 'svelte/elements'
4 import { generateID } from './helper'
5
6 export type Size = keyof typeof sizeClass
7 export type Shadow = keyof typeof shadowClass
8
9 export const sizeClass = {
10 sm: 'px-3 py-1',
11 md: 'px-3.5 py-1.5',
12 lg: 'px-5 py-3',
13 }
14
15 export const shadowClass = {
16 sm: 'shadow-xs',
17 none: 'shadow-none',
18 }
19
20 interface Props extends Omit<HTMLInputAttributes, 'size' | 'prefix'> {
21 label?: string | undefined
22 placeholder?: string
23 disabled?: boolean
24 required?: boolean
25 size?: Size
26 id?: string
27 inlineAffixes?: boolean
28 shadow?: Shadow
29 element?: HTMLInputElement | undefined
30 class?: string
31 customLabel?: import('svelte').Snippet
32 prefix?: import('svelte').Snippet
33 suffix?: import('svelte').Snippet
34 children?: import('svelte').Snippet
35 }
36
37 export type { Props as TextInputProps }
38</script>
39
40<script lang="ts">
41 const borderClass = `border border-slate-200 border-b-slate-300 dark:border-zinc-800`
42
43 let {
44 label = undefined,
45 value = $bindable(),
46 placeholder = '',
47 disabled = false,
48 required = false,
49 size = 'md',
50 id = generateID(),
51 inlineAffixes = false,
52 shadow = 'sm',
53 element = $bindable(),
54 class: clazz = '',
55 customLabel: passedCustomLabel,
56 prefix,
57 suffix,
58 children,
59 ...rest
60 }: Props = $props()
61</script>
62
63<div class="flex flex-col gap-1 {clazz}">
64 {#if passedCustomLabel || label}
65 <Label
66 for={id}
67 text={label}
68 class={[
69 'peer-invalid:text-red-500 relative',
70 required && "after:content-['*'] after:text-red-500 after:ml-1",
71 ]}
72 >
73 {@render passedCustomLabel?.()}
74 </Label>
75 {/if}
76 <div
77 class={[
78 shadowClass[shadow],
79 borderClass,
80 `border focus-within:border-primary-900 dark:focus-within:border-primary-100 focus-within:ring-2
81 ring-slate-300 dark:ring-zinc-700
82 transition-colors
83 rounded-xl flex flex-row items-center text-sm`,
84 clazz,
85 ]}
86 >
87 {#if prefix}
88 <div
89 class={[
90 'rounded-xl rounded-r-none text-slate-600 dark:text-zinc-400',
91 inlineAffixes && 'bg-white dark:bg-zinc-900 pr-0 w-8',
92 sizeClass[size],
93 ]}
94 >
95 {@render prefix?.()}
96 </div>
97 {/if}
98 <input
99 type="text"
100 {id}
101 {placeholder}
102 {disabled}
103 bind:value
104 bind:this={element}
105 {required}
106 {...rest}
107 class={[
108 sizeClass[size],
109 `bg-white dark:bg-zinc-900
110 focus:outline-hidden rounded-xl text-sm w-full disabled:bg-slate-100
111 disabled:cursor-not-allowed dark:disabled:bg-zinc-800 invalid:border-red-500!
112 peer invalid:text-red-500 z-10`,
113 prefix && 'rounded-l-none',
114 prefix && inlineAffixes && 'border-l-0',
115 suffix && 'rounded-r-none',
116 suffix && inlineAffixes && 'border-r-0',
117 clazz,
118 ]}
119 />
120 {#if suffix}
121 <div
122 class={[
123 'rounded-xl rounded-l-none text-slate-600 dark:text-zinc-400 h-full',
124 inlineAffixes && 'bg-white dark:bg-zinc-900 pl-0',
125 ]}
126 >
127 {@render suffix?.()}
128 </div>
129 {/if}
130 </div>
131 {@render children?.()}
132</div>