Радиокнопки со стилизованными компонентами для React - PullRequest
2 голосов
/ 03 июня 2019

Я пытаюсь реализовать переключатель в React, используя стилированные компоненты, используя эту ручку в качестве примера, однако селектор братьев и сестер сбивает меня с толку. Как превратить это

.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;
}

в CSS в стилизованном компоненте?

Кто-нибудь может указать на мою ошибку или сделать быструю демонстрацию ручки? Спасибо! Вот мой полный код:

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;
      }
    }

  }
`;

и использование компонента:

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

Ответы [ 2 ]

1 голос
/ 03 июня 2019

Я создал codesandbox , чтобы исправить проблему css и проблему управления состоянием.Предоставленный код проще и не использует устаревший контекстный API React (здесь документ)

1 голос
/ 03 июня 2019

Вам не хватает ~ перед селектором Fill:

  &: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;
      }
    }

Похоже, у вас также есть проблема с тем, как вы фактически обновляете состояние в этом примере, но это не связано со стилем: на RadioGroup вы не передаете onChange опору до Radio. То, как оператор распространения делает так, что ваше const rest включает только свойства, которые вы еще не определили внутри const. Поэтому вам нужно удалить оттуда объявление onChange, чтобы оно вошло в rest.

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

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

Рабочий пример: https://codesandbox.io/s/serene-cdn-1fw1i

...