Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Accessible <Radio> and <RadioGroup> #2029

Merged
merged 16 commits into from
Apr 26, 2022
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
sebald marked this conversation as resolved.
Show resolved Hide resolved

### 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