Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Button] Upload file input type #9716

Closed
prakharrke opened this issue Jan 3, 2018 · 19 comments
Closed

[Button] Upload file input type #9716

prakharrke opened this issue Jan 3, 2018 · 19 comments
Labels
component: button This is the name of the generic UI component, not the React module! component: upload This is the name of the generic UI component, not the React module! new feature New feature or request

Comments

@prakharrke
Copy link

Is there a component for file input type which allows us to upload images?

@oliviertassinari oliviertassinari added the support: question Community support but can be turned into an improvement label Jan 3, 2018
@oliviertassinari
Copy link
Member

oliviertassinari commented Jan 3, 2018

So far, we have a demo: https://v4.mui.com/components/buttons/#upload-button

<input
accept="image/*"
className={classes.input}
id="contained-button-file"
multiple
type="file"
/>
<label htmlFor="contained-button-file">
<Button variant="contained" component="span">
Upload
</Button>
</label>

However, mind this issue: #22141.

@nickperkinslondon
Copy link

nickperkinslondon commented Mar 5, 2020

Is there a component for file input type which allows us to upload images?

...so I guess the answer is "no"

@mbrookes
Copy link
Member

mbrookes commented Mar 5, 2020

I guess the answer is "no"

Dropzone is in the roadmap: https://github.com/mui/material-ui/issues/22434.

@dandv
Copy link
Contributor

dandv commented Jun 3, 2020

There's a 3rd party component: https://github.com/Yuvaleros/material-ui-dropzone.

@munkacsimark
Copy link

...so I guess the answer is "no". ( and that code doesn't work for me )

Make sure Button has component="span", that helped me.

@andykao1213
Copy link

Can anyone explains why we should set component to span ?
I found that this can work with div main ... , but it cannot work with button (which is the default element).
Is there any specification for why button element behave so different with other elements?
(I know this is not related to Material UI, but I it would be awesome if someone can explain to this. Thanks 🙏 )

@oliviertassinari oliviertassinari added the component: upload This is the name of the generic UI component, not the React module! label Feb 7, 2021
@oliviertassinari
Copy link
Member

@andykao1213 I imagine there are two issues with input > button. It's not a valid HTML markup and it prevents the label to handle the click event.

@narkai
Copy link

narkai commented Mar 2, 2021

Hello, I think I came to a satisfying solution 👍

When designing forms, the TextField component is used a lot because it has a label, an error helper text which is very useful for validation, it can be multiline, it's compatible with <select>...

Why not using it for file input ?

Therefore, here is an example component that retains all the benefits, at least for me.

import { useRef, useState } from "react";
import { makeStyles, useTheme } from "@material-ui/core/styles";
import Box from "@material-ui/core/Box";
import TextField from "@material-ui/core/TextField";
import ButtonBase from "@material-ui/core/ButtonBase";

const FileInput = ({ label, onChange, error }) => {
  const ref = useRef();
  const theme = useTheme();
  const classes = useStyles();
  const [attachment, setAttachment] = useState();

  const handleChange = (event) => {
    const files = Array.from(event.target.files);
    const [file] = files;
    setAttachment(file);
    if (!!onChange) onChange({ target: { value: file } });
  };

  return (
    <Box
      position="relative"
      height={98}
      color={
        !!error ? theme.palette.error.main : theme.palette.background.paper
      }
      borderBottom={4}
    >
      <Box position="absolute" top={0} bottom={0} left={0} right={0} mx={2}>
        <TextField
          className={classes.field}
          InputProps={{ disableUnderline: true }}
          margin="normal"
          fullWidth
          disabled
          label={label}
          value={attachment?.name || ""}
          error={!!error}
          helperText={error?.message || " "}
        />
      </Box>
      <ButtonBase
        className={classes.button}
        component="label"
        onKeyDown={(e) => e.keyCode === 32 && ref.current?.click()}
      >
        <input
          ref={ref}
          type="file"
          accept="image/*"
          hidden
          onChange={handleChange}
        />
      </ButtonBase>
    </Box>
  );
};

const useStyles = makeStyles((theme) => ({
  field: {
    "& .MuiFormLabel-root.Mui-disabled": {
      color: theme.palette.text.secondary,
    },
  },
  button: {
    width: "100%",
    height: "100%",
    overflow: "hidden",
  },
}));

export default FileInput;

The TextField component is used for datas display only (disabled & to be styled as needed), a ButtonBase component is used for input and triggering.

It would be easy to adapt it for multiple files with a multiline TextField...

IMO this approach allows for a good separation of concerns and makes me think of TextField as a good direction for a file input component...

@narkai
Copy link

narkai commented Mar 2, 2021

Here is codesandbox to see the result : https://codesandbox.io/s/react-mui-file-input-cfsno

@kdany25
Copy link

kdany25 commented Jan 12, 2022

Thank you @narkai

@somahargitai
Copy link

Here is codesandbox to see the result : https://codesandbox.io/s/react-mui-file-input-cfsno

It fails with

Property 'onChange' is missing in type '{ label: string; error: { message: string; }; }' but required in type '{ label: any; onChange: any; error: any; }'.ts(2741)

@bryanltobing
Copy link

if we set the component props in <Button/> to span. the button will lose the keyboard navigation functionality

@dlasagno
Copy link

I'm posting the solution I came up with after a bit of research:

import Button from '@mui/material/Button';
import InputAdornment from '@mui/material/InputAdornment';
import TextField from '@mui/material/TextField';
import { useRef } from 'react';

export default function FileInlnput() {
  const inputRef = useRef<HTMLInputElement>(null);

  return (
    <TextField
      type="file"
      inputRef={inputRef}
      InputProps={{
        startAdornment: (
          <InputAdornment position="start">
            <Button
              variant="outlined"
              size="small"
              onClick={(e) =>
                inputRef.current?.dispatchEvent(
                  new MouseEvent('click', {
                    view: window,
                    bubbles: true,
                    cancelable: true,
                    buttons: 1,
                  }),
                )
              }
            >
              Choose File
            </Button>
          </InputAdornment>
        ),
      }}
      sx={{
        '& input::file-selector-button': {
          display: 'none',
        },
      }}
    />
  );
}

This solution retains the ability to drag an drop the files and uses TextField as the input.
I haven't tested this extensively, so I don't know about its shortcomings.

@viclafouch
Copy link
Contributor

Or there is a library for this :

https://viclafouch.github.io/mui-file-input/ (React 18 / MUI V5)

import React from 'react'
import { MuiFileInput } from 'mui-file-input'

const MyComponent = () => {
  const [value, setValue] = React.useState(null)

  const handleChange = (newValue) => {
    setValue(newValue)
  }

  return <MuiFileInput value={value} onChange={handleChange} />
}

@ruano-a
Copy link

ruano-a commented Dec 3, 2022

Or there is a library for this :

https://viclafouch.github.io/mui-file-input/ (React 18 / MUI V5)

import React from 'react'
import { MuiFileInput } from 'mui-file-input'

const MyComponent = () => {
  const [value, setValue] = React.useState(null)

  const handleChange = (newValue) => {
    setValue(newValue)
  }

  return <MuiFileInput value={value} onChange={handleChange} />
}

Doesn't seem usable in current state with react hook form (which is quite an issue). It's lacking a reference forward (to the inner input I'd assume?). I don't see either a way to restrict the file types, which is kinda a big deal (maybe I'm blind though).

@RafaelAugustScherer
Copy link

Or there is a library for this :

https://viclafouch.github.io/mui-file-input/ (React 18 / MUI V5)

import React from 'react'
import { MuiFileInput } from 'mui-file-input'

const MyComponent = () => {
  const [value, setValue] = React.useState(null)

  const handleChange = (newValue) => {
    setValue(newValue)
  }

  return <MuiFileInput value={value} onChange={handleChange} />
}

The support for multiple files selection is not implemented very well... there is no accept= option for limiting file extensions and for some reason it doesn't set the value of the input component, so the required form validation doesn't work properly.
Would be awesome if MUI implements this natively showing the selected file name, not just a simple button.

@RafaelAugustScherer
Copy link

RafaelAugustScherer commented Dec 14, 2022

As of end of 2022 I've made a Multiple File Input based on @narkai 's example in TypeScript.
It still isn't perfect considering it opens to "type" whenever the required form validation fails. But I guess it is good enough.

Live Code

import React, { useRef } from 'react';
import { Box, Button, InputAdornment, TextField } from '@mui/material';
import { AttachFile } from '@mui/icons-material';

interface spreadSheetSelectProps {
  files: File[] | null;
  setFiles: React.Dispatch<React.SetStateAction<File[] | null>>;
}

export const SpreadsheetSelect: React.FC<spreadSheetSelectProps> = (
  { files, setFiles }
) => {
  // const [files, setFiles] = useState<File[] | null>(null);
  const ref = useRef<HTMLInputElement>(null);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (!e.target.files) return;

    const files = Array.from(e.target.files);
    setFiles(files);
  };

  const getFileNames = () => (
    files?.reduce(
      (fileNames, file) => `${fileNames} ${fileNames !== '' ? ',' : ''} ${file.name}`, ''
    )
    || ''
  );

  return (
    <Box
      position="relative"
      height={57}
      width="100%"
    >
      <Box position="absolute" width="100%">
        <TextField
          fullWidth
          label="Select a spreadsheet"
          value={getFileNames()}
          required
          sx={{ pointerEvents: 'none' }}
          InputProps={{
            endAdornment: (
              <InputAdornment position="end">
                <AttachFile />
              </InputAdornment>
            )
          }}
        />
      </Box>
      <Button
        component="label"
        onKeyDown={(e) => e.key === '32' && ref.current?.click()}
        fullWidth
        sx={{ height: '100%' }}
      >
        <input
          ref={ref}
          type="file"
          onChange={handleChange}
          accept="application/vnd.ms-excel, .xlsx, .xls, .csv"
          hidden
          multiple
        />
      </Button>
    </Box>
  );
};

export default SpreadsheetSelect;

@hyeon21
Copy link

hyeon21 commented Sep 2, 2023

Hi! This is my example

const [file, setFile] = useState();
const [fileName, setFileName] = useState('');

const changeFile = (e) => {
  const file = e.target.files[0];
  setFileName(file.name);
  setFile(file);
}

<TextField value={fileName} label="File Name" />
<Button variant="contained" component="label">
  Upload File
  <input type="file" name="file" onChange={changeFile} hidden />
</Button>

@oliviertassinari oliviertassinari added the component: button This is the name of the generic UI component, not the React module! label Sep 2, 2023
@oliviertassinari oliviertassinari changed the title Is there a component for file input type? [Button] Add simple file input type demo Sep 2, 2023
@oliviertassinari oliviertassinari added new feature New feature or request and removed support: question Community support but can be turned into an improvement labels Sep 2, 2023
@oliviertassinari oliviertassinari changed the title [Button] Add simple file input type demo [Button] Upload file input type Sep 2, 2023
@oliviertassinari
Copy link
Member

oliviertassinari commented Sep 2, 2023

Joy UI has a demo with it: https://mui.com/joy-ui/react-button/#file-upload. I created #38766, I think we can add the exact same one in Material UI. I don't see why it should be any different.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: button This is the name of the generic UI component, not the React module! component: upload This is the name of the generic UI component, not the React module! new feature New feature or request
Projects
None yet
Development

No branches or pull requests