Search code examples
reactjsradio-buttonstyled-components

Radio Buttons with Styled Components for React


I am trying to implement a radio button set in React using Styled components using this pen as an example however the sibling selector is tripping me up. How to turn this

.radio-input:checked ~ .radio-fill {
  width: calc(100% - 4px);
  height: calc(100% - 4px);
  transition: width 0.2s ease-out, height 0.2s ease-out;
}
.radio-input:checked ~ .radio-fill::before {
  opacity: 1;
  transition: opacity 1s ease;
}

into css in a styled component?

Can anyone point out my mistake or make a quick pen demo? Thanks! Here is my full code:

import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { colors } from '../../../theme/vars';

export class RadioGroup extends React.Component {
  getChildContext() {
    const { name, selectedValue, onChange } = this.props;
    return {
      radioGroup: {
        name, selectedValue, onChange,
      },
    };
  }

  render() {
    const { Component, name, selectedValue, onChange, children, ...rest } = this.props;
    return <Component role="radiogroup" {...rest}>{children}</Component>;
  }
}

RadioGroup.childContextTypes = {
  radioGroup: PropTypes.object,
};

RadioGroup.propTypes = {
  name: PropTypes.string,
  selectedValue: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.bool,
  ]),
  children: PropTypes.node.isRequired,
  Component: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.func,
    PropTypes.object,
  ]),
};

RadioGroup.defaultProps = {
  name: '',
  selectedValue: '',
  Component: 'div',
};




// eslint-disable-next-line react/no-multi-comp
export class Radio extends React.Component {
  render() {
    const { name, selectedValue } = this.context.radioGroup;
    const { onChange, value, labelText } = this.props;
    let checked = false;

    if (selectedValue !== undefined) {
      checked = (value === selectedValue);
    }

    console.log('value: ', value);
    console.log('checked: ', checked);
    console.log('selectedValue: ', selectedValue);

    return (
      <Root>
        <Input
          type="radio"
          name={name}
          value={value}
          checked={checked}
          aria-checked={checked}
          onChange={onChange}
        />
        <Fill />
        {/* <div style={{ marginLeft: '25px' }}>{labelText}</div> */}
      </Root>
    );
  }
}

Radio.contextTypes = {
  radioGroup: PropTypes.object,
};

Radio.propTypes = {
  onChange: PropTypes.func,
  value: PropTypes.string,
  labelText: PropTypes.string,
};

Radio.defaultProps = {
  onChange: () => {},
  value: '',
  labelText: '',
};


const Root = styled.div`
  width: ${props => props.size ? props.size : 20}px;
  height: ${props => props.size ? props.size : 20}px;
  position: relative;

  &::before {
    content: '';
    border-radius: 100%;
    border: 1px solid ${props => props.borderColor ? props.borderColor : '#DDD'};
    background: ${props => props.backgroundColor ? props.backgroundColor : '#FAFAFA'};
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    box-sizing: border-box;
    pointer-events: none;
    z-index: 0;
  }
`;

const Fill = styled.div`
  background: ${props => props.fillColor ? props.fillColor : '#A475E4'};
  width: 0;
  height: 0;
  border-radius: 100%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  transition: width 0.2s ease-in, height 0.2s ease-in;
  pointer-events: none;
  z-index: 1;

  &::before {
    content: '';
    opacity: 0;
    width: calc(20px - 4px);
    position: absolute;
    height: calc(20px - 4px);
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    border: 1px solid ${props => props.borderActive ? props.borderActive : '#A475E4'};
    border-radius: 100%;
  }
`;

const Input = styled.input`
  opacity: 0;
  z-index: 2;
  position: absolute;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;

  &:focus {
    outline: none;
  }

  &:checked {
    ${Fill} {
      width: calc(100% - 8px);
      height: calc(100% - 8px);
      transition: width 0.2s ease-out, height 0.2s ease-out;

      &::before {
        opacity: 1;
        transition: opacity 1s ease;
      }
    }

  }
`;

and the usage of the component:

<RadioGroup name="setYAxis" onChange={e => this.toggleSetYAxis(e)} selectedValue={this.state.setYAxis}>
  <Radio value="autoscale" labelText="Autoscale" />
  <Radio value="manual" labelText="Manual" />
</RadioGroup>

Solution

  • I created a codesandbox to fix the css issue and the state management problem. The provided code is simpler and does not rely on an outdated React context API (doc here)