Skip to content

Commit

Permalink
feat(list): Add arrow key a11y support. (#2871)
Browse files Browse the repository at this point in the history
Add accessibility support for using arrow keys to navigate the list.
  • Loading branch information
williamernest committed Jun 6, 2018
1 parent 3de2d40 commit 7c06e9f
Show file tree
Hide file tree
Showing 11 changed files with 1,112 additions and 110 deletions.
154 changes: 91 additions & 63 deletions demos/list.html

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@
"mdc-icon-button",
"mdc-icon-toggle",
"mdc-line-ripple",
"mdc-list",
"mdc-menu",
"mdc-notched-outline",
"mdc-radio",
Expand Down
3 changes: 3 additions & 0 deletions packages/material-components-web/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import * as iconButton from '@material/icon-button/index';
import * as iconToggle from '@material/icon-toggle/index';
import * as linearProgress from '@material/linear-progress/index';
import * as lineRipple from '@material/line-ripple/index';
import * as list from '@material/list/index';
import * as menu from '@material/menu/index';
import * as notchedOutline from '@material/notched-outline/index';
import * as radio from '@material/radio/index';
Expand Down Expand Up @@ -55,6 +56,7 @@ autoInit.register('MDCIconButtonToggle', iconButton.MDCIconButtonToggle);
autoInit.register('MDCIconToggle', iconToggle.MDCIconToggle);
autoInit.register('MDCLineRipple', lineRipple.MDCLineRipple);
autoInit.register('MDCLinearProgress', linearProgress.MDCLinearProgress);
autoInit.register('MDCList', list.MDCList);
autoInit.register('MDCNotchedOutline', notchedOutline.MDCNotchedOutline);
autoInit.register('MDCRadio', radio.MDCRadio);
autoInit.register('MDCSnackbar', snackbar.MDCSnackbar);
Expand Down Expand Up @@ -82,6 +84,7 @@ export {
iconToggle,
lineRipple,
linearProgress,
list,
menu,
notchedOutline,
radio,
Expand Down
143 changes: 96 additions & 47 deletions packages/mdc-list/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,13 @@ path: /catalog/lists/
</a>
</div>-->

MDC List provides styles which implement [Material Design Lists](https://material.io/go/design-lists) -
"A single continuous column of tessellated subdivisions of equal width." Both single-line and two-line lists are
supported (with three-line lists [planned](https://github.com/material-components/material-components-web/issues/31)).
MDC Lists are designed to be accessible and RTL aware.
Lists are continuous, vertical indexes of text or images.

## Design & API Documentation

<ul class="icon-list">
<li class="icon-list-item icon-list-item--spec">
<a href="https://material.io/go/design-lists">Material Design guidelines: Lists</a>
<a href="https://material.io/design/components/lists.html">Material Design guidelines: Lists</a>
</li>
<li class="icon-list-item icon-list-item--link">
<a href="https://material-components.github.io/material-components-web-catalog/#/component/list">Demo</a>
Expand All @@ -37,29 +34,28 @@ MDC Lists are designed to be accessible and RTL aware.
npm install @material/list
```

## Usage
## Basic Usage

### HTML Structure

#### Single-Line List
A basic list consists simply of the list itself, and list items taking up one line.

List items (rows) can contain primary and secondary actions. Lists items can contain 1 supporting graphic tile and/or 1 metadata tile that are positioned at the start and end of the list item, respectively.


```html
<ul class="mdc-list">
<ul class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">Single-line item</li>
<li class="mdc-list-item">Single-line item</li>
<li class="mdc-list-item">Single-line item</li>
</ul>
```

#### Two-Line List
While in theory you can add any number of "lines" to a list item, you can use the `mdc-list--two-line` combined with some extra markup around the text to style a list in the two-line list style as defined by [the spec](https://material.io/go/design-lists#lists-specs) (see "Two-line lists").
## Variants

### Two-Line List

You can use the `mdc-list--two-line` combined with some extra markup around the text to style a list
in the double line list style as defined by
[the spec](https://material.io/design/components/lists.html#specs) (see "Double line").

```html
<ul class="mdc-list mdc-list--two-line">
<ul class="mdc-list mdc-list--two-line" aria-orientation="vertical">
<li class="mdc-list-item">
<span class="mdc-list-item__text">
First-line text
Expand Down Expand Up @@ -87,32 +83,34 @@ While in theory you can add any number of "lines" to a list item, you can use th
</ul>
```

#### List Groups
### List Groups

Multiple related lists can be grouped together using the `mdc-list-group` class on a containing element.

```html
<div class="mdc-list-group">
<h3 class="mdc-list-group__subheader">List 1</h3>
<ul class="mdc-list">
<ul class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">line item</li>
<li class="mdc-list-item">line item</li>
<li class="mdc-list-item">line item</li>
</ul>

<h3 class="mdc-list-group__subheader">List 2</h3>
<ul class="mdc-list">
<ul class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">line item</li>
<li class="mdc-list-item">line item</li>
<li class="mdc-list-item">line item</li>
</ul>
</div>
```

#### List Dividers
### List Dividers

MDC List contains an `mdc-list-divider` class which can be used as full-width or inset subdivisions either within lists themselves, or standalone between related groups of content.

```html
<ul class="mdc-list">
<ul class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">Item 1 - Division 1</li>
<li class="mdc-list-item">Item 2 - Division 1</li>
<li role="separator" class="mdc-list-divider"></li>
Expand All @@ -126,56 +124,107 @@ MDC List contains an `mdc-list-divider` class which can be used as full-width or
OR

```html
<ul class="mdc-list">
<ul class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">Item 1 - List 1</li>
<li class="mdc-list-item">Item 2 - List 1</li>
</ul>
<hr class="mdc-list-divider">
<ul class="mdc-list">
<ul class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">Item 1 - List 2</li>
<li class="mdc-list-item">Item 2 - List 2</li>
</ul>
```

## Style Customization

### CSS Classes

CSS Class | Description
--- | ---
`mdc-list` | Mandatory, for the list element
`mdc-list--non-interactive` | Optional, disables interactivity affordances
`mdc-list--dense` | Optional, styles the density of the list, making it appear more compact
`mdc-list--avatar-list` | Optional, configures the leading tiles of each row to display images instead of icons. This will make the graphics of the list items larger
`mdc-list--two-line` | Optional, modifier to style list with two lines (primary and secondary lines)
`mdc-list-item` | Mandatory, for the list item element
`mdc-list-item__text` | Optional, primary text for the row (displayed as middle column of the list item)
`mdc-list-item__secondary-text` | Optional, secondary text for the list item. Displayed below the primary text. Should be the child of `mdc-list-item__text`
`mdc-list-item--selected` | Optional, styles the row in an selected* state
`mdc-list-item--activated` | Optional, styles the row in an activated* state
`mdc-list` | Mandatory, for the list element.
`mdc-list--non-interactive` | Optional, disables interactivity affordances.
`mdc-list--dense` | Optional, styles the density of the list, making it appear more compact.
`mdc-list--avatar-list` | Optional, configures the leading tiles of each row to display images instead of icons. This will make the graphics of the list items larger.
`mdc-list--two-line` | Optional, modifier to style list with two lines (primary and secondary lines).
`mdc-list-item` | Mandatory, for the list item element.
`mdc-list-item__text` | Optional, primary text for the row (displayed as middle column of the list item).
`mdc-list-item__secondary-text` | Optional, secondary text for the list item. Displayed below the primary text. Should be the child of `mdc-list-item__text`.
`mdc-list-item--selected` | Optional, styles the row in an selected* state.
`mdc-list-item--activated` | Optional, styles the row in an activated* state.
`mdc-list-item__graphic` | Optional, the first tile in the row (in LTR languages, the first column of the list item). Typically an icon or image.
`mdc-list-item__meta` | Optional, the last tile in the row (in LTR languages, the last column of the list item). Typically small text, icon. or image.
`mdc-list-group` | Optional, wrapper around two or more mdc-list elements to be grouped together
`mdc-list-group__subheader` | Optional, heading text displayed above each list in a group
`mdc-list-divider` | Optional, for list divider element
`mdc-list-divider--padded` | Optional, leaves gaps on each side of divider to match padding of `list-item__meta`
`mdc-list-divider--inset` | Optional, increases the leading margin of the divider so that it does not intersect the avatar column
`mdc-list-group` | Optional, wrapper around two or more mdc-list elements to be grouped together.
`mdc-list-group__subheader` | Optional, heading text displayed above each list in a group.
`mdc-list-divider` | Optional, for list divider element.
`mdc-list-divider--padded` | Optional, leaves gaps on each side of divider to match padding of `list-item__meta`.
`mdc-list-divider--inset` | Optional, increases the leading margin of the divider so that it does not intersect the avatar column.

> NOTE: `mdc-list-divider` class can be used between list items (example 1) *OR* between two lists (example 2)
> NOTE: `mdc-list-divider` class can be used between list items (example 1) *OR* between two lists (example 2).
> NOTE: the difference between selected and activated states:
* *Selected* state should be implemented on the `.list-item` when it is likely to change soon. Eg., selecting one or more photos to share in Google Photos.
* Multiple items can be selected at the same time when using the *selected* state
* Multiple items can be selected at the same time when using the *selected* state.
* *Activated* state is similar to selected state, however should only be implemented once within a specific list.
* *Activated* state is more permanent than selected state, and will **NOT** change soon relative to the lifetime of the page.

### Sass Mixins

Mixin | Description
--- | ---
`mdc-list-item-primary-text-ink-color($color)` | Sets the ink color of the primary text of the list item
`mdc-list-item-secondary-text-ink-color($color)` | Sets the ink color of the secondary text of the list item
`mdc-list-item-graphic-fill-color($color)` | Sets background ink color of the graphic element within list item
`mdc-list-item-graphic-ink-color($color)` | Sets ink color of the graphic element within list item
`mdc-list-item-meta-ink-color($color)` | Sets ink color of the meta element within list item
`mdc-list-divider-color($color)` | Sets divider ink color
`mdc-list-group-subheader-ink-color($color)` | Sets ink color of subheader text within list group
`mdc-list-item-primary-text-ink-color($color)` | Sets the ink color of the primary text of the list item.
`mdc-list-item-secondary-text-ink-color($color)` | Sets the ink color of the secondary text of the list item.
`mdc-list-item-graphic-fill-color($color)` | Sets background ink color of the graphic element within list item.
`mdc-list-item-graphic-ink-color($color)` | Sets ink color of the graphic element within list item.
`mdc-list-item-meta-ink-color($color)` | Sets ink color of the meta element within list item.
`mdc-list-divider-color($color)` | Sets divider ink color.
`mdc-list-group-subheader-ink-color($color)` | Sets ink color of subheader text within list group.

### Accessibility

The MDCList JavaScript component implements the WAI-ARIA best practices for
[Listbox](https://www.w3.org/TR/wai-aria-practices-1.1/#Listbox). This includes overriding the default tab behavior
within the list component. You should not add `tabindex` to any of the `li` elements in a list.

As the user navigates through the list, any `button` or `a` elements within the list will receive `tabindex="-1"`
when the list item is not focused. When the list item receives focus, the child `button` and `a` elements will
receive `tabIndex="0"`. This allows for the user to tab through list items elements and then tab to the
first element after the list. The `Arrow`, `Home`, and `End` keys should be used for navigating internal list elements.
The MDCList will perform the following actions for each key press

Key | Action
--- | ---
`ArrowUp` | When the list is in a vertical orientation, it will cause the previous list item to receive focus.
`ArrowDown` | When the list is in a vertical orientation, it will cause the next list item to receive focus.
`ArrowLeft` | When the list is in a horizontal orientation (default), it will cause the previous list item to receive focus.
`ArrowRight` | When the list is in a horizontal orientation (default), it will cause the next list item to receive focus.
`Home` | Will cause the first list item in the list to receive focus.
`End` | Will cause the last list item in the list to receive focus.

## Usage within Web Frameworks

If you are using a JavaScript framework, such as React or Angular, you can create a List for your framework. Depending on your needs, you can use the _Simple Approach: Wrapping MDC Web Vanilla Components_, or the _Advanced Approach: Using Foundations and Adapters_. Please follow the instructions [here](../../docs/integrating-into-frameworks.md).

### `MDCListAdapter`

Method Signature | Description
--- | ---
`getListItemCount() => Number` | Returns the total number of list items (elements with `mdc-list-item` class) that are direct children of the `root_` element.
`getFocusedElementIndex() => Number` | Returns the `index` value of the currently focused element.
`getListItemIndex(ele: Element) => Number` | Returns the `index` value of the provided `ele` element.
`focusItemAtIndex(ndx: Number) => void` | Focuses the list item at the `ndx` value specified.
`setTabIndexForListItemChildren(ndx: Number, value: Number) => void` | Sets the `tabindex` attribute to `value` for each child `button` and `a` element in the list item at the `ndx` specified.

### `MDCListFoundation`

Method Signature | Description
--- | ---
`setWrapFocus(value: Boolean) => void` | Sets the list to allow the up arrow on the first element to focus the last element of the list and vice versa.
`setVerticalOrientation(value: Boolean) => void` | Sets the list to an orientation causing the keys used for navigation to change. `true` results in the Up/Down arrow keys being used. `false` results in the Left/Right arrow keys being used.
`handleFocusIn(evt: Event) => void` | Handles the changing of `tabindex` to `0` for all `button` and `a` elements when a list item receives focus.
`handleFocusOut(evt: Event) => void` | Handles the changing of `tabindex` to `-1` for all `button` and `a` elements when a list item loses focus.
`handleKeydown(evt: Event) => void` | Handles determining if a focus action should occur when a key event is triggered.
`focusNextElement(index: Number) => void` | Handles focusing the next element using the current `index`.
`focusPrevElement(index: Number) => void` | Handles focusing the previous element using the current `index`.
`focusFirstElement() => void` | Handles focusing the first element in a list.
`focusLastElement() => void` | Handles focusing the last element in a list.
59 changes: 59 additions & 0 deletions packages/mdc-list/adapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* @license
* Copyright 2018 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* eslint no-unused-vars: [2, {"args": "none"}] */

/**
* Adapter for MDC List. Provides an interface for managing focus.
*
* Additionally, provides type information for the adapter to the Closure
* compiler.
*
* Implement this adapter for your framework of choice to delegate updates to
* the component in your framework of choice. See architecture documentation
* for more details.
* https://github.com/material-components/material-components-web/blob/master/docs/code/architecture.md
*
* @record
*/
class MDCListAdapter {
/** @return {Number} */
getListItemCount() {}

/**
* @return {Number} */
getFocusedElementIndex() {}

/** @param {Element} node */
getListItemIndex(node) {}

/**
* Focuses list item at the index specified.
* @param {Number} ndx
*/
focusItemAtIndex(ndx) {}

/**
* Sets the tabindex to the value specified for all button/a element children of
* the list item at the index specified.
* @param {Number} listItemIndex
* @param {Number} tabIndexValue
*/
setTabIndexForListItemChildren(listItemIndex, tabIndexValue) {}
}

export {MDCListAdapter};
31 changes: 31 additions & 0 deletions packages/mdc-list/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @license
* Copyright 2018 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/** @enum {string} */
const cssClasses = {
LIST_ITEM_CLASS: 'mdc-list-item',
};

/** @enum {string} */
const strings = {
ARIA_ORIENTATION: 'aria-orientation',
ARIA_ORIENTATION_VERTICAL: 'vertical',
FOCUSABLE_CHILD_ELEMENTS: 'button:not(:disabled), a',
ITEMS_SELECTOR: '.mdc-list-item',
};

export {strings, cssClasses};
Loading

0 comments on commit 7c06e9f

Please sign in to comment.