Files
electric-cherry/src/lib/ui/components/primitives/Input.svelte
T
josh 8ac420fe61 Improve operational UX, recovery flows, and action safety
Implement the approved UX review across Fleet, Betas, Library, deployments, and Settings. Add reviewed promotion and reconciliation plans, atomic queued-removal cancellation, recoverable beta drafts and launch inputs, accessible controls, and mobile improvements.

Validated with 473 tests, a clean typecheck, production build, and isolated desktop/mobile browser walkthroughs. Include the review and implementation coverage notes.
2026-09-19 21:34:34 -04:00

112 lines
3.1 KiB
Svelte

<script lang="ts">
import { getContext } from 'svelte';
import { fieldContext, type FieldContext } from '../../field-context.js';
const field = getContext<FieldContext | undefined>(fieldContext);
interface Props {
value?: string;
type?: 'text' | 'search' | 'email' | 'number' | 'url' | 'password' | 'file';
placeholder?: string;
id?: string;
name?: string;
required?: boolean;
disabled?: boolean;
readonly?: boolean;
autocomplete?: import('svelte/elements').FullAutoFill;
mono?: boolean;
onchange?: (e: Event) => void;
oninput?: (e: Event) => void;
accept?: string;
ariaLabel?: string;
}
let {
value = $bindable(''),
type = 'text',
placeholder,
id,
name,
required = false,
disabled = false,
readonly = false,
autocomplete,
mono = false,
onchange,
oninput,
accept,
ariaLabel
}: Props = $props();
</script>
<input
class="input"
class:mono
{type}
{id}
{name}
{placeholder}
{required}
{disabled}
{readonly}
{autocomplete}
{accept}
aria-label={ariaLabel}
aria-labelledby={ariaLabel ? undefined : field?.labelId}
aria-describedby={field?.messageId}
aria-invalid={field?.invalid || undefined}
bind:value
{onchange}
{oninput}
/>
<style>
.input {
width: 100%;
padding: 9px 12px;
background: var(--surface-sunken);
border: 1px solid var(--border-control);
border-radius: var(--radius-md);
color: var(--text-primary);
font-family: var(--font-body);
font-size: var(--text-sm);
line-height: 1.4;
transition:
border-color var(--dur-fast) var(--ease-standard),
background-color var(--dur-fast) var(--ease-standard),
box-shadow var(--dur-fast) var(--ease-standard);
}
.input.mono { font-family: var(--font-mono); }
.input::placeholder { color: var(--text-quaternary); }
.input:hover { border-color: var(--border-control-hover); }
.input:focus {
outline: none;
border-color: var(--accent-primary);
background: var(--surface);
box-shadow: 0 0 0 3px rgba(46, 134, 209, 0.15);
}
.input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.input[type='file'] {
padding: 6px;
cursor: pointer;
}
.input[type='file']::file-selector-button {
margin-right: var(--space-3);
padding: 6px 12px;
background: var(--surface-raised);
color: var(--text-primary);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
cursor: pointer;
font-family: var(--font-body);
font-size: var(--text-xs);
font-weight: var(--weight-medium);
transition: background-color var(--dur-fast) var(--ease-standard);
}
.input[type='file']::file-selector-button:hover {
background: var(--surface-overlay);
}
</style>