-
Notifications
You must be signed in to change notification settings - Fork 601
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
9 changed files
with
352 additions
and
6 deletions.
There are no files selected for viewing
This file contains 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 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 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 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 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 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 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}" | ||
?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 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 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