mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-08 05:29:51 +00:00
274 lines
7.8 KiB
TypeScript
274 lines
7.8 KiB
TypeScript
import { type ChangeEvent, forwardRef, useImperativeHandle, useMemo, useRef } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import {
|
|
ArrowDownOutlined as ArrowDownOutlinedIcon,
|
|
ArrowUpOutlined as ArrowUpOutlinedIcon,
|
|
MinusOutlined as MinusOutlinedIcon,
|
|
PlusOutlined as PlusOutlinedIcon,
|
|
} from "@ant-design/icons";
|
|
import { useControllableValue } from "ahooks";
|
|
import { Button, Input, type InputProps, type InputRef, Space } from "antd";
|
|
import { produce } from "immer";
|
|
|
|
export type MultipleInputProps = Omit<InputProps, "count" | "defaultValue" | "showCount" | "value" | "onChange" | "onPressEnter" | "onClear"> & {
|
|
allowClear?: boolean;
|
|
defaultValue?: string[];
|
|
maxCount?: number;
|
|
minCount?: number;
|
|
showSortButton?: boolean;
|
|
value?: string[];
|
|
onChange?: (value: string[]) => void;
|
|
onValueChange?: (index: number, element: string) => void;
|
|
onValueCreate?: (index: number) => void;
|
|
onValueRemove?: (index: number) => void;
|
|
onValueSort?: (oldIndex: number, newIndex: number) => void;
|
|
};
|
|
|
|
const MultipleInput = ({
|
|
allowClear = false,
|
|
disabled,
|
|
maxCount,
|
|
minCount,
|
|
showSortButton = true,
|
|
onValueChange,
|
|
onValueCreate,
|
|
onValueSort,
|
|
onValueRemove,
|
|
...props
|
|
}: MultipleInputProps) => {
|
|
const { t } = useTranslation();
|
|
|
|
const itemRefs = useRef<MultipleInputItemInstance[]>([]);
|
|
|
|
const [value, setValue] = useControllableValue<string[]>(props, {
|
|
valuePropName: "value",
|
|
defaultValue: [],
|
|
defaultValuePropName: "defaultValue",
|
|
trigger: "onChange",
|
|
});
|
|
|
|
const handleCreate = () => {
|
|
const newValue = produce(value, (draft) => {
|
|
draft.push("");
|
|
});
|
|
setValue(newValue);
|
|
setTimeout(() => itemRefs.current[newValue.length - 1]?.focus(), 0);
|
|
|
|
onValueCreate?.(newValue.length - 1);
|
|
};
|
|
|
|
const handleChange = (index: number, element: string) => {
|
|
const newValue = produce(value, (draft) => {
|
|
draft[index] = element;
|
|
});
|
|
setValue(newValue);
|
|
|
|
onValueChange?.(index, element);
|
|
};
|
|
|
|
const handleInputBlur = (index: number) => {
|
|
if (!allowClear && !value[index]) {
|
|
const newValue = produce(value, (draft) => {
|
|
draft.splice(index, 1);
|
|
});
|
|
setValue(newValue);
|
|
}
|
|
};
|
|
|
|
const handleClickUp = (index: number) => {
|
|
if (index === 0) {
|
|
return;
|
|
}
|
|
|
|
const newValue = produce(value, (draft) => {
|
|
const temp = draft[index - 1];
|
|
draft[index - 1] = draft[index];
|
|
draft[index] = temp;
|
|
});
|
|
setValue(newValue);
|
|
|
|
onValueSort?.(index, index - 1);
|
|
};
|
|
|
|
const handleClickDown = (index: number) => {
|
|
if (index === value.length - 1) {
|
|
return;
|
|
}
|
|
|
|
const newValue = produce(value, (draft) => {
|
|
const temp = draft[index + 1];
|
|
draft[index + 1] = draft[index];
|
|
draft[index] = temp;
|
|
});
|
|
setValue(newValue);
|
|
|
|
onValueSort?.(index, index + 1);
|
|
};
|
|
|
|
const handleClickAdd = (index: number) => {
|
|
const newValue = produce(value, (draft) => {
|
|
draft.splice(index + 1, 0, "");
|
|
});
|
|
setValue(newValue);
|
|
setTimeout(() => itemRefs.current[index + 1]?.focus(), 0);
|
|
|
|
onValueCreate?.(index + 1);
|
|
};
|
|
|
|
const handleClickRemove = (index: number) => {
|
|
const newValue = produce(value, (draft) => {
|
|
draft.splice(index, 1);
|
|
});
|
|
setValue(newValue);
|
|
|
|
onValueRemove?.(index);
|
|
};
|
|
|
|
return value == null || value.length === 0 ? (
|
|
<Button block color="primary" disabled={disabled || maxCount === 0} size={props.size} variant="dashed" onClick={handleCreate}>
|
|
{t("common.button.add")}
|
|
</Button>
|
|
) : (
|
|
<Space className="w-full" direction="vertical" size="small">
|
|
{Array.from(value).map((element, index) => {
|
|
const allowUp = index > 0;
|
|
const allowDown = index < value.length - 1;
|
|
const allowRemove = minCount == null || value.length > minCount;
|
|
const allowAdd = maxCount == null || value.length < maxCount;
|
|
|
|
return (
|
|
<MultipleInputItem
|
|
{...props}
|
|
key={index}
|
|
ref={(ref) => (itemRefs.current[index] = ref!)}
|
|
allowAdd={allowAdd}
|
|
allowClear={allowClear}
|
|
allowDown={allowDown}
|
|
allowRemove={allowRemove}
|
|
allowUp={allowUp}
|
|
disabled={disabled}
|
|
defaultValue={undefined}
|
|
showSortButton={showSortButton}
|
|
value={element}
|
|
onBlur={() => handleInputBlur(index)}
|
|
onChange={(val) => handleChange(index, val)}
|
|
onClickAdd={() => handleClickAdd(index)}
|
|
onClickDown={() => handleClickDown(index)}
|
|
onClickUp={() => handleClickUp(index)}
|
|
onClickRemove={() => handleClickRemove(index)}
|
|
/>
|
|
);
|
|
})}
|
|
</Space>
|
|
);
|
|
};
|
|
|
|
type MultipleInputItemProps = Omit<
|
|
MultipleInputProps,
|
|
"defaultValue" | "maxCount" | "minCount" | "preset" | "value" | "onChange" | "onValueCreate" | "onValueRemove" | "onValueSort" | "onValueChange"
|
|
> & {
|
|
allowAdd: boolean;
|
|
allowRemove: boolean;
|
|
allowUp: boolean;
|
|
allowDown: boolean;
|
|
defaultValue?: string;
|
|
value?: string;
|
|
onChange?: (value: string) => void;
|
|
onClickAdd?: () => void;
|
|
onClickDown?: () => void;
|
|
onClickUp?: () => void;
|
|
onClickRemove?: () => void;
|
|
};
|
|
|
|
type MultipleInputItemInstance = {
|
|
focus: InputRef["focus"];
|
|
blur: InputRef["blur"];
|
|
select: InputRef["select"];
|
|
};
|
|
|
|
const MultipleInputItem = forwardRef<MultipleInputItemInstance, MultipleInputItemProps>(
|
|
(
|
|
{
|
|
allowAdd,
|
|
allowClear,
|
|
allowDown,
|
|
allowRemove,
|
|
allowUp,
|
|
disabled,
|
|
showSortButton,
|
|
size,
|
|
onClickAdd,
|
|
onClickDown,
|
|
onClickUp,
|
|
onClickRemove,
|
|
...props
|
|
}: MultipleInputItemProps,
|
|
ref
|
|
) => {
|
|
const inputRef = useRef<InputRef>(null);
|
|
|
|
const [value, setValue] = useControllableValue<string>(props, {
|
|
valuePropName: "value",
|
|
defaultValue: "",
|
|
defaultValuePropName: "defaultValue",
|
|
trigger: "onChange",
|
|
});
|
|
|
|
const upBtn = useMemo(() => {
|
|
if (!showSortButton) return null;
|
|
return <Button icon={<ArrowUpOutlinedIcon />} color="default" disabled={disabled || !allowUp} type="text" onClick={onClickUp} />;
|
|
}, [allowUp, disabled, showSortButton, onClickUp]);
|
|
const downBtn = useMemo(() => {
|
|
if (!showSortButton) return null;
|
|
return <Button icon={<ArrowDownOutlinedIcon />} color="default" disabled={disabled || !allowDown} type="text" onClick={onClickDown} />;
|
|
}, [allowDown, disabled, showSortButton, onClickDown]);
|
|
const removeBtn = useMemo(() => {
|
|
return <Button icon={<MinusOutlinedIcon />} color="default" disabled={disabled || !allowRemove} type="text" onClick={onClickRemove} />;
|
|
}, [allowRemove, disabled, onClickRemove]);
|
|
const addBtn = useMemo(() => {
|
|
return <Button icon={<PlusOutlinedIcon />} color="default" disabled={disabled || !allowAdd} type="text" onClick={onClickAdd} />;
|
|
}, [allowAdd, disabled, onClickAdd]);
|
|
|
|
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
setValue(e.target.value);
|
|
};
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
focus: (options) => {
|
|
inputRef.current?.focus(options);
|
|
},
|
|
blur: () => {
|
|
inputRef.current?.blur();
|
|
},
|
|
select: () => {
|
|
inputRef.current?.select();
|
|
},
|
|
}));
|
|
|
|
return (
|
|
<div className="flex flex-nowrap items-center space-x-2">
|
|
<div className="flex-grow">
|
|
<Input
|
|
{...props}
|
|
ref={inputRef}
|
|
className={undefined}
|
|
style={undefined}
|
|
allowClear={allowClear}
|
|
defaultValue={undefined}
|
|
value={value}
|
|
onChange={handleInputChange}
|
|
/>
|
|
</div>
|
|
<Button.Group size={size}>
|
|
{removeBtn}
|
|
{upBtn}
|
|
{downBtn}
|
|
{addBtn}
|
|
</Button.Group>
|
|
</div>
|
|
);
|
|
}
|
|
);
|
|
|
|
export default MultipleInput;
|