-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #28 from receter/radio-and-checkbox-group
Radio and checkbox group
- Loading branch information
Showing
36 changed files
with
956 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
## Radio Group | ||
|
||
In `HTML`, you can group radio buttons by using the `name` attribute. You can do the same with the `Radio` component in sys42. The simplest way to create a group of radio buttons is as follows: | ||
|
||
```jsx | ||
<> | ||
<Radio name="myGroup" value="option1" onChange={handleChangeGroup} /> Option 1 | ||
<br /> | ||
<Radio name="myGroup" value="option2" onChange={handleChangeGroup} /> Option 2 | ||
</> | ||
``` | ||
|
||
While this works, the labels are not associated with the radio buttons. To associate a label with a radio button, you can either associate a label or use the `LabeledControl` component: | ||
|
||
```jsx | ||
<> | ||
<LabeledControl | ||
control={ | ||
<Radio name="myGroup" value="option1" onChange={handleChangeGroup} /> | ||
} | ||
> | ||
Option 1 | ||
</LabeledControl> | ||
<LabeledControl | ||
control={ | ||
<Radio name="myGroup" value="option2" onChange={handleChangeGroup} /> | ||
} | ||
> | ||
Option 2 | ||
</LabeledControl> | ||
</> | ||
``` | ||
|
||
This will render `label` elements that are properly associated with the radio buttons: | ||
|
||
```html | ||
<label> | ||
<input name="myGroup" type="radio" value="option1" /> | ||
Option 1 | ||
</label> | ||
``` | ||
|
||
### Group Labeling for Accessibility (a11y) | ||
|
||
For accessibility reasons, it is recommended to provide a label for the group of radio buttons. In HTML, one common method is to use a `fieldset` and `legend`: | ||
|
||
```html | ||
<fieldset> | ||
<legend>Choose an option</legend> | ||
<label> | ||
<input name="myGroup" type="radio" value="option1" /> | ||
Option 1 | ||
</label> | ||
<label> | ||
<input name="myGroup" type="radio" value="option2" /> | ||
Option 2 | ||
</label> | ||
</fieldset> | ||
``` | ||
|
||
Another method is to use `role="radiogroup"` along with `aria-labelledby` or `aria-label` attributes: | ||
|
||
```html | ||
<div role="radiogroup" aria-label="Choose an option"> | ||
<label> | ||
<input name="myGroup" type="radio" value="option1" /> | ||
Option 1 | ||
</label> | ||
<label> | ||
<input name="myGroup" type="radio" value="option2" /> | ||
Option 2 | ||
</label> | ||
</div> | ||
``` | ||
|
||
### Using the `RadioGroup` Component | ||
|
||
You can achieve the same output with the `RadioGroup` component: | ||
|
||
```jsx | ||
<RadioGroup | ||
value={selected} | ||
onChangeValue={handleChangeValue} | ||
name="myGroup" | ||
aria-label="Choose an option" | ||
> | ||
<RadioGroupItem value="option1">Option 1</RadioGroupItem> | ||
<RadioGroupItem value="option2">Option 2</RadioGroupItem> | ||
</RadioGroup> | ||
``` | ||
|
||
If you place the `RadioGroup` inside a `fieldset` or the `FormFieldSet` component, you can omit the `aria-label` in this case, as the `fieldset` provides sufficient semantic structure. | ||
|
||
The `name` attribute is not stricly required because the checked state is controlled by React. But a `name` can be set in order to make sure all input elements have the name attribute set. | ||
|
||
```jsx | ||
<FormFieldSet label="Choose an option"> | ||
<RadioGroup value={selected} onChangeValue={handleChangeValue}> | ||
<RadioGroupItem value="option1">Option 1</RadioGroupItem> | ||
<RadioGroupItem value="option2">Option 2</RadioGroupItem> | ||
</RadioGroup> | ||
</FormFieldSet> | ||
``` | ||
|
||
This approach eliminates the need for additional `aria` attributes because the semantics of `fieldset` and `legend` are sufficient. The rendered HTML would look like this: | ||
|
||
```html | ||
<fieldset> | ||
<legend>Choose an option</legend> | ||
<div role="radiogroup"> | ||
<label> | ||
<input type="radio" value="option1" /> | ||
Option 1 | ||
</label> | ||
<label> | ||
<input type="radio" value="option2" /> | ||
Option 2 | ||
</label> | ||
</div> | ||
</fieldset> | ||
``` | ||
|
||
## Checkbox Group | ||
|
||
The `CheckboxGroup` component is similar to the `RadioGroup` component. Here is an example of how to use the `CheckboxGroup` component: | ||
|
||
```jsx | ||
<FormFieldSet label="Choose options"> | ||
<CheckboxGroup | ||
value={checkboxGroupValue} | ||
onChangeValue={handleChangeValue} | ||
onChange={handleChange} | ||
name="myGroup" | ||
> | ||
<CheckboxGroupItem value="option1">Option 1</CheckboxGroupItem> | ||
<CheckboxGroupItem value="option2">Option 2</CheckboxGroupItem> | ||
<CheckboxGroupItem value="option3">Option 3</CheckboxGroupItem> | ||
</CheckboxGroup> | ||
</FormFieldSet> | ||
``` | ||
|
||
This will render a group of checkboxes with a label: | ||
|
||
```html | ||
<fieldset> | ||
<legend>Choose an option</legend> | ||
<div role="group"> | ||
<label> | ||
<input name="myGroup" type="radio" value="option1" /> | ||
Option 1 | ||
</label> | ||
<label> | ||
<input name="myGroup" type="radio" value="option2" /> | ||
Option 2 | ||
</label> | ||
</div> | ||
</fieldset> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { useValue } from "react-cosmos/client"; | ||
|
||
import { | ||
CheckboxGroup, | ||
CheckboxGroupItem, | ||
FormFieldSet, | ||
Stack, | ||
} from "../lib/main"; | ||
|
||
export default function CheckboxGroupFixture() { | ||
const [option1Checked, setOption1Checked] = useValue("option1", { | ||
defaultValue: false, | ||
}); | ||
const [option2Checked, setOption2Checked] = useValue("option2", { | ||
defaultValue: false, | ||
}); | ||
const [option3Checked, setOption3Checked] = useValue("option3", { | ||
defaultValue: false, | ||
}); | ||
|
||
const checkboxGroupValue: string[] = []; | ||
if (option1Checked) { | ||
checkboxGroupValue.push("option1"); | ||
} | ||
if (option2Checked) { | ||
checkboxGroupValue.push("option2"); | ||
} | ||
if (option3Checked) { | ||
checkboxGroupValue.push("option3"); | ||
} | ||
|
||
function handleChangeValue(values: string[]) { | ||
setOption1Checked(values.includes("option1")); | ||
setOption2Checked(values.includes("option2")); | ||
setOption3Checked(values.includes("option3")); | ||
} | ||
|
||
function handleChange(e: React.ChangeEvent<HTMLInputElement>) { | ||
console.log(e); | ||
} | ||
|
||
return ( | ||
<Stack> | ||
<div> | ||
<h2>Checkbox Group</h2> | ||
<CheckboxGroup | ||
value={checkboxGroupValue} | ||
onChangeValue={handleChangeValue} | ||
onChange={handleChange} | ||
aria-label="Choose an option" | ||
> | ||
<CheckboxGroupItem value="option1">Option 1</CheckboxGroupItem> | ||
<CheckboxGroupItem value="option2">Option 2</CheckboxGroupItem> | ||
<CheckboxGroupItem value="option3">Option 3</CheckboxGroupItem> | ||
</CheckboxGroup> | ||
</div> | ||
<div> | ||
<h2>Checkbox Group in Field Set</h2> | ||
<FormFieldSet label="Choose an option"> | ||
<CheckboxGroup | ||
value={checkboxGroupValue} | ||
onChangeValue={handleChangeValue} | ||
onChange={handleChange} | ||
> | ||
<CheckboxGroupItem value="option1">Option 1</CheckboxGroupItem> | ||
<CheckboxGroupItem value="option2">Option 2</CheckboxGroupItem> | ||
<CheckboxGroupItem value="option3">Option 3</CheckboxGroupItem> | ||
</CheckboxGroup> | ||
</FormFieldSet> | ||
</div> | ||
<div> | ||
<h2>Checkbox Group with Text in Between</h2> | ||
<CheckboxGroup | ||
value={checkboxGroupValue} | ||
onChangeValue={handleChangeValue} | ||
onChange={handleChange} | ||
aria-label="Choose an option" | ||
> | ||
<CheckboxGroupItem value="option1">Option 1</CheckboxGroupItem> | ||
<CheckboxGroupItem value="option2">Option 2</CheckboxGroupItem> | ||
<div>Text</div> | ||
<CheckboxGroupItem value="option3">Option 3</CheckboxGroupItem> | ||
</CheckboxGroup> | ||
</div> | ||
</Stack> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { useFixtureSelect } from "react-cosmos/client"; | ||
|
||
import { FormFieldSet, RadioGroup, RadioGroupItem, Stack } from "../lib/main"; | ||
|
||
export default function RadioGroupFixture() { | ||
const [selected, setSelected] = useFixtureSelect("Selected", { | ||
options: ["option1", "option2", "option3"], | ||
defaultValue: "default", | ||
}); | ||
|
||
function handleChangeValue(value: string) { | ||
setSelected(value as "option1" | "option2" | "option3"); | ||
} | ||
|
||
function handleChange(e: React.ChangeEvent<HTMLInputElement>) { | ||
console.log(e); | ||
} | ||
|
||
return ( | ||
<Stack> | ||
<div> | ||
<h2>Radio Group</h2> | ||
<RadioGroup | ||
value={selected} | ||
onChangeValue={handleChangeValue} | ||
onChange={handleChange} | ||
aria-label="Choose an option" | ||
> | ||
<RadioGroupItem value="option1">Option 1</RadioGroupItem> | ||
<RadioGroupItem value="option2">Option 2</RadioGroupItem> | ||
<RadioGroupItem value="option3">Option 3</RadioGroupItem> | ||
</RadioGroup> | ||
</div> | ||
<div> | ||
<h2>Radio Group in Field Set</h2> | ||
<FormFieldSet label="Choose an option"> | ||
<RadioGroup | ||
value={selected} | ||
onChangeValue={handleChangeValue} | ||
onChange={handleChange} | ||
> | ||
<RadioGroupItem value="option1">Option 1</RadioGroupItem> | ||
<RadioGroupItem value="option2">Option 2</RadioGroupItem> | ||
<RadioGroupItem value="option3">Option 3</RadioGroupItem> | ||
</RadioGroup> | ||
</FormFieldSet> | ||
</div> | ||
<div> | ||
<h2>Radio Group with Text in Between</h2> | ||
<RadioGroup | ||
value={selected} | ||
onChangeValue={handleChangeValue} | ||
onChange={handleChange} | ||
aria-label="Choose an option" | ||
> | ||
<RadioGroupItem value="option1">Option 1</RadioGroupItem> | ||
<RadioGroupItem value="option2">Option 2</RadioGroupItem> | ||
<div>Text</div> | ||
<RadioGroupItem value="option3">Option 3</RadioGroupItem> | ||
</RadioGroup> | ||
</div> | ||
</Stack> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { createComponent } from "../helpers"; | ||
|
||
import { renderCheckboxGroup } from "./render"; | ||
import { CheckboxGroupProps, useCheckboxGroup } from "./useCheckboxGroup"; | ||
|
||
export const CheckboxGroup = createComponent<CheckboxGroupProps, "div">( | ||
"div", | ||
(hookOptions) => { | ||
const { elementProps, elementRef, renderArgs } = | ||
useCheckboxGroup(hookOptions); | ||
return ( | ||
<div {...elementProps} ref={elementRef}> | ||
{renderCheckboxGroup(renderArgs)} | ||
</div> | ||
); | ||
}, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { createContext } from "react"; | ||
|
||
export type CheckboxGroupContextType = { | ||
value: string[]; | ||
name?: string; | ||
onChangeCheckbox: (event: React.ChangeEvent<HTMLInputElement>) => void; | ||
}; | ||
|
||
export const CheckboxGroupContext = createContext< | ||
CheckboxGroupContextType | undefined | ||
>(undefined); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import type { | ||
BaseCheckboxGroupProps, | ||
BaseCheckboxGroupRenderArgs, | ||
} from "./useBaseCheckboxGroup"; | ||
import type { CheckboxGroupProps } from "./useCheckboxGroup"; | ||
|
||
export { CheckboxGroup } from "./CheckboxGroup"; | ||
export { useCheckboxGroup } from "./useCheckboxGroup"; | ||
export { useBaseCheckboxGroup } from "./useBaseCheckboxGroup"; | ||
export { renderCheckboxGroup } from "./render"; | ||
export { CheckboxGroupContext } from "./context"; | ||
|
||
export type { | ||
BaseCheckboxGroupProps, | ||
BaseCheckboxGroupRenderArgs, | ||
CheckboxGroupProps, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { CheckboxGroupContext } from "./context"; | ||
import { BaseCheckboxGroupRenderArgs } from "./useBaseCheckboxGroup"; | ||
|
||
export function renderCheckboxGroup(args: BaseCheckboxGroupRenderArgs) { | ||
const { children, ctx } = args; | ||
|
||
return ( | ||
<CheckboxGroupContext.Provider value={ctx}> | ||
{children} | ||
</CheckboxGroupContext.Provider> | ||
); | ||
} |
Oops, something went wrong.