update from cdrl

This commit is contained in:
2026-07-15 11:20:47 -04:00
parent 0f9f42be5a
commit f1745b8a1b
8 changed files with 195 additions and 353 deletions

View File

@@ -107,8 +107,9 @@ export interface FormInputProps
extends
FormInputCommonProps,
Pick<JSX.InputHTMLAttributes<HTMLInputElement>, CommonInputAttrs | "type" | "max" | "min" | "step" | "accept"> {
small?: boolean;
ref?: (el: HTMLInputElement) => void;
prefix?: JSXElement;
small?: boolean;
class?: string;
onDark?: boolean;
}
@@ -121,10 +122,6 @@ export interface FormTextareaProps
onDark?: boolean;
}
export interface FormInputGroupProps extends FormInputProps {
prefix?: JSXElement;
}
export interface FormNumberInputProps extends FormInputProps {
int?: boolean;
unsigned?: boolean;
@@ -171,7 +168,7 @@ function _comboboxRootCls(className?: string, fieldWidth: ComboboxFieldWidth = "
}
export interface FormComboboxProps extends Pick<JSX.SelectHTMLAttributes<HTMLSelectElement>, CommonSelectAttrs> {
options: FormSelectOption[];
options?: FormSelectOption[];
placeholder?: string;
searchable?: boolean;
searchPlaceholder?: string;
@@ -227,90 +224,104 @@ export interface FormMultiSelectTriggerProps {
class?: string;
}
export function FormInput(props: FormInputProps) {
const cls = () => _inputCls(props.small, props.error, props.success, props.class, props.onDark);
const inputValue = () => readAccessor(props.value, "");
return <div class="ui-form">
<input
ref={(el) => typeof props.ref === "function" && props.ref(el)}
class={cls()}
type={props.type}
value={inputValue()}
placeholder={props.placeholder}
autocomplete={props.autocomplete || (props.passwordManagerIgnore ? "off" : undefined)}
inputmode={props.inputmode}
data-1p-ignore={props.passwordManagerIgnore}
data-lpignore={props.passwordManagerIgnore ? "other" : undefined}
data-form-type={props.passwordManagerIgnore}
data-bwignore={props.passwordManagerIgnore}
data-protonpass-ignore={props.passwordManagerIgnore}
disabled={props.disabled}
maxlength={props.maxlength}
min={props.min}
max={props.max}
step={props.step}
name={props.name}
id={props.id}
oninput={props.oninput}
onchange={props.onchange}
onBlur={props.onblur}
onFocus={props.onfocus}
onKeyDown={props.onkeydown}
/>
<Show when={props.error}>
<span class={FORM_ERROR}>{props.error}</span>
</Show>
<Show when={props.success}>
<span class={FORM_SUCCESS}>{props.success}</span>
</Show>
</div>;
export function FormInputOld(props: FormInputProps) {
const [local, inputProps] = splitProps(props, [
"prefix",
"small",
"error",
"success",
"class",
"onDark",
"ref",
"autocomplete",
"passwordManagerIgnore",
]);
return (
<div class="ui-form">
<input
ref={(el) => typeof local.ref === "function" && local.ref(el)}
class={_inputCls(local.small, local.error, local.success, local.class, local.onDark)}
autocomplete={local.autocomplete || (local.passwordManagerIgnore ? "off" : undefined)}
data-1p-ignore={local.passwordManagerIgnore}
data-lpignore={local.passwordManagerIgnore ? "other" : undefined}
data-form-type={local.passwordManagerIgnore}
data-bwignore={local.passwordManagerIgnore}
data-protonpass-ignore={local.passwordManagerIgnore}
{...inputProps}
/>
<Show when={local.error}>
<span class={FORM_ERROR}>{local.error}</span>
</Show>
<Show when={local.success}>
<span class={FORM_SUCCESS}>{local.success}</span>
</Show>
</div>
);
}
export function FormInputGroup(props: FormInputGroupProps) {
const pfxCls = () => _prefixCls(props.small, props.error, props.onDark);
const inputCls = () => _inputCls(props.small, props.error, props.success, "rounded-l-none" + (props.class ? " " + props.class : ""), props.onDark);
const inputValue = () => readAccessor(props.value, "");
return <div class="ui-form">
<div class={INPUT_GROUP}>
<span class={pfxCls()}>{props.prefix}</span>
<span class="grow flex">
<input
ref={(el) => typeof props.ref === "function" && props.ref(el)}
class={inputCls()}
type={props.type}
value={inputValue()}
placeholder={props.placeholder}
autocomplete={props.autocomplete || (props.passwordManagerIgnore ? "off" : undefined)}
inputmode={props.inputmode}
data-1p-ignore={props.passwordManagerIgnore}
data-lpignore={props.passwordManagerIgnore ? "other" : undefined}
data-form-type={props.passwordManagerIgnore}
data-bwignore={props.passwordManagerIgnore}
data-protonpass-ignore={props.passwordManagerIgnore}
disabled={props.disabled}
maxlength={props.maxlength}
name={props.name}
id={props.id}
oninput={props.oninput}
onchange={props.onchange}
onBlur={props.onblur}
onFocus={props.onfocus}
onKeyDown={props.onkeydown}
/>
</span>
</div>
<Show when={props.error}>
<span class={FORM_ERROR}>{props.error}</span>
export function FormInput(props: FormInputProps) {
const [local, inputProps] = splitProps(props, [
"prefix",
"small",
"error",
"success",
"class",
"onDark",
"ref",
"autocomplete",
"passwordManagerIgnore",
]);
const cls = local.prefix ? ("rounded-l-none" + (local.class ? " " + local.class : "")) : local.class;
const InputEl = () => (
<input
ref={(el) => typeof local.ref === "function" && local.ref(el)}
class={_inputCls(local.small, local.error, local.success, cls, local.onDark)}
autocomplete={local.autocomplete || (local.passwordManagerIgnore ? "off" : undefined)}
data-1p-ignore={local.passwordManagerIgnore}
data-lpignore={local.passwordManagerIgnore ? "other" : undefined}
data-form-type={local.passwordManagerIgnore}
data-bwignore={local.passwordManagerIgnore}
data-protonpass-ignore={local.passwordManagerIgnore}
{...inputProps}
/>
);
return (
<Show
when={local.prefix}
fallback={
<div class="ui-form">
<InputEl />
<Show when={local.error}>
<span class={FORM_ERROR}>{local.error}</span>
</Show>
<Show when={local.success}>
<span class={FORM_SUCCESS}>{local.success}</span>
</Show>
</div>
}
>
<div class="ui-form">
<div class={INPUT_GROUP}>
<span class={_prefixCls(local.small, local.error, local.onDark)}>{local.prefix}</span>
<span class="grow flex"><InputEl /></span>
</div>
<Show when={local.error}>
<span class={FORM_ERROR}>{local.error}</span>
</Show>
<Show when={local.success}>
<span class={FORM_SUCCESS}>{local.success}</span>
</Show>
</div>
</Show>
<Show when={props.success}>
<span class={FORM_SUCCESS}>{props.success}</span>
</Show>
</div>;
);
}
export function FormNumberInput(props: FormNumberInputProps) {
const [local, inputProps] = splitProps(props, ["small", "ref", "class", "oninput", "int", "unsigned"]);
const [local, inputProps] = splitProps(props, ["oninput", "int", "unsigned"]);
var regex: RegExp;
if (local.int) {
@@ -339,45 +350,11 @@ export function FormNumberInput(props: FormNumberInputProps) {
}
};
return <FormInput
ref={(el) => typeof local.ref === "function" && local.ref(el)}
type="text"
inputMode="decimal"
oninput={handleInput}
class={local.class}
small={local.small}
{...inputProps}
/>;
return <FormInput inputMode="decimal" oninput={handleInput} {...inputProps} />;
}
export function FormCarNumberInput(props: FormInputProps) {
const [local, inputProps] = splitProps(props, ["small", "ref", "class", "oninput"]);
const handleInput = (e) => {
if (!e || !e.currentTarget) return;
const input = e.currentTarget;
const raw = input.value.toUpperCase().replace(/[^0-9A-Z]/g, "");
const match = raw.match(/^([0-9]{0,2})([A-Z]{0,2})/);
input.value = match ? match[1] + match[2] : "";
if (typeof local.oninput === "function") {
local.oninput(e);
}
};
return <FormInput
ref={(el) => typeof local.ref === "function" && local.ref(el)}
type="text"
maxLength="4"
oninput={handleInput}
class={local.class}
small={local.small}
placeholder="e.g. 28"
{...inputProps}
/>;
}
export function FormCurrencyInput(props: {hideSymbol?: boolean} & FormInputProps) {
const [local, inputProps] = splitProps(props, ["small", "ref", "class", "oninput", "hideSymbol"]);
export function FormCurrencyInput(props: {showIcon?: boolean} & FormInputProps) {
const [local, inputProps] = splitProps(props, ["oninput", "showIcon"]);
const handleInput = (e) => {
if (!e || !e.currentTarget) return;
@@ -396,32 +373,12 @@ export function FormCurrencyInput(props: {hideSymbol?: boolean} & FormInputProp
}
};
if (local.hideSymbol) {
return <FormInput
ref={(el) => typeof local.ref === "function" && local.ref(el)}
type="text"
inputMode="decimal"
oninput={handleInput}
class={local.class}
small={local.small}
{...inputProps}
/>;
} else {
return <FormInputGroup
ref={(el) => typeof local.ref === "function" && local.ref(el)}
prefix="$"
type="text"
inputMode="decimal"
oninput={handleInput}
class={local.class}
small={local.small}
{...inputProps}
/>;
}
return <FormInput prefix={local.showIcon ? "$" : null} inputMode="decimal" oninput={handleInput} {...inputProps} />;
}
export function FormPercentInput(props: FormInputProps) {
const [local, inputProps] = splitProps(props, ["small", "ref", "class", "oninput"]);
export function FormPercentInput(props: {showIcon?: boolean} & FormInputProps) {
const [local, inputProps] = splitProps(props, ["oninput", "showIcon"]);
const handleInput = (e) => {
if (!e || !e.currentTarget) return;
@@ -436,20 +393,11 @@ export function FormPercentInput(props: FormInputProps) {
}
};
return <FormInputGroup
ref={(el) => typeof local.ref === "function" && local.ref(el)}
prefix="%"
type="text"
inputMode="decimal"
oninput={handleInput}
class={local.class}
small={local.small}
{...inputProps}
/>;
return <FormInput prefix={local.showIcon ? "%" : null} inputMode="decimal" oninput={handleInput} {...inputProps} />;
}
export function FormPhoneInput(props: FormInputProps) {
const [local, inputProps] = splitProps(props, ["small", "ref", "class", "oninput"]);
export function FormPhoneInput(props: {showIcon?: boolean; icon?: string} & FormInputProps) {
const [local, inputProps] = splitProps(props, ["showIcon", "icon", "oninput"]);
const handleInput = (e) => {
if (!e || !e.currentTarget) return;
@@ -473,138 +421,45 @@ export function FormPhoneInput(props: FormInputProps) {
}
};
return <FormInput
ref={(el) => typeof local.ref === "function" && local.ref(el)}
type="text"
inputMode="numeric"
oninput={handleInput}
class={local.class}
small={local.small}
placeholder="(555) 555-5555"
{...inputProps}
/>;
}
// @TODO replace this with showIcon prop
export function FormPhoneInputWithIcon(props: {icon?: string} & FormInputGroupProps) {
const [local, inputProps] = splitProps(props, ["icon", "small", "ref", "class", "oninput"]);
const handleInput = (e) => {
if (!e || !e.currentTarget) return;
const input = e.currentTarget;
let digits = input.value.replace(/\D/g, "");
digits = digits.slice(0, 10);
let formatted = "";
if (digits.length > 0) {
formatted = "(" + digits.slice(0, 3);
}
if (digits.length > 3) {
formatted += ") " + digits.slice(3, 6);
}
if (digits.length > 6) {
formatted += "-" + digits.slice(6, 10);
}
input.value = formatted;
if (typeof local.oninput === "function") {
local.oninput(e);
}
};
const iconEl = <IconInline icon={local.icon || "phone"} size={16} />;
return <FormInputGroup
prefix={iconEl}
ref={(el) => typeof local.ref === "function" && local.ref(el)}
type="text"
inputMode="numeric"
oninput={handleInput}
class={local.class}
small={local.small}
placeholder="(555) 555-5555"
{...inputProps}
/>;
return (
<FormInput
prefix={local.showIcon !== false ? <IconInline icon={local.icon || "phone"} size={16} /> : null}
inputMode="numeric"
oninput={handleInput}
placeholder="(555) 555-5555"
{...inputProps}
/>
);
}
export function FormEmailInput(props: {showIcon?: boolean} & FormInputProps) {
const [local, inputProps] = splitProps(props, ["showIcon", "small", "ref", "class", "error", "success", "onDark"]);
const showIcon = () => local.showIcon !== false;
const pfxCls = () => _prefixCls(local.small, local.error, local.onDark);
const inputCls = () => _inputCls(local.small, local.error, local.success, "rounded-l-none" + (local.class ? " " + local.class : ""), local.onDark);
const [local, inputProps] = splitProps(props, ["showIcon"]);
return <Show when={showIcon()} fallback={
return (
<FormInput
ref={(el) => typeof local.ref === "function" && local.ref(el)}
prefix={local.showIcon !== false ? <IconInline icon="envelope" size={16} /> : null}
type="email"
inputMode="email"
class={local.class}
small={local.small}
error={local.error}
success={local.success}
onDark={local.onDark}
{...inputProps}
/>}>
<div class="ui-form">
<div class={INPUT_GROUP}>
<span class={pfxCls()}>
<IconInline icon="envelope" size={16} />
</span>
<span class="grow flex">
<input
ref={(el) => typeof local.ref === "function" && local.ref(el)}
type="email"
inputMode="email"
class={inputCls()}
{...inputProps}
/>
</span>
</div>
<Show when={local.error}>
<span class={FORM_ERROR}>{local.error}</span>
</Show>
<Show when={local.success}>
<span class={FORM_SUCCESS}>{local.success}</span>
</Show>
</div>
</Show>;
/>
);
}
export function FormURLInput(props: {showIcon: boolean} & FormInputProps) {
const [local, inputProps] = splitProps(props, ["showIcon", "small", "ref", "class", "onDark"]);
const showIcon = () => local.showIcon !== false;
const pfxCls = () => _prefixCls(local.small, false, local.onDark);
const inputCls = () => _inputCls(local.small, false, false, "rounded-l-none" + (local.class ? " " + local.class : ""), local.onDark);
export function FormURLInput(props: {showIcon?: boolean} & FormInputProps) {
const [local, inputProps] = splitProps(props, ["showIcon"]);
return <Show when={showIcon()} fallback={
return (
<FormInput
ref={(el) => typeof local.ref === "function" && local.ref(el)}
prefix={local.showIcon !== false ? <IconInline icon="globe" size={16} /> : null}
type="text"
inputMode="url"
class={local.class}
small={local.small}
onDark={local.onDark}
{...inputProps}
/>}>
<div class="ui-form">
<div class={INPUT_GROUP}>
<span class={pfxCls()}>
<IconInline icon="globe" size={16} />
</span>
<span class="grow flex">
<input
ref={(el) => typeof local.ref === "function" && local.ref(el)}
type="text"
inputMode="url"
class={inputCls()}
{...inputProps}
/>
</span>
</div>
</div>
</Show>;
/>
);
}
export function FormZipCodeInput(props: FormInputProps) {
const [local, inputProps] = splitProps(props, ["small", "ref", "class", "oninput"]);
const [local, inputProps] = splitProps(props, ["oninput"]);
const handleInput = (e) => {
if (!e || !e.currentTarget) return;
@@ -617,18 +472,15 @@ export function FormZipCodeInput(props: FormInputProps) {
return b ? a + "-" + b : a;
});
if (typeof props.oninput === "function") {
props.oninput(e);
if (typeof local.oninput === "function") {
local.oninput(e);
}
};
return <FormInput
ref={(el) => typeof local.ref === "function" && local.ref(el)}
type="text"
inputMode="numeric"
oninput={handleInput}
class={local.class}
small={local.small}
placeholder="12345"
{...inputProps}
/>;
@@ -807,7 +659,6 @@ export function FormSignaturePad(props: { value?: () => string; onchange?: (svg:
// @TODO Fix this
export function FormSelect(props: FormSelectProps & { children?: JSXElement }) {
const [local, selectProps] = splitProps(props, ["small", "ref", "class", "children", "value", "onDark"]);
const cls = () => _inputCls(local.small, false, false, local.class, local.onDark);
let selectEl;
// Set value via deferred effect so it runs AFTER children (<option>s) are in the DOM.
// The spread's createRenderEffect runs immediately (before children), so select.value
@@ -823,7 +674,7 @@ export function FormSelect(props: FormSelectProps & { children?: JSXElement }) {
return <div class="ui-form">
<select
ref={(el) => { selectEl = el; if (local.ref) local.ref(el); }}
class={cls()}
class={_inputCls(local.small, false, false, local.class, local.onDark)}
{...selectProps}
>{local.children}</select>
</div>;
@@ -967,24 +818,23 @@ export function FormSearchableSelect(props: {ref: string} & FormComboboxProps) {
}
export function FormTextarea(props: FormTextareaProps) {
const cls = () => _textareaCls(props.small, props.error, props.class, props.onDark);
const inputValue = () => readAccessor(props.value, "");
const [local, inputProps] = splitProps(props, [
"small",
"error",
"class",
"onDark",
"ref",
]);
const cls = () => _textareaCls(local.small, local.error, local.class, local.onDark);
return <div class="ui-form">
<textarea
ref={(el) => typeof props.ref === "function" && props.ref(el)}
ref={(el) => typeof local.ref === "function" && local.ref(el)}
class={cls()}
value={inputValue()}
placeholder={props.placeholder}
rows={props.rows}
disabled={props.disabled}
name={props.name}
id={props.id}
oninput={props.oninput}
onchange={props.onchange}
onblur={props.onblur}
{...inputProps}
/>
<Show when={props.error}>
<span class={FORM_ERROR}>{props.error}</span>
<Show when={local.error}>
<span class={FORM_ERROR}>{local.error}</span>
</Show>
</div>;
}