React - jss nested Sass-like styling is not working - reactjs

I have an application where I am using Material-UI v.3 as a UI framework. I have a label with button that has display: none as the default state, that I would like to show on hover over the parent element.
I have made a simple codesandbox here of the relevant parts. The jsx code looks like this:
<div className={classes.volumeWrapper}>
<VolumeUpIcon />
<input
accept="image/*"
className={classes.fileInput}
id="contained-button-file"
multiple
type="file"
/>
<label htmlFor="contained-button-file">
<Button
variant="contained"
component="span"
className={classes.fileButton}
>
<AddIcon />
</Button>
</label>
</div>
And the styling for it is this:
fileInput: {
display: "none"
},
volumeWrapper: {
"& label": {
display: "none"
},
"& :hover": {
"& label": {
display: "block"
}
}
}
Why is this not working, and what am I doing wrong here?

Related

How can I mark an Upload/Upload.Dragger component from Ant Design in React as 'Required'?

This is my Form.Item component for example and adding rules doesn't seem to work. I don't want the form to be submitted unless something is uploaded in the Upload area. Tried putting the validation rules inside <Form.Item> as well but that didn't work either.
<Form.Item
style={{ display: "inline-block", }}
>
<Upload.Dragger
multiple
rules={[
{
required: true,
message: "Please upload",
},
]}
>
<Button icon={<UploadOutlined />}>Add File</Button>
<div style={{ display: "inline-block", marginLeft: "5px", marginRight: "5px", }}>or drag and drop file here</div>
</Upload.Dragger >
</Form.Item>
Update:
Using Form component outside the <form.Item> with properties like name etc worked for me.
Check the following code, Use rules property in Form.item component
<Form.Item
name="dragger"
rules={[
{
required: true,
message: 'Please upload',
},
]}
>
<Upload.Dragger multiple>
<Button icon={<UploadOutlined />}>Add File</Button>
<div
style={{
display: 'inline-block',
marginLeft: '5px',
marginRight: '5px',
}}
>
or drag and drop file here
</div>
</Upload.Dragger>
</Form.Item>

How to fix an upload icon to a file upload input (material UI)

I'm currently trying to build a file upload Input field with an icon as an input adornment, using Material UI. I want to be able to click the icon to upload a file.
Following the MUI documentation here: input adornment, I have tried to follow the exact formatting except using an IconButton instead of the Password visibility icon in the demo.
Here is a sample of my code:
<FormControl className={classes.formControl}>
<InputLabel htmlFor="upload-script">Sim Script</InputLabel>
<Input
id="upload-script"
type="file"
value={values.script}
onChange={() => handleChange('script')}
endAdornment={
<InputAdornment position="end">
<IconButton aria-label="upload">
<PublishIcon /> // (here is my icon)
</IconButton>
</InputAdornment>
}
/>
</FormControl>
This works, but the result is not at all what I was intending- here is a screenshot of what it looks like in the browser:
You can see my upload icon all the way on the right, but the input field clearly has its own upload button and placeholder text ('No file chosen').
Looking online, I saw a suggestion here: (similar StackOverflow post) that says we should add style: {{ display: none }} to the Input component, and add component="span" as a property of the IconButton. When I try this, however, this is what the browser gives us:
(this is all the way scrolled... my sources icon is gone, the Input field has no line delimiter underneath, the spacing is clearly messed up...)
Clearly, neither of these is what I want haha. Can anyone help?? (I hope this description is good enough...)
Thank you so much!!
-
EDIT:
Here is what it looks like with #Shiladitya's initial solution:
But I want there to be a line for the text field! I want it to look exactly like the 'Sim Binary' text field, except with an upload icon on the right instead of a dropdown arrow.
Here you go with a solution
const handleFileUpload = () => {}
<>
<input
style={{
display: "none"
}}
accept=".json" // specify the file type that you wanted to accept
id="choose-file"
type="file"
onChange={handleFileUpload}
/>
<label htmlFor="choose-file">
<IconButton aria-label="upload">
<PublishIcon />
</IconButton>
</label>
</>
EDIT with Textfield
<TextField
className={classes.margin}
id="input-with-icon-textfield"
label="TextField"
InputProps={{
endAdornment: (
<>
<input
style={{
display: "none"
}}
accept=".json"
id="choose-file"
type="file"
onChange={handleFileUpload}
/>
<label htmlFor="choose-file">
<IconButton aria-label="upload">
<PublishIcon />
</IconButton>
</label>
</>
),
}}
/>
I've recently returned and edited my component, which was inspired by #Shiladitya but now uses a better system that I somehow missed before:
<TextField
className={classes.formControl}
id={`sim-script-${sim.uuid}`}
label="Sim Script- click the Upload Icon"
required
inputRef={scriptInputRef} // To focus on the field after clicking icon
value={sim.script}
InputProps={{
readOnly: true,
endAdornment: (
<>
<AdjustedTooltip title="Upload script" arrow>
<IconButton
aria-label="upload"
className={classes.uploadIcon}
component="label" // THIS IS THE GENIUS CHANGE
>
<PublishIcon />
<input
hidden
type="file"
onChange={(e) => scriptUpload(e)}
/>
</IconButton>
</AdjustedTooltip>
</>
),
}}
/>
This way, we can remove all the input and label wrappers, and instead just set the Material UI Button to have the attribute component="label", with the input tag inside the icon button.
Also here is my onChange/scriptUpload function, in case people are interested:
const scriptUpload = (e: ChangeEvent<HTMLInputElement>) => {
// the first/only file selected by user
if (e.target.files?.item(0)) {
onChange(sim.uuid, "script", e.target.files?.item(0)!.name);
}
// Focus textfield
if (scriptInputRef.current) {
scriptInputRef.current.focus();
}
};
Another Solution.
* {
margin: 0 auto;
font-family: Helvetica;
}
html {
background-color: #e9e9e9;
}
#choose-file {
display: none;
}
#wrapper {
display: inline-flex;
justify-content: center;
margin-left: 20px;
background-color: orange;
width: 60px;
align-items: center;
cursor: pointer;
}
<form>
<label id="first">
Sim Binary
<div id="wrapper">
<input
accept=".json"
id="choose-file"
type="file"
/>
⇪
</div>
</label>
</form>

Using Material-ui for creating an inline form with label,text, select and button elements (similar to Boostrap)

I am struggling to create an inline form using Material-UI and React which resembles the following
Bootstrap HTML snippet
I created the above using the HTML snippet below. You can try this out at W3Schools.
<div class="container">
<h2>Inline form</h2>
<p>Make the viewport larger than 768px wide to see that all of the form elements are inline, left aligned, and the labels are alongside.</p>
<form class="form-inline" action="/action_page.php">
<div class="form-group">
<label for="sel1">Select search field:</label>
<select class="form-control" id="sel1">
<option>FirstName</option>
<option>LastName</option>
<option>PostCode</option>
<option>Gender</option>
</select>
</div>
<div class="form-group">
<input type="email" class="form-control" id="email" placeholder="Enter search pattern" name="email">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
My attempt using Material-ui
<label htmlFor="selectsearchfield">Select search fields</label>
<span> </span>
<NativeSelect id="selectsearchfield" value={{}}>
<option value="FirstName">FirstName</option>
<option value="LastName">LastName</option>
</NativeSelect>
<span> </span>
<TextField
label="" id="outlined-size-small" defaultValue="" variant="outlined" size="small" disableUnderline />
<span> </span>
<Button size="medium" variant="contained" color="primary">Search</Button>
Question
It can be clearly seen that the output from Bootstrap is far more professional.
Please, any suggestions on how to improve the inline form using Material UI look n feel so that it comes close to Bootstrap.
Thank you,
Sau
You can make use of material-ui makeStyles and make it responsive also:-
import React, { useState } from "react";
import "./style.css";
import classNames from "classnames";
import { makeStyles } from "#material-ui/core/styles";
import {
Button,
FormControl,
OutlinedInput,
TextField,
Typography,
Select,
MenuItem
} from "#material-ui/core";
export default function App() {
const classes = useStyles();
const fields = ["FirstName", "LastName", "PostCode", "Gender"];
const [searcBy, setSearchBy] = useState("FirstName");
const [searchText, setSearchText] = useState("");
return (
<div className={classes.root}>
<form className={classes.form}>
<FormControl className={classNames(classes.formControl, classes.text)}>
<Typography variant="body1" className={classes.type}>
Select search fields:
</Typography>
</FormControl>
<FormControl
className={classNames(classes.formControl, classes.select)}
>
<Select
labelId="typesLabel"
label="Types"
input={<OutlinedInput classes={{ input: classes.input }} />}
value={searcBy}
onChange={e => setSearchBy(e.target.value)}
>
{fields.map(field => (
<MenuItem key={field} value={field}>
{field}
</MenuItem>
))}
</Select>
</FormControl>
<FormControl
className={classNames(classes.formControl, classes.search)}
>
<TextField
label="Enter search pattern"
variant="outlined"
size="small"
value={searchText}
onChange={e => setSearchText(e.target.value)}
/>
</FormControl>
<Button
type="submit"
variant="contained"
color="primary"
className={classes.submitBtn}
>
Primary
</Button>
</form>
</div>
);
}
const useStyles = makeStyles(theme => ({
root: {
display: "flex",
justifyContent: "center",
alignItems: "center"
},
form: {
width: 800,
display: "flex",
flexWrap: "wrap",
justifyContent: "center",
alignItems: "center"
},
container: {
display: "flex",
justifyContent: "center",
alignItems: "center"
},
text: {
minWidth: 120
},
type: {
fontWeight: 600
},
formControl: {
marginRight: theme.spacing(1),
[theme.breakpoints.down("xs")]: {
minWidth: "100%",
marginRight: theme.spacing(0),
marginBottom: theme.spacing(1)
}
},
input: {
padding: "10px 14px"
},
select: {
maxWidth: 130
},
search: {
maxWidth: 180
},
submitBtn: {
[theme.breakpoints.down("xs")]: {
width: "100%"
}
}
}));
Or you can also incorporate the use of Grid from #material-ui/core.Grid instead of using 'flex' like the above example.
Here is the result:-
a bit of explanation of what happened above:-
having input={<OutlinedInput classes={{ input: classes.input }} />} as select element props will enable us to create our own outline instead of depending on the given example code from material-ui select documentation code.
the reason why we do this is since you want to make it exactly as you did with Bootstraps example above. We need to somehow make the select element tad shorter in height. With element TextField, you can just specify size="small". But for select, that option is not available. That's why we have this approach. Or you can directly change the select input element overall styles with withStyles of material-ui/core/styles.
You can also refer to this sandbox code to see the actual working result.

How can i make an avatar chooser with Material UI

I'm using Material UI v3 within a react project (react v15.6).
What i did so far?
In the sign up page i can get an image from the user to use as his/her profile photo.
What i want to do
I would like to have a shade on the avatar photo to show him that is clickable. like this image...
avatarChooserImage
Someone can tell me, how can i do such a thing? I have no clue. I tried to use plain CSS, but much effort for nothing.
You can do something simple like this,
<IconButton>
<Avatar
src="/images/example.jpg"
style={{
margin: "10px",
width: "60px",
height: "60px",
}}
/>
</IconButton>
Which allow you to have a clickable avatar.
But since you are using it as file input too, maybe you can do something like this,
<input
accept="image/*"
className={classes.input}
id="contained-button-file"
multiple
type="file"
/>
<label htmlFor="contained-button-file">
<IconButton>
<Avatar
src="/images/example.jpg"
style={{
margin: "10px",
width: "60px",
height: "60px",
}}
/>
</IconButton>
</label>
To do an overlay for "edit", have a look at css image overlay. This explains how to place a layer on top on Avatar Icon, it should answer you question.
P.S Accept this as the right answer if it answers your question, thank you.
There's a similar example of this in the documentation:
https://material-ui.com/components/buttons/#upload-button
Instead of using "PhotoCamera" , you can use "Avatar". Allowing you to click on your avatar to upload an image like this for example:
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Avatar from '#material-ui/core/Avatar';
import IconButton from '#material-ui/core/IconButton';
const useStyles = makeStyles((theme) => ({
root: {
alignSelf: 'center',
justifyContent: "center",
alignItems: "center",
display: 'flex',
'& > *': {
margin: theme.spacing(1),
},
},
input: {
display: "none",
},
large: {
width: theme.spacing(7),
height: theme.spacing(7),
},
}));
export default function ProfileAvatar() {
const classes = useStyles();
return (
<div className={classes.root}>
<input accept="image/*" className={classes.input} id="icon-button-file" type="file" />
<label htmlFor="icon-button-file">
<IconButton color="primary" aria-label="upload picture" component="span">
<Avatar src="https://www.w3schools.com/howto/img_avatar.png" className={classes.large} />
</IconButton>
</label>
</div>
);
}
An hidden input that accepts images only,
and an IconButton with component="span" property, surrounded by the input's label:
<input accept="image/*" id="upload-avatar-pic" type="file" hidden />
<label htmlFor="upload-avatar-pic">
<IconButton component="span">
<Avatar />
</IconButton>
</label>

How to enable file upload on React's Material UI simple input?

I am creating a simple form to upload file using electron-react-boilerplate with redux form & material ui.
The problem is that I do not know how to create input file field because material ui does not support upload file input.
Any ideas on how to achieve this?
The API provides component for this purpose.
<Button
variant="contained"
component="label"
>
Upload File
<input
type="file"
hidden
/>
</Button>
newer MUI version:
<input
accept="image/*"
className={classes.input}
style={{ display: 'none' }}
id="raised-button-file"
multiple
type="file"
/>
<label htmlFor="raised-button-file">
<Button variant="raised" component="span" className={classes.button}>
Upload
</Button>
</label>
You need to wrap your input with component, and add containerElement property with value 'label' ...
<RaisedButton
containerElement='label' // <-- Just add me!
label='My Label'>
<input type="file" />
</RaisedButton>
You can read more about it in this GitHub issue.
EDIT: Update 2019.
Check at the bottom answer from #galki
TLDR;
<input
accept="image/*"
className={classes.input}
style={{ display: 'none' }}
id="raised-button-file"
multiple
type="file"
/>
<label htmlFor="raised-button-file">
<Button variant="raised" component="span" className={classes.button}>
Upload
</Button>
</label>
Here's an example using an IconButton to capture input (photo/video capture) using v3.9.2:
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import IconButton from '#material-ui/core/IconButton';
import PhotoCamera from '#material-ui/icons/PhotoCamera';
import Videocam from '#material-ui/icons/Videocam';
const styles = (theme) => ({
input: {
display: 'none'
}
});
class MediaCapture extends Component {
static propTypes = {
classes: PropTypes.object.isRequired
};
state: {
images: [],
videos: []
};
handleCapture = ({ target }) => {
const fileReader = new FileReader();
const name = target.accept.includes('image') ? 'images' : 'videos';
fileReader.readAsDataURL(target.files[0]);
fileReader.onload = (e) => {
this.setState((prevState) => ({
[name]: [...prevState[name], e.target.result]
}));
};
};
render() {
const { classes } = this.props;
return (
<Fragment>
<input
accept="image/*"
className={classes.input}
id="icon-button-photo"
onChange={this.handleCapture}
type="file"
/>
<label htmlFor="icon-button-photo">
<IconButton color="primary" component="span">
<PhotoCamera />
</IconButton>
</label>
<input
accept="video/*"
capture="camcorder"
className={classes.input}
id="icon-button-video"
onChange={this.handleCapture}
type="file"
/>
<label htmlFor="icon-button-video">
<IconButton color="primary" component="span">
<Videocam />
</IconButton>
</label>
</Fragment>
);
}
}
export default withStyles(styles, { withTheme: true })(MediaCapture);
It is work for me ("#material-ui/core": "^4.3.1"):
<Fragment>
<input
color="primary"
accept="image/*"
type="file"
onChange={onChange}
id="icon-button-file"
style={{ display: 'none', }}
/>
<label htmlFor="icon-button-file">
<Button
variant="contained"
component="span"
className={classes.button}
size="large"
color="primary"
>
<ImageIcon className={classes.extendedIcon} />
</Button>
</label>
</Fragment>
If you're using React function components, and you don't like to work with labels or IDs, you can also use a reference.
const uploadInputRef = useRef(null);
return (
<Fragment>
<input
ref={uploadInputRef}
type="file"
accept="image/*"
style={{ display: "none" }}
onChange={onChange}
/>
<Button
onClick={() => uploadInputRef.current && uploadInputRef.current.click()}
variant="contained"
>
Upload
</Button>
</Fragment>
);
Official recommendation
import * as React from 'react';
import { styled } from '#mui/material/styles';
import Button from '#mui/material/Button';
import IconButton from '#mui/material/IconButton';
import PhotoCamera from '#mui/icons-material/PhotoCamera';
import Stack from '#mui/material/Stack';
const Input = styled('input')({
display: 'none',
});
export default function UploadButtons() {
return (
<Stack direction="row" alignItems="center" spacing={2}>
<label htmlFor="contained-button-file">
<Input accept="image/*" id="contained-button-file" multiple type="file" />
<Button variant="contained" component="span">
Upload
</Button>
</label>
<label htmlFor="icon-button-file">
<Input accept="image/*" id="icon-button-file" type="file" />
<IconButton color="primary" aria-label="upload picture" component="span">
<PhotoCamera />
</IconButton>
</label>
</Stack>
);
}
Nov 2020
With Material-UI and React Hooks
import * as React from "react";
import {
Button,
IconButton,
Tooltip,
makeStyles,
Theme,
} from "#material-ui/core";
import { PhotoCamera } from "#material-ui/icons";
const useStyles = makeStyles((theme: Theme) => ({
root: {
"& > *": {
margin: theme.spacing(1),
},
},
input: {
display: "none",
},
faceImage: {
color: theme.palette.primary.light,
},
}));
interface FormProps {
saveFace: any; //(fileName:Blob) => Promise<void>, // callback taking a string and then dispatching a store actions
}
export const FaceForm: React.FunctionComponent<FormProps> = ({ saveFace }) => {
const classes = useStyles();
const [selectedFile, setSelectedFile] = React.useState(null);
const handleCapture = ({ target }: any) => {
setSelectedFile(target.files[0]);
};
const handleSubmit = () => {
saveFace(selectedFile);
};
return (
<>
<input
accept="image/jpeg"
className={classes.input}
id="faceImage"
type="file"
onChange={handleCapture}
/>
<Tooltip title="Select Image">
<label htmlFor="faceImage">
<IconButton
className={classes.faceImage}
color="primary"
aria-label="upload picture"
component="span"
>
<PhotoCamera fontSize="large" />
</IconButton>
</label>
</Tooltip>
<label>{selectedFile ? selectedFile.name : "Select Image"}</label>. . .
<Button onClick={() => handleSubmit()} color="primary">
Save
</Button>
</>
);
};
You can use Material UI's Input and InputLabel components. Here's an example if you were using them to input spreadsheet files.
import { Input, InputLabel } from "#material-ui/core";
const styles = {
hidden: {
display: "none",
},
importLabel: {
color: "black",
},
};
<InputLabel htmlFor="import-button" style={styles.importLabel}>
<Input
id="import-button"
inputProps={{
accept:
".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel",
}}
onChange={onInputChange}
style={styles.hidden}
type="file"
/>
Import Spreadsheet
</InputLabel>
import AddPhotoIcon from "#mui/icons-material/AddAPhoto";
import Fab from "#mui/material/Fab";
<Fab color="primary" aria-label="add-image" sx={{ position: "fixed", bottom: 16, right: 16, overflow: "hidden" }}>
<input
type="file"
onChange={imageHandler}
accept=".jpg, .jpeg, .png"
accept="image/*"
multiple
style={{ //make this hidden and display only the icon
position: "absolute",
top: "-35px",
left: 0,
height: "calc(100% + 36px)",
width: "calc(100% + 5px)",
outline: "none",
}}
/>
<AddPhotoIcon />
</Fab>
Just the same as what should be but change the button component to be label like so
<form id='uploadForm'
action='http://localhost:8000/upload'
method='post'
encType="multipart/form-data">
<input type="file" id="sampleFile" style="display: none;" />
<Button htmlFor="sampleFile" component="label" type={'submit'}>Upload</Button>
</form>
<input type="file"
id="fileUploadButton"
style={{ display: 'none' }}
onChange={onFileChange}
/>
<label htmlFor={'fileUploadButton'}>
<Button
color="secondary"
className={classes.btnUpload}
variant="contained"
component="span"
startIcon={
<SvgIcon fontSize="small">
<UploadIcon />
</SvgIcon>
}
>
Upload
</Button>
</label>
Make sure Button has component="span", that helped me.
Here an example:
return (
<Box alignItems='center' display='flex' justifyContent='center' flexDirection='column'>
<Box>
<input accept="image/*" id="upload-company-logo" type='file' hidden />
<label htmlFor="upload-company-logo">
<Button component="span" >
<Paper elevation={5}>
<Avatar src={formik.values.logo} className={classes.avatar} variant='rounded' />
</Paper>
</Button>
</label>
</Box>
</Box>
)
Typescript version of #tomatentobi's javascript solution
const uploadInputRef = useRef<HTMLInputElement | null>(null);
return (
<>
<input
ref={uploadInputRef}
type="file"
accept="image/*"
style={{ display: "none" }}
onChange={onChange}
/>
<Button
onClick={() => uploadInputRef.current && uploadInputRef.current.click()}
variant="contained">
Upload
</Button>
</>
);
This worked for me.
<Button variant="contained" component="label" >
UPLOAD
<input accept="image/*" hidden type="file" />
</Button>
You can pursue all the comments above, those are really great, However, I have another option for customizing your component, if you want to follow.
// Import
import { styled } from '#mui/material/styles';
import { Input } from "#mui/material";
// Custom style
const CustomFileInput = styled(Input)(({ theme }) => {
return {
color: "white",
'::before': {
border: 'none',
position: 'static',
content: 'none'
},
'::after': {
border: 'none',
position: 'static',
content: 'none'
}
}
});
// Using that component
<CustomFileInput type="file" />
Both #galki and #elijahcarrel method works fine.
If anyone trying to do unit-testing(jest) for these two answers.
You wont be able to use the button component with (specially if you are using disabled=true
expect(getByRole("button", {name: "Upload"})).not.toBeEnabled();
instead use this
expect(getByLabelText("Upload")).not.toBeEnabled();
This is for Select Image File
<IconButton color="primary" component="label">
<input type="file" accept="image/*" hidden />
<AttachFileIcon fontSize="medium" />
</IconButton>
NOTE : React Material UI Component (IconButton, AttachFileIcon)
Or there is library for MUI 5 / React 18 : https://viclafouch.github.io/mui-file-input/ 🔥
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} />
}
Try This
import React from 'react'
import { MuiFileInput } from 'mui-file-input'
export default function MyComponent () {
const [file, setFile] = React.useState(null)
const handleChange = (newFile) => {
setFile(newFile)
}
return (
<MuiFileInput value={file} onChange={handleChange} />
)
}
npm install mui-file-input --save
npm install #mui/icons-material
or
yarn add mui-file-input
yarn add #mui/icons-material
another way is this and we can add the name of the file as a value for TextField.
<TextField
value={state.value}
label="upload profile picture"
sx={{ m: 1, width: '25ch' }}
InputProps={{
fullWidth: true,
startAdornment: (
<IconButton component="label">
<AttachFileIcon />
<input
type="file"
hidden
onChange={handleUploadInput}
name="[name]"
/>
</IconButton>
)
}}
/>

Resources