如何创建自定义单选按钮?

时间:2020-05-19 22:13:39

标签: css reactjs sass radio-button

我一直在网上寻找创建自定义复选框和单选按钮的方法。我已经设法创建了一个复选框,但是单选框存在的一个问题是单击它不会激活或触发输入中的onChange调用。我目前有这个:

const customButtons = props => {
 const [isChecked, setChecked] = React.useState(false);

 const toggleCheck = e => {
  setChecked(e.target.checked || !isChecked)
 }

 return (
  <>
   <span className={container}>
    <input type={props.type} checked={isChecked} onChange={e => toggleCheck(e)} id={props.id} />
    <span></span>
   </span>
  </>
 ) 
}

我已经使用css来获得覆盖单选按钮的范围,并制作了原来的单选按钮display: none;,但是当我单击范围圆圈时,它不会触发单击。我在范围中添加了onClick:<span onClick={toggleCheck}>,但这会导致单击两次时取消选中单选按钮。在维持原始行为的同时实现自定义单选按钮的更好方法是什么?

如果这很重要,我也在使用scss。

2 个答案:

答案 0 :(得分:1)

我尝试了您的示例,并使用了toggleCheck登录,并且它被单选和复选框触发。

CustomButtons组件

import React from "react";

const CustomButtons = props => {
    const [isChecked, setChecked] = React.useState(false);

    const toggleCheck = e => {
        console.log(e);
        setChecked(e.target.checked || !isChecked)
    };

    return (
        <>
           <span>
            <input type={props.type} checked={isChecked} onChange={e => toggleCheck(e)} id={props.id}/>
            <span>{props.text}</span>
           </span>
        </>
    )
};

export default CustomButtons

如何在App.js中使用自定义按钮

<CustomButtons type={"radio"} text={"One"}/>
<CustomButtons type={"radio"} text={"Two"}/>

<CustomButtons type={"checkbox"} text={"One"}/>
<CustomButtons type={"checkbox"} text={"Two"}/>

答案 1 :(得分:1)

如果输入未设置为radio,则您的方法同时适用于checkboxesdisplay: none,就像正常输入一样。但是,如果将它们设置为显示:无,则实际上是在UI事件中隐藏它们,因此它们根本不会触发任何点击。


TLDR :更好的方法是,在输入上设置opacity: 0,使用带有htmlFor的标签来触发更改。然后设置标签伪元素的样式,使其看起来像收音机。

此处是指向Live Code Sandbox的链接


由于您未提供样式,因此很难说出您在视觉上如何布置自定义输入。用我的方法,

  • 大多数UI仅在选择一个选项时需要使用radios,而在选择多个选项时则使用checkboxes。也就是说,将状态从单个无线电选项提升到父无线电组组件,然后传递无线电状态变得容易,同时让复选框控制它们各自的状态,因为它们彼此独立。 p>

  • 另一个观察结果是您的收音机缺少name属性(Reason why you were seeing multiple clicks with just fewer or no change at all),使它们彼此不相交。要将它们放置在一个组中,它们需要共享一个公用的name属性,这样,您只需将每个无线电的选项值作为目标即可。

  • 一旦选择了所有没有公共组(无名称属性)的单选选项,就无法在UI上取消选择它们,因此它们不会触发任何进一步的onChange事件。因此,如果不是必需的,建议添加一个重置选项以清除这些选项。

这是每个无线电输入组件的代码。

const RadioInput = ({ name, label, value, isChecked, handleChange }) => {
  const handleRadioChange = e => {
    const { id } = e.currentTarget;
    handleChange(id); // Send back id to radio group for comparison
  };

  return (
    <div>
      {/* Target this input: opacity 0 */}
      <input
        type="radio"
        className="custom-radio"
        name={name}
        id={value} // htlmlFor targets this id.
        checked={isChecked}
        onChange={handleRadioChange}
      />
      <label htmlFor={value}>
        <span>{label}</span>
      </label>
    </div>
  );
};

请参见,通常在编写自定义输入以覆盖本机输入时,如果将label元素作为目标并利用其for的{​​{1}}属性来选择输入会更容易。从以前的工作来看,很难用自定义元素来吸引所有屏幕阅读器,尤其是当您覆盖的本地htmlFor设置为不显示时。

我认为,最好绝对定位它,将其不透明度设置为零,并让标签触发它的变化。

链接到Sandbox here


组件的完整代码

App.js

input

样式



import React, { useState } from "react";
import "./styles.scss";

/*
Let Checkbox the controls its own state.
Styling 'custom-radio', but only make the borders square in .scss file.
*/
const CheckboxInput = ({ name, label }) => {
  const [isChecked, setIsChecked] = useState(false);

  const toggleCheck = e => {
    setIsChecked(() => !isChecked);
  };

  return (
    <div>
      <input
        type="checkbox"
        className="custom-radio"
        name={name}
        id={name}
        checked={isChecked}
        onChange={toggleCheck}
      />
      <label htmlFor={name}>
        <span>{label}</span>
      </label>
    </div>
  );
};

/*
The custom radio input, uses the same styles like the checkbox, and relies on the 
radio group parent for its state.
*/
const RadioInput = ({ name, label, value, isChecked, handleChange }) => {
  const handleRadioChange = e => {
    const { id } = e.currentTarget;
    handleChange(id);
  };

  return (
    <div>
      <input
        type="radio"
        className="custom-radio"
        name={name}
        id={value}
        checked={isChecked}
        onChange={handleRadioChange}
      />
      <label htmlFor={value}>
        <span>{label}</span>
      </label>
    </div>
  );
};

/*
This is what control the radio options. Each radio input has the same name attribute
that way you can have multiple groups on the form.
*/
const RadioGropupInput = () => {
  const [selectedInput, setSelectedInput] = useState("");

  const handleChange = inputValue => {
    setSelectedInput(inputValue);
  };

  return (
    <>
      <div>
        {/*
          You could map these values instead from an array of options
          And an option to clear the selections if they are not mandatory.
          PS: Add aria attributes for accessibility
        */}
        <RadioInput
          name="option"
          value="option-1"
          label="First Choice"
          isChecked={selectedInput === "option-1"}
          handleChange={handleChange}
        />
        <RadioInput
          name="option"
          value="option-2"
          label="Second Choice"
          isChecked={selectedInput === "option-2"}
          handleChange={handleChange}
        />
        <RadioInput
          name="option"
          value="option-3"
          label="Third Choice"
          isChecked={selectedInput === "option-3"}
          handleChange={handleChange}
        />
      </div>
    </>
  );
};

export default () => (
  <div className="App">
    <RadioGropupInput />
    <hr />
    <CheckboxInput name="remember-me" label="Remember Me" />
    <CheckboxInput name="subscribe" label="Subscribe" />
  </div>
);

我再次强调,请不要忘记为自定义输入添加aria属性。 同样,您可以测试live Sandbox