Vizzuality/landgriffon

View on GitHub
marketing/src/components/forms/input/component.tsx

Summary

Maintainability
A
0 mins
Test Coverage
import React from 'react';
import Hint from '../hint';
import classnames from 'classnames';

const THEMES = {
  default: {
    base: 'appearance-none block w-full px-3 py-2 border rounded-md shadow-sm text-gray-900 placeholder-gray-500 focus:outline-none text-sm',
    icon: 'absolute w-5 h-5 text-gray-400 transform -translate-y-1/2 top-1/2 left-3',
    unit: 'absolute right-3 text-sm text-gray-500',
  },
  'inline-primary': {
    base: 'appearance-none w-full text-sm text-center text-green-500 font-bold border-0 border-b-2 border-green-500 px-0 py-0 focus:outline-none focus:border-green-500 focus:ring-0',
    icon: 'absolute w-4 h-4 text-green-500 transform -translate-y-1/2 top-1/2 left-0',
    unit: 'absolute right-0 text-sm font-bold text-green-500',
  },
};

type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
  theme?: 'default' | 'inline-primary';
  className?: string;
  type?: string;
  // https://github.com/tailwindlabs/heroicons/issues/64#issuecomment-859168741
  icon?: (props: React.ComponentProps<'svg'>) => JSX.Element;
  unit?: string;
  error?: string | boolean;
  showHint?: boolean;
};

const Input = React.forwardRef<HTMLInputElement, InputProps>(
  (
    { className, type = 'text', theme = 'default', icon, unit, error, showHint = true, ...props },
    ref,
  ) => {
    const Icon = icon;

    return (
      <div className={classnames('relative mt-1', className)}>
        <div className="flex items-center">
          {icon && <Icon className={THEMES[theme].icon} />}
          <input
            className={classnames([THEMES[theme].base], {
              // The Checkbox component should be used instead, but just in case.
              'px-0': type === 'checkbox',
              'bg-gray-400 h-1 appearance-none': type === 'range',
              'pl-10': !!icon && theme !== 'inline-primary',
              'pl-3': !!icon && theme === 'inline-primary',
              'border-gray-200 focus:ring-green-500 focus:border-green-500':
                theme === 'default' && !error,
              'border-red-600 focus:ring-red-600 focus:border-red-600': !!error,
              'pr-8': type === 'search',
            })}
            type={type}
            ref={ref}
            // We need to make space for units. This is far from ideal, should be able to
            // approximate a padding based on the unit's character length to a good degree.
            // ~10px per character, + 14px for extra right padding.
            // If the input is the 'search' type, we won't be displaying units, and we'll
            // already be giving a right padding via TailwindCSS classes in order to make
            // room for the "clear search" button.
            style={{
              marginRight:
                type !== 'search' &&
                unit &&
                unit?.length * 10 + (theme === 'inline-primary' ? 2 : 14),
            }}
            {...props}
          />
          {unit && <span className={THEMES[theme].unit}>{unit}</span>}
        </div>
        {error && showHint && <Hint>{error}</Hint>}
      </div>
    );
  },
);

Input.displayName = 'Input';

export default Input;