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.
112 lines
3.1 KiB
Svelte
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>
|