mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 04:09:12 +00:00
add radio button; refactor radio group
This commit is contained in:
@@ -49,6 +49,7 @@
|
||||
- [Progress Ring](/components/progress-ring)
|
||||
- [QR Code](/components/qr-code)
|
||||
- [Radio](/components/radio)
|
||||
- [Radio Button](/components/radio-button)
|
||||
- [Radio Group](/components/radio-group)
|
||||
- [Range](/components/range)
|
||||
- [Rating](/components/rating)
|
||||
|
||||
381
docs/components/radio-button.md
Normal file
381
docs/components/radio-button.md
Normal file
@@ -0,0 +1,381 @@
|
||||
# Radio Button
|
||||
|
||||
[component-header:sl-radio-button]
|
||||
|
||||
Radios buttons allow the user to select a single option from a group using a button-like control.
|
||||
|
||||
Radio buttons are designed to be used with [radio groups](/components/radio-group). When a radio button has focus, the arrow keys can be used to change the selected option just like standard radio controls.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button name="a" value="1" checked>Option 1</sl-radio-button>
|
||||
<sl-radio-button name="a" value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button name="a" value="3">Option 3</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton name="option" value="1" checked>
|
||||
Option 1
|
||||
</SlRadioButton>
|
||||
<SlRadioButton name="option" value="2">
|
||||
Option 2
|
||||
</SlRadioButton>
|
||||
<SlRadioButton name="option" value="3">
|
||||
Option 3
|
||||
</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
);
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Checked
|
||||
|
||||
To set the initial checked state, use the `checked` attribute.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button name="option" value="1" checked>Option 1</sl-radio-button>
|
||||
<sl-radio-button name="option" value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button name="option" value="3">Option 3</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton name="option" value="1" checked>
|
||||
Option 1
|
||||
</SlRadioButton>
|
||||
<SlRadioButton name="option" value="2">
|
||||
Option 2
|
||||
</SlRadioButton>
|
||||
<SlRadioButton name="option" value="3">
|
||||
Option 3
|
||||
</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
);
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` attribute to disable a radio button.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button name="option" value="1" checked>Option 1</sl-radio-button>
|
||||
<sl-radio-button name="option" value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button name="option" value="3" disabled>Option 3</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton name="option" value="1" checked>
|
||||
Option 1
|
||||
</SlRadioButton>
|
||||
<SlRadioButton name="option" value="2">
|
||||
Option 2
|
||||
</SlRadioButton>
|
||||
<SlRadioButton name="option" value="3" disabled>
|
||||
Option 3
|
||||
</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
);
|
||||
```
|
||||
|
||||
### Variants
|
||||
|
||||
Use the `variant` attribute to set the button's variant.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button variant="default" name="option" value="1" checked>Option 1</sl-radio-button>
|
||||
<sl-radio-button variant="default" name="option" value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button variant="default" name="option" value="3">Option 3</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
|
||||
<br />
|
||||
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button variant="primary" name="option" value="1" checked>Option 1</sl-radio-button>
|
||||
<sl-radio-button variant="primary" name="option" value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button variant="primary" name="option" value="3">Option 3</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
|
||||
<br />
|
||||
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button variant="success" name="option" value="1" checked>Option 1</sl-radio-button>
|
||||
<sl-radio-button variant="success" name="option" value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button variant="success" name="option" value="3">Option 3</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
|
||||
<br />
|
||||
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button variant="neutral" name="option" value="1" checked>Option 1</sl-radio-button>
|
||||
<sl-radio-button variant="neutral" name="option" value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button variant="neutral" name="option" value="3">Option 3</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
|
||||
<br />
|
||||
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button variant="warning" name="option" value="1" checked>Option 1</sl-radio-button>
|
||||
<sl-radio-button variant="warning" name="option" value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button variant="warning" name="option" value="3">Option 3</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
|
||||
<br />
|
||||
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button variant="success" name="option" value="1" checked>Option 1</sl-radio-button>
|
||||
<sl-radio-button variant="success" name="option" value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button variant="success" name="option" value="3">Option 3</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton variant="default" name="option" value="1" checked>Option 1</SlRadioButton>
|
||||
<SlRadioButton variant="default" name="option" value="2">Option 2</SlRadioButton>
|
||||
<SlRadioButton variant="default" name="option" value="3">Option 3</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
|
||||
<br />
|
||||
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton variant="primary" name="option" value="1" checked>Option 1</SlRadioButton>
|
||||
<SlRadioButton variant="primary" name="option" value="2">Option 2</SlRadioButton>
|
||||
<SlRadioButton variant="primary" name="option" value="3">Option 3</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
|
||||
<br />
|
||||
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton variant="success" name="option" value="1" checked>Option 1</SlRadioButton>
|
||||
<SlRadioButton variant="success" name="option" value="2">Option 2</SlRadioButton>
|
||||
<SlRadioButton variant="success" name="option" value="3">Option 3</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
|
||||
<br />
|
||||
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton variant="neutral" name="option" value="1" checked>Option 1</SlRadioButton>
|
||||
<SlRadioButton variant="neutral" name="option" value="2">Option 2</SlRadioButton>
|
||||
<SlRadioButton variant="neutral" name="option" value="3">Option 3</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
|
||||
<br />
|
||||
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton variant="warning" name="option" value="1" checked>Option 1</SlRadioButton>
|
||||
<SlRadioButton variant="warning" name="option" value="2">Option 2</SlRadioButton>
|
||||
<SlRadioButton variant="warning" name="option" value="3">Option 3</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
|
||||
<br />
|
||||
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton variant="success" name="option" value="1" checked>Option 1</SlRadioButton>
|
||||
<SlRadioButton variant="success" name="option" value="2">Option 2</SlRadioButton>
|
||||
<SlRadioButton variant="success" name="option" value="3">Option 3</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
);
|
||||
```
|
||||
|
||||
### Sizes
|
||||
|
||||
Use the `size` attribute to change a radio button's size.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button size="small" name="option" value="1" checked>Option 1</sl-radio-button>
|
||||
<sl-radio-button size="small" name="option" value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button size="small" name="option" value="3">Option 3</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
|
||||
<br />
|
||||
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button size="medium" name="option" value="1" checked>Option 1</sl-radio-button>
|
||||
<sl-radio-button size="medium" name="option" value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button size="medium" name="option" value="3">Option 3</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
|
||||
<br />
|
||||
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button size="large" name="option" value="1" checked>Option 1</sl-radio-button>
|
||||
<sl-radio-button size="large" name="option" value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button size="large" name="option" value="3">Option 3</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton size="small" name="option" value="1" checked>Option 1</SlRadioButton>
|
||||
<SlRadioButton size="small" name="option" value="2">Option 2</SlRadioButton>
|
||||
<SlRadioButton size="small" name="option" value="3">Option 3</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
|
||||
<br />
|
||||
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton size="medium" name="option" value="1" checked>Option 1</SlRadioButton>
|
||||
<SlRadioButton size="medium" name="option" value="2">Option 2</SlRadioButton>
|
||||
<SlRadioButton size="medium" name="option" value="3">Option 3</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
|
||||
<br />
|
||||
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton size="large" name="option" value="1" checked>Option 1</SlRadioButton>
|
||||
<SlRadioButton size="large" name="option" value="2">Option 2</SlRadioButton>
|
||||
<SlRadioButton size="large" name="option" value="3">Option 3</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
);
|
||||
```
|
||||
|
||||
### Pill Buttons
|
||||
|
||||
Use the `pill` attribute to give radio buttons rounded edges.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button pill size="small" name="option" value="1" checked>Option 1</sl-radio-button>
|
||||
<sl-radio-button pill size="small" name="option" value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button pill size="small" name="option" value="3">Option 3</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
|
||||
<br />
|
||||
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button pill size="medium" name="option" value="1" checked>Option 1</sl-radio-button>
|
||||
<sl-radio-button pill size="medium" name="option" value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button pill size="medium" name="option" value="3">Option 3</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
|
||||
<br />
|
||||
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button pill size="large" name="option" value="1" checked>Option 1</sl-radio-button>
|
||||
<sl-radio-button pill size="large" name="option" value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button pill size="large" name="option" value="3">Option 3</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton pill size="small" name="option" value="1" checked>Option 1</SlRadioButton>
|
||||
<SlRadioButton pill size="small" name="option" value="2">Option 2</SlRadioButton>
|
||||
<SlRadioButton pill size="small" name="option" value="3">Option 3</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
|
||||
<br />
|
||||
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton pill size="medium" name="option" value="1" checked>Option 1</SlRadioButton>
|
||||
<SlRadioButton pill size="medium" name="option" value="2">Option 2</SlRadioButton>
|
||||
<SlRadioButton pill size="medium" name="option" value="3">Option 3</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
|
||||
<br />
|
||||
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton pill size="large" name="option" value="1" checked>Option 1</SlRadioButton>
|
||||
<SlRadioButton pill size="large" name="option" value="2">Option 2</SlRadioButton>
|
||||
<SlRadioButton pill size="large" name="option" value="3">Option 3</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
);
|
||||
```
|
||||
|
||||
### Prefix and Suffix Icons
|
||||
|
||||
Use the `prefix` and `suffix` slots to add icons.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button name="a" value="1" checked>
|
||||
<sl-icon slot="prefix" name="archive"></sl-icon>
|
||||
Option 1
|
||||
</sl-radio-button>
|
||||
|
||||
<sl-radio-button name="a" value="2">
|
||||
<sl-icon slot="suffix" name="bag"></sl-icon>
|
||||
Option 2
|
||||
</sl-radio-button>
|
||||
|
||||
<sl-radio-button name="a" value="3">
|
||||
<sl-icon slot="prefix" name="gift"></sl-icon>
|
||||
<sl-icon slot="suffix" name="cart"></sl-icon>
|
||||
Option 3
|
||||
</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlIcon, SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton name="a" value="1" checked>
|
||||
<SlIcon slot="prefix" name="archive" />
|
||||
Option 1
|
||||
</SlRadioButton>
|
||||
|
||||
<SlRadioButton name="a" value="2">
|
||||
<SlIcon slot="suffix" name="bag" />
|
||||
Option 2
|
||||
</SlRadioButton>
|
||||
|
||||
<SlRadioButton name="a" value="3">
|
||||
<SlIcon slot="prefix" name="gift" />
|
||||
<SlIcon slot="suffix" name="cart" />
|
||||
Option 3
|
||||
</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
);
|
||||
```
|
||||
|
||||
### Buttons with Icons
|
||||
|
||||
You can omit button labels and use icons instead. Make sure to set a `label` attribute on each icon so screen readers will announce each option correctly.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button name="a" value="1" checked>
|
||||
<sl-icon name="emoji-frown" label="Sad"></sl-icon>
|
||||
</sl-radio-button>
|
||||
|
||||
<sl-radio-button name="a" value="2">
|
||||
<sl-icon name="emoji-neutral" label="Neutral"></sl-icon>
|
||||
</sl-radio-button>
|
||||
|
||||
<sl-radio-button name="a" value="3">
|
||||
<sl-icon name="emoji-smile" label="Happy"></sl-icon>
|
||||
</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
[component-metadata:sl-radio-button]
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[component-header:sl-radio-group]
|
||||
|
||||
Radio Groups are used to group multiple radios so they function as a single control.
|
||||
Radio groups are used to group multiple [radios](/components/radio) or [radio buttons](/components/radio-button) so they function as a single form control.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option">
|
||||
@@ -32,9 +32,9 @@ const App = () => (
|
||||
|
||||
## Examples
|
||||
|
||||
### Showing the Fieldset
|
||||
### Showing the Label
|
||||
|
||||
You can show a fieldset and legend that wraps the radio group using the `fieldset` attribute.
|
||||
You can show the fieldset and legend that wraps the radio group using the `fieldset` attribute. If you don't use this option, you should still provide a label so screen readers announce the control correctly.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option" fieldset>
|
||||
@@ -62,4 +62,34 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### Radio Buttons
|
||||
|
||||
[Radio buttons](/components/radio-button) offer an alternate way to display radio controls. In this case, an internal [button group](/components/button-group) is used to group the buttons into a single, cohesive control.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio-button name="option" value="1" checked>Option 1</sl-radio-button>
|
||||
<sl-radio-button name="option" value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button name="option" value="3">Option 3</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadioButton name="option" value="1" checked>
|
||||
Option 1
|
||||
</SlRadioButton>
|
||||
<SlRadioButton name="option" value="2">
|
||||
Option 2
|
||||
</SlRadioButton>
|
||||
<SlRadioButton name="option" value="3">
|
||||
Option 3
|
||||
</SlRadioButton>
|
||||
</SlRadioGroup>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-radio-group]
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
[component-header:sl-radio]
|
||||
|
||||
Radios allow the user to select one option from a group of many.
|
||||
Radios allow the user to select a single option from a group.
|
||||
|
||||
Radios are designed to be used with [radio groups](/components/radio-group). As such, all of the examples on this page utilize them to demonstrate their correct usage.
|
||||
Radios are designed to be used with [radio groups](/components/radio-group).
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option">
|
||||
@@ -36,16 +36,15 @@ const App = () => (
|
||||
|
||||
## Examples
|
||||
|
||||
### Disabled
|
||||
### Checked
|
||||
|
||||
Use the `disabled` attribute to disable a radio.
|
||||
To set the initial checked state, use the `checked` attribute.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio name="option" value="1" checked>Option 1</sl-radio>
|
||||
<sl-radio name="option" value="2">Option 2</sl-radio>
|
||||
<sl-radio name="option" value="3">Option 3</sl-radio>
|
||||
<sl-radio name="option" value="4" disabled>Disabled</sl-radio>
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
@@ -63,8 +62,35 @@ const App = () => (
|
||||
<SlRadio name="option" value="3">
|
||||
Option 3
|
||||
</SlRadio>
|
||||
<SlRadio name="option" value="4" disabled>
|
||||
Disabled
|
||||
</SlRadioGroup>
|
||||
);
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` attribute to disable a radio.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio name="option" value="1" checked>Option 1</sl-radio>
|
||||
<sl-radio name="option" value="2">Option 2</sl-radio>
|
||||
<sl-radio name="option" value="3" disabled>Option 3</sl-radio>
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadio name="option" value="1" checked>
|
||||
Option 1
|
||||
</SlRadio>
|
||||
<SlRadio name="option" value="2">
|
||||
Option 2
|
||||
</SlRadio>
|
||||
<SlRadio name="option" value="3" disabled>
|
||||
Option 3
|
||||
</SlRadio>
|
||||
</SlRadioGroup>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,8 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
|
||||
|
||||
## Next
|
||||
|
||||
- Added the experimental `<sl-radio-button>` component
|
||||
- Added `button-group` and `button-group__base` parts to `<sl-radio-group>`
|
||||
- Fixed a bug that prevented form submission from working as expected in some cases
|
||||
- Fixed a bug that prevented `<sl-split-panel>` from toggling `vertical` properly [#703](https://github.com/shoelace-style/shoelace/issues/703)
|
||||
- Fixed a bug that prevented `<sl-color-picker>` from rendering a color initially [#704](https://github.com/shoelace-style/shoelace/issues/704)
|
||||
@@ -125,7 +127,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
|
||||
|
||||
- 🚨 BREAKING: changed the `type` attribute to `variant` in `<sl-alert>`, `<sl-badge>`, `<sl-button>`, and `<sl-tag>` since it's more appropriate and to disambiguate from other `type` attributes
|
||||
- 🚨 BREAKING: removed `base` part from `<sl-divider>` to simplify the styling API
|
||||
- Added experimental `<sl-split-panel>` component
|
||||
- Added the experimental `<sl-split-panel>` component
|
||||
- Added `focus()` and `blur()` methods to `<sl-select>` [#625](https://github.com/shoelace-style/shoelace/pull/625)
|
||||
- Fixed a bug where setting `tooltipFormatter` on `<sl-range>` in JSX causes React@experimental to error out
|
||||
- Fixed a bug where clicking on a slotted icon in `<sl-button>` wouldn't submit forms [#626](https://github.com/shoelace-style/shoelace/issues/626)
|
||||
|
||||
@@ -2,6 +2,8 @@ import { LitElement, html } from 'lit';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import styles from './button-group.styles';
|
||||
|
||||
const BUTTON_CHILDREN = ['sl-button', 'sl-radio-button'];
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
* @status stable
|
||||
@@ -75,7 +77,7 @@ export default class SlButtonGroup extends LitElement {
|
||||
}
|
||||
|
||||
function findButton(el: HTMLElement) {
|
||||
return el.tagName.toLowerCase() === 'sl-button' ? el : el.querySelector('sl-button');
|
||||
return BUTTON_CHILDREN.includes(el.tagName.toLowerCase()) ? el : el.querySelector(BUTTON_CHILDREN.join(','));
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -26,8 +26,8 @@ export default css`
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
padding: 0;
|
||||
transition: var(--sl-transition-fast) background-color, var(--sl-transition-fast) color,
|
||||
var(--sl-transition-fast) border, var(--sl-transition-fast) box-shadow;
|
||||
transition: var(--sl-transition-x-fast) background-color, var(--sl-transition-x-fast) color,
|
||||
var(--sl-transition-x-fast) border, var(--sl-transition-x-fast) box-shadow;
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
@@ -235,7 +235,8 @@ export default css`
|
||||
color: var(--sl-color-neutral-700);
|
||||
}
|
||||
|
||||
.button--outline.button--default:hover:not(.button--disabled) {
|
||||
.button--outline.button--default:hover:not(.button--disabled),
|
||||
.button--outline.button--default.button--checked:not(.button--disabled) {
|
||||
border-color: var(--sl-color-primary-600);
|
||||
background-color: var(--sl-color-primary-600);
|
||||
color: var(--sl-color-neutral-0);
|
||||
@@ -258,7 +259,8 @@ export default css`
|
||||
color: var(--sl-color-primary-600);
|
||||
}
|
||||
|
||||
.button--outline.button--primary:hover:not(.button--disabled) {
|
||||
.button--outline.button--primary:hover:not(.button--disabled),
|
||||
.button--outline.button--primary.button--checked:not(.button--disabled) {
|
||||
background-color: var(--sl-color-primary-600);
|
||||
color: var(--sl-color-neutral-0);
|
||||
}
|
||||
@@ -280,7 +282,8 @@ export default css`
|
||||
color: var(--sl-color-success-600);
|
||||
}
|
||||
|
||||
.button--outline.button--success:hover:not(.button--disabled) {
|
||||
.button--outline.button--success:hover:not(.button--disabled),
|
||||
.button--outline.button--success.button--checked:not(.button--disabled) {
|
||||
background-color: var(--sl-color-success-600);
|
||||
color: var(--sl-color-neutral-0);
|
||||
}
|
||||
@@ -302,7 +305,8 @@ export default css`
|
||||
color: var(--sl-color-neutral-600);
|
||||
}
|
||||
|
||||
.button--outline.button--neutral:hover:not(.button--disabled) {
|
||||
.button--outline.button--neutral:hover:not(.button--disabled),
|
||||
.button--outline.button--neutral.button--checked:not(.button--disabled) {
|
||||
background-color: var(--sl-color-neutral-600);
|
||||
color: var(--sl-color-neutral-0);
|
||||
}
|
||||
@@ -324,7 +328,8 @@ export default css`
|
||||
color: var(--sl-color-warning-600);
|
||||
}
|
||||
|
||||
.button--outline.button--warning:hover:not(.button--disabled) {
|
||||
.button--outline.button--warning:hover:not(.button--disabled),
|
||||
.button--outline.button--warning.button--checked:not(.button--disabled) {
|
||||
background-color: var(--sl-color-warning-600);
|
||||
color: var(--sl-color-neutral-0);
|
||||
}
|
||||
@@ -346,7 +351,8 @@ export default css`
|
||||
color: var(--sl-color-danger-600);
|
||||
}
|
||||
|
||||
.button--outline.button--danger:hover:not(.button--disabled) {
|
||||
.button--outline.button--danger:hover:not(.button--disabled),
|
||||
.button--outline.button--danger.button--checked:not(.button--disabled) {
|
||||
background-color: var(--sl-color-danger-600);
|
||||
color: var(--sl-color-neutral-0);
|
||||
}
|
||||
|
||||
@@ -126,8 +126,6 @@ describe('<sl-button>', () => {
|
||||
const button = el.querySelector<SlButton>('sl-button')!;
|
||||
const handleSubmit = sinon.spy((event: SubmitEvent) => event.preventDefault());
|
||||
|
||||
console.log(form, button);
|
||||
|
||||
form.addEventListener('submit', handleSubmit);
|
||||
button.click();
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@ import styles from './button.styles';
|
||||
* @slot suffix - Used to append an icon or similar element to the button.
|
||||
*
|
||||
* @csspart base - The component's internal wrapper.
|
||||
* @csspart prefix - The prefix container.
|
||||
* @csspart prefix - The prefix slot's container.
|
||||
* @csspart label - The button's label.
|
||||
* @csspart suffix - The suffix container.
|
||||
* @csspart suffix - The suffix slot's container.
|
||||
* @csspart caret - The button's caret.
|
||||
*/
|
||||
@customElement('sl-button')
|
||||
|
||||
10
src/components/radio-button/radio-button.styles.ts
Normal file
10
src/components/radio-button/radio-button.styles.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '~/styles/component.styles';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
99
src/components/radio-button/radio-button.test.ts
Normal file
99
src/components/radio-button/radio-button.test.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
import type SlRadioGroup from '~/components/radio-group/radio-group';
|
||||
import type SlRadioButton from './radio-button';
|
||||
|
||||
describe('<sl-radio-button>', () => {
|
||||
it('should be disabled with the disabled attribute', async () => {
|
||||
const el = await fixture<SlRadioButton>(html` <sl-radio-button disabled></sl-radio-button> `);
|
||||
|
||||
expect(el.input.disabled).to.be.true;
|
||||
});
|
||||
|
||||
it('should be valid by default', async () => {
|
||||
const el = await fixture<SlRadioButton>(html` <sl-radio-button></sl-radio-button> `);
|
||||
|
||||
expect(el.invalid).to.be.false;
|
||||
});
|
||||
|
||||
it('should fire sl-change when clicked', async () => {
|
||||
const el = await fixture<SlRadioButton>(html` <sl-radio-button></sl-radio-button> `);
|
||||
setTimeout(() => el.input.click());
|
||||
const event = await oneEvent(el, 'sl-change');
|
||||
expect(event.target).to.equal(el);
|
||||
expect(el.checked).to.be.true;
|
||||
});
|
||||
|
||||
it('should fire sl-change when toggled via keyboard - space', async () => {
|
||||
const el = await fixture<SlRadioButton>(html` <sl-radio-button></sl-radio-button> `);
|
||||
el.input.focus();
|
||||
setTimeout(() => sendKeys({ press: ' ' }));
|
||||
const event = await oneEvent(el, 'sl-change');
|
||||
expect(event.target).to.equal(el);
|
||||
expect(el.checked).to.be.true;
|
||||
});
|
||||
|
||||
it('should fire sl-change when toggled via keyboard - arrow key', async () => {
|
||||
const radioGroup = await fixture<SlRadioGroup>(html`
|
||||
<sl-radio-group>
|
||||
<sl-radio-button id="radio-1"></sl-radio-button>
|
||||
<sl-radio-button id="radio-2"></sl-radio-button>
|
||||
</sl-radio-group>
|
||||
`);
|
||||
const radio1 = radioGroup.querySelector<SlRadioButton>('#radio-1')!;
|
||||
const radio2 = radioGroup.querySelector<SlRadioButton>('#radio-2')!;
|
||||
radio1.input.focus();
|
||||
setTimeout(() => sendKeys({ press: 'ArrowRight' }));
|
||||
const event = await oneEvent(radio2, 'sl-change');
|
||||
expect(event.target).to.equal(radio2);
|
||||
expect(radio2.checked).to.be.true;
|
||||
});
|
||||
|
||||
it('should not get checked when disabled', async () => {
|
||||
const radioGroup = await fixture<SlRadioGroup>(html`
|
||||
<sl-radio-group>
|
||||
<sl-radio-button checked></sl-radio-button>
|
||||
<sl-radio-button disabled></sl-radio-button>
|
||||
</sl-radio-group>
|
||||
`);
|
||||
const radio1 = radioGroup.querySelector<SlRadioButton>('sl-radio-button[checked]')!;
|
||||
const radio2 = radioGroup.querySelector<SlRadioButton>('sl-radio-button[disabled]')!;
|
||||
|
||||
radio2.click();
|
||||
await Promise.all([radio1.updateComplete, radio2.updateComplete]);
|
||||
|
||||
expect(radio1.checked).to.be.true;
|
||||
expect(radio2.checked).to.be.false;
|
||||
});
|
||||
|
||||
describe('when submitting a form', () => {
|
||||
it('should submit the correct value', async () => {
|
||||
const form = await fixture<HTMLFormElement>(html`
|
||||
<form>
|
||||
<sl-radio-group>
|
||||
<sl-radio-button id="radio-1" name="a" value="1" checked></sl-radio-button>
|
||||
<sl-radio-button id="radio-2" name="a" value="2"></sl-radio-button>
|
||||
<sl-radio-button id="radio-2" name="a" value="3"></sl-radio-button>
|
||||
</sl-radio-group>
|
||||
<sl-button type="submit">Submit</sl-button>
|
||||
</form>
|
||||
`);
|
||||
const button = form.querySelector('sl-button')!;
|
||||
const radio = form.querySelectorAll('sl-radio-button')[1]!;
|
||||
const submitHandler = sinon.spy((event: SubmitEvent) => {
|
||||
formData = new FormData(form);
|
||||
event.preventDefault();
|
||||
});
|
||||
let formData: FormData;
|
||||
|
||||
form.addEventListener('submit', submitHandler);
|
||||
radio.click();
|
||||
button.click();
|
||||
|
||||
await waitUntil(() => submitHandler.calledOnce);
|
||||
|
||||
expect(formData!.get('a')).to.equal('2');
|
||||
});
|
||||
});
|
||||
});
|
||||
100
src/components/radio-button/radio-button.ts
Normal file
100
src/components/radio-button/radio-button.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import styles from '~/components/button/button.styles';
|
||||
import RadioBase from '~/internal/radio';
|
||||
import { HasSlotController } from '~/internal/slot';
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
* @status stable
|
||||
*
|
||||
* @event sl-blur - Emitted when the button loses focus.
|
||||
* @event sl-focus - Emitted when the button gains focus.
|
||||
*
|
||||
* @slot - The button's label.
|
||||
* @slot prefix - Used to prepend an icon or similar element to the button.
|
||||
* @slot suffix - Used to append an icon or similar element to the button.
|
||||
*
|
||||
* @csspart base - The component's internal wrapper.
|
||||
* @csspart prefix - The prefix slot's container.
|
||||
* @csspart label - The button's label.
|
||||
* @csspart suffix - The suffix slot's container.
|
||||
*/
|
||||
@customElement('sl-radio-button')
|
||||
export default class SlRadioButton extends RadioBase {
|
||||
static styles = styles;
|
||||
|
||||
private readonly hasSlotController = new HasSlotController(this, '[default]', 'prefix', 'suffix');
|
||||
|
||||
/** The button's variant. */
|
||||
@property({ reflect: true }) variant: 'default' | 'primary' | 'success' | 'neutral' | 'warning' | 'danger' =
|
||||
'default';
|
||||
|
||||
/** The button's size. */
|
||||
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
|
||||
|
||||
/**
|
||||
* This will be true when the control is in an invalid state. Validity in range inputs is determined by the message
|
||||
* provided by the `setCustomValidity` method.
|
||||
*/
|
||||
@property({ type: Boolean, reflect: true }) invalid = false;
|
||||
|
||||
/** Draws a pill-style button with rounded edges. */
|
||||
@property({ type: Boolean, reflect: true }) pill = false;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<button
|
||||
part="base"
|
||||
class=${classMap({
|
||||
button: true,
|
||||
'button--default': this.variant === 'default',
|
||||
'button--primary': this.variant === 'primary',
|
||||
'button--success': this.variant === 'success',
|
||||
'button--neutral': this.variant === 'neutral',
|
||||
'button--warning': this.variant === 'warning',
|
||||
'button--danger': this.variant === 'danger',
|
||||
'button--small': this.size === 'small',
|
||||
'button--medium': this.size === 'medium',
|
||||
'button--large': this.size === 'large',
|
||||
'button--checked': this.checked,
|
||||
'button--disabled': this.disabled,
|
||||
'button--focused': this.hasFocus,
|
||||
'button--outline': true,
|
||||
'button--pill': this.pill,
|
||||
'button--has-label': this.hasSlotController.test('[default]'),
|
||||
'button--has-prefix': this.hasSlotController.test('prefix'),
|
||||
'button--has-suffix': this.hasSlotController.test('suffix')
|
||||
})}
|
||||
?disabled=${this.disabled}
|
||||
type="button"
|
||||
name=${ifDefined(this.name)}
|
||||
value=${ifDefined(this.value)}
|
||||
aria-selected=${this.checked ? 'true' : 'false'}
|
||||
aria-disabled=${this.disabled ? 'true' : 'false'}
|
||||
tabindex=${this.disabled ? '-1' : '0'}
|
||||
@blur=${this.handleBlur}
|
||||
@focus=${this.handleFocus}
|
||||
@click=${this.handleClick}
|
||||
>
|
||||
<span part="prefix" class="button__prefix">
|
||||
<slot name="prefix"></slot>
|
||||
</span>
|
||||
<span part="label" class="button__label">
|
||||
<slot></slot>
|
||||
</span>
|
||||
<span part="suffix" class="button__suffix">
|
||||
<slot name="suffix"></slot>
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'sl-radio-button': SlRadioButton;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,25 @@
|
||||
import { html, LitElement } from 'lit';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import '~/components/button-group/button-group';
|
||||
import type SlRadio from '~/components/radio/radio';
|
||||
import { emit } from '~/internal/event';
|
||||
import styles from './radio-group.styles';
|
||||
|
||||
const RADIO_CHILDREN = ['sl-radio', 'sl-radio-button'];
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
* @status stable
|
||||
*
|
||||
* @dependency sl-button-group
|
||||
*
|
||||
* @slot - The default slot where radio controls are placed.
|
||||
* @slot label - The radio group label. Required for proper accessibility. Alternatively, you can use the label prop.
|
||||
*
|
||||
* @csspart base - The component's internal wrapper.
|
||||
* @csspart label - The radio group label.
|
||||
* @csspart label - The radio group's label.
|
||||
* @csspart button-group - The button group that wraps radio buttons.
|
||||
* @csspart button-group__base - The button group's `base` part.
|
||||
*/
|
||||
@customElement('sl-radio-group')
|
||||
export default class SlRadioGroup extends LitElement {
|
||||
@@ -21,6 +27,8 @@ export default class SlRadioGroup extends LitElement {
|
||||
|
||||
@query('slot:not([name])') defaultSlot: HTMLSlotElement;
|
||||
|
||||
@state() hasButtonGroup = false;
|
||||
|
||||
/** The radio group label. Required for proper accessibility. Alternatively, you can use the label slot. */
|
||||
@property() label = '';
|
||||
|
||||
@@ -33,14 +41,14 @@ export default class SlRadioGroup extends LitElement {
|
||||
}
|
||||
|
||||
getAllRadios() {
|
||||
return this.defaultSlot
|
||||
.assignedElements({ flatten: true })
|
||||
.filter(el => el.tagName.toLowerCase() === 'sl-radio') as SlRadio[];
|
||||
return [...this.querySelectorAll(RADIO_CHILDREN.join(','))].filter(el =>
|
||||
RADIO_CHILDREN.includes(el.tagName.toLowerCase())
|
||||
) as SlRadio[];
|
||||
}
|
||||
|
||||
handleRadioClick(event: MouseEvent) {
|
||||
const target = event.target as HTMLElement;
|
||||
const checkedRadio = target.closest('sl-radio');
|
||||
const checkedRadio = target.closest(RADIO_CHILDREN.map(selector => `${selector}:not([disabled])`).join(','));
|
||||
|
||||
if (checkedRadio) {
|
||||
const radios = this.getAllRadios();
|
||||
@@ -73,8 +81,6 @@ export default class SlRadioGroup extends LitElement {
|
||||
radios[index].checked = true;
|
||||
radios[index].input.tabIndex = 0;
|
||||
|
||||
emit(radios[index], 'sl-change');
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
@@ -83,6 +89,8 @@ export default class SlRadioGroup extends LitElement {
|
||||
const radios = this.getAllRadios();
|
||||
const checkedRadio = radios.find(radio => radio.checked);
|
||||
|
||||
this.hasButtonGroup = !!radios.find(radio => radio.tagName.toLowerCase() === 'sl-radio-button');
|
||||
|
||||
radios.forEach(radio => {
|
||||
radio.setAttribute('role', 'radio');
|
||||
radio.input.tabIndex = -1;
|
||||
@@ -96,6 +104,10 @@ export default class SlRadioGroup extends LitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const defaultSlot = html`
|
||||
<slot @click=${this.handleRadioClick} @keydown=${this.handleKeyDown} @slotchange=${this.handleSlotChange}></slot>
|
||||
`;
|
||||
|
||||
return html`
|
||||
<fieldset
|
||||
part="base"
|
||||
@@ -107,11 +119,9 @@ export default class SlRadioGroup extends LitElement {
|
||||
<legend part="label" class="radio-group__label">
|
||||
<slot name="label">${this.label}</slot>
|
||||
</legend>
|
||||
<slot
|
||||
@click=${this.handleRadioClick}
|
||||
@keydown=${this.handleKeyDown}
|
||||
@slotchange=${this.handleSlotChange}
|
||||
></slot>
|
||||
${this.hasButtonGroup
|
||||
? html`<sl-button-group part="button-group">${defaultSlot}</sl-button-group>`
|
||||
: defaultSlot}
|
||||
</fieldset>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import type SlRadio from './radio';
|
||||
describe('<sl-radio>', () => {
|
||||
it('should be disabled with the disabled attribute', async () => {
|
||||
const el = await fixture<SlRadio>(html` <sl-radio disabled></sl-radio> `);
|
||||
const radio = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
|
||||
const radio = el.input;
|
||||
|
||||
expect(radio.disabled).to.be.true;
|
||||
});
|
||||
@@ -20,7 +20,7 @@ describe('<sl-radio>', () => {
|
||||
|
||||
it('should fire sl-change when clicked', async () => {
|
||||
const el = await fixture<SlRadio>(html` <sl-radio></sl-radio> `);
|
||||
setTimeout(() => el.shadowRoot!.querySelector('input')!.click());
|
||||
setTimeout(() => el.input.click());
|
||||
const event = await oneEvent(el, 'sl-change');
|
||||
expect(event.target).to.equal(el);
|
||||
expect(el.checked).to.be.true;
|
||||
@@ -28,8 +28,7 @@ describe('<sl-radio>', () => {
|
||||
|
||||
it('should fire sl-change when toggled via keyboard - space', async () => {
|
||||
const el = await fixture<SlRadio>(html` <sl-radio></sl-radio> `);
|
||||
const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
|
||||
input.focus();
|
||||
el.input.focus();
|
||||
setTimeout(() => sendKeys({ press: ' ' }));
|
||||
const event = await oneEvent(el, 'sl-change');
|
||||
expect(event.target).to.equal(el);
|
||||
@@ -43,16 +42,32 @@ describe('<sl-radio>', () => {
|
||||
<sl-radio id="radio-2"></sl-radio>
|
||||
</sl-radio-group>
|
||||
`);
|
||||
const radio1 = radioGroup.querySelector<SlRadio>('sl-radio#radio-1')!;
|
||||
const radio2 = radioGroup.querySelector<SlRadio>('sl-radio#radio-2')!;
|
||||
const input1 = radio1.shadowRoot!.querySelector<HTMLInputElement>('input')!;
|
||||
input1.focus();
|
||||
const radio1 = radioGroup.querySelector<SlRadio>('#radio-1')!;
|
||||
const radio2 = radioGroup.querySelector<SlRadio>('#radio-2')!;
|
||||
radio1.input.focus();
|
||||
setTimeout(() => sendKeys({ press: 'ArrowRight' }));
|
||||
const event = await oneEvent(radio2, 'sl-change');
|
||||
expect(event.target).to.equal(radio2);
|
||||
expect(radio2.checked).to.be.true;
|
||||
});
|
||||
|
||||
it('should not get checked when disabled', async () => {
|
||||
const radioGroup = await fixture<SlRadioGroup>(html`
|
||||
<sl-radio-group>
|
||||
<sl-radio checked></sl-radio>
|
||||
<sl-radio disabled></sl-radio>
|
||||
</sl-radio-group>
|
||||
`);
|
||||
const radio1 = radioGroup.querySelector<SlRadio>('sl-radio[checked]')!;
|
||||
const radio2 = radioGroup.querySelector<SlRadio>('sl-radio[disabled]')!;
|
||||
|
||||
radio2.click();
|
||||
await Promise.all([radio1.updateComplete, radio2.updateComplete]);
|
||||
|
||||
expect(radio1.checked).to.be.true;
|
||||
expect(radio2.checked).to.be.false;
|
||||
});
|
||||
|
||||
describe('when submitting a form', () => {
|
||||
it('should submit the correct value', async () => {
|
||||
const form = await fixture<HTMLFormElement>(html`
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { html, LitElement } from 'lit';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { live } from 'lit/directives/live.js';
|
||||
import { emit } from '~/internal/event';
|
||||
import { FormSubmitController } from '~/internal/form-control';
|
||||
import { watch } from '~/internal/watch';
|
||||
import RadioBase from '~/internal/radio';
|
||||
import styles from './radio.styles';
|
||||
|
||||
/**
|
||||
@@ -24,112 +22,9 @@ import styles from './radio.styles';
|
||||
* @csspart label - The radio label.
|
||||
*/
|
||||
@customElement('sl-radio')
|
||||
export default class SlRadio extends LitElement {
|
||||
export default class SlRadio extends RadioBase {
|
||||
static styles = styles;
|
||||
|
||||
@query('input[type="radio"]') input: HTMLInputElement;
|
||||
|
||||
// @ts-expect-error -- Controller is currently unused
|
||||
private readonly formSubmitController = new FormSubmitController(this, {
|
||||
value: (control: SlRadio) => (control.checked ? control.value : undefined)
|
||||
});
|
||||
|
||||
@state() private hasFocus = false;
|
||||
|
||||
/** The radio's name attribute. */
|
||||
@property() name: string;
|
||||
|
||||
/** The radio's value attribute. */
|
||||
@property() value: string;
|
||||
|
||||
/** Disables the radio. */
|
||||
@property({ type: Boolean, reflect: true }) disabled = false;
|
||||
|
||||
/** Draws the radio in a checked state. */
|
||||
@property({ type: Boolean, reflect: true }) checked = false;
|
||||
|
||||
/**
|
||||
* This will be true when the control is in an invalid state. Validity in range inputs is determined by the message
|
||||
* provided by the `setCustomValidity` method.
|
||||
*/
|
||||
@property({ type: Boolean, reflect: true }) invalid = false;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.setAttribute('role', 'radio');
|
||||
}
|
||||
|
||||
/** Simulates a click on the radio. */
|
||||
click() {
|
||||
this.input.click();
|
||||
}
|
||||
|
||||
/** Sets focus on the radio. */
|
||||
focus(options?: FocusOptions) {
|
||||
this.input.focus(options);
|
||||
}
|
||||
|
||||
/** Removes focus from the radio. */
|
||||
blur() {
|
||||
this.input.blur();
|
||||
}
|
||||
|
||||
/** Checks for validity and shows the browser's validation message if the control is invalid. */
|
||||
reportValidity() {
|
||||
return this.input.reportValidity();
|
||||
}
|
||||
|
||||
/** Sets a custom validation message. If `message` is not empty, the field will be considered invalid. */
|
||||
setCustomValidity(message: string) {
|
||||
this.input.setCustomValidity(message);
|
||||
this.invalid = !this.input.checkValidity();
|
||||
}
|
||||
|
||||
getAllRadios() {
|
||||
const radioGroup = this.closest('sl-radio-group');
|
||||
|
||||
// Radios must be part of a radio group
|
||||
if (radioGroup === null) {
|
||||
return [this];
|
||||
}
|
||||
|
||||
return [...radioGroup.querySelectorAll<SlRadio>('sl-radio')].filter((radio: this) => radio.name === this.name);
|
||||
}
|
||||
|
||||
handleBlur() {
|
||||
this.hasFocus = false;
|
||||
emit(this, 'sl-blur');
|
||||
}
|
||||
|
||||
@watch('checked')
|
||||
handleCheckedChange() {
|
||||
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
|
||||
|
||||
if (this.hasUpdated) {
|
||||
emit(this, 'sl-change');
|
||||
}
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
this.checked = true;
|
||||
}
|
||||
|
||||
@watch('disabled', { waitUntilFirstUpdate: true })
|
||||
handleDisabledChange() {
|
||||
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
|
||||
|
||||
// Disabled form controls are always valid, so we need to recheck validity when the state changes
|
||||
if (this.hasUpdated) {
|
||||
this.input.disabled = this.disabled;
|
||||
this.invalid = !this.input.checkValidity();
|
||||
}
|
||||
}
|
||||
|
||||
handleFocus() {
|
||||
this.hasFocus = true;
|
||||
emit(this, 'sl-focus');
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<label
|
||||
|
||||
115
src/internal/radio.ts
Normal file
115
src/internal/radio.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { LitElement } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
import { emit } from '~/internal/event';
|
||||
import { FormSubmitController } from '~/internal/form-control';
|
||||
import { watch } from '~/internal/watch';
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
* @status stable
|
||||
*
|
||||
* @slot - The radio's label.
|
||||
*
|
||||
* @event sl-blur - Emitted when the control loses focus.
|
||||
* @event sl-change - Emitted when the control's checked state changes.
|
||||
* @event sl-focus - Emitted when the control gains focus.
|
||||
*
|
||||
* @csspart base - The component's internal wrapper.
|
||||
* @csspart control - The radio control.
|
||||
* @csspart checked-icon - The container the wraps the checked icon.
|
||||
* @csspart label - The radio label.
|
||||
*/
|
||||
export default abstract class RadioBase extends LitElement {
|
||||
@query('input[type="radio"], button') input: HTMLInputElement;
|
||||
|
||||
protected readonly formSubmitController = new FormSubmitController(this, {
|
||||
value: (control: RadioBase) => (control.checked ? control.value : undefined)
|
||||
});
|
||||
|
||||
@state() protected hasFocus = false;
|
||||
|
||||
/** The radio's name attribute. */
|
||||
@property() name: string;
|
||||
|
||||
/** The radio's value attribute. */
|
||||
@property() value: string;
|
||||
|
||||
/** Disables the radio. */
|
||||
@property({ type: Boolean, reflect: true }) disabled = false;
|
||||
|
||||
/** Draws the radio in a checked state. */
|
||||
@property({ type: Boolean, reflect: true }) checked = false;
|
||||
|
||||
/**
|
||||
* This will be true when the control is in an invalid state. Validity in range inputs is determined by the message
|
||||
* provided by the `setCustomValidity` method.
|
||||
*/
|
||||
@property({ type: Boolean, reflect: true }) invalid = false;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.setAttribute('role', 'radio');
|
||||
}
|
||||
|
||||
/** Simulates a click on the radio. */
|
||||
click() {
|
||||
this.input.click();
|
||||
}
|
||||
|
||||
/** Sets focus on the radio. */
|
||||
focus(options?: FocusOptions) {
|
||||
this.input.focus(options);
|
||||
}
|
||||
|
||||
/** Removes focus from the radio. */
|
||||
blur() {
|
||||
this.input.blur();
|
||||
}
|
||||
|
||||
/** Checks for validity and shows the browser's validation message if the control is invalid. */
|
||||
reportValidity() {
|
||||
return this.input.reportValidity();
|
||||
}
|
||||
|
||||
/** Sets a custom validation message. If `message` is not empty, the field will be considered invalid. */
|
||||
setCustomValidity(message: string) {
|
||||
this.input.setCustomValidity(message);
|
||||
this.invalid = !this.input.checkValidity();
|
||||
}
|
||||
|
||||
handleBlur() {
|
||||
this.hasFocus = false;
|
||||
emit(this, 'sl-blur');
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
if (!this.disabled) {
|
||||
this.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
handleFocus() {
|
||||
this.hasFocus = true;
|
||||
emit(this, 'sl-focus');
|
||||
}
|
||||
|
||||
@watch('checked')
|
||||
handleCheckedChange() {
|
||||
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
|
||||
|
||||
if (this.hasUpdated) {
|
||||
emit(this, 'sl-change');
|
||||
}
|
||||
}
|
||||
|
||||
@watch('disabled', { waitUntilFirstUpdate: true })
|
||||
handleDisabledChange() {
|
||||
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
|
||||
|
||||
// Disabled form controls are always valid, so we need to recheck validity when the state changes
|
||||
if (this.hasUpdated) {
|
||||
this.input.disabled = this.disabled;
|
||||
this.invalid = !this.input.checkValidity();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ export { default as SlProgressBar } from './components/progress-bar/progress-bar
|
||||
export { default as SlProgressRing } from './components/progress-ring/progress-ring';
|
||||
export { default as SlQrCode } from './components/qr-code/qr-code';
|
||||
export { default as SlRadio } from './components/radio/radio';
|
||||
export { default as SlRadioButton } from './components/radio-button/radio-button';
|
||||
export { default as SlRadioGroup } from './components/radio-group/radio-group';
|
||||
export { default as SlRange } from './components/range/range';
|
||||
export { default as SlRating } from './components/rating/rating';
|
||||
|
||||
Reference in New Issue
Block a user