Skip to content

Commit

Permalink
feat: Accessible <Radio> and <RadioGroup> (#2029)
Browse files Browse the repository at this point in the history
Co-authored-by: Timo Zehnle <timo.zehnle@reservix.de>
  • Loading branch information
sebald and ti10le authored Apr 26, 2022
1 parent 37d3cf4 commit bbe8ad9
Show file tree
Hide file tree
Showing 25 changed files with 1,024 additions and 407 deletions.
9 changes: 9 additions & 0 deletions .changeset/calm-keys-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@marigold/docs": minor
"@marigold/components": minor
"@marigold/theme-b2b": minor
"@marigold/theme-core": minor
"@marigold/theme-unicorn": minor
---

feat: Accessible <Radio> and <RadioGroup>
2 changes: 1 addition & 1 deletion docs/content/components/checkbox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { DoAndDont } from '../../src/components/DoAndDont';

# Checkbox

The `Checkbox` components allows users to select one or more options from a list of options. In order to group multiple checkboxes, use the `CheckboxGroup` component.
The `Checkbox` component allows users to select one or more options from a set of options. In order to group multiple checkboxes, use the `CheckboxGroup` component.

<MarigoldTheme>
<Checkbox defaultChecked>I will not talk about Fight Club</Checkbox>
Expand Down
115 changes: 43 additions & 72 deletions docs/content/components/radio.mdx
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
---
title: Radio
figma: https://www.figma.com/file/DFKyTGHAoDxOsUBPszLLxP/%F0%9F%8F%B5%EF%B8%8FMarigold?node-id=467%3A332
---

import { FigmaLink } from '../../src/components/FigmaLink';

# Radio

With the Radio Button component you can add a HTML `<input>` element with `type="radio"` to your form.
The variant can be added with the variant prop. The default variant is `__default`.
The `Radio` and `Radio.Group` components allow users to select one option from a set of options. They are useful for presenting a list of options to users and collecting their responses.

```tsx
<Radio.Group label="Choose desert" defaultValue="apple">
<Radio value="ice cream">🍨 Ice Cream</Radio>
<Radio value="cupcake">🧁 Cupcake</Radio>
<Radio value="cookie" disabled>
🍪 Cookie (sold out)
</Radio>
<Radio value="apple">🍎 Apple</Radio>
</Radio.Group>
```

```tsx onlyCode
import { Radio } from '@marigold/components';
Expand All @@ -21,90 +32,50 @@ import { Radio } from '@marigold/components';

## Props

| Property | Type | Default |
| :------------------------ | :-------- | :---------- |
| `id` | `string` | |
| `variant` (optional) | `string` | `__default` |
| `labelVariant` (optional) | `string` | `inline` |
| `required` (optional) | `boolean` | `false` |
| `disabled` (optional) | `boolean` | `false` |
| `error` (optional) | `boolean` | `false` |
| `errorMessage` (optional) | `string` | |
| Name | Type | Default | Description |
| :----------------- | :-------- | :---------- | :----------------------------------------------------------------------------------------------------------------- |
| `variant` | `string` | | Use a different _variant_ from theme |
| `size` | `string` | `'level-1'` | Use a different _size_ from theme |
| `error` (optional) | `boolean` | `false` | If `true`, the checkbox is considered invalid and if set the `errorMessage` is shown instead of the `description`. |
| ... | | | Yes you can use all regular attributes of `input`! |

## Examples

### Radio standard labeled
### Simple Radio

```tsx
() => {
const [state, setState] = React.useState('Mastercard');
const onChange = changeEvent => {
setState(changeEvent.currentTarget.value);
};
return (
<>
<Radio
id="mc"
name="payment"
onChange={onChange}
checked={state === 'Mastercard'}
value="Mastercard"
>
Mastercard
</Radio>
<br />
<Radio
id="vi"
name="payment"
onChange={onChange}
checked={state === 'Visa'}
value="Visa"
>
Visa
</Radio>
<br />
<Radio
id="ae"
name="payment"
onChange={onChange}
checked={state === 'AmericanExpress'}
value="AmericanExpress"
>
AmericanExpress
</Radio>
</>
);
};
<Radio.Group label="Radio Group">
<Radio value="1">Option 1</Radio>
<Radio value="2">Option 2</Radio>
<Radio value="3" disabled>
Option 3
</Radio>
<Radio value="4">Option 4</Radio>
</Radio.Group>
```

### Radio disabled
### Disabed Radio

```tsx
<>
<Radio id="disabled" disabled>
Disabled
<Radio.Group label="Radio Group" disabled>
<Radio value="1">Option 1</Radio>
<Radio value="2">Option 2</Radio>
<Radio value="3" disabled>
Option 3
</Radio>
<br />
<Radio id="checkedDisabled" checked disabled>
Checked and disabled
</Radio>
</>
<Radio value="4">Option 4</Radio>
</Radio.Group>
```

### Radio with required label

```tsx
<>
<Radio checked id="required" value="required" required>
This label is required
<Radio.Group label="Radio Group" required>
<Radio value="1">Option 1</Radio>
<Radio value="2">Option 2</Radio>
<Radio value="3" disabled>
Option 3
</Radio>
</>
```

### Radio with error and errorMessage

```tsx
<Radio id="error" value="error" error errorMessage="radio error">
This radio has an error
</Radio>
<Radio value="4">Option 4</Radio>
</Radio.Group>
```
3 changes: 3 additions & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@react-aria/listbox": "^3.3.1",
"@react-aria/menu": "^3.4.3",
"@react-aria/overlays": "^3.7.2",
"@react-aria/radio": "^3.1.10",
"@react-aria/select": "^3.4.1",
"@react-aria/separator": "^3.1.3",
"@react-aria/ssr": "^3.1.0",
Expand All @@ -50,13 +51,15 @@
"@react-stately/list": "^3.3.0",
"@react-stately/menu": "^3.2.6",
"@react-stately/overlays": "^3.1.3",
"@react-stately/radio": "^3.3.5",
"@react-stately/select": "^3.1.3",
"@react-stately/table": "^3.1.3",
"@react-stately/toggle": "^3.2.5",
"@react-stately/tooltip": "^3.0.5",
"@react-stately/tree": "^3.2.3",
"@react-types/checkbox": "^3.2.6",
"@react-types/dialog": "^3.3.1",
"@react-types/radio": "^3.1.5",
"@react-types/shared": "^3.8.0",
"@react-types/tooltip": "^3.1.5",
"react-is": "^17.0.2"
Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/Checkbox/Checkbox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const theme = {
},
},
},
sies: {
size: {
large: {
label: {
fontSize: 'large-1',
Expand Down
16 changes: 7 additions & 9 deletions packages/components/src/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,6 @@ export interface CheckboxThemeExtension

// SVG Icon
// ---------------
interface IconProps extends StateAttrProps {
css?: CSSObject;
checked?: boolean;
indeterminate?: boolean;
}

const CheckMark = () => (
<svg viewBox="0 0 12 10">
<path
Expand All @@ -54,6 +48,12 @@ const IndeterminateMark = () => (
</svg>
);

interface IconProps extends StateAttrProps {
css?: CSSObject;
checked?: boolean;
indeterminate?: boolean;
}

const Icon = ({ css, checked, indeterminate, ...props }: IconProps) => {
const icon = indeterminate ? <IndeterminateMark /> : <CheckMark />;
return (
Expand All @@ -68,7 +68,7 @@ const Icon = ({ css, checked, indeterminate, ...props }: IconProps) => {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
p: '1px',
p: 1,
}}
css={css}
{...props}
Expand Down Expand Up @@ -181,7 +181,6 @@ export const Checkbox = ({
return (
<Box
as="label"
variant="checkbox"
__baseCSS={{
display: 'flex',
alignItems: 'center',
Expand All @@ -194,7 +193,6 @@ export const Checkbox = ({
>
<Box
as="input"
type="checkbox"
ref={ref}
css={{
position: 'absolute',
Expand Down
15 changes: 7 additions & 8 deletions packages/components/src/Checkbox/CheckboxGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@ export interface CheckboxGroupContextProps extends CheckboxGroupState {
size?: string;
}

/**
* Needs to be falsy so we can check if a checkbox is used as standalone
* or in a group.
*/
export const CheckboxGroupContext = createContext<CheckboxGroupContextProps>(
/**
* Needs to be falsy so we can check if a checkbox is used as standalone
* or in a group.
*/
null as any
);

export const useCheckboxGroupContext = () => useContext(CheckboxGroupContext);

// Theme Extension
Expand All @@ -43,7 +42,7 @@ export interface CheckboxGroupThemeExtension
interface CheckboxGroupProps
extends Omit<ComponentProps<'div'>, 'onChange'>,
AriaCheckboxGroupProps {
children?: ReactNode;
children: ReactNode;
variant?: string;
size?: string;
label?: ReactNode;
Expand All @@ -68,14 +67,14 @@ export const CheckboxGroup = ({
error,
...rest
}: CheckboxGroupProps) => {
// Adjust props to the react-aria API
const props = {
isRequired: required,
isDisabled: disabled,
isReadOnly: readOnly,
validationState: error ? 'invalid' : 'valid',
...rest,
} as const;

const state = useCheckboxGroupState(props);
const { groupProps, labelProps } = useCheckboxGroup(props, state);

Expand All @@ -97,7 +96,7 @@ export const CheckboxGroup = ({
__baseCSS={{
display: 'flex',
flexDirection: 'column',
alignItems: 'left',
alignItems: 'start',
}}
css={styles.group}
>
Expand Down
60 changes: 19 additions & 41 deletions packages/components/src/Radio/Radio.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,24 @@
import React, { useState } from 'react';
import React from 'react';
import type { Meta, ComponentStory } from '@storybook/react';

import { Radio } from './Radio';
import { Radio } from '@marigold/components';

export default {
title: 'Components/Radio',
parameters: {
actions: {
handles: ['click'],
},
},
argTypes: {
variant: {
label: {
control: {
type: 'text',
},
description: 'Radio variant',
defaultValue: '__default',
description: 'Label',
defaultValue: 'The Label',
},
labelVariant: {
orientation: {
control: {
type: 'text',
type: 'select',
options: ['horizontal', 'vertical'],
},
description: 'Radio label variant',
defaultValue: 'inline',
},
children: {
control: {
type: 'text',
},
description: 'Label',
defaultValue: 'Radio Label',
description: 'Orientation',
},
required: {
control: {
Expand All @@ -53,26 +41,16 @@ export default {
description: 'Error',
defaultValue: false,
},
errorMessage: {
control: {
type: 'text',
},
description: 'Error Message',
},
},
} as Meta;

export const Basic: ComponentStory<typeof Radio> = ({
onChange,
checked,
...args
}) => {
const [isChecked, setChecked] = useState(false);
return (
<Radio
onChange={() => setChecked(!isChecked)}
checked={isChecked}
{...args}
/>
);
};
export const Basic: ComponentStory<typeof Radio.Group> = args => (
<Radio.Group {...args}>
<Radio value="1">Option 1</Radio>
<Radio value="2">Option 2</Radio>
<Radio value="3" disabled>
Option 3
</Radio>
<Radio value="4">Option 4</Radio>
</Radio.Group>
);
Loading

0 comments on commit bbe8ad9

Please sign in to comment.