Files
Website/src/lib/components/tui/TuiInput.svelte

219 lines
4.3 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import Icon from "@iconify/svelte";
import { getButtonStyle, parseColorText, getSegmentStyle } from "./utils";
import { themeColors } from "$lib/stores/theme";
import type { InputLine } from "./types";
import "$lib/assets/css/tui-input.css";
interface Props {
line: InputLine;
inline?: boolean;
value?: string;
oninput?: (value: string) => void;
onchange?: (value: string) => void;
onfocus?: () => void;
onblur?: () => void;
}
let {
line,
inline = false,
value = $bindable(""),
oninput,
onchange,
onfocus,
onblur,
}: Props = $props();
const labelSegments = $derived(
line.content ? parseColorText(line.content, $themeColors.colorMap) : [],
);
const placeholder = $derived(line.inputPlaceholder || "");
const inputType = $derived(line.inputType || "text");
const isDisabled = $derived(line.inputDisabled || false);
const hasError = $derived(line.inputError);
const errorMessage = $derived(line.inputErrorMessage || "");
const prefix = $derived(line.inputPrefix || "");
const suffix = $derived(line.inputSuffix || "");
function handleInput(e: Event) {
const target = e.target as HTMLInputElement;
value = target.value;
oninput?.(value);
}
function handleChange(e: Event) {
const target = e.target as HTMLInputElement;
onchange?.(target.value);
}
function handleFocus() {
onfocus?.();
}
function handleBlur() {
onblur?.();
}
</script>
<div
class="tui-input"
class:inline
class:error={hasError}
class:disabled={isDisabled}
style="--input-color: {getButtonStyle(line.style)}"
>
{#if line.content}
<label class="input-label">
{#if line.icon}
<Icon icon={line.icon} width="14" class="label-icon" />
{/if}
{#each labelSegments as segment}
{#if segment.icon}
<Icon icon={segment.icon} width="14" class="inline-icon" />
{:else if getSegmentStyle(segment)}
<span style={getSegmentStyle(segment)}>{segment.text}</span>
{:else}
{segment.text}
{/if}
{/each}
</label>
{/if}
<div class="input-wrapper">
<span class="input-prompt"></span>
{#if prefix}
<span class="input-affix prefix">{prefix}</span>
{/if}
<input
type={inputType}
{placeholder}
bind:value
disabled={isDisabled}
oninput={handleInput}
onchange={handleChange}
onfocus={handleFocus}
onblur={handleBlur}
/>
{#if suffix}
<span class="input-affix suffix">{suffix}</span>
{/if}
</div>
{#if hasError && errorMessage}
<div class="input-error">
<Icon icon="mdi:alert-circle" width="12" />
<span>{errorMessage}</span>
</div>
{/if}
</div>
<style>
.tui-input {
margin: 0.5rem 0;
font-size: 0.9rem;
}
.tui-input.inline {
display: inline-flex;
flex-direction: column;
margin: 0 0.5rem 0 0;
}
.input-label {
display: flex;
align-items: center;
gap: 0.35rem;
margin-bottom: 0.35rem;
color: var(--terminal-muted);
font-size: 0.85rem;
}
:global(.label-icon) {
color: var(--input-color);
}
.input-wrapper {
display: flex;
align-items: center;
gap: 0.35rem;
padding: 0.5rem 0.75rem;
background: color-mix(in srgb, var(--terminal-bg) 80%, black);
border: 1px solid var(--terminal-muted);
border-radius: 4px;
transition: all 0.15s ease;
}
.input-wrapper:focus-within {
border-color: var(--input-color);
box-shadow: 0 0 0 1px
color-mix(in srgb, var(--input-color) 30%, transparent);
}
.tui-input.error .input-wrapper {
border-color: #f38ba8;
}
.tui-input.disabled .input-wrapper {
opacity: 0.5;
cursor: not-allowed;
}
.input-prompt {
color: var(--input-color);
font-weight: bold;
user-select: none;
}
.input-affix {
color: var(--terminal-muted);
font-size: 0.85rem;
}
.input-affix.prefix {
margin-right: 0.25rem;
}
.input-affix.suffix {
margin-left: 0.25rem;
}
input {
flex: 1;
min-width: 0;
background: transparent;
border: none;
color: var(--terminal-text);
font-family: inherit;
font-size: inherit;
outline: none;
}
input::placeholder {
color: var(--terminal-muted);
opacity: 0.6;
}
input:disabled {
cursor: not-allowed;
}
.input-error {
display: flex;
align-items: center;
gap: 0.35rem;
margin-top: 0.35rem;
color: #f38ba8;
font-size: 0.8rem;
}
/* Mobile: inline inputs become full-width */
@media (max-width: 768px) {
.tui-input.inline {
display: flex;
width: 100%;
margin: 0.5rem 0;
}
}
</style>