Como trocar o placeholder do input type='file'?!
Profile picture

Junior Alves

Senior Developer

Foto: Unsplash

18 de outubro de 20225 minutos de leitura

Como trocar o placeholder do input type='file'?!

Conheça uma maneira incrível de personalizar seus inputs no React

revista de mockups aberta sobre a mesa

Introdução

Se você já trabalha com desenvolvimento web a um certo tempo, com certeza, em algum momento, já precisou de criar ou utilizar um componente de upload input type='file'.

Em algum momento você quis alterar o placeholder do input type='file' e se deparou com um problema: não tem como de forma nativa, ele não possui nenhuma propriedade para essa finalidade!

Mas tem existe uma maneira e é o que vamos aprender agora!

Pronto, então bora!

Um spoiler

Imagem do resultado final

Esse é o resultado final que nosso componente de Upload! Legal né?!

O componente Upload

src/components/Upload/index.tsx
import { forwardRef, InputHTMLAttributes } from 'react';
import { FiInbox as BoxIcon } from 'react-icons/fi';
 
import PhotoPreview from 'components/PhotoPreview';
 
import * as S from './styles';
 
type Props = {
  accept?: string;
  isRequired?: boolean;
  isDisabled?: boolean;
  file: File | null;
  emptyText?: string;
};
 
export type UploadProps = InputHTMLAttributes<HTMLInputElement> & Props;
 
const Upload: React.ForwardRefRenderFunction<HTMLInputElement, UploadProps> = (
  {
    file,
    accept = '',
    isRequired = false,
    isDisabled = false,
    emptyText = 'Clique ou arreste um arquivo',
    ...rest
  },
  ref
) => {
  const nameImage = file ? file.name : emptyText;
 
  return (
      <S.Container isDisabled={isDisabled}>
        <S.Content hasFile={!!file}>
          <BoxIcon size={32} />
 
          {/* Um título da imagem anexada */}
          <S.Text>{nameImage}</S.Text>
 
          <input
            {...rest}
            accept={accept}
            ref={ref}
            type="file"
            title={file ? `${file.name}` : 'No file selected'} // Para acessibilidade
          />
 
          {/* Thumbnail da imagem anexada */}
          <PhotoPreview src={file ? URL.createObjectURL(file) : ''} />
        </S.Content>
      </S.Container>
  );
};
 
export default forwardRef(Upload);

A estilização

src/components/Upload/styles.ts
import { darken, lighten } from 'polished';
import styled, { css } from 'styled-components';
 
type ContainerProps = {
  isDisabled: boolean;
};
 
type ContentProps = {
  hasFile: boolean;
};
 
export const Container = styled.div<ContainerProps>`
  ${({ theme, isDisabled }) => css`
    width: 100%;
 
    background-color: ${lighten(0.1, '#D9D9D9')};
 
    border: 0.16rem dashed ${lighten(0.6, '#343a40')};
    border-radius: 0.4rem;
 
    display: flex;
    align-items: center;
    justify-content: center;
 
    padding: 1.6rem;
 
    transition: border 0.3s ease-in-out;
 
    position: relative;
 
    input {
      position: absolute;
      right: 0;
      left: 0;
      top: 0;
      bottom: 0;
 
      cursor: pointer;
      width: 100%;
 
      margin: 0 auto;
 
      background-color: #fafafa;
      opacity: 0;
    }
 
    &:hover {
      border-color: ${darken(0.1, '#24A66B')};
    }
 
    ${isDisabled &&
    css`
      cursor: not-allowed;
 
      color: #91aab4;
 
      input {
        pointer-events: none;
      }
 
      ${Content} {
        > svg {
          color: #91aab4;
        }
 
        ${Text} {
          color: #91aab4;
        }
      }
 
      &:hover {
        border-color: ${lighten(0.6, '#343a40')};
      }
    `}
  `}
`;
 
export const Content = styled.div<ContentProps>`
  ${({ theme, hasFile }) => css`
    width: 100%;
 
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
 
    > svg {
      color: #3e606f;
 
      ${hasFile &&
      css`
        color: #24a66b;
      `}
    }
 
    ${Text} {
      ${hasFile &&
      css`
        color: #24a66b;
      `}
    }
  `}
`;
 
export const Text = styled.p`
  ${({ theme }) => css`
    font-size: 1.6rem;
    color: #3e606f;
 
    margin-top: 1.6rem;
  `}
`;

Vamos criar também um component PhotoPreview para quando o usuário anexar alguma imagem no nosso componente de Upload, ele consiga ver uma thumbnail dela.

Imagem do resultado final com preview

O componente PhotoPreview

src/components/PhotoPreview/index.tsx
 
import * as S from './styles';
 
type PhotoPreviewProps = {
  src: string;
};
 
const PhotoPreview = ({ src }: PhotoPreviewProps) => {
  return <S.Container>{src && <S.ImageContainer src={src} />}</S.Container>;
};
 
export default PhotoPreview;

Os estilos PhotoPreview

src/components/PhotoPreview/styles.ts
import styled, { css } from 'styled-components';
 
export const Container = styled.div`
  ${({ theme }) => css`
    margin-top: 1rem;
 
    cursor: pointer;
  `}
`;
 
export const ImageContainer = styled.img`
  ${({ theme }) => css`
    border-radius: 0.4rem;
    object-fit: cover;
 
    width: 100%;
    height: 20rem;
 
    border: 1px solid #91aab4;
 
    overflow: hidden;
  `}
`;

Utilizando nosso componente

Para utilizar nosso componente, precisamos apenas nos lembra de um detalhe: é um componente de input, portanto precisamos de um form por volta dele.

const [imagePreview, setImagePreview] = useState<File | null>(null);
 
const handleUpload = (event: ChangeEvent<HTMLInputElement>) => {
    const files = event.target.files;
 
    if (!files?.length) {
      setImagePreview(null);
 
      return;
    }
 
    setImagePreview(files[0]);
};
 
return (
    <form>
        <Upload
            file={imagePreview} // O arquivo anexado
            onChange={handleUpload} // Função para alterar o state do arquivo
            isRequired
        />
    </form>
)

Observações finais

Como você deve ter percebido, a utilização do componente é bem simples, porém, da forma que está, em cada formulário que precisarmos desse componente, vamos ter que ter um estado imagePreview e uma função handleUpload, certo?

O que podemos fazer nesse caso é criar um custom hook para não precisamos ficar copiando e colando esse código em todos os lugares. #FicaADica

Conclusão

O objetivo desse post era trazer uma forma de estilizar e personalizar um componente básico que é o input type='file', mas fazer isso de uma forma diferente, usando a criatividade para pensar fora da caixa.

Conseguimos criar nosso próprio componente de upload utilizando conceitos básicos de CSS e do React. O que quero que perceba é que, entendendo os conceitos básicos, já é possível criar muitas coisas interessantes, basta usar a criatividade.

Espero que tenha gostado, muito obrigado por ler até aqui! Valeu e até a próxima!

Curtiu? Compartilhe esse post:

Todos os direitos reseverdos © Junior Alves 2024