I am trying to make a column of checkboxes using Ant design. I have tried:
<Checkbox.Group>
<Checkbox>Hello</Checkbox>
<Checkbox>Hello1</Checkbox>
<Checkbox>Hello2</Checkbox>
</Checkbox.Group>
And:
<CheckboxGroup options={plainOptions} value={this.state.checkedList} onChange={this.onChange} />
However, there does not seem to be an option to make checkboxes appear in a column instead of a row. I know I can use flexbox, but I was hoping Ant design would have a builtin method for making checkboxes appear vertically (in a column) instead of appearing in a horizontal row.
Is there a simple solution for stacking the checkboxes vertically?
Doesn't appear like it does since the <label>s it renders are display: inline-block. The simplest solution (other than rewriting the component) is to change .ant-checkbox-group-item's attributes from (you can always wrap the CheckBox with another class name to apply selectively, instead of globally):
.ant-checkbox-group-item {
display: inline-block;
margin-right: 8px;
}
to:
.ant-checkbox-group-item {
display: block;
margin-right: 0;
}
Which will make it appear like so:
Optionally, you can also add a display: inline-block attribute to .ant-checkbox-group's styles.
Which will then make it appear like so:
Another solution is to create some reusable components and apply the above via the style property (admittedly, not an elegant solution since CheckboxGroup's onChange function expects to handle options, but you'd have to intercept them in order to alter the Checkbox's style property).
Working example: https://codesandbox.io/s/w0voxxxzm5
Checkbox.js
import React from "react";
import { Checkbox } from "antd";
export default ({ disabled, label, value, handleChange }) => (
<Checkbox
style={{ display: "block", marginLeft: 0 }}
disabled={disabled || false}
label={label}
checked={value}
onChange={handleChange}
>
{label}
</Checkbox>
);
CheckboxGroup.js
import React from "react";
import Checkbox from "./Checkbox";
export default ({ options, ...props }) => (
<div
className="ant-checkbox-group"
style={{ display: "inline-block", marginRight: 10 }}
>
{options.map(label => (
<Checkbox
key={label}
label={label}
disabled={props.disabled}
handleChange={props.handleChange}
value={props[label]}
/>
))}
</div>
);
Form.js
import React, { Component } from "react";
import CheckboxGroup from "./CheckboxGroup";
const options1 = ["Apple", "Pear", "Orange"];
const options2 = ["Strawberry", "Grape", "Mango"];
const options3 = ["Kiwi", "Banana", "Cherry"];
export default class Form extends Component {
state = {};
handleChange = ({ target: { label, checked } }) =>
this.setState({ [label]: checked });
handleSubmit = e => {
e.preventDefault();
alert(JSON.stringify(this.state, null, 4));
};
render = () => (
<form onSubmit={this.handleSubmit}>
<CheckboxGroup
{...this.state}
options={options1}
handleChange={this.handleChange}
/>
<CheckboxGroup
{...this.state}
options={options2}
handleChange={this.handleChange}
/>
<CheckboxGroup
{...this.state}
options={options3}
handleChange={this.handleChange}
disabled
/>
<div style={{ marginTop: 20 }}>
<button className="ant-btn ant-btn-primary" type="submit">
Submit
</button>
</div>
</form>
);
}
you can do this, and the content in item is also aligned and right!
.ant-checkbox-group {
display: flex;
flex-direction: column;
}
Following Matt Carlotta example if you are using a variant of styled component, you can achieve that with
export const ColumnCheckbox = styled(Checkbox.Group)`
.ant-checkbox-group-item {
display: block;
margin-right: 0;
}
`
<ColumnCheckbox options={['Hello','Hello1','Hello2']}/>
Not sure if this was always working (since the question is 2 years old), but it is possible to arrange the checkboxes within a group vertically without tweaking the styles by simply wrapping each of them in a <Row> component:
import { Checkbox, Row, Form } from 'antd';
function onChange(checkedValues) {
console.log('checked = ', checkedValues);
}
const GoodFruitForm = () =>
<Form>
<Form.Item label="Good Fruit">
<Checkbox.Group onChange={onChange}>
<Row><Checkbox value='Apple'>Apple</Checkbox></Row>
<Row><Checkbox value='Pear'>Pear</Checkbox></Row>
</Checkbox.Group>
</Form.Item>
</Form>
And the content renders as expected:
render() {
return (
<EnhanceSubContent title={TITLE}>
<Alert
className={styles.alertMargin}
message="Select the Events below to receive notification."
type="info"
showIcon
/>
<div className={styles.content}>
<Checkbox.Group style={{ width: '100%' }} onChange={this.onChange}>
<Row>
<Col span={12}>
<Space direction="vertical">
<Checkbox value="Link Up">Link Up</Checkbox>
<Checkbox value="Link Down">Link Down</Checkbox>
<Checkbox value="Power On">Power On</Checkbox>
<Checkbox value="Power Off">Power Off</Checkbox>
<Checkbox value="Cold Start">Cold Start</Checkbox>
<Checkbox value="Warm Start">Warm Start</Checkbox>
<Checkbox value="Port Link Up">Port Link Up</Checkbox>
</Space>
</Col>
<Col span={12}>
<Space direction="vertical">
<Checkbox value="Port Link Down">Port Link Down</Checkbox>
<Checkbox value="Authentication Failure">
Authentication Failure
</Checkbox>
<Checkbox value="Power1 ok">Power1 ok</Checkbox>
<Checkbox value="Power1 failure">Power1 failure</Checkbox>
<Checkbox value="Power2 ok">Power2 ok</Checkbox>
<Checkbox value="Power2 failure">Power2 failure</Checkbox>
</Space>
</Col>
</Row>
</Checkbox.Group>
<Divider />
</div>
<div className={styles.buttonsContianer}>
<Space align="end">
<Button type="primary"> Apply </Button>
<Button className={styles.cancelButton}> Reset </Button>
</Space>
</div>
</EnhanceSubContent>
);
}
}
Or you can use styled-components, so you will not change .ant-checkbox-group-item style globally.
import { Checkbox } from 'antd';
import styled from 'styled-components'
const VerticalCheckboxGroup = styled(Checkbox.Group)`
.ant-checkbox-group-item {
display: block!important;
}
`
Related
I try to build a site similar to wikipedia and its Edit functionality trouble me
think I send some data from DB like this
{
"post":[
{"title":"SOME TITLE","date":"20-12-2021"},
{"title":"SOME TITLE 2","date":"20-11-2021"}
]
}
with this data I need to show data to user and as well option to edit as well
so this means title and date are in some sort state variable I guess
how to achieve this I don't know I make question right but please consider wikipedia edit feature as example
how to implement it in react
Thanks
import { Form, Input, Button, Col, Row } from "antd";
import { PlusOutlined } from "#ant-design/icons";
const minusStyle = {
position: "relative",
margin: "0 8px",
color: "#999",
fontSize: "24px",
cursor: "pointer",
transition: "all 0.3s",
};
const PanditComponent = ({
panditNamez,
panditContactz,
onChangeSetPanditName,
onChangeSetPanditContact,
}) => {
console.log(panditContactz,panditNamez,onChangeSetPanditContact,onChangeSetPanditName)
return (
<Form style={{ marginTop: "8px" }}>
<Row>
<Col span={11}>
<Form.Item onChange={onChangeSetPanditName}>
<Input value={panditNamez} placeholder="Pandit Name"></Input>
</Form.Item>
</Col>
<Col span={11}>
<Form.Item onChange={onChangeSetPanditContact}>
<Input value={panditContactz} placeholder="Pandit Contact"></Input>
</Form.Item>
</Col>
<Col span={2}>
<Button icon={<PlusOutlined />} style={minusStyle}></Button>
</Col>
</Row>
</Form>
);
};
export default PanditComponent;
and this code to render the elements
data.pandit.map((p, index) => (
<PanditComponent
key={index}
panditNamez={panditDBItems[index]["name"]}
panditContactz={panditDBItems[index]["contact"]}
onChangeSetPanditName={(e) =>
// setPanditDBItems([{ name: "AAAAA", contact: "1234" }])
setPanditDBItems([{...panditDBItems,}])
}
onChangeSetPanditContact={(e) => console.log(e.target.value)}
/>
))
: null}
OK I solve this
if we break this question into simple words how to update state of array of objects like
panditDBItems = [{name:"aa",contact:12},{name:"b",contact:21}]
we can use do something
panditDBItems.map((p, index) => (
<PanditComponent
key={index}
panditNamez={panditDBItems[index]["name"]}
panditContactz={panditDBItems[index]["contact"]}
onChangeSetPanditName={(e) => {
let newPanditDBItems = [...panditDBItems];
newPanditDBItems[index]["name"] = e.target.value;
setPanditDBItems(newPanditDBItems);
}}
onChangeSetPanditContact={(e) => {
let newPanditDBItems = [...panditDBItems];
newPanditDBItems[index]["contact"] = e.target.value;
setPanditDBItems(newPanditDBItems);
}}
/>
)
I'm implementing the multi-step wizard example with Material-UI components and it works well with the useField() hook but I cannot figure out how to bring setFieldValue() into scope, so I can use it from a wizard step.
I've seen suggestions to use the connect() higher-order component but I have no idea how to do that.
Here is a snippet of my code: CodeSandbox, and the use case:
A wizard step has some optional fields that can be shown/hidden using a Material-UI Switch. I would like the values in the optional fields to be cleared when the switch is toggled off.
I.e.
Toggle switch on.
Enter data in Comments field.
Toggle switch off.
Comments value is cleared.
Toggle switch on.
Comments field is empty.
Hoping someone can help! Thanks.
I came across this answer the other day but discarded it because I couldn't get it working.
It does actually work but I'm in two minds as to whether it's the right approach.
const handleOptionalChange = (form) => {
setOptional(!optional)
form.setFieldValue('optionalComments', '', false)
}
<FormGroup>
<FormControlLabel
control={
// As this element is not a Formik field, it has no access to the Formik context.
// Wrap with Field to gain access to the context.
<Field>
{({ field, form }) => (
<Switch
checked={optional}
onChange={() => handleOptionalChange(form)}
name="optional"
color="primary"
/>
)}
</Field>
}
label="Optional"
/>
</FormGroup>
CodeSandbox.
I believe this is what you're after: CodeSandbox. I forked your CodeSandbox.
I tried to follow your code as closely as possible and ended up not using WizardStep. The step variable is returning a React component that is a child to Formik. Formik is rendered with props e.g. setFieldValue, which can be passed down to its children. In order to pass the setFieldValue as a prop to step, I had to use cloneElement() (https://reactjs.org/docs/react-api.html#cloneelement), which allows me to clone the step component and add props to it as follows.
// FormikWizard.js
<Formik
initialValues={snapshot}
onSubmit={handleSubmit}
validate={step.props.validate}
>
{(formik) => (
<Form>
<DialogContent className={classes.wizardDialogContent}>
<Stepper
className={classes.wizardDialogStepper}
activeStep={stepNumber}
alternativeLabel
>
{steps.map((step) => (
<Step key={step.props.name}>
<StepLabel>{step.props.name}</StepLabel>
</Step>
))}
</Stepper>
<Box
className={classes.wizardStepContent}
data-cy="wizardStepContent"
>
{React.cloneElement(step, {
setFieldValue: formik.setFieldValue
})}
</Box>
</DialogContent>
<DialogActions
className={classes.wizardDialogActions}
data-cy="wizardDialogActions"
>
<Button onClick={handleCancel} color="primary">
Cancel
</Button>
<Button
disabled={stepNumber <= 0}
onClick={() => handleBack(formik.values)}
color="primary"
>
Back
</Button>
<Button
disabled={formik.isSubmitting}
type="submit"
variant="contained"
color="primary"
>
{isFinalStep ? "Submit" : "Next"}
</Button>
</DialogActions>
</Form>
)}
</Formik>
To access the setFieldValue prop in the child component, in App.js, I created a new component called StepOne and used it to wrap around the inputs, instead of using WizardStep. Now I am able to access setFieldValue and use it in the handleOptionalChange function.
// App.js
import React, { useState } from "react";
import "./styles.css";
import { makeStyles } from "#material-ui/core/styles";
import Box from "#material-ui/core/Box";
import CssBaseline from "#material-ui/core/CssBaseline";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import FormGroup from "#material-ui/core/FormGroup";
import Switch from "#material-ui/core/Switch";
import FormikTextField from "./FormikTextField";
import { Wizard, WizardStep } from "./FormikWizard";
const useStyles = makeStyles((theme) => ({
content: {
display: "flex",
flexFlow: "column nowrap",
alignItems: "center",
width: "100%"
}
}));
const initialValues = {
forename: "",
surname: "",
optionalComments: ""
};
const StepOne = ({ setFieldValue }) => {
const classes = useStyles();
const [optional, setOptional] = useState(false);
const displayOptional = optional ? null : "none";
const handleOptionalChange = () => {
setFieldValue("optionalComments", "");
setOptional(!optional);
};
return (
<Box className={classes.content}>
<FormikTextField
fullWidth
size="small"
variant="outlined"
name="forename"
label="Forename"
type="text"
/>
<FormikTextField
fullWidth
size="small"
variant="outlined"
name="surname"
label="Surname"
type="text"
/>
<FormGroup>
<FormControlLabel
control={
<Switch
checked={optional}
onChange={handleOptionalChange}
name="optional"
color="primary"
/>
}
label="Optional"
/>
</FormGroup>
<FormikTextField
style={{ display: displayOptional }}
fullWidth
size="small"
variant="outlined"
name="optionalComments"
label="Comments"
type="text"
/>
</Box>
);
};
function App(props) {
return (
<>
<CssBaseline />
<Wizard
title="My Wizard"
open={true}
initialValues={initialValues}
onCancel={() => {
return;
}}
onSubmit={async (values) => {
console.log(JSON.stringify(values));
}}
>
<StepOne />
<StepTwo />
</Wizard>
</>
);
}
export default App;
Alternative
To use setFieldValue in Formik, the easiest way would be to have the all input elements within the <Formik></Formik tags. You could conditionally render the input elements based on what step you're on as follows. This gives the inputs a direct access to setFieldValue so you can call setFieldValue("optionalComments", "") on the Switch input which will clear the comments on each toggle. Although this may mean you'll have a longer form, I don't think this is necessarily a bad thing.
<Formik>
<Form>
{step === 1 && <div>
// Insert inputs here
</div>}
{step === 2 && <div>
<TextField
onChange={(event) => setFieldValue("someField", event.target.value)}
/>
<Switch
checked={optional}
onChange={() => {
setFieldValue("optionalComments", "");
setOptional(!optional);
}}
name="optional"
color="primary"
/>
</div>}
</Form>
</Formik>
I'm trying to submit form onSubmitbut its not firing the this.commentSubmit function, if i take <form></form> out, use <Button onSubmit> function it works however i need the form wrapped around the Textfield for the backend to read the req.body.comment_body to work.
Comment.js
import React from "react";
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
const Comment = (props) => (
<form onSubmit={props.onSubmit}>
<TextField
type="text"
id="outlined-multiline-static"
label="Write A Comment"
multiline
name="comment_body"
value={props.commentBody}
rows="10"
fullWidth
margin="normal"
variant="outlined"
onChange={props.commentChange}
/>
<Button type="submit" variant="outlined" component="span" color="primary">
Post A Comment
</Button>
</form>
)
export default Comment;
Image Container Component
import React from "react";
import Button from '#material-ui/core/Button';
import Grid from '#material-ui/core/Grid';
import Typography from '#material-ui/core/Typography';
import Paper from '#material-ui/core/Paper';
import Divider from '#material-ui/core/Divider';
import Image from './Image';
import Axios from '../Axios';
import moment from 'moment';
import Comment from './Comment';
class ImageContainer extends React.Component{
state = {
isComment: false,
comment_body: ""
}
handleCommentChange = (e) => {
this.setState({
comment_body: e.target.value
})
}
writeComment = (id) => {
this.setState({
isComment: this.state.isComment ? '' : id // check if you state is filled to toggle on/off comment
})
}
commentSubmit = (e) => {
e.preventDefault();
console.log(this.state.comment_body); // doesn't get console.log
Axios.post('/images/newComment', this.state.comment_body).then( (response )=> {
const newComment = { ...response.data};
console.log(newComment);
this.setState({
comment_body: ''
})
})
}
render(){
const { img, deleteImg } = this.props
return(
<Grid item sm={12} md={12} style={{ margin: '30px 0px'}}>
<Paper style={{padding:'20px 20px'}}>
{/* // empty image_title */}
<Typography style={{ padding: '30px 5px', letterSpacing:'8px', textTransform:'uppercase'}} variant="h4" align="center">{img.image_title}</Typography>
<Divider style={{ width: '150px', margin:'10px auto', backgroundColor:'#000000'}} variant="middle" />
<Image image_url={img.img_url} />
<Typography variant="h6" align="center">{img.user.username}</Typography>
<Typography variant="h6" align="center">{moment(img.created_at).calendar()}</Typography>
<Button onClick ={() => this.writeComment(img.id)} variant="outlined" component="span" color="primary">
{this.state.isComment === img.id ? "Close" : "Write A Comment"}
</Button>
{/* here were prevent comments being selected for all items in the array, renders the comment form you clicked on. */}
{this.state.isComment === img.id ?
<Comment onSubmit={this.commentSubmit}
commentBody={this.state.comment_body }
commentChange={this.handleCommentChange}/>
: null}
{/* hide delete button when user enters comment */}
{!this.state.isComment ? <Button style={{margin: '0px 20px'}} onClick={deleteImg} variant="outlined" component="span" color="primary">
Delete
</Button> : null}
</Paper>
</Grid>
)
}
}
export default ImageContainer
Alternatively this works but i don't think the back end reads the comment_body value
import React from "react";
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
const Comment = (props) => {
// <form onSubmit={props.onSubmit}>
return(
<div>
<TextField
type="text"
id="outlined-multiline-static"
label="Write A Comment"
multiline
name="comment_body"
value={props.commentBody}
rows="10"
fullWidth
margin="normal"
variant="outlined"
onChange={props.commentChange}
/>
<Button onClick={props.onSubmit} type="submit" variant="outlined" component="span" color="primary">
Post A Comment
</Button>
</div>
);
// </form>
}
export default Comment;
backend
router.post('/newComment', async (req, res) => {
const comment = new Comment({
comment_body: req.body.comment_body,
user_id: req.user.id
})
comment.save().then( (comment) => {
return res.status(200).json(comment);
})
})
The problem lies with <Button> from Material-ui not being an actual button but rather a combination of a <span>. So if you have a type="submit" the form doesn't do anything.
If you change your material-ui <Button> to a native <button> it works as expected.
Here's an example: https://codesandbox.io/embed/56615445-fj6sc
I have a temporary Drawer with Tabs inside. When the Drawer is open and the color theme is toggled, the custom styles for the component in the tab are removed, breaking the styling. If I close the Drawer or toggle between tabs, it fixes the issue. This is only happening in two of the tabs, not all of them. See the pictures below.
For simplicity, I'll show the code for the Check Results tab. The Cog icon is being stripped of its styling and defaulting to the left.
...
const styles: StyleRulesCallback = () => ({
buttonDiv: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'flex-end',
},
});
...
type Props = StateProps & DispatchProps & WithStyles<typeof styles>;
class CheckResultsPanel extends React.Component<Props> {
...
render() {
this.selectedKeys = [];
const tree = this.renderTree();
const { classes } = this.props;
return (
<div id="checkResultsPanel">
<div className={classes.buttonDiv}>
<IconButton onClick={this.designSettingsClick} aria-label="Design Settings">
<SettingsIcon />
</IconButton>
</div>
<Divider />
{tree}
</div>
);
}
The components in question are being imported to the Drawer like so:
<div className="right-sidebar-container">
<div id="loadCaseObjectTabs" className={classes.root}>
<AppBar position="static">
<Tabs value={this.state.topValue} onChange={this.topHandleChange} fullWidth>
<Tab label="Objects" />
<Tab label="Load Cases" />
</Tabs>
</AppBar>
{topValue === 0 ? <div className={classes.tabs}><NavigationPanel /></div> : undefined}
{topValue === 1 ? <div className={classes.tabs}><LoadCasesPanel /></div> : undefined}
</div>
<div id="checkResultsPropertiesTabs" className={classes.root}>
<AppBar position="static">
<Tabs value={bottomTabIndices[this.props.bottom]} onChange={this.bottomHandleChange} fullWidth>
<Tab label="Check Results" />
<Tab label="Properties" />
</Tabs>
</AppBar>
{this.props.bottom === 'checkResults' ? <div className={classes.tabs}><CheckResultsPanel /></div> : undefined}
{this.props.bottom === 'properties' ? <div className={classes.tabs}><PropertiesPanel /></div> : undefined}
</div>
</div>
We discovered the issue. Apologies for the vague example. The issue was caused by optimization we implemented. The component was only set to update if certain props were changed and the classes were not being passed through this logic. When the themes were toggled, it changed all the styling for the application but since the component did not update the styles were not being applied.
shouldComponentUpdate(nextProps: Props) {
...
if (this.props.classes !== nextProps.classes) return true;
...
}
In Material UI Documentation, they showed how to create an "Upload" button by hiding the input file and then adding the Button component inside the label. (see: https://material-ui.com/demos/buttons/)
Now, I want a different button, so I'm working with ButtonBase but it's not working - file select pop-up doesn't show. I'm not sure if I'm missing anything, maybe I'm missing some parameter to pass it?
<input
accept="image/*"
className="d-none"
id="image-upload"
type="file"
/>
<label htmlFor="image-upload"
className="d-block" >
<Button component="span">
Upload
</Button> {/* working */}
<ButtonBase>
test
</ButtonBase> {/* not working*/}
</label>
ButtonBase API: https://material-ui.com/api/button-base/
First, what version are you running? Material-UI is a very fast project so you need to make sure you're checking the documentation for whatever version you are at.
I favor using explicit events (ref in this case) and this works for me under 3.1.0
<input
ref={'file-upload'}
type='file'
/>
<ButtonBase
onClick={e => {
this.refs['file-upload'].click()
}}
>
<div style={{
color: 'red',
backgroundColor: 'yellow',
border: '1px solid green',
}}
>
Upload!
</div>
</ButtonBase>
<hr />
<Button
type='file'
onClick={e => {
this.refs['file-upload'].click()
}}
>
File Upload Material
</Button>
I use something similar to this in one of my projects and I just hide the <input type='file' /> element.
the same can be implement with hooks
export default function myForm(props) {
const inputEl = React.useRef(null);
const onButtonClick = () => {
console.log("inside")
// `current` points to the mounted file input element
inputEl.current.click();
};
return (
<React.Fragment>
Upload Photos
<br/>
<input
accept="image/*"
className={classes.input}
id="outlined-button-file"
multiple
type="file"
ref={inputEl}
/>
<label htmlFor="outlined-button-file">
<ButtonBases
onClick={()=>onButtonClick()}
/>
</label>
</React.Fragment>
)}
Don't forget to call onClick inside ButtonBasses component.
export default function ButtonBases(props) {
const classes = useStyles();
return (
<div className={classes.root}>
<ButtonBase
...
onClick={props.onClick}
>
....
</ButtonBase>
))}
</div>
);
}