index.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import * as React from "react"
  2. import { cname } from "../utils"
  3. import { SearchIcon } from "../../bs-icons/search"
  4. import { generateUUID } from "../utils"
  5. import { MinusCircledIcon } from "@radix-ui/react-icons"
  6. import { EyeOpenIcon, EyeNoneIcon } from "@radix-ui/react-icons"
  7. import { useState } from "react"
  8. import sousuo from "../../../assets/npc/sousuo.png"
  9. export interface InputProps
  10. extends React.InputHTMLAttributes<HTMLInputElement> { }
  11. const Input = React.forwardRef<HTMLInputElement, InputProps>(
  12. ({ className, type, ...props }, ref) => {
  13. return (
  14. <input
  15. type={type}
  16. className={cname(
  17. "flex h-9 w-full rounded-md border-input bg-[#FAFBFC] px-3 py-1 text-sm text-[#111] shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-[#666] focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 ",
  18. className
  19. )}
  20. ref={ref}
  21. {...props}
  22. />
  23. )
  24. }
  25. )
  26. Input.displayName = "Input"
  27. const SearchInput = React.forwardRef<HTMLInputElement, InputProps & { inputClassName?: string, iconClassName?: string }>(
  28. ({ className, inputClassName, iconClassName, ...props }, ref) => {
  29. return <div className={cname("relative", className)}>
  30. {/* <SearchIcon className={cname("h-5 w-5 absolute left-2 top-2 text-[#666666]", iconClassName)} /> */}
  31. <div className="absolute w-[40px] h-[100%] flex items-center">
  32. <img src={sousuo} alt="" className="w-[14px] absolute left-[14px]" />
  33. </div>
  34. <Input type="text" ref={ref} className={cname("w-[244px] h-[34px] pl-[40px] npcInput2", inputClassName)} {...props}></Input>
  35. </div>
  36. }
  37. )
  38. SearchInput.displayName = "SearchInput"
  39. const PasswordInput = React.forwardRef<HTMLInputElement, InputProps & { inputClassName?: string, iconClassName?: string }>(
  40. ({ className, inputClassName, iconClassName, ...props }, ref) => {
  41. const [type, setType] = useState('password')
  42. const handleShowPwd = () => {
  43. type === 'password' ? setType('text') : setType('password')
  44. }
  45. return <div className={cname("relative flex place-items-center", className)}>
  46. <Input type={type} ref={ref} autocomplete="new-password" className={cname("pr-8 bg-[#1a1a1a] text-[#fff] ", inputClassName)} {...props}></Input>
  47. {
  48. type === 'password'
  49. ? <EyeNoneIcon onClick={handleShowPwd} className={cname("absolute right-2 text-[#fff] dark:text-[#fff] cursor-pointer", iconClassName)}/>
  50. : <EyeOpenIcon onClick={handleShowPwd} className={cname("absolute right-2 text-[#fff] dark:text-[#fff] cursor-pointer", iconClassName)}/>
  51. }
  52. </div>
  53. }
  54. )
  55. PasswordInput.displayName = 'PasswordInput'
  56. /**
  57. * 多行文本
  58. */
  59. export interface TextareaProps
  60. extends React.TextareaHTMLAttributes<HTMLTextAreaElement> { }
  61. const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
  62. ({ className, ...props }, ref) => {
  63. return (
  64. <textarea
  65. className={cname(
  66. "flex min-h-[80px] w-full rounded-md border border-input bg-[#FAFBFC] px-3 py-2 text-sm text-[#111] shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
  67. className
  68. )}
  69. ref={ref}
  70. {...props}
  71. />
  72. )
  73. }
  74. )
  75. Textarea.displayName = "Textarea"
  76. /**
  77. * input list
  78. */
  79. const InputList = React.forwardRef<HTMLDivElement, InputProps & {
  80. rules: any[],
  81. value?: string[],
  82. inputClassName?: string,
  83. defaultValue?: string[],
  84. onChange?: (values: string[]) => void
  85. }>(
  86. ({ rules, className, inputClassName, value = [], defaultValue = [], ...props }, ref) => {
  87. // 初始化 inputs 状态,为每个值分配唯一 ID
  88. const [inputs, setInputs] = React.useState(() =>
  89. value.map(val => ({ id: generateUUID(8), value: val }))
  90. );
  91. React.useEffect(() => {
  92. // 仅为新增的值分配新的 ID
  93. const updatedInputs = value.map((val, index) => {
  94. return inputs[index] && inputs[index].value === val
  95. ? inputs[index] // 如果当前输入项与外部值相同,则保持不变
  96. : { id: generateUUID(8), value: val }; // 否则,创建新的输入项
  97. });
  98. setInputs(updatedInputs);
  99. }, [value]); // 依赖项中包含 value,确保外部 value 更新时同步更新
  100. const handleChange = (newValue, id, index) => {
  101. let newInputs = inputs.map(input =>
  102. input.id === id ? { ...input, value: newValue } : input
  103. );
  104. // push
  105. if (index === newInputs.length - 1) {
  106. newInputs = ([...newInputs, { id: generateUUID(8), value: '' }]);
  107. }
  108. setInputs(newInputs);
  109. props.onChange(newInputs.map(input => input.value));
  110. };
  111. // delete input
  112. const handleRemoveInput = (id) => {
  113. const newInputs = inputs.filter(input => input.id !== id);
  114. setInputs(newInputs);
  115. props.onChange(newInputs.map(input => input.value));
  116. };
  117. return <div className={cname('', className)}>
  118. {
  119. inputs.map((item, index) => (
  120. <div className="relative mt-2">
  121. <Input
  122. key={item.id}
  123. defaultValue={item.value}
  124. className={cname('pr-8 npcInput2', inputClassName)}
  125. placeholder={props.placeholder || ''}
  126. onChange={(e) => handleChange(e.target.value, item.id, index)}
  127. onInput={(e) => {
  128. rules.some(rule => {
  129. if (rule.maxLength && e.target.value.length > rule.maxLength) {
  130. e.target.nextSibling.textContent = rule.message;
  131. e.target.nextSibling.style.display = '';
  132. return true;
  133. }
  134. e.target.nextSibling.style.display = 'none';
  135. })
  136. }}
  137. // onFocus={(e) => {
  138. // if (e.target.value && index === inputs.length - 1) {
  139. // setInputs([...inputs, { id: generateUUID(8), value: '' }]);
  140. // }
  141. // }}
  142. ></Input>
  143. <p className="text-sm text-red-500" style={{ display: 'none' }}></p>
  144. {index !== inputs.length - 1 && <MinusCircledIcon onClick={(e) => {
  145. e.target.previousSibling.style.display = 'none';
  146. handleRemoveInput(item.id)
  147. }} className="absolute top-2.5 right-2 text-gray-500 hover:text-gray-700 cursor-pointer" />}
  148. </div>
  149. ))
  150. }
  151. </div>
  152. }
  153. )
  154. export { Input, SearchInput, PasswordInput, Textarea, InputList }