gitscope
← marzneshin / v0.1.0 / app/dashboard/src/components/input/index.tsx
https://dev.kronoz.win/r/marzneshin.git
app/dashboard/src/components/input/index.tsx · TypeScript
Blame Raw
  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);