Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Material-UI <Radio /> with a <Button />-like appearance

Using only Material-UI, is there any way to style <Radio /> objects with a <Button /> type of appearance? If not, what would be the most simple alternative?

After spending the past few days reading documentation and experimenting, I don't feel any closer to a solution. Thanks to anyone who can lend some guidance.

Here's my starting point (without the <Button /> experiments) in case anyone would like to use what I'm working with:

import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import RadioGroup from '@material-ui/core/RadioGroup';
import Radio from '@material-ui/core/Radio';
import Button from '@material-ui/core/Button';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormControl from '@material-ui/core/FormControl';

const styles = theme => ({
  textField: {
    marginLeft: theme.spacing.unit,
    marginRight: theme.spacing.unit,
    width: '100%',
  },
  formControl: {
    display: 'flex',
    flexBasis: '100%',
    alignItems: 'center',
  }
});

class QRadios extends React.PureComponent {
  constructor(props, context) {
    super(props, context);
    this.state = {
      value: this.props.value,
    };
  }

  handleChange = event => {
    this.setState({
      value: event.target.value,
    },
      this.props.onChange(event)); 
  };

  render() {
    const { classes } = this.props;

    return (
      <FormControl component="ul" className={classes.formControl} required>
        <RadioGroup
          row={true}
          id={this.props.id}
          name={this.props.name}
          value={this.state.value}
          onChange={this.handleChange}
        >
          <FormControlLabel
            htmlFor={this.props.id}
            value="good"
            control={<Radio />} 
          />
          <FormControlLabel
            htmlFor={this.props.id}
            value="okay"
            control={<Radio />}
          />
          <FormControlLabel
            htmlFor={this.props.id}
            value="bad"
            control={<Radio />} 
          />
          <FormControlLabel
            htmlFor={this.props.id}
            value="na"
            control={<Radio />} 
          />
        </RadioGroup>
      </FormControl>
    );
  }
}

QRadios.propTypes = {
  classes: PropTypes.object.isRequired,
};

export default withStyles(styles)(QRadios);

Update:

For anyone who is using an older version of Material-UI that lacks the built-in solution as per the answer below, you'll have to create your own CSS for the buttons. If you need help, this is how I implemented it:

import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import green from '@material-ui/core/colors/green';
import yellow from '@material-ui/core/colors/yellow';
import red from '@material-ui/core/colors/red';
import grey from '@material-ui/core/colors/grey';
import RadioGroup from '@material-ui/core/RadioGroup';
import Radio from '@material-ui/core/Radio';
import FormControl from '@material-ui/core/FormControl';
import InputLabel from '@material-ui/core/InputLabel';

const styles = theme => ({
  formControl: {
    display: 'flex',
    flexBasis: '100%',
    alignItems: 'stretch',
    margin: theme.spacing.unit * 3 / 2
  },
  formGroup: {
    alignItems: 'stretch',
    justifyContent: 'space-around',
    flexWrap: 'nowrap'
  },
  radioFlex: {
    flex: '1 1 auto',
    margin: '0 5px'
  },
  greenButtonRoot: {
    backgroundColor: '#00000004',
    borderRadius: 5,
    border: '1px solid',
    color: green.A700,
    fontFamily: 'monospace',
    fontSize: '180%',
    height: 32,
    '&$checked': {
      backgroundColor: green.A700,
      color: 'white'
    },
    '&:not($checked):hover': {
      backgroundColor: green['50']
    },
    transition: 'background-color 250ms'
  },
  yellowButtonRoot: {
    backgroundColor: '#00000004',
    borderRadius: 5,
    border: '1px solid',
    color: yellow['700'],
    fontFamily: 'monospace',
    fontSize: '200%',
    height: 32,
    '&$checked': {
      backgroundColor: yellow.A700,
      color: 'white'
    },
    '&:not($checked):hover': {
      backgroundColor: yellow['100']
    },
    transition: 'background-color 250ms'
  },
  redButtonRoot: {
    backgroundColor: '#00000004',
    borderRadius: 5,
    border: '1px solid',
    color: red.A700,
    fontFamily: 'monospace',
    fontSize: '160%',
    height: 32,
    '&$checked': {
      backgroundColor: red.A700,
      color: 'white'
    },
    '&:not($checked):hover': {
      backgroundColor: red['50']
    },
    transition: 'background-color 250ms'
  },
  greyButtonRoot: {
    backgroundColor: '#00000004',
    borderRadius: 5,
    border: '1px solid',
    color: grey['700'],
    fontFamily: 'monospace',
    fontSize: '180%',
    height: 32,
    '&$checked': {
      backgroundColor: grey['700'],
      color: 'white'
    },
    '&:not($checked):hover': {
      backgroundColor: grey['200']
    },
    transition: 'background-color 250ms'
  },
  disabled: {
    backgroundColor: '#00000004'
  },
  checked: {}
});

function QRadios(props) {
  const {
    classes,
    error,
    required,
    id,
    label,
    name,
    binaryChoice,
    value,
    onChange,
    onBlur
  } = props;

  return (
    <FormControl className={classes.formControl} required={required}>
      <InputLabel
        error={error}
        required={required}
        shrink
        style={{
          position: 'relative',
          marginBottom: '10px'
        }}
      >
        {label}
      </InputLabel>
      <RadioGroup
        className={classes.formGroup}
        row

        id={id}
        name={name}

        value={value}
        onChange={onChange}
        onBlur={onBlur}
      >
        <Radio
          htmlFor={id}
          className={classes.radioFlex}

          value="good"
          classes={{
            root: classes.greenButtonRoot,
            checked: classes.checked
          }}
          icon="〇"
          checkedIcon="〇"
        />
        <Radio
          htmlFor={id}
          className={classes.radioFlex}

          value="okay"
          classes={{
            root: classes.yellowButtonRoot,
            checked: classes.checked,
            disabled: classes.disabled
          }}
          icon="△"
          checkedIcon="△"
          disabled={binaryChoice}
        />
        <Radio
          htmlFor={id}
          className={classes.radioFlex}

          value="bad"
          classes={{
            root: classes.redButtonRoot,
            checked: classes.checked
          }}
          icon="✕"
          checkedIcon="✕"
        />
        <Radio
          htmlFor={id}
          className={classes.radioFlex}

          value="na"
          classes={{
            root: classes.greyButtonRoot,
            checked: classes.checked
          }}
          icon="-"
          checkedIcon="-"
        />
      </RadioGroup>
    </FormControl>
  );
}

QRadios.propTypes = {
  classes: PropTypes.object.isRequired,
  required: PropTypes.bool,
  error: PropTypes.bool,
  id: PropTypes.string.isRequired,
  label: PropTypes.string.isRequired,
  name: PropTypes.string.isRequired,
  binaryChoice: PropTypes.bool,
  value: PropTypes.string,
  onChange: PropTypes.func.isRequired,
  onBlur: PropTypes.func
};

QRadios.defaultProps = {
  required: false,
  binaryChoice: false,
  error: false,
  onBlur: null,
  value: ''
};

export default withStyles(styles)(QRadios);

Usage...

<QRadios
    error={!whateverThing.isValid}
    id="whateverThing"
    name="whateverThing"
    label="What's your judgement of whateverThing?"
    value={whateverThing.value}
    onChange={this.someHandlerFunc}
    onBlur={this.someCheckFunc}
/>
like image 653
Warren Halderman Avatar asked Aug 01 '18 05:08

Warren Halderman


2 Answers

Have you looked into https://material-ui.com/components/toggle-button/ or https://material-ui.com/api/toggle-button-group/? I think it's a relatively new addition (i.e. didn't exist when you resolved this), but it might be helpful for anyone who finds this question later.

The ToggleButtonGroup will control the selected state of its child buttons when given its own value prop.

Here's the example given on that page:

      <ToggleButtonGroup
        value={alignment}
        exclusive
        onChange={handleAlignment}
        aria-label="text alignment"
      >
        <ToggleButton value="left" aria-label="left aligned">
          <FormatAlignLeftIcon />
        </ToggleButton>
        <ToggleButton value="center" aria-label="centered">
          <FormatAlignCenterIcon />
        </ToggleButton>
        <ToggleButton value="right" aria-label="right aligned">
          <FormatAlignRightIcon />
        </ToggleButton>
        <ToggleButton value="justify" aria-label="justified" disabled>
          <FormatAlignJustifyIcon />
        </ToggleButton>
      </ToggleButtonGroup>
like image 155
Chase Street Dev Avatar answered Nov 16 '22 18:11

Chase Street Dev


This was you can make a radio button have a button like, i have created a pen for this please check https://codepen.io/praveen-rao-chavan/pen/JBpgLX

<section>
  <h1>Simple material design CSS only radio button example</h1>
  <div>
    <ul  class="donate-now">
    <li class="md-radio">
      <input id="1" type="radio" name="g" checked>
      <label for="1">Option 1</label>
    </li>
    <li class="md-radio">
      <input id="2" type="radio" name="g">
      <label for="2">Option 2</label>
    </li>
      </ul>
  </div>
</section>

CSS

@import url(https://fonts.googleapis.com/css?family=Roboto);

$md-radio-checked-color: rgb(51, 122, 183);
$md-radio-border-color: rgba(0, 0, 0, 0.54);
$md-radio-size: 20px;
$md-radio-checked-size: 10px; 
$md-radio-ripple-size: 15px;

@keyframes ripple {
  0% {
    box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.0);
  }
  50% { 
    box-shadow: 0px 0px 0px $md-radio-ripple-size rgba(0, 0, 0, 0.1);
  }
  100% {
    box-shadow: 0px 0px 0px $md-radio-ripple-size rgba(0, 0, 0, 0);
  }
}

.md-radio {
    margin: 16px 0;

    &.md-radio-inline {
        display: inline-block;
    }

    input[type="radio"] {
        display: none;
        &:checked + label:before {
            --border-color: $md-radio-checked-color;
            --animation: ripple 0.2s linear forwards;   
        }
        &:checked + label:after {
            --transform: scale(1);
        }
    }

    label {
        display: inline-block;
        height:$md-radio-size;
        position: relative;
        padding: 0 ($md-radio-size + 10px);
        margin-bottom: 0;
        cursor: pointer;
        vertical-align: bottom;
        &:before, &:after {
            position: absolute;            
            content: '';  
            border-radius: 50%;
            transition: all .3s ease;
            transition-property: transform, border-color;
        }
        &:before {
            left: 0;
            top: 0;
            width: $md-radio-size;
            height: $md-radio-size;
            border: 2px solid $md-radio-border-color;
        }
        &:after {
            top: $md-radio-size / 2 - $md-radio-checked-size / 2;
            left: $md-radio-size / 2 - $md-radio-checked-size / 2;
            width:$md-radio-checked-size;
            height:$md-radio-checked-size;
            transform: scale(0);
            background:$md-radio-checked-color;
        }
    }
}



// *************************************

// *************************************
*, *:before, *:after {
  box-sizing: border-box;
}

body {
  background:#f0f0f0;
  position: absolute;
  width:100%;
  padding:0;
  margin:0;
  font-family: "Roboto", sans-serif;
  color: #333;
}

section {
  background:white;
  margin:0 auto;
  padding: 4em;
  max-width: 800px;
  h1 {
    margin: 0 0 2em;
  }
}



.donate-now {
     list-style-type:none;
     margin:25px 0 0 0;
     padding:0;
}

.donate-now li {
     float:left;
     margin:0 5px 0 0;
    width:100px;
    height:40px;
    position:relative;
}

.donate-now label, .donate-now input {
    display:block;
    position:absolute;
    top:0;
    left:0;
    right:0;
    bottom:0;
}

.donate-now input[type="radio"] {
    opacity:0.011;
    z-index:100;
}

.donate-now input[type="radio"]:checked + label {
    background:yellow;
}

.donate-now label {
     padding:5px;
     border:1px solid #CCC; 
     cursor:pointer;
    z-index:90;
}

.donate-now label:hover {
     background:#DDD;
}

You can customize it to your styles.

like image 23
Praveen Rao Chavan.G Avatar answered Nov 16 '22 18:11

Praveen Rao Chavan.G