1import {
2 chakra,
3 FormControl,
4 FormErrorMessage,
5 FormLabel,
6 Input as ChakraInput,
7 InputGroup,
8 InputLeftAddon,
9 InputProps as ChakraInputProps,
10 InputRightAddon,
11 InputRightElement,
12 NumberDecrementStepper,
13 NumberIncrementStepper,
14 NumberInput,
15 NumberInputField,
16 NumberInputStepper
17} from '@chakra-ui/react';
18import { XMarkIcon } from '@heroicons/react/24/outline';
19import classNames from 'classnames';
20import React, { PropsWithChildren, ReactNode } from 'react';
21
22const ClearIcon = chakra(XMarkIcon, {
23 baseStyle: {
24 w: 4,
25 h: 4,
26 },
27});
28
29export type InputProps = PropsWithChildren<
30 {
31 value?: string;
32 className?: string;
33 endAdornment?: ReactNode;
34 startAdornment?: ReactNode;
35 type?: string;
36 placeholder?: string;
37 onChange?: (e: any) => void;
38 onBlur?: (e: any) => void;
39 onClick?: (e: any) => void;
40 name?: string;
41 error?: string;
42 disabled?: boolean;
43 step?: number;
44 label?: string;
45 clearable?: boolean;
46 } & ChakraInputProps
47>;
48
49export const Input = React.forwardRef<HTMLInputElement, InputProps>(
50 (
51 {
52 disabled,
53 step,
54 label,
55 className,
56 startAdornment,
57 endAdornment,
58 type = 'text',
59 placeholder,
60 onChange,
61 onBlur,
62 name,
63 value,
64 onClick,
65 error,
66 clearable = false,
67 ...props
68 },
69 ref
70 ) => {
71 const clear = () => {
72 if (onChange)
73 onChange({
74 target: {
75 value: '',
76 name,
77 },
78 });
79 };
80 const { size = 'md' } = props;
81 const Component = type == 'number' ? NumberInputField : ChakraInput;
82 const Wrapper = type == 'number' ? NumberInput : React.Fragment;
83 const wrapperProps =
84 type == 'number'
85 ? {
86 keepWithinRange: true,
87 precision: 5,
88 format: (value: string | number) => {
89 return isNaN(parseFloat(String(value)))
90 ? value
91 : Number(parseFloat(String(value)).toFixed(5)) === 0
92 ? value
93 : Number(parseFloat(String(value)).toFixed(5));
94 },
95 min: 0,
96 step,
97 name,
98 type,
99 placeholder,
100 onChange: (v: any) => {
101 if (onChange) onChange(v);
102 },
103 onBlur,
104 value,
105 onClick,
106 disabled,
107 flexGrow: 1,
108 size,
109 }
110 : {};
111 return (
112 <FormControl isInvalid={!!error}>
113 {label && <FormLabel>{label}</FormLabel>}
114 <InputGroup
115 size={size}
116 w="full"
117 rounded="md"
118 _focusWithin={{
119 outline: '2px solid',
120 outlineColor: 'primary.200',
121 }}
122 bg={disabled ? 'gray.100' : 'transparent'}
123 _dark={{ bg: disabled ? 'gray.600' : 'transparent' }}
124 >
125 {startAdornment && <InputLeftAddon>{startAdornment}</InputLeftAddon>}
126 <Wrapper {...wrapperProps}>
127 {/* @ts-ignore */}
128 <Component
129 name={name}
130 ref={ref}
131 step={step}
132 className={classNames(className)}
133 type={type}
134 placeholder={placeholder}
135 onChange={onChange}
136 onBlur={onBlur}
137 value={value}
138 onClick={onClick}
139 disabled={disabled}
140 flexGrow={1}
141 _focusVisible={{
142 outline: 'none',
143 borderTopColor: 'transparent',
144 borderRightColor: 'transparent',
145 borderBottomColor: 'transparent',
146 }}
147 _disabled={{
148 cursor: 'not-allowed',
149 }}
150 {...props}
151 roundedLeft={startAdornment ? '0' : 'md'}
152 roundedRight={endAdornment ? '0' : 'md'}
153 />
154 {type == 'number' && (
155 <>
156 <NumberInputStepper>
157 <NumberIncrementStepper />
158 <NumberDecrementStepper />
159 </NumberInputStepper>
160 </>
161 )}
162 </Wrapper>
163 {endAdornment && (
164 <InputRightAddon
165 borderLeftRadius={0}
166 borderRightRadius="6px"
167 bg="transparent"
168 >
169 {endAdornment}
170 </InputRightAddon>
171 )}
172 {clearable && value && value.length && (
173 <InputRightElement
174 borderLeftRadius={0}
175 borderRightRadius="6px"
176 bg="transparent"
177 onClick={clear}
178 cursor="pointer"
179 >
180 <ClearIcon />
181 </InputRightElement>
182 )}
183 </InputGroup>
184 {!!error && <FormErrorMessage>{error}</FormErrorMessage>}
185 </FormControl>
186 );
187 }
188);