How do I use the SpeedDial to upload a file? - reactjs

(this seem to have been asked previously but I couldn't find any hint on if it was actually answered)
MUI has a good demo for creating upload buttons which boils down to:
<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">
<PhotoCamera />
</IconButton>
</label>
What I wonder is how to implement the same using the Speed Dial. Inherently the SpeedDialAction seems to materialize as a <button/>, but it's not possible to e.g. wrap the SpeedDialAction in a <label htmlFor /> as its parent will try to set some props on it and will fail.
So how do I initiate the file selection from within the Speed Dial or a FAB in general?

You can create a wrapper component that forwards props to SpeedDialAction.
function UploadSpeedDialAction(props) {
return (
<React.Fragment>
<input
accept="image/*"
style={{ display: "none" }}
id="icon-button-file"
type="file"
/>
<label htmlFor="icon-button-file">
<SpeedDialAction
icon={<CloudUploadIcon />}
tooltipTitle="upload"
component="span"
{...props}
></SpeedDialAction>
</label>
</React.Fragment>
);
}
https://codesandbox.io/s/material-demo-forked-h6s4l
(Note to future readers: For v5, time allowing, we hope to rationalise where props rather than context are used to control children, in order to solve exactly this kind of issue. So check whether this solution is still needed.)

It is - in my knowledge - not possible to add the htmlFor in any way. So what I would do is to add a hidden input type file and then add a ref to it. Then in the onclick of the SpeedDialAction button I would call a handler function that clicks on the input ref. Like this:
const inputRef = useRef();
const handleFileUploadClick = () => {
inputRef.current.click();
};
Then your SpeedDialAction:
<SpeedDialAction
onClick={handleFileUploadClick}
... the rest of your props
/>
And then finnaly your actual input:
<input
style={{ display: "none" }}
ref={inputRef}
accept="image/*"
id="contained-button-file"
multiple
type="file"
/>
Working demo: https://codesandbox.io/s/material-demo-forked-f9i6q?file=/demo.tsx:1691-1868

Related

Select a item from a mui component listbox in cypress

This is the element.
<input aria-invalid="false" autocomplete="off" placeholder="Category" type="text" class="MuiOutlinedInput-input MuiInputBase-input MuiInputBase-inputAdornedEnd MuiAutocomplete-input MuiAutocomplete-inputFocused MuiAutocomplete-input MuiAutocomplete-inputFocused css-16sx77j" aria-autocomplete="list" autocapitalize="none" spellcheck="false" value="" id="mui-621338585" aria-controls="mui-621338585-listbox" aria-activedescendant="mui-621338585-option-3"> So this is a listbox by name Category, that contains various options when I click the dropdown arrow. The number after "mui-" is dynamic.
This is what I tried:
cy.get('[id^=”mui-"]').eq(2);
Also tried:
cy.get('[id^=”mui-"]')
.find('[aria-activedescendant*="-option-"]').eq(2);
And tried:
cy.get('[aria-activedescendant*="-option-2"]');
Could someone point me in the right direction to select an option from the listbox? Appreciate your help.
Don't over-think the problem, just use text in the component.
Libraries like React Material-UI generate quite complicated HTML to handle styling and animation and it's difficult to pick the right parts from that generated HTML.
Given source code like this (taken from MUI demo page)
<Autocomplete
disablePortal
id="combo-box-demo"
options={top100Films}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Movie" />}
/>
You get a generated structure like this (leaving out classes used for styling)
<div class="MuiAutocomplete-root" data-cy="movie-autocomplete">
<div>
<label for="combo-box-demo" id="combo-box-demo-label">Movie</label>
<div>
<input id="combo-box-demo" type="text" role="combobox" value="">
...
</div>
</div>
</div>
The MUIAutocomplete-root is outer element - you can test it using the text in the elements.
cy.contains('.MuiAutocomplete-root', 'Movie') // identify Autocomplete component
.click() // open it
cy.contains('The Godfather').click() // choose an option
cy.contains('.MuiAutocomplete-root', 'Movie')
.find('input')
.should('have.value', 'The Godfather') // verify the value in the input
With data-cy attribute
If you add a data-cy attribute to the component,
<Autocomplete
data-cy='movie-autocomplete'
disablePortal
id="combo-box-demo"
options={top100Films}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Movie" />}
/>
The test becomes simpler
cy.get('[data-cy="movie-autocomplete"]').click()
cy.contains('The Godfather').click()
cy.get('[data-cy="movie-autocomplete"]')
.find('input')
.should('have.value', 'The Godfather')

How to change text of "file" <Input> antd

I believe the title is self-explanatory. I have checked the docs and some other threads, but I simply cannot manage to change both texts of the label: nor "Choose file", neither "No file chosen". Below is what I have tried so far. How could I change those texts?
{/* <label htmlFor="test" id="files">Test with classic label</label> */}
<Input
// addonAfter="test1"
// addonBefore="test2"
type="file"
onChange={handleFileSelected}
text="test"
/>
The text of the input's file element is controlled by the browser so you can not directly change that. What you can do is instead creating a customizable button that uses a ref to control the click on the input:
const ref = useRef<HTMLInputElement>(null);
<button onClick={() => ref.current?.click()}>Click Here</button>
<input
type='file'
ref={ref}
onChange={handleFileSelected}
style={{ display: "none" }} />
Ant Design also has Upload component for file uploading which could be styled easily

HintText of a TextField component in Material UI does not hide its value when start typing into the field

I have recently started exploring Material UI and I have run into this strange behavior of a hintText in a TextField Component(the one from Material UI)
This is my code:
/* in my component ... */
/* ... */
render() {
const actions = [
<FlatButton
key="1"
label="Cancel"
primary
onTouchTap={this.handleClose}
/>,
<FlatButton
key="2"
label="Submit"
primary
type="submit"
onTouchTap={this.handleSubmit}
/>
];
return (
<div>
<IconButton
tooltip="Add Asset"
onTouchTap={this.handleOpen}>
<Add color={"#000"} />
</IconButton>
<Dialog
title="Add"
actions={actions}
modal
open={this.state.open}>
<form>
<TextField hintText="Type"
value={this.state.name}
onChange={this.handleName}/>
</form>
</Dialog>
</div>
);
}
So when I start typing in the textfield, the hinttext remains, resulting in unreadable text due to letters over another letters.
I would really appreciate it if someone could help me. :)
image
Try using placholder="Type" rather than hintText="Type".
The solution for this is that you will have to update the variable name in the function handleName everytime the user updates the field. So the complete code is:
<TextField
hintText="Type"
value={this.state.name}
onChange={this.handleName}
/>
and the function handleName:
handleName=(event)=>{
this.setState({name:event.target.value});
}
It should work. If not, let me know in the comments below!

reactJS material UI Icon button to upload file

I want to know how can I open the directory to upload a file using an IconButton?
<IconButton
iconClassName="fa fa-plus-square"
onClick={(e) => e.stopPropagation()}
type='file'
/>
using the code below shows the icon button and another button for the upload file
<IconButton iconClassName="fa fa-plus-square" onClick={(e) => e.stopPropagation()}>
<input type="file type='file'>
</IconButton>
I think the standard way from material ui examples:
<input accept="image/*" className={classes.input} id="icon-button-file" type="file" />
<label htmlFor="icon-button-file">
<IconButton color="primary" className={classes.button} component="span">
<PhotoCamera />
</IconButton>
</label>
A few things:
You don't need type='file' on the IconButton, just on the input
IconButton does not expect its children to be anything other than an SVGIcon, so I recommend that you use a regular button
I wouldn't call stopPropagation in this case
You have a typo in your type prop for the input. You have type="file type='file'. It should just be type="file"
So, putting that all together:
<FlatButton label="Choose file" labelPosition="before">
<input type="file" style={styles.exampleImageInput} />
</FlatButton>
If you still want it to be an icon rather than a button, I suspect you can do something with <input> or add it as the children to FlatButton with no label.
You can achieve the same with IconButton using the following code:
<IconButton onClick={() => this.fileUpload.click()}>
<FontIcon className="material-icons" >
add_a_photo
</FontIcon>
</IconButton>
<input type="file" ref={(fileUpload) => {
this.fileUpload = fileUpload;
}}
style={{ visibility: 'hidden'}} onChange={this.groupImgUpload} />

Popover in material-ui - display glitches when using components

It's a trivial issue but I'm not seeing where the problem exactly lies.
As far as I know, React offers the ability to pull stuff apart into (somewhat) independent components which results in cleaner pages because not everything is crammed into one huge HTML file. Or so the philosophy goes, I think.
Basically, when I do this, everything works fine:
<Popover open={this.props.popover === LoginPopoverState.LOGIN}
anchorEl={this.props.anchorEl}
onRequestClose={this.handleHideLogin.bind(this, dispatch)}>
<div style={loginStyle}>
<TextField hintText="Username oder E-Mail" ref="username_login" floatingLabelText="Username / E-Mail"/><br />
<TextField hintText="Passwort" type="password" ref="password_login" floatingLabelText="Passwort" /><br />
<RaisedButton label="Login" onTouchTap={e => this.handleLoginTap(e, dispatch)} />
<p>Zur <a href="javascript://" onTouchTap={e => this.handleShowRegisterTap(e, dispatch)}>Registration</a>.</p>
</div>
</Popover>
Which looks like this:
However, when I pull out the <div> into another module and then do something like this:
import LoginFragment from './loginBar/LoginFragment.jsx'
<Popover open={this.props.popover === LoginPopoverState.LOGIN}
anchorEl={this.props.anchorEl}
onRequestClose={this.handleHideLogin.bind(this, dispatch)}>
<LoginFragment />
</Popover>
where LoginFragment.jsx contains something like this:
var LoginFragment = React.createClass({
render() {
return (
<TextField hintText="Username oder E-Mail" ref="username_login" floatingLabelText="Username / E-Mail"/>
)
}
})
Which results in this hot mess:
So, what is going on here?
It can be a CSS styling issue, since you aren´t wrapping your fields inside a div anymore (and you are now skipping the loginStyle that was applied to this containing div.)
So, in the old version you had:
<Popover ...>
<div style={loginStyle}>
<TextField ...><br />
<TextField ...><br />
<RaisedButton .../>
<p>...</p>
</div>
</Popover>
But what you are including in your new version right now, using a single LoginFragment component, once expanded, would look like:
<Popover ...>
<TextField ...>
</Popover>
In this version, the wrapping divs (and their styling) would be missing. Including <div style={loginStyle}> in your code again would fix any CSS styling conflict, or help find and isolate the issue. Like so:
import LoginFragment from './loginBar/LoginFragment.jsx'
<Popover open={this.props.popover === LoginPopoverState.LOGIN}
anchorEl={this.props.anchorEl}
onRequestClose={this.handleHideLogin.bind(this, dispatch)}>
<div style={loginStyle}>
<LoginFragment />
</div>
</Popover>

Resources