-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Adds Radio and RadioGroup as new web components #27113
Changes from 126 commits
7f9af9e
70c55e0
f3cb7f4
a72d10f
2941fa7
9b350fc
2cf59ce
79b6bff
5558b93
3c045c3
2370967
c3d414b
f9d2722
9cdce0a
69191b8
92621d4
b819408
9eac6e8
9948aa4
ee1afd4
7fb992d
80de0f1
9bed712
5eb76ea
6a03c6d
de938a4
4ee0e0b
48c4d8a
923a696
22f2a82
6729fad
88f874f
62649a1
2de1087
40843d7
be9d76a
da17886
cad6469
daa81a8
af2f4ab
fea6a0d
7e68029
48e4acc
7ef442a
ca1f966
0e7de1a
d1cc79d
58a9094
8057376
522147e
d132f2e
611c3c9
9c4eb6b
267e655
3c39bfe
bd3d0c4
15c2d3a
f818800
0234822
7cb1d5b
87f36b2
added85
3ae6f81
127eb13
cd063a2
c9b9249
97a5a5e
86fb9c5
790f1a8
d947e13
3c0f692
bca6add
bf7bee3
1230bc0
19002da
f09a49f
e1f6473
f273e93
8c0eae2
8fd62c0
6d32614
b4646b7
09d8c95
74bb90a
b4fec61
6518c80
1dda4d5
4495bf7
831e720
c058a2e
0f0d7ec
9984b79
8e9a8bd
197010c
444f8c3
e4601a0
52901ab
b05729e
d4ffc45
12de504
612877b
664a698
4087145
e46a081
9f97980
2778e3a
b059373
82c2a85
6921a19
02b9cff
30d7c05
d89294e
96a50f3
592421e
5392798
7619086
e865390
9a68e6e
c1d3458
a7b2a11
c7cc45f
b5c84d8
329bd37
fae698a
e475e21
81c5095
4d56030
163def9
9e554eb
3a45ff2
8e61ff1
01887bf
5423421
42b2a13
596c031
9a6705f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"type": "prerelease", | ||
"comment": "feat(radio): add radio and radio-group web components", | ||
"packageName": "@fluentui/web-components", | ||
"email": "brianbrady@microsoft.com", | ||
"dependentChangeType": "patch" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
# Radio Group | ||
|
||
> RadioGroup lets people select a single option from two or more Radio items. Use RadioGroup to present all available choices if there's enough space.. | ||
> RadioGroup lets users select a single option from two or more Radio items. Use RadioGroup to present all available choices if there's enough space.. | ||
|
||
<br /> | ||
|
||
|
@@ -36,13 +36,14 @@ Used anywhere an author might group a list of radio options. | |
|
||
### **Fields** | ||
|
||
| Name | Privacy | Type | Default | Description | | ||
| ------------- | ------- | ------------------------ | ------------ | ----------------------------------------------------------------------------------------------------- | | ||
| `disabled` | public | `boolean` | `false` | Disables the radio group and child radios. | | ||
| `name` | public | `string` | | The name of the radio group. Setting this value will set the name value for all child radio elements. | | ||
| `value` | public | `string` | | The value of the checked radio. | | ||
| `orientation` | public | `horizontal \| vertical` | `horizontal` | The orientation of the group | | ||
| default slot | public | `HTMLElement[]` | | The default slot expecting Radio items | | ||
| Name | Privacy | Type | Default | Description | | ||
| ------------- | ------- | ------------------------ | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| `disabled` | public | `boolean` | `false` | Disables the radio group and child radios. | | ||
| `name` | public | `string` | | The name of the radio group. Setting this value will set the name value for all child radio elements. | | ||
| `value` | public | `string` | | The value of the checked radio. | | ||
| `orientation` | public | `horizontal \| vertical` | `horizontal` | The orientation of the group | | ||
| `stacked` | public | `boolean` | `false` | When the orientation attribute is set to `horizontal`, `stacked` arranges each radio item in a row, with the labels displayed beneath the radio indicators. However, when the orientation is set to `vertical` and the stacked attribute is also present, the stacked styles will be overridden, and the default `vertical` layout will be applied. | | ||
| default slot | public | `HTMLElement[]` | | The default slot expecting Radio items | | ||
|
||
<br /> | ||
|
||
|
@@ -57,9 +58,9 @@ Used anywhere an author might group a list of radio options. | |
|
||
### **Events** | ||
|
||
| Name | Type | Description | | ||
| -------- | ---- | ---------------------------------------------------- | | ||
| `change` | | Fires a custom 'change' event when the value changes | | ||
| Name | Event Type | Target | Arguments | Description | | ||
| -------- | ------------- | ---------------- | --------- | ----------------------------------------------------------------------------------------------------------------- | | ||
| `change` | `CustomEvent` | `FASTRadioGroup` | none | Fired when the value of the RadioGroup changes (i.e., when a different radio button within the group is selected) | | ||
|
||
<br /> | ||
|
||
|
@@ -97,9 +98,10 @@ Used anywhere an author might group a list of radio options. | |
|
||
### **WAI-ARIA Roles, States, and Properties** | ||
|
||
| Attributes | value | Description | | ||
| ----------------- | ----- | ---------------------------------------- | | ||
| `aria-labelledby` | | used to associate a label with the group | | ||
| Attributes | value | Description | | ||
| ----------------- | -------------- | ---------------------------------------- | | ||
| `aria-labelledby` | | used to associate a label with the group | | ||
| `role` | `"radiogroup"` | used to define a group of radio buttons | | ||
|
||
<br /> | ||
<hr /> | ||
|
@@ -122,6 +124,6 @@ Used anywhere an author might group a list of radio options. | |
<br /> | ||
|
||
**Property Mapping** | ||
| Fluent UI React 9 | Fluent Web Components 3 | Description of difference | | ||
|-------------------|------------------------ |---------------------------| | ||
| `layout` | `orientation` | React implementation requires user to pass either `"horizontal"` or `"horizontal-stacked"` through `layout` prop. <br /> WC3 implementation requires user to either pass `"vertical"` or "`horizontal"` through `orientation` attribute. | ||
| Fluent UI React 9 | Fluent Web Components | Description of difference | | ||
|-------------------|-------------------------- |---------------------------| | ||
| `layout` | `orientation` + `stacked` | React implementation requires user to pass either `"horizontal"` or `"horizontal-stacked"` through `layout` prop. <br /> WC3 implementation requires user to either pass `"vertical"` or "`horizontal"` through `orientation` attribute. Additionally, adding the `boolean` attribute `stacked` when the orientation is set to `horizontal` will create the `horizontal-stacked` layout available in FUIR9. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trying to understand, whats the difference between horizontal-stacked and orientation="vertical"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @brianchristopherbrady updated the description. Is that more clear? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { FluentDesignSystem } from '../fluent-design-system.js'; | ||
import { definition } from './radio-group.definition.js'; | ||
|
||
definition.define(FluentDesignSystem.registry); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export * from './radio-group.js'; | ||
export { definition as RadioGroupDefinition } from './radio-group.definition.js'; | ||
export { styles as RadioGroupStyles } from './radio-group.styles.js'; | ||
export { template as RadioGroupTemplate } from './radio-group.template.js'; | ||
brianchristopherbrady marked this conversation as resolved.
Show resolved
Hide resolved
|
||
export { RadioGroupOrientation } from '@microsoft/fast-foundation'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { FluentDesignSystem } from '../fluent-design-system.js'; | ||
import { RadioGroup } from './radio-group.js'; | ||
import { styles } from './radio-group.styles.js'; | ||
import { template } from './radio-group.template.js'; | ||
|
||
/** | ||
* The Fluent RadioGroup Element. | ||
* | ||
* | ||
* @public | ||
* @remarks | ||
* HTML Element: \<fluent-radio-group\> | ||
*/ | ||
export const definition = RadioGroup.compose({ | ||
name: `${FluentDesignSystem.prefix}-radio-group`, | ||
template, | ||
styles, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
import { html } from '@microsoft/fast-element'; | ||
import type { Args, Meta } from '@storybook/html'; | ||
import { RadioGroupOrientation } from '@microsoft/fast-foundation'; | ||
import { renderComponent } from '../helpers.stories.js'; | ||
import { RadioGroup as FluentRadioGroup } from './radio-group.js'; | ||
import './define.js'; | ||
import '../radio/define.js'; | ||
|
||
type RadioGroupStoryArgs = Args & FluentRadioGroup; | ||
type RadioGroupStoryMeta = Meta<RadioGroupStoryArgs>; | ||
|
||
const storyTemplate = html<RadioGroupStoryArgs>` | ||
<fluent-radio-group | ||
aria-labelledby="label-1" | ||
?disabled=${x => x.disabled} | ||
?stacked=${x => x.stacked} | ||
orientation=${x => x.orientation} | ||
name="radio-story" | ||
> | ||
<span id="label-1" slot="label">Favorite Fruit</span> | ||
<fluent-radio value="apple">Apple</fluent-radio> | ||
<fluent-radio value="pear">Pear</fluent-radio> | ||
<fluent-radio value="banana">Banana</fluent-radio> | ||
<fluent-radio value="orange">Orange</fluent-radio> | ||
</fluent-radio-group> | ||
`; | ||
|
||
export default { | ||
title: 'Components/RadioGroup', | ||
args: { | ||
disabled: false, | ||
orientation: RadioGroupOrientation.horizontal, | ||
}, | ||
argTypes: { | ||
disabled: { | ||
control: { | ||
type: 'boolean', | ||
}, | ||
table: { | ||
type: { | ||
summary: 'Sets disabled state on radio', | ||
}, | ||
defaultValue: { | ||
summary: 'false', | ||
}, | ||
}, | ||
}, | ||
stacked: { | ||
control: { | ||
type: 'boolean', | ||
}, | ||
table: { | ||
type: { | ||
summary: 'Creates a stacked layout for horizontal radio buttons', | ||
}, | ||
defaultValue: { | ||
summary: 'false', | ||
}, | ||
}, | ||
}, | ||
orientation: { | ||
control: { | ||
type: 'select', | ||
options: Object.values(RadioGroupOrientation), | ||
}, | ||
defaultValue: RadioGroupOrientation.horizontal, | ||
table: { | ||
type: { | ||
summary: 'Sets orientation of radio group', | ||
}, | ||
defaultValue: { | ||
summary: RadioGroupOrientation.horizontal, | ||
}, | ||
}, | ||
}, | ||
change: { | ||
action: 'change', | ||
table: { | ||
type: { | ||
summary: 'Event that is fired when the selected radio button changes', | ||
}, | ||
defaultValue: { | ||
summary: null, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} as RadioGroupStoryMeta; | ||
|
||
export const RadioGroup = renderComponent(storyTemplate).bind({}); | ||
|
||
export const RadioGroupLabelledby = renderComponent(html<RadioGroupStoryArgs>` | ||
<fluent-radio-group aria-labelledby="label-2" name="radio-story"> | ||
<span id="label-2" slot="label">Favorite Fruit</span> | ||
<fluent-radio value="apple">Apple</fluent-radio> | ||
<fluent-radio value="pear">Pear</fluent-radio> | ||
<fluent-radio value="banana">Banana</fluent-radio> | ||
<fluent-radio value="orange">Orange</fluent-radio> | ||
</fluent-radio-group> | ||
`); | ||
|
||
export const RadioGroupLayoutVertical = renderComponent(html<RadioGroupStoryArgs>` | ||
<fluent-radio-group aria-labelledby="label-3" orientation="vertical" name="radio-story"> | ||
<span id="label-3" slot="label">Favorite Fruit</span> | ||
<fluent-radio value="apple">Apple</fluent-radio> | ||
<fluent-radio value="pear">Pear</fluent-radio> | ||
<fluent-radio value="banana">Banana</fluent-radio> | ||
<fluent-radio value="orange">Orange</fluent-radio> | ||
</fluent-radio-group> | ||
`); | ||
|
||
export const RadioGroupLayoutHorizontal = renderComponent(html<RadioGroupStoryArgs>` | ||
<fluent-radio-group aria-labelledby="label-4" orientation="horizontal" name="radio-story"> | ||
<span id="label-4" slot="label">Favorite Fruit</span> | ||
<fluent-radio value="apple">Apple</fluent-radio> | ||
<fluent-radio value="pear">Pear</fluent-radio> | ||
<fluent-radio value="banana">Banana</fluent-radio> | ||
<fluent-radio value="orange">Orange</fluent-radio> | ||
</fluent-radio-group> | ||
`); | ||
|
||
export const RadioGroupLayoutHorizontalStacked = renderComponent(html<RadioGroupStoryArgs>` | ||
<fluent-radio-group stacked aria-labelledby="label-5" orientation="horizontal" name="radio-story"> | ||
<span id="label-5" slot="label">Favorite Fruit</span> | ||
<fluent-radio value="apple">Apple</fluent-radio> | ||
<fluent-radio value="pear">Pear</fluent-radio> | ||
<fluent-radio value="banana">Banana</fluent-radio> | ||
<fluent-radio value="orange">Orange</fluent-radio> | ||
</fluent-radio-group> | ||
`); | ||
|
||
export const RadioGroupDefaultChecked = renderComponent(html<RadioGroupStoryArgs>` | ||
<fluent-radio-group aria-labelledby="label-6" orientation="horizontal" name="radio-story"> | ||
<span id="label-6" slot="label">Favorite Fruit</span> | ||
<fluent-radio value="apple">Apple</fluent-radio> | ||
<fluent-radio checked value="pear">Pear</fluent-radio> | ||
<fluent-radio value="banana">Banana</fluent-radio> | ||
<fluent-radio value="orange">Orange</fluent-radio> | ||
</fluent-radio-group> | ||
`); | ||
|
||
export const RadioGroupDisabled = renderComponent(html<RadioGroupStoryArgs>` | ||
<fluent-radio-group disabled aria-labelledby="label-7" name="radio-story"> | ||
<span id="label-7" slot="label">Favorite Fruit</span> | ||
<fluent-radio checked value="apple">Apple</fluent-radio> | ||
<fluent-radio checked value="pear">Pear</fluent-radio> | ||
<fluent-radio value="banana">Banana</fluent-radio> | ||
<fluent-radio value="orange">Orange</fluent-radio> | ||
</fluent-radio-group> | ||
`); | ||
|
||
export const RadioGroupDisabledItem = renderComponent(html<RadioGroupStoryArgs>` | ||
<fluent-radio-group aria-labelledby="label-8" name="radio-story"> | ||
<span id="label-8" slot="label">Favorite Fruit</span> | ||
<fluent-radio id="baby" checked value="apple">Apple</fluent-radio> | ||
<fluent-radio disabled value="pear">Pear</fluent-radio> | ||
<fluent-radio value="banana">Banana</fluent-radio> | ||
<fluent-radio value="orange">Orange</fluent-radio> | ||
</fluent-radio-group> | ||
`); | ||
|
||
const getLabelContent = (): string | undefined => { | ||
const radioGroup = document.querySelector('#radio-group-fruit') as FluentRadioGroup; | ||
|
||
if (!radioGroup) return; // add a check to make sure radioGroup exists | ||
const selectedRadio = radioGroup.value as string; | ||
if (selectedRadio) { | ||
return `Favorite fruit: ${selectedRadio.charAt(0).toUpperCase() + selectedRadio.slice(1)}`; | ||
} else { | ||
return 'Please select your favorite fruit'; | ||
} | ||
}; | ||
|
||
const handleChange = (event: CustomEvent) => { | ||
const radioGroup = document.querySelector('#radio-group-fruit') as FluentRadioGroup; | ||
|
||
if (!radioGroup) return; // add a check to make sure radioGroup exists | ||
|
||
const selectedRadio = radioGroup.value as string; | ||
const labelElement = radioGroup.querySelector('[slot="label"]') as HTMLSpanElement; | ||
if (selectedRadio) { | ||
const labelContent = selectedRadio.charAt(0).toUpperCase() + selectedRadio.slice(1); | ||
labelElement.textContent = `Favorite fruit: ${labelContent}`; | ||
} | ||
}; | ||
|
||
export const RadioGroupChangeEvent = renderComponent(html<RadioGroupStoryArgs>` | ||
<fluent-radio-group | ||
id="radio-group-fruit" | ||
aria-labelledby="label-8" | ||
name="radio-story" | ||
@change="${(event: CustomEvent) => handleChange(event)}" | ||
> | ||
<span id="label-8" slot="label">${getLabelContent}</span> | ||
<fluent-radio checked value="apple">Apple</fluent-radio> | ||
<fluent-radio value="pear">Pear</fluent-radio> | ||
<fluent-radio value="Banana">Banana</fluent-radio> | ||
<fluent-radio value="Orange">Orange</fluent-radio> | ||
</fluent-radio-group> | ||
`); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { css } from '@microsoft/fast-element'; | ||
import { display } from '@microsoft/fast-foundation'; | ||
import { | ||
colorCompoundBrandForeground1, | ||
colorNeutralForeground1, | ||
colorNeutralForeground3, | ||
colorNeutralForegroundDisabled, | ||
colorNeutralStrokeAccessible, | ||
fontFamilyBase, | ||
fontSizeBase300, | ||
fontWeightRegular, | ||
lineHeightBase300, | ||
spacingHorizontalS, | ||
spacingHorizontalXS, | ||
spacingVerticalS, | ||
} from '../theme/design-tokens.js'; | ||
|
||
/** RadioGroup styles | ||
* @public | ||
*/ | ||
export const styles = css` | ||
${display('flex')} | ||
|
||
:host { | ||
--control-border-color: ${colorNeutralStrokeAccessible}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just move all these to the host of radio |
||
--checked-indicator-background-color: ${colorCompoundBrandForeground1}; | ||
--state-color: ${colorNeutralForeground3}; | ||
align-items: flex-start; | ||
flex-direction: column; | ||
row-gap: ${spacingVerticalS}; | ||
} | ||
:host([disabled]) { | ||
--control-border-color: ${colorNeutralForegroundDisabled}; | ||
--checked-indicator-background-color: ${colorNeutralForegroundDisabled}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since these are for radio I'd move the definitions for these there. Then simply reassign them in your disabled selector. so
|
||
--state-color: ${colorNeutralForegroundDisabled}; | ||
} | ||
::slotted([slot='label']) { | ||
color: ${colorNeutralForeground1}; | ||
padding: ${spacingVerticalS} ${spacingHorizontalS} ${spacingVerticalS} ${spacingHorizontalXS}; | ||
brianchristopherbrady marked this conversation as resolved.
Show resolved
Hide resolved
|
||
font: ${fontWeightRegular} ${fontSizeBase300} / ${lineHeightBase300} ${fontFamilyBase}; | ||
cursor: default; | ||
} | ||
.positioning-region { | ||
display: flex; | ||
flex-wrap: wrap; | ||
} | ||
:host([orientation='vertical']) .positioning-region { | ||
flex-direction: column; | ||
justify-content: flex-start; | ||
} | ||
:host([orientation='horizontal']) .positioning-region { | ||
flex-direction: row; | ||
} | ||
:host([orientation='horizontal']) ::slotted([role='radio']) { | ||
padding-inline-end: ${spacingHorizontalS}; | ||
} | ||
:host([orientation='horizontal'][stacked]) ::slotted([role='radio']) { | ||
display: flex; | ||
flex-direction: column; | ||
padding-inline: ${spacingHorizontalS}; | ||
height: auto; | ||
align-items: center; | ||
justify-content: center; | ||
} | ||
:host([disabled]) ::slotted([role='radio']) { | ||
pointer-events: none; | ||
} | ||
`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The readme mentions
changed
callback. We should document the args passed to the callbacks.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And also cover them by stories. In a separate work item.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added information to the Events table in the README and created a storybook example in the
RadioGroup
story. Let me know what you think.