-
Notifications
You must be signed in to change notification settings - Fork 598
feat: add radio component #2985
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
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
29e0c9c
adding radio web component
3d9e389
created global map for radio groups
627d960
cleaning up radioGroups map properly
dca1bc1
navigation for radio groups added
5ab6023
pr comments addressed
be1213e
fixed arrow navigation per the aria spec
d4a14af
only checking with spacebar if radio is unchecked
2976142
removed group behavior from radio
5c6885e
several pr comments around styles fixed
4dad782
added aria-labeledby example
4af2839
moved checked and readonly inline in the template, cleanup of fixture
7998012
updated spec to not include the name attribute, grouping will be done…
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# fast-radio | ||
An implementation of a [radio](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio) as a form-connected web-component. | ||
|
||
For more information view the [component specification](./radio.spec.md). |
32 changes: 32 additions & 0 deletions
32
packages/web-components/fast-components/src/radio/fixtures/base.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<fast-design-system-provider use-defaults> | ||
<h1>Radio</h1> | ||
|
||
<h4>Defaults</h4> | ||
<fast-radio></fast-radio> | ||
<div> | ||
<fast-radio>label</fast-radio> | ||
</div> | ||
|
||
<h4>Checked</h4> | ||
<fast-radio value="checked" checked></fast-radio> | ||
|
||
<!-- Required --> | ||
<h4>Required</h4> | ||
<fast-radio value="required" required></fast-radio> | ||
|
||
<!-- Disabled --> | ||
<h4>Disabled</h1> | ||
<fast-radio disabled></fast-radio> | ||
<fast-radio disabled>label</fast-radio> | ||
<fast-radio disabled checked>checked</fast-radio> | ||
|
||
<h4>Visual vs audio label</h4> | ||
<fast-radio> | ||
<span aria-label="Audio label">Visible label</span> | ||
</fast-radio> | ||
|
||
<div style="display: flex; flex-direction: column;margin-top: 12px;"> | ||
<label id="label1">Outside label</label> | ||
<fast-radio aria-labelledby="label1"></fast-radio> | ||
</div> | ||
</fast-design-system-provider> |
14 changes: 14 additions & 0 deletions
14
packages/web-components/fast-components/src/radio/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { customElement } from "@microsoft/fast-element"; | ||
import { Radio } from "./radio"; | ||
import { RadioTemplate as template } from "./radio.template"; | ||
import { RadioStyles as styles } from "./radio.styles"; | ||
|
||
@customElement({ | ||
name: "fast-radio", | ||
template, | ||
styles, | ||
}) | ||
export class FASTRadio extends Radio {} | ||
export * from "./radio.template"; | ||
export * from "./radio.styles"; | ||
export * from "./radio"; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
packages/web-components/fast-components/src/radio/radio.stories.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { FASTDesignSystemProvider } from "../design-system-provider"; | ||
import Examples from "./fixtures/base.html"; | ||
import { FASTRadio } from "."; | ||
|
||
// Prevent tree-shaking | ||
FASTRadio; | ||
FASTDesignSystemProvider; | ||
|
||
export default { | ||
title: "Radio", | ||
}; | ||
|
||
export const Radio = () => Examples; |
137 changes: 137 additions & 0 deletions
137
packages/web-components/fast-components/src/radio/radio.styles.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import { css } from "@microsoft/fast-element"; | ||
import { disabledCursor, display } from "../styles"; | ||
import { focusVisible } from "../styles/focus"; | ||
import { SystemColors } from "../styles/system-colors"; | ||
import { heightNumber } from "../styles/size"; | ||
import { | ||
neutralFillInputHoverBehavior, | ||
neutralFillInputRestBehavior, | ||
neutralForegroundRestBehavior, | ||
neutralOutlineHoverBehavior, | ||
neutralOutlineRestBehavior, | ||
} from "../styles/recipes"; | ||
|
||
export const RadioStyles = css` | ||
${display("inline-flex")} :host { | ||
--input-size: calc((${heightNumber} / 2) + var(--design-unit)); | ||
align-items: center; | ||
outline: none; | ||
margin: calc(var(--design-unit) * 1px) 0; | ||
${ | ||
/* | ||
* Chromium likes to select label text or the default slot when | ||
* the radio button is clicked. Maybe there is a better solution here? | ||
*/ "" | ||
} user-select: none; | ||
position: relative; | ||
flex-direction: row; | ||
transition: all 0.2s ease-in-out; | ||
} | ||
|
||
.control { | ||
position: relative; | ||
width: calc(var(--input-size) * 1px); | ||
height: calc(var(--input-size) * 1px); | ||
box-sizing: border-box; | ||
border-radius: 50%; | ||
border: calc(var(--outline-width) * 1px) solid var(--neutral-outline-rest); | ||
background: var(--neutral-fill-input-rest); | ||
outline: none; | ||
cursor: pointer; | ||
} | ||
|
||
.label { | ||
font-family: var(--body-font); | ||
color: var(--neutral-foreground-rest); | ||
${ | ||
/* Need to discuss with Brian how HorizontalSpacingNumber can work. https://github.com/microsoft/fast-dna/issues/2766 */ "" | ||
} padding-inline-start: calc(var(--design-unit) * 2px + 2px); | ||
margin-inline-end: calc(var(--design-unit) * 2px + 2px); | ||
cursor: pointer; | ||
${ | ||
/* Font size is temporary - replace when adaptive typography is figured out */ "" | ||
} font-size: calc(1rem + (var(--density) * 2px)); | ||
} | ||
|
||
.checked-indicator { | ||
position: absolute; | ||
top: 5px; | ||
left: 5px; | ||
right: 5px; | ||
bottom: 5px; | ||
border-radius: 50%; | ||
display: inline-block; | ||
flex-shrink: 0; | ||
background: var(--neutral-foreground-rest); | ||
fill: var(--neutral-foreground-rest); | ||
opacity: 0; | ||
pointer-events: none; | ||
} | ||
|
||
.control:hover { | ||
background: var(--neutral-fill-input-hover); | ||
border-color: var(--neutral-outline-hover); | ||
} | ||
|
||
:host(:${focusVisible}) .control { | ||
box-shadow: 0 0 0 1px var(--neutral-focus) inset; | ||
border-color: var(--neutral-focus); | ||
} | ||
|
||
:host(.disabled) .label, | ||
:host(.readonly) .label, | ||
:host(.readonly) .control, | ||
:host(.disabled) .control { | ||
cursor: ${disabledCursor}; | ||
} | ||
|
||
:host(.checked) .checked-indicator { | ||
opacity: 1; | ||
} | ||
|
||
:host(.disabled) { | ||
opacity: var(--disabled-opacity); | ||
} | ||
|
||
@media (forced-colors: active) { | ||
.control, .control:hover, .control:active { | ||
forced-color-adjust: none; | ||
border-color: ${SystemColors.FieldText}; | ||
background: ${SystemColors.Field}; | ||
} | ||
|
||
.checked-indicator { | ||
fill: ${SystemColors.FieldText}; | ||
} | ||
|
||
:host(:${focusVisible}) .control { | ||
border-color: ${SystemColors.Highlight}; | ||
} | ||
|
||
:host(.disabled) { | ||
opacity: 1; | ||
} | ||
|
||
:host(.disabled) .label { | ||
forced-color-adjust: none; | ||
color: ${SystemColors.GrayText}; | ||
} | ||
|
||
:host(.disabled) .control { | ||
forced-color-adjust: none; | ||
border-color: ${SystemColors.GrayText}; | ||
} | ||
|
||
:host(.disabled) .checked-indicator { | ||
forced-color-adjust: none; | ||
fill: ${SystemColors.GrayText}; | ||
background: ${SystemColors.GrayText}; | ||
} | ||
} | ||
`.withBehaviors( | ||
neutralFillInputHoverBehavior, | ||
neutralFillInputRestBehavior, | ||
neutralForegroundRestBehavior, | ||
neutralOutlineHoverBehavior, | ||
neutralOutlineRestBehavior | ||
); |
31 changes: 31 additions & 0 deletions
31
packages/web-components/fast-components/src/radio/radio.template.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { html, when } from "@microsoft/fast-element"; | ||
import { Radio } from "./radio"; | ||
|
||
export const RadioTemplate = html<Radio>` | ||
<template | ||
role="radio" | ||
class="${x => (x.checked ? "checked" : "")} ${x => | ||
x.readOnly ? "readonly" : ""}" | ||
aria-checked="${x => x.checked}" | ||
marjonlynch marked this conversation as resolved.
Show resolved
Hide resolved
|
||
?aria-required="${x => x.required}" | ||
?aria-disabled="${x => x.disabled}" | ||
?aria-readonly="${x => x.readOnly}" | ||
tabindex="${x => (x.disabled ? null : 0)}" | ||
@keypress="${(x, c) => x.keypressHandler(c.event as KeyboardEvent)}" | ||
@click="${(x, c) => x.clickHandler(c.event as MouseEvent)}" | ||
> | ||
<div part="control" class="control"> | ||
<slot name="checked-indicator"> | ||
<div part="checked-indicator" class="checked-indicator"></div> | ||
</slot> | ||
</div> | ||
${when( | ||
x => x.childNodes.length, | ||
html` | ||
<label part="label" class="label"> | ||
<slot></slot> | ||
</label> | ||
` | ||
)} | ||
</template> | ||
`; |
119 changes: 119 additions & 0 deletions
119
packages/web-components/fast-components/src/radio/radio.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import { attr, observable } from "@microsoft/fast-element"; | ||
import { keyCodeSpace } from "@microsoft/fast-web-utilities"; | ||
import { FormAssociated } from "../form-associated"; | ||
|
||
export class Radio extends FormAssociated<HTMLInputElement> { | ||
@attr({ attribute: "readonly", mode: "boolean" }) | ||
public readOnly: boolean; // Map to proxy element | ||
private readOnlyChanged(): void { | ||
if (this.proxy instanceof HTMLElement) { | ||
this.proxy.readOnly = this.readOnly; | ||
} | ||
} | ||
|
||
@attr | ||
public name: string; // Map to proxy element | ||
protected nameChanged(): void { | ||
if (this.proxy instanceof HTMLElement) { | ||
this.proxy.name = this.name; | ||
} | ||
} | ||
|
||
/** | ||
* The element's value to be included in form submission when checked. | ||
* Default to "on" to reach parity with input[type="radio"] | ||
*/ | ||
public value: string = "on"; // Map to proxy element. | ||
private valueChanged(): void { | ||
if (this.proxy instanceof HTMLElement) { | ||
this.proxy.value = this.value; | ||
} | ||
} | ||
|
||
/** | ||
* Provides the default checkedness of the input element | ||
* Passed down to proxy | ||
*/ | ||
@attr({ attribute: "checked", mode: "boolean" }) | ||
public checkedAttribute: boolean; | ||
private checkedAttributeChanged(): void { | ||
this.defaultChecked = this.checkedAttribute; | ||
} | ||
|
||
/** | ||
* Initialized to the value of the checked attribute. Can be changed independently of the "checked" attribute, | ||
* but changing the "checked" attribute always additionally sets this value. | ||
*/ | ||
@observable | ||
public defaultChecked: boolean = !!this.checkedAttribute; | ||
private defaultCheckedChanged(): void { | ||
if (!this.dirtyChecked) { | ||
// Setting this.checked will cause us to enter a dirty state, | ||
// but if we are clean when defaultChecked is changed, we want to stay | ||
// in a clean state, so reset this.dirtyChecked | ||
this.checked = this.defaultChecked; | ||
this.dirtyChecked = false; | ||
} | ||
} | ||
|
||
/** | ||
* The checked state of the control | ||
*/ | ||
@observable | ||
public checked: boolean = this.defaultChecked; | ||
private checkedChanged(): void { | ||
if (!this.dirtyChecked) { | ||
this.dirtyChecked = true; | ||
} | ||
|
||
if (this.proxy instanceof HTMLElement) { | ||
this.proxy.checked = this.checked; | ||
} | ||
|
||
this.$emit("change"); | ||
this.checkedAttribute = this.checked; | ||
this.updateForm(); | ||
} | ||
|
||
protected proxy: HTMLInputElement = document.createElement("input"); | ||
|
||
/** | ||
* Tracks whether the "checked" property has been changed. | ||
* This is necessary to provide consistent behavior with | ||
* normal input radios | ||
*/ | ||
private dirtyChecked: boolean = false; | ||
|
||
constructor() { | ||
super(); | ||
this.proxy.setAttribute("type", "radio"); | ||
} | ||
|
||
public connectedCallback(): void { | ||
super.connectedCallback(); | ||
this.updateForm(); | ||
} | ||
|
||
private updateForm(): void { | ||
const value = this.checked ? this.value : null; | ||
this.setFormValue(value, value); | ||
} | ||
|
||
public keypressHandler = (e: KeyboardEvent): void => { | ||
super.keypressHandler(e); | ||
switch (e.keyCode) { | ||
case keyCodeSpace: | ||
if (!this.checked) { | ||
this.checked = true; | ||
} | ||
break; | ||
} | ||
}; | ||
|
||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ | ||
public clickHandler = (e: MouseEvent): void => { | ||
if (!this.disabled && !this.readOnly) { | ||
this.checked = !this.checked; | ||
} | ||
}; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.