91 lines
2.5 KiB
React
91 lines
2.5 KiB
React
import React from "react";
|
|
|
|
/**
|
|
* Text input with optional label, leading icon and error state.
|
|
*/
|
|
export function Input({
|
|
label,
|
|
value,
|
|
onChange,
|
|
placeholder,
|
|
leadingIcon = null,
|
|
trailing = null,
|
|
error = null,
|
|
disabled = false,
|
|
size = "md",
|
|
type = "text",
|
|
fullWidth = true,
|
|
style = {},
|
|
...rest
|
|
}) {
|
|
const [focus, setFocus] = React.useState(false);
|
|
const height = size === "sm" ? "var(--control-h-sm)" : "var(--control-h)";
|
|
const borderColor = error
|
|
? "var(--status-error)"
|
|
: focus
|
|
? "var(--action-primary)"
|
|
: "var(--border-control)";
|
|
|
|
return (
|
|
<label style={{ display: "block", width: fullWidth ? "100%" : "auto" }}>
|
|
{label && (
|
|
<span
|
|
style={{
|
|
display: "block",
|
|
marginBottom: "6px",
|
|
font: "var(--weight-medium) var(--text-xs)/1 var(--font-sans)",
|
|
color: "var(--text-secondary)",
|
|
}}
|
|
>
|
|
{label}
|
|
</span>
|
|
)}
|
|
<span
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "8px",
|
|
height,
|
|
padding: "0 10px",
|
|
background: "var(--ctv-bg-sunken)",
|
|
border: `1px solid ${borderColor}`,
|
|
borderRadius: "var(--radius-sm)",
|
|
boxShadow: focus && !error ? "0 0 0 3px var(--focus-ring)" : "none",
|
|
transition: "border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard)",
|
|
opacity: disabled ? 0.5 : 1,
|
|
...style,
|
|
}}
|
|
>
|
|
{leadingIcon && (
|
|
<span style={{ display: "flex", color: "var(--text-disabled)", flex: "0 0 auto" }}>{leadingIcon}</span>
|
|
)}
|
|
<input
|
|
type={type}
|
|
value={value}
|
|
onChange={onChange}
|
|
placeholder={placeholder}
|
|
disabled={disabled}
|
|
onFocus={() => setFocus(true)}
|
|
onBlur={() => setFocus(false)}
|
|
style={{
|
|
flex: 1,
|
|
minWidth: 0,
|
|
background: "transparent",
|
|
border: "none",
|
|
outline: "none",
|
|
color: "var(--text-primary)",
|
|
font: "var(--weight-normal) var(--text-sm)/1 var(--font-sans)",
|
|
}}
|
|
{...rest}
|
|
/>
|
|
{trailing && <span style={{ display: "flex", flex: "0 0 auto" }}>{trailing}</span>}
|
|
</span>
|
|
{error && (
|
|
<span style={{ display: "block", marginTop: "5px", font: "var(--text-xs)/1.3 var(--font-sans)", color: "var(--status-error)" }}>
|
|
{error}
|
|
</span>
|
|
)}
|
|
</label>
|
|
);
|
|
}
|