This commit is contained in:
2026-02-13 19:04:07 +01:00
parent 9505331165
commit 69d432b6af
42 changed files with 650 additions and 360 deletions
@@ -8,7 +8,7 @@ let pressInterval: any;
let pressTimeout: any;
type Props = Pick<InputProps<any>, "min" | "max" | "step"> & {
updateValue: (v: string) => void;
setValue: React.Dispatch<React.SetStateAction<string>>;
getNormalizedValue: (v: string) => void;
buttonDownRef: React.MutableRefObject<boolean>;
inputRef: React.RefObject<HTMLInputElement | null>;
@@ -19,7 +19,7 @@ type Props = Pick<InputProps<any>, "min" | "max" | "step"> & {
*/
export default function NumberInputButtons({
getNormalizedValue,
updateValue,
setValue,
min,
max,
step,
@@ -65,12 +65,14 @@ export default function NumberInputButtons({
const existingNumberValue = twuiNumberfy(existingValue);
if (max && existingNumberValue >= twuiNumberfy(max)) {
return updateValue(String(max));
return setValue(String(max));
} else if (min && existingNumberValue < twuiNumberfy(min)) {
return updateValue(String(min));
return setValue(String(min));
} else {
updateValue(
String(existingNumberValue + twuiNumberfy(step || DEFAULT_STEP))
setValue(
String(
existingNumberValue + twuiNumberfy(step || DEFAULT_STEP),
),
);
}
}
@@ -80,10 +82,12 @@ export default function NumberInputButtons({
const existingNumberValue = twuiNumberfy(existingValue);
if (min && existingNumberValue <= twuiNumberfy(min)) {
updateValue(String(min));
setValue(String(min));
} else {
updateValue(
String(existingNumberValue - twuiNumberfy(step || DEFAULT_STEP))
setValue(
String(
existingNumberValue - twuiNumberfy(step || DEFAULT_STEP),
),
);
}
}
+62 -61
View File
@@ -27,13 +27,16 @@ let timeout: any;
let validationFnTimeout: any;
let externalValueChangeTimeout: any;
export type InputProps<KeyType extends string> = DetailedHTMLProps<
InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
export type InputProps<KeyType extends string> = Omit<
DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>,
"prefix" | "suffix"
> &
DetailedHTMLProps<
TextareaHTMLAttributes<HTMLTextAreaElement>,
HTMLTextAreaElement
Omit<
DetailedHTMLProps<
TextareaHTMLAttributes<HTMLTextAreaElement>,
HTMLTextAreaElement
>,
"prefix" | "suffix"
> & {
label?: string;
variant?: "normal" | "warning" | "error" | "inactive";
@@ -60,16 +63,14 @@ export type InputProps<KeyType extends string> = DetailedHTMLProps<
invalidMessage?: string;
validationFunction?: (
value: string,
element?: HTMLInputElement | HTMLTextAreaElement
element?: HTMLInputElement | HTMLTextAreaElement,
) => Promise<TWUISelectValidityObject>;
changeHandler?: (
value: string,
element?: HTMLInputElement | HTMLTextAreaElement
) => void;
changeHandler?: (value: string) => void;
autoComplete?: (typeof AutocompleteOptions)[number];
name?: KeyType;
valueUpdate?: string;
numberText?: boolean;
rawNumber?: boolean;
setReady?: React.Dispatch<React.SetStateAction<boolean>>;
decimal?: number;
info?: string | ReactNode;
@@ -91,7 +92,7 @@ let refreshes = 0;
* @className twui-clear-input-field-button
*/
export default function Input<KeyType extends string>(
inputProps: InputProps<KeyType>
inputProps: InputProps<KeyType>,
) {
const {
label,
@@ -119,10 +120,12 @@ export default function Input<KeyType extends string>(
changeHandler,
validity: existingValidity,
clearInputProps,
rawNumber,
...props
} = inputProps;
function getFinalValue(v: any) {
if (rawNumber) return twuiNumberfy(v);
if (numberText) {
return (
twuiNumberfy(v, decimal).toLocaleString() +
@@ -141,16 +144,19 @@ export default function Input<KeyType extends string>(
const [validity, setValidity] = React.useState<TWUISelectValidityObject>(
existingValidity || {
isValid: true,
}
},
);
const inputRef = componentRef || React.useRef<HTMLInputElement>(null);
const textAreaRef = componentRef || React.useRef<HTMLTextAreaElement>(null);
const buttonDownRef = React.useRef(false);
const [value, setValue] = React.useState(
props.defaultValue ? String(props.defaultValue) : "",
);
const [focus, setFocus] = React.useState(false);
const [inputType, setInputType] = React.useState(
numberText ? "text" : props.type
numberText ? "text" : props.type,
);
const DEFAULT_DEBOUNCE = 500;
@@ -180,23 +186,20 @@ export default function Input<KeyType extends string>(
setValidity(existingValidity);
}, [existingValidity]);
const updateValueFn = (
val: string,
el?: HTMLInputElement | HTMLTextAreaElement
) => {
const updateValueFn = (val: string) => {
if (buttonDownRef.current) return;
if (changeHandler) {
window.clearTimeout(externalValueChangeTimeout);
externalValueChangeTimeout = setTimeout(() => {
changeHandler(val, el);
changeHandler(val);
}, finalDebounce);
}
if (typeof val == "string") {
if (!val.match(/./)) {
setValidity({ isValid: true });
props.value = "";
setValue("");
if (istextarea && textAreaRef.current) {
textAreaRef.current.value = "";
} else if (inputRef?.current) {
@@ -223,7 +226,7 @@ export default function Input<KeyType extends string>(
if (validationRegex && !validationRegex.test(val)) {
return;
}
validationFunction(val, el).then((res) => {
validationFunction(val).then((res) => {
setValidity(res);
});
}, finalDebounce);
@@ -233,28 +236,36 @@ export default function Input<KeyType extends string>(
React.useEffect(() => {
if (typeof props.value !== "string" || !props.value.match(/./)) return;
updateValueFn(String(props.value));
setValue(String(props.value));
}, [props.value]);
React.useEffect(() => {
if (istextarea && textAreaRef.current) {
} else if (inputRef?.current) {
inputRef.current.value = getFinalValue(value);
}
updateValueFn(value);
}, [value]);
function handleValueChange(
e: React.ChangeEvent<HTMLInputElement> &
React.ChangeEvent<HTMLTextAreaElement>
React.ChangeEvent<HTMLTextAreaElement>,
) {
const newValue = e.target.value;
updateValue(newValue, e.target);
setValue(newValue);
props.onChange?.(e);
}
function updateValue(
v: string,
el?: HTMLInputElement | HTMLTextAreaElement
) {
if (istextarea && textAreaRef.current) {
} else if (inputRef?.current) {
inputRef.current.value = getFinalValue(v);
}
updateValueFn(v, el);
}
// function updateValue(
// v: string,
// el?: HTMLInputElement | HTMLTextAreaElement,
// ) {
// if (istextarea && textAreaRef.current) {
// } else if (inputRef?.current) {
// inputRef.current.value = getFinalValue(v);
// }
// updateValueFn(v);
// }
const targetComponent = istextarea ? (
<textarea
@@ -265,7 +276,7 @@ export default function Input<KeyType extends string>(
className={twMerge(
"w-full outline-none bg-transparent grow",
"twui-textarea",
props.className
props.className,
)}
ref={textAreaRef}
onFocus={(e) => {
@@ -294,7 +305,7 @@ export default function Input<KeyType extends string>(
"dark:bg-transparent dark:outline-none dark:border-none",
"p-0 grow",
"twui-input",
props.className
props.className,
)}
ref={inputRef}
onFocus={(e) => {
@@ -318,8 +329,8 @@ export default function Input<KeyType extends string>(
title={`${finalLabel}${props.required ? " (Required)" : ""}`}
{...wrapperWrapperProps}
className={twMerge(
"w-full gap-1.5 relative z-0 hover:z-10",
wrapperWrapperProps?.className
"w-full gap-1.5 relative z-0 hover:z-100",
wrapperWrapperProps?.className,
)}
>
<div
@@ -353,7 +364,7 @@ export default function Input<KeyType extends string>(
: "opacity-50 pointer-events-none"
: undefined,
"twui-input-wrapper",
wrapperProps?.className
wrapperProps?.className,
)}
>
{showLabel && (
@@ -365,7 +376,7 @@ export default function Input<KeyType extends string>(
"dark:text-foreground-dark/80 dark:bg-background-dark whitespace-nowrap",
"overflow-hidden overflow-ellipsis z-20 px-1.5 rounded-t-default",
"twui-input-label",
labelProps?.className
labelProps?.className,
)}
>
{finalLabel}
@@ -378,11 +389,7 @@ export default function Input<KeyType extends string>(
</label>
)}
{prefix && (
<div className="opacity-60 pointer-events-none whitespace-nowrap">
{prefix}
</div>
)}
{prefix && prefix}
{targetComponent}
@@ -394,7 +401,7 @@ export default function Input<KeyType extends string>(
"p-1 -my-2 -mx-1 opacity-0 cursor-pointer w-7 h-7",
"bg-background-light dark:bg-background-dark",
"twui-clear-input-field-button",
clearInputProps?.className
clearInputProps?.className,
)}
onClick={(e) => {
e.preventDefault();
@@ -406,7 +413,7 @@ export default function Input<KeyType extends string>(
textAreaRef.current.value = "";
}
updateValue("");
setValue("");
clearInputProps?.onClick?.(e);
}}
>
@@ -439,21 +446,11 @@ export default function Input<KeyType extends string>(
</div>
) : null}
{suffix ? (
<div
{...suffixProps}
className={twMerge(
"opacity-60 pointer-events-none whitespace-nowrap",
suffixProps?.className
)}
>
{suffix}
</div>
) : null}
{suffix ? suffix : null}
{numberText ? (
<NumberInputButtons
updateValue={updateValue}
setValue={setValue}
inputRef={inputRef}
getNormalizedValue={getNormalizedValue}
max={props.max}
@@ -468,18 +465,22 @@ export default function Input<KeyType extends string>(
target={
<Row className="gap-1">
<Info size={12} className="opacity-40" />
<Span size="smaller" className="opacity-70">
<Span
size="smaller"
className="opacity-70 hover:opacity-100"
>
{info}
</Span>
</Row>
}
openDebounce={700}
className="z-1000"
hoverOpen
>
<Paper
className={twMerge(
"min-w-[250px] shadow-lg shadow-slate-200 dark:shadow-white/10",
"max-w-[300px] w-full"
"max-w-[300px] w-full bg-background-light! dark:bg-background-dark! z-1000",
)}
>
<Stack className="gap-2 items-center">