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

[Radio Button] Proposal (Editor's draft) #360

Closed
wants to merge 8 commits into from
4 changes: 2 additions & 2 deletions site/src/components/anatomy-component.css
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@
color: black;
}

#show-slots:not(:checked) ~ .component-anatomy .slot::before {
.show-slots:not(:checked) ~ .component-anatomy .slot::before {
display: none;
}

#show-slots:not(:checked) ~ .component-anatomy .slot {
.show-slots:not(:checked) ~ .component-anatomy .slot {
padding: 0;
margin: 0;
border: none;
Expand Down
6 changes: 3 additions & 3 deletions site/src/components/anatomy-components.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react'
import './anatomy-component.css'

export const AnatomyWrapper = ({ children }) => (
export const AnatomyWrapper = ({ children, name = 'show-slots' }) => (
<div className="component-anatomy-wrapper">
<input type="checkbox" id="show-slots" />
<label htmlFor="show-slots"> Show slots</label>
<input type="checkbox" id={name} className="show-slots" />
<label htmlFor={name}> Show slots</label>
<div className="component-anatomy">{children}</div>
</div>
)
Expand Down
19 changes: 19 additions & 0 deletions site/src/components/radio-anatomy.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react'
import { AnatomyWrapper, Host, Part, Slot } from './anatomy-components'

const RadioAnatomy = () => {
return (
<AnatomyWrapper name="radio">
<Host name="openui-radio">
<Slot name="label">
<Part name="label" />
</Slot>
<Slot name="checked-indicator">
<Part name="checked-indicator" />
</Slot>
</Host>
</AnatomyWrapper>
)
}

export default RadioAnatomy
25 changes: 25 additions & 0 deletions site/src/components/radio-group-anatomy.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react'
import { AnatomyWrapper, Host, Part, Slot } from './anatomy-components'

const RadioGroup = () => {
return (
<AnatomyWrapper name="radio-group">
<Host name="openui-radio-group">
<Slot name="label">
<Part name="label" />
</Slot>
<Slot name="required-indicator">
<Part name="required-indicator" />
</Slot>
<Slot name="choices">
<Part name="choices" />
</Slot>
<Slot name="error-message">
<Part name="error-message" />
</Slot>
</Host>
</AnatomyWrapper>
)
}

export default RadioGroup
188 changes: 188 additions & 0 deletions site/src/pages/components/radio.proposal.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
---
menu: Proposals
name: Radio (Editor's Draft)
path: /components/radio
pathToResearch: /components/radio.research
layout: ../../layouts/ComponentLayout.astro
---

import RadioButtonAnatomy from '../../components/radio-anatomy'
import RadioButtonGroupAnatomy from '../../components/radio-group-anatomy'

## Overview

The `<oui-radio>` element is a control that allows the user to select a single choice out of a set of choices. It has a binary "checked" state, either checked or unchecked (where "checked" is the selected choice, while all the other remain "unchecked".

Because the `<oui-radio>` is a choice from a set of choices, the choices are logically grouped by an implicit or explicit `<oui-radio-group>`. This proposal will deal with the existence of both elements, the different ways they can interact and their properties.

### Use Cases

The `<oui-radio-group>` is used in cases very similar to a `<select>` element, where the user is presented with a set of options from which they can select a single one.

Differently from `<select>`, where all the `<option>` are condensed into a single box, the `<oui-radio-group>` has all the `<oui-radio>` visible, readily available to be read and selected. Choosing between a `<select>` and `<oui-radio-group>` is a matter of layout and information density preference, in most cases.

Also differently from a `<select>`, there is currently no `multiple` attribute to allow a multiple choice group, and this document will not propose one for now.

## Prior Art/Examples

- [Radio input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio)
- [WAI-ARIA Radio](https://www.w3.org/TR/wai-aria-practices/#radiobutton)
- [WHATWG](<https://html.spec.whatwg.org/multipage/input.html#radio-button-state-(type=radio)>)

[Further examples on research](/components/radio.research).

## Radio Design

This section relates to a single `<oui-radio>`.

The radio can have a `disabled` attribute, meaning it's a possible choice, but that is currently unavailable.

If used inside a `<oui-radio-group>`, it gets implicitly bound to the group, becoming one of the choices. If declared outside of it, it should contain a `group` attribute equal to the group's `name` attribute.

It can also have a `required` or a `readonly` attribute. Because standalone radios with such attributes would not make much sense, by placing these attributes on a single `<oui-radio>`, the whole radio group would be understood as `required` or `readonly`, be it either an explicit or implicit group.

### API

#### Properties

| Attribute Name | Type | Default Value | Description |
| ---------------- | -------- | ------------- | ---------------------------------------------------------------------------------------------------- |
| `checked` | `bool` | `false` | Controls whether the radio is checked or unchecked. |
| `defaultChecked` | `bool` | `false` | The initial value for `checked`. Defaults to false. |
| `value` | `string` | `null` | The value of the radio (this is the value of a single possible choice). |
| `autofocus` | `bool` | `false` | Get focus by default. |
| `disabled` | `bool` | `false` | Prevents the user from interacting with the control. Defaults to false. |
| `group` | `string` | `null` | Binds the radio to a radio group. While inside a radio group, it is implicitly defined. |
| `form` | `string` | `null` | Associates the element with a form in the document whose `id` is this value. |
| `readonly` | `bool` | `false` | Makes its group unavailable for user interaction, but the selected choice should still be submitted. |
| `required` | `bool` | `false` | Makes its group required to have a selected choice. |

#### Events

| Event Name | Detail Type | Bubbles | Composed | Cancellable | Dispatch Behavior |
| ---------- | ----------- | ------- | -------- | ----------- | -------------------------------------------------------------- |
| `change` | none | `true` | `true` | `false` | Fired when the radio's `checked` state is changed by the user. |
| `focus` | none | `true` | `true` | `false` | Fired when the radio gets focus. |
| `blur` | none | `true` | `true` | `false` | Fired when the radio loses focus. |

### Anatomy

<RadioButtonAnatomy />

#### DOM Structure

```html
<oui-radio>
<slot name="label">
<label part="label"></label>
</slot>
<slot name="checked-indicator">
<div part="checked-indicator"></div>
</slot>
</oui-radio>
```

#### Slots

| Slot Name | Description | Fallback Content |
| ------------------- | --------------------------------------------------- | ------------------------------------------------ |
| `label` | Add custom markup for the control's label | Empty |
| `checked-indicator` | Content to indicate the radio is in a checked state | Element with checked and indeterminate indcators |

#### CSS Parts

| Slot Name | Description |
| ------------------- | ------------------------------------------------------ |
| `label` | The control's label (and implicit value) |
| `checked-indicator` | Indicates the radio is in a checked or unchecked state |

## Radio Group Design

This section relates to a `<oui-radio-group>`. It can host multiple `<oui-radio>` elements.

### API

#### Properties

| Attribute Name | Type | Default Value | Description |
| -------------- | -------- | ------------- | ------------------------------------------------------------------------------------------------ |
| `name` | `string` | `null` | Represents the name of the control, to be used upon form submission . |
| `value` | `string` | `null` | The value of the selected choice. |
| `autofocus` | `bool` | `false` | Get focus by default. Defaults to the first option, unless explicitly defined among the options. |
| `disabled` | `bool` | `false` | Prevents the user from interacting with any of the choices. Defaults to false. |
| `form` | `string` | `null` | Associates the element with a form in the document whose `id` is this value. |
| `readonly` | `bool` | `false` | Makes it unavailable for user interaction, but the selected choice should still be submitted. |
| `required` | `bool` | `false` | Makes it required to have a selected choice. |

#### Events

...

### Anatomy

<RadioButtonGroupAnatomy />

#### DOM Structure (Implicit Grouping)

The following structure represents a group with the `choices` slot filled with radios (their internal structures are omitted for brevity).

```html
<oui-radio-group name="group-name">
<slot name="label">
<label part="label"></label>
</slot>
<slot name="required-indicator">
<div part="required-indicator"></div>
</slot>
<slot name="choices">
<div part="choices">
<oui-radio />
<oui-radio />
<oui-radio />
...
</div>
</slot>
</oui-radio-group>
```

#### DOM Structure (Explicit Grouping)

The following structure, in contrast, represents a group with an empty `choices` slot. The choices are placed outside the group, providing layout flexibility. Each choice has a `group` attribute with value equal to the radio group's name, making them explicitly bound as choices of that group.

There is also the possibility of having both implicit and explicit choices.

```html
<oui-radio-group name="group-name">
<slot name="label">
<label part="label"></label>
</slot>
<slot name="required-indicator">
<div part="required-indicator"></div>
</slot>
<slot name="choices">
<div part="choices">... (it could also be populated)</div>
</slot>
</oui-radio-group>

<oui-radio group="group-name" />
<oui-radio group="group-name" />
<oui-radio group="group-name" />
```

#### Slots

| Slot Name | Description | Fallback Content |
| -------------------- | ------------------------------------------------------------------------- | ---------------- |
| `label` | Add custom markup for the control's label | Empty |
| `required-indicator` | Content to indicate the radio group is required to have a selected option | '\*' |
| `choices` | Slot for placing the radios | Empty |
| `error-message` | Content of any error messages | Empty |

#### CSS Parts

| Slot Name | Description |
| -------------------- | --------------------------------------------------------------- |
| `label` | The control's label |
| `required-indicator` | Indicates the radio group is required to have a selected option |
| `choices` | Container for the choices |
| `error-message` | Container of any error messages |
Loading