diff --git a/CHANGELOG.md b/CHANGELOG.md
index af197727ac..7006d2dd84 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Fixes
- Ensure `Popup` properly flips values of `offset` prop in RTL @kuzhelov ([#612](https://github.com/stardust-ui/react/pull/612))
+- Fix `List` - items should be selectable @sophieH29 ([#566](https://github.com/stardust-ui/react/pull/566))
### Features
- Add `color` prop to `Text` component @Bugaa92 ([#597](https://github.com/stardust-ui/react/pull/597))
diff --git a/docs/src/examples/components/List/Content/ListExampleEndMedia.shorthand.tsx b/docs/src/examples/components/List/Content/ListExampleEndMedia.shorthand.tsx
index 3955f2f76a..6915275332 100644
--- a/docs/src/examples/components/List/Content/ListExampleEndMedia.shorthand.tsx
+++ b/docs/src/examples/components/List/Content/ListExampleEndMedia.shorthand.tsx
@@ -21,6 +21,6 @@ const items = [
},
]
-const ListExample = () =>
+const ListExample = () =>
export default ListExample
diff --git a/docs/src/examples/components/List/Content/ListExampleEndMedia.tsx b/docs/src/examples/components/List/Content/ListExampleEndMedia.tsx
index 6b7a375791..3b99d18770 100644
--- a/docs/src/examples/components/List/Content/ListExampleEndMedia.tsx
+++ b/docs/src/examples/components/List/Content/ListExampleEndMedia.tsx
@@ -8,17 +8,17 @@ const ListExample = () => (
)
diff --git a/docs/src/examples/components/List/Types/ListExample.shorthand.tsx b/docs/src/examples/components/List/Types/ListExample.shorthand.tsx
index 866bfe7a56..10f591fff3 100644
--- a/docs/src/examples/components/List/Types/ListExample.shorthand.tsx
+++ b/docs/src/examples/components/List/Types/ListExample.shorthand.tsx
@@ -25,6 +25,6 @@ const items = [
},
]
-const ListExampleSelection = ({ knobs }) =>
+const ListExampleSelectable = ({ knobs }) =>
-export default ListExampleSelection
+export default ListExampleSelectable
diff --git a/docs/src/examples/components/List/Types/ListExample.tsx b/docs/src/examples/components/List/Types/ListExample.tsx
index da2e3c1f4a..250ab8ef2a 100644
--- a/docs/src/examples/components/List/Types/ListExample.tsx
+++ b/docs/src/examples/components/List/Types/ListExample.tsx
@@ -1,7 +1,7 @@
import React from 'react'
import { List, Image } from '@stardust-ui/react'
-const ListExampleSelection = ({ knobs }) => (
+const ListExampleSelectable = ({ knobs }) => (
}
@@ -24,4 +24,4 @@ const ListExampleSelection = ({ knobs }) => (
)
-export default ListExampleSelection
+export default ListExampleSelectable
diff --git a/docs/src/examples/components/List/Types/ListExampleSelection.shorthand.tsx b/docs/src/examples/components/List/Types/ListExampleSelectable.shorthand.tsx
similarity index 79%
rename from docs/src/examples/components/List/Types/ListExampleSelection.shorthand.tsx
rename to docs/src/examples/components/List/Types/ListExampleSelectable.shorthand.tsx
index fe7842da57..49a526710e 100644
--- a/docs/src/examples/components/List/Types/ListExampleSelection.shorthand.tsx
+++ b/docs/src/examples/components/List/Types/ListExampleSelectable.shorthand.tsx
@@ -25,8 +25,6 @@ const items = [
},
]
-const selection = knobs => (knobs === undefined ? true : knobs.selection)
+const ListExampleSelectable = () =>
-const ListExampleSelection = ({ knobs }) =>
-
-export default ListExampleSelection
+export default ListExampleSelectable
diff --git a/docs/src/examples/components/List/Types/ListExampleSelection.tsx b/docs/src/examples/components/List/Types/ListExampleSelectable.tsx
similarity index 72%
rename from docs/src/examples/components/List/Types/ListExampleSelection.tsx
rename to docs/src/examples/components/List/Types/ListExampleSelectable.tsx
index ff00e6ef0c..55029d1256 100644
--- a/docs/src/examples/components/List/Types/ListExampleSelection.tsx
+++ b/docs/src/examples/components/List/Types/ListExampleSelectable.tsx
@@ -1,32 +1,30 @@
import React from 'react'
import { List, Image } from '@stardust-ui/react'
-const selection = knobs => (knobs === undefined ? true : knobs.selection)
-
-const ListExampleSelection = ({ knobs }) => (
-
+const ListExampleSelectable = () => (
+
}
header="Irving Kuhic"
headerMedia="7:26:56 AM"
content="Program the sensor to the SAS alarm through the haptic SQL card!"
- selection={selection(knobs)}
+ selectable
/>
}
header="Skyler Parks"
headerMedia="11:30:17 PM"
content="Use the online FTP application to input the multi-byte application!"
- selection={selection(knobs)}
+ selectable
/>
}
header="Dante Schneider"
headerMedia="5:22:40 PM"
content="The GB pixel is down, navigate the virtual interface!"
- selection={selection(knobs)}
+ selectable
/>
)
-export default ListExampleSelection
+export default ListExampleSelectable
diff --git a/docs/src/examples/components/List/Types/ListExampleSelectableControlled.shorthand.tsx b/docs/src/examples/components/List/Types/ListExampleSelectableControlled.shorthand.tsx
new file mode 100644
index 0000000000..f493b03105
--- /dev/null
+++ b/docs/src/examples/components/List/Types/ListExampleSelectableControlled.shorthand.tsx
@@ -0,0 +1,48 @@
+import * as React from 'react'
+import { List, Image } from '@stardust-ui/react'
+
+class SelectableListControlledExample extends React.Component {
+ state = { selectedIndex: -1 }
+
+ items = [
+ {
+ key: 'irving',
+ media: ,
+ header: 'Irving Kuhic',
+ headerMedia: '7:26:56 AM',
+ content: 'Program the sensor to the SAS alarm through the haptic SQL card!',
+ },
+ {
+ key: 'skyler',
+ media: ,
+ header: 'Skyler Parks',
+ headerMedia: '11:30:17 PM',
+ content: 'Use the online FTP application to input the multi-byte application!',
+ },
+ {
+ key: 'dante',
+ media: ,
+ header: 'Dante Schneider',
+ headerMedia: '5:22:40 PM',
+ content: 'The GB pixel is down, navigate the virtual interface!',
+ },
+ ]
+
+ render() {
+ return (
+ {
+ alert(
+ `List is requested to change its selectedIndex state to "${newProps.selectedIndex}"`,
+ )
+ this.setState({ selectedIndex: newProps.selectedIndex })
+ }}
+ items={this.items}
+ />
+ )
+ }
+}
+
+export default SelectableListControlledExample
diff --git a/docs/src/examples/components/List/Types/ListExampleSelection.knobs.tsx b/docs/src/examples/components/List/Types/ListExampleSelection.knobs.tsx
deleted file mode 100644
index f8494fc867..0000000000
--- a/docs/src/examples/components/List/Types/ListExampleSelection.knobs.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import PropTypes from 'prop-types'
-import React from 'react'
-import Knobs from 'docs/src/components/Knobs/Knobs'
-
-const ListExampleSelectionKnobs: any = props => {
- const { onKnobChange, selection } = props
-
- return (
-
-
-
- )
-}
-
-ListExampleSelectionKnobs.propTypes = {
- onKnobChange: PropTypes.func.isRequired,
- selection: PropTypes.bool,
-}
-
-ListExampleSelectionKnobs.defaultProps = {
- selection: true,
-}
-
-export default ListExampleSelectionKnobs
diff --git a/docs/src/examples/components/List/Types/index.tsx b/docs/src/examples/components/List/Types/index.tsx
index 0d78cbf48a..7483e6ff44 100644
--- a/docs/src/examples/components/List/Types/index.tsx
+++ b/docs/src/examples/components/List/Types/index.tsx
@@ -1,4 +1,4 @@
-import React from 'react'
+import * as React from 'react'
import ComponentExample from 'docs/src/components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/src/components/ComponentDoc/ExampleSection'
@@ -10,9 +10,14 @@ const Types = () => (
examplePath="components/List/Types/ListExample"
/>
+
)
diff --git a/docs/src/prototypes/SearchPage/SearchPage.tsx b/docs/src/prototypes/SearchPage/SearchPage.tsx
index c655a20ad5..fa784abf6c 100644
--- a/docs/src/prototypes/SearchPage/SearchPage.tsx
+++ b/docs/src/prototypes/SearchPage/SearchPage.tsx
@@ -97,7 +97,7 @@ class SearchPage extends React.Component {
Results {results.length} of {DATA_RECORDS.length}
-
+
)}
diff --git a/src/components/List/List.tsx b/src/components/List/List.tsx
index cee47f7d2e..009b69940e 100644
--- a/src/components/List/List.tsx
+++ b/src/components/List/List.tsx
@@ -6,7 +6,7 @@ import * as PropTypes from 'prop-types'
import {
customPropTypes,
childrenExist,
- UIComponent,
+ AutoControlledComponent,
UIComponentProps,
ChildrenComponentProps,
commonPropTypes,
@@ -15,7 +15,7 @@ import ListItem from './ListItem'
import { listBehavior } from '../../lib/accessibility'
import { Accessibility, AccessibilityActionHandlers } from '../../lib/accessibility/types'
import { ContainerFocusHandler } from '../../lib/accessibility/FocusHandling/FocusContainer'
-import { Extendable, ShorthandValue } from '../../../types/utils'
+import { Extendable, ShorthandValue, ComponentEventHandler } from '../../../types/utils'
export interface ListProps extends UIComponentProps, ChildrenComponentProps {
/**
@@ -30,8 +30,21 @@ export interface ListProps extends UIComponentProps, ChildrenComponentProps {
/** Shorthand array of props for ListItem. */
items?: ShorthandValue[]
- /** A selection list formats list items as possible choices. */
- selection?: boolean
+ /** A selectable list formats list items as possible choices. */
+ selectable?: boolean
+
+ /** Index of the currently selected item. */
+ selectedIndex?: number
+
+ /** Initial selectedIndex value. */
+ defaultSelectedIndex?: number
+
+ /**
+ * Event for request to change 'selectedIndex' value.
+ * @param {SyntheticEvent} event - React's original SyntheticEvent.
+ * @param {object} data - All props and proposed value.
+ */
+ onSelectedIndexChange?: ComponentEventHandler
/** Truncates content */
truncateContent?: boolean
@@ -41,13 +54,14 @@ export interface ListProps extends UIComponentProps, ChildrenComponentProps {
}
export interface ListState {
- selectedItemIndex: number
+ focusedIndex: number
+ selectedIndex?: number
}
/**
* A list displays a group of related content.
*/
-class List extends UIComponent, ListState> {
+class List extends AutoControlledComponent, ListState> {
static displayName = 'List'
static className = 'ui-list'
@@ -59,9 +73,12 @@ class List extends UIComponent, ListState> {
accessibility: PropTypes.func,
debug: PropTypes.bool,
items: customPropTypes.collectionShorthand,
- selection: PropTypes.bool,
+ selectable: PropTypes.bool,
truncateContent: PropTypes.bool,
truncateHeader: PropTypes.bool,
+ selectedIndex: PropTypes.number,
+ defaultSelectedIndex: PropTypes.number,
+ onSelectedIndexChange: PropTypes.func,
}
static defaultProps = {
@@ -69,14 +86,15 @@ class List extends UIComponent, ListState> {
accessibility: listBehavior as Accessibility,
}
+ static autoControlledProps = ['selectedIndex']
+ getInitialAutoControlledState() {
+ return { selectedIndex: -1, focusedIndex: 0 }
+ }
+
static Item = ListItem
// List props that are passed to each individual Item props
- static itemProps = ['debug', 'selection', 'truncateContent', 'truncateHeader', 'variables']
-
- public state = {
- selectedItemIndex: 0,
- }
+ static itemProps = ['debug', 'selectable', 'truncateContent', 'truncateHeader', 'variables']
private focusHandler: ContainerFocusHandler = null
private itemRefs = []
@@ -100,6 +118,22 @@ class List extends UIComponent, ListState> {
},
}
+ constructor(props, context) {
+ super(props, context)
+
+ this.focusHandler = new ContainerFocusHandler(
+ () => this.props.items.length,
+ index => {
+ this.setState({ focusedIndex: index }, () => {
+ const targetComponent = this.itemRefs[index] && this.itemRefs[index].current
+ const targetDomNode = ReactDOM.findDOMNode(targetComponent) as any
+
+ targetDomNode && targetDomNode.focus()
+ })
+ },
+ )
+ }
+
renderComponent({ ElementType, classes, accessibility, rest }) {
const { children } = this.props
@@ -115,36 +149,32 @@ class List extends UIComponent, ListState> {
)
}
- componentDidMount() {
- this.focusHandler = new ContainerFocusHandler(
- () => this.props.items.length,
- index => {
- this.setState({ selectedItemIndex: index }, () => {
- const targetComponent = this.itemRefs[index] && this.itemRefs[index].current
- const targetDomNode = ReactDOM.findDOMNode(targetComponent) as any
-
- targetDomNode && targetDomNode.focus()
- })
- },
- )
- }
-
renderItems() {
const { items } = this.props
- const { selectedItemIndex } = this.state
+ const { focusedIndex, selectedIndex } = this.state
+
+ this.focusHandler.syncFocusedIndex(focusedIndex)
this.itemRefs = []
return _.map(items, (item, idx) => {
const maybeSelectableItemProps = {} as any
- if (this.props.selection) {
+ if (this.props.selectable) {
const ref = React.createRef()
this.itemRefs[idx] = ref
- maybeSelectableItemProps.tabIndex = idx === selectedItemIndex ? 0 : -1
maybeSelectableItemProps.ref = ref
- maybeSelectableItemProps.onFocus = () => this.focusHandler.syncFocusedItemIndex(idx)
+ maybeSelectableItemProps.onFocus = () => this.setState({ focusedIndex: idx })
+ maybeSelectableItemProps.onClick = e => {
+ this.trySetState({ selectedIndex: idx })
+ _.invoke(this.props, 'onSelectedIndexChange', e, {
+ ...this.props,
+ ...{ selectedIndex: idx },
+ })
+ }
+ maybeSelectableItemProps.selected = idx === selectedIndex
+ maybeSelectableItemProps.tabIndex = idx === focusedIndex ? 0 : -1
}
const itemProps = {
diff --git a/src/components/List/ListItem.tsx b/src/components/List/ListItem.tsx
index febdea30d7..b6af86fd62 100644
--- a/src/components/List/ListItem.tsx
+++ b/src/components/List/ListItem.tsx
@@ -1,5 +1,5 @@
import * as React from 'react'
-
+import * as _ from 'lodash'
import * as PropTypes from 'prop-types'
import {
createShorthandFactory,
@@ -10,8 +10,8 @@ import {
} from '../../lib'
import ItemLayout from '../ItemLayout/ItemLayout'
import { listItemBehavior } from '../../lib/accessibility'
-import { Accessibility } from '../../lib/accessibility/types'
-import { Extendable } from '../../../types/utils'
+import { Accessibility, AccessibilityActionHandlers } from '../../lib/accessibility/types'
+import { Extendable, ComponentEventHandler } from '../../../types/utils'
export interface ListItemProps extends UIComponentProps, ContentComponentProps {
/**
@@ -20,7 +20,7 @@ export interface ListItemProps extends UIComponentProps, ContentComponentProps
}
/**
* A list item contains a single piece of content within a list.
*/
-class ListItem extends UIComponent, ListItemState> {
+class ListItem extends UIComponent> {
static create: Function
static displayName = 'ListItem'
@@ -67,11 +73,14 @@ class ListItem extends UIComponent, ListItemState> {
important: PropTypes.bool,
media: PropTypes.any,
- selection: PropTypes.bool,
+ selectable: PropTypes.bool,
+ selected: PropTypes.bool,
+
truncateContent: PropTypes.bool,
truncateHeader: PropTypes.bool,
accessibility: PropTypes.func,
+ onClick: PropTypes.func,
}
static defaultProps = {
@@ -79,17 +88,18 @@ class ListItem extends UIComponent, ListItemState> {
accessibility: listItemBehavior as Accessibility,
}
- constructor(props: ListItemProps) {
- super(props, null)
-
- this.state = {
- isHovering: false,
- }
+ protected actionHandlers: AccessibilityActionHandlers = {
+ performClick: event => {
+ this.handleClick(event)
+ event.preventDefault()
+ },
}
- private itemRef = React.createRef()
+ handleClick = e => {
+ _.invoke(this.props, 'onClick', e, this.props)
+ }
- renderComponent({ ElementType, classes, accessibility, rest, styles }) {
+ renderComponent({ classes, accessibility, rest, styles }) {
const {
as,
debug,
@@ -121,8 +131,9 @@ class ListItem extends UIComponent, ListItemState> {
headerCSS={styles.header}
headerMediaCSS={styles.headerMedia}
contentCSS={styles.content}
- ref={this.itemRef}
+ onClick={this.handleClick}
{...accessibility.attributes.root}
+ {...accessibility.keyHandlers.root}
{...rest}
/>
)
diff --git a/src/index.ts b/src/index.ts
index af883aeb8b..2edc275517 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -72,7 +72,7 @@ export { default as Label, LabelProps } from './components/Label/Label'
export { default as Layout, LayoutPropsWithDefaults, LayoutProps } from './components/Layout/Layout'
export { default as List, ListProps } from './components/List/List'
-export { default as ListItem, ListItemState, ListItemProps } from './components/List/ListItem'
+export { default as ListItem, ListItemProps } from './components/List/ListItem'
export { default as Menu, MenuProps } from './components/Menu/Menu'
export { default as MenuItem, MenuItemState, MenuItemProps } from './components/Menu/MenuItem'
diff --git a/src/lib/accessibility/Behaviors/List/listBehavior.ts b/src/lib/accessibility/Behaviors/List/listBehavior.ts
index 360e89f59e..54f91bf632 100644
--- a/src/lib/accessibility/Behaviors/List/listBehavior.ts
+++ b/src/lib/accessibility/Behaviors/List/listBehavior.ts
@@ -4,10 +4,10 @@ import basicListBehavior from './basicListBehavior'
/**
* @description
- * Defines a behavior 'BasicListBehavior' or 'SelectableListBehavior' based on property 'selection'.
+ * Defines a behavior 'BasicListBehavior' or 'SelectableListBehavior' based on property 'selectable'.
*/
const ListBehavior: Accessibility = (props: any) =>
- props.selection ? selectableListBehavior(props) : basicListBehavior(props)
+ props.selectable ? selectableListBehavior(props) : basicListBehavior(props)
export default ListBehavior
diff --git a/src/lib/accessibility/Behaviors/List/listItemBehavior.ts b/src/lib/accessibility/Behaviors/List/listItemBehavior.ts
index 09292f93ff..714bf96c3f 100644
--- a/src/lib/accessibility/Behaviors/List/listItemBehavior.ts
+++ b/src/lib/accessibility/Behaviors/List/listItemBehavior.ts
@@ -4,10 +4,10 @@ import { Accessibility } from '../../types'
/**
* @description
- * Defines a behavior "BasicListItemBehavior" or "SelectableListItemBehavior" based on "selection" property.
+ * Defines a behavior "BasicListItemBehavior" or "SelectableListItemBehavior" based on "selectable" property.
*/
const listItemBehavior: Accessibility = (props: any) =>
- props.selection ? selectableListItemBehavior(props) : basicListItemBehavior(props)
+ props.selectable ? selectableListItemBehavior(props) : basicListItemBehavior(props)
export default listItemBehavior
diff --git a/src/lib/accessibility/Behaviors/List/selectableListItemBehavior.ts b/src/lib/accessibility/Behaviors/List/selectableListItemBehavior.ts
index 31f88d0b33..ae946d29a2 100644
--- a/src/lib/accessibility/Behaviors/List/selectableListItemBehavior.ts
+++ b/src/lib/accessibility/Behaviors/List/selectableListItemBehavior.ts
@@ -1,19 +1,24 @@
import { Accessibility } from '../../types'
+import * as keyboardKey from 'keyboard-key'
/**
* @specification
* Adds role='option'. This role is used for a selectable item in a list.
- * Adds attribute 'aria-selected=true' based on the property 'active'. Based on this screen readers will recognize the selected state of the item.
+ * Adds attribute 'aria-selected=true' based on the property 'selected'. Based on this screen readers will recognize the selected state of the item.
*/
const selectableListItemBehavior: Accessibility = (props: any) => ({
attributes: {
root: {
role: 'option',
- 'aria-selected': !!props['active'],
- ...(props.focusableItemProps && {
- tabIndex: props.focusableItemProps.isFocused ? '0' : '-1',
- }),
+ 'aria-selected': !!props['selected'],
+ },
+ },
+ keyActions: {
+ root: {
+ performClick: {
+ keyCombinations: [{ keyCode: keyboardKey.Enter }, { keyCode: keyboardKey.Spacebar }],
+ },
},
},
})
diff --git a/src/lib/accessibility/FocusHandling/FocusContainer.ts b/src/lib/accessibility/FocusHandling/FocusContainer.ts
index 14238fdb47..ef2aea39bd 100644
--- a/src/lib/accessibility/FocusHandling/FocusContainer.ts
+++ b/src/lib/accessibility/FocusHandling/FocusContainer.ts
@@ -1,29 +1,29 @@
import * as _ from 'lodash'
export class ContainerFocusHandler {
- private focusedItemIndex = 0
+ private focusedIndex = 0
constructor(private getItemsCount: () => number, private readonly setFocusAt: (number) => void) {}
private noItems = (): boolean => this.getItemsCount() === 0
- private constrainFocusedItemIndex(): void {
- if (this.focusedItemIndex < 0) {
- this.focusedItemIndex = 0
+ private constrainFocusedIndex(): void {
+ if (this.focusedIndex < 0) {
+ this.focusedIndex = 0
}
const itemsCount = this.getItemsCount()
- if (this.focusedItemIndex >= itemsCount) {
- this.focusedItemIndex = itemsCount - 1
+ if (this.focusedIndex >= itemsCount) {
+ this.focusedIndex = itemsCount - 1
}
}
- public getFocusedItemIndex(): number {
- return this.focusedItemIndex
+ public getFocusedIndex(): number {
+ return this.focusedIndex
}
- public syncFocusedItemIndex(withCurrentIndex: number) {
- this.focusedItemIndex = withCurrentIndex
+ public syncFocusedIndex(withCurrentIndex: number) {
+ this.focusedIndex = withCurrentIndex
}
public movePrevious(): void {
@@ -31,10 +31,10 @@ export class ContainerFocusHandler {
return
}
- this.focusedItemIndex -= 1
- this.constrainFocusedItemIndex()
+ this.focusedIndex -= 1
+ this.constrainFocusedIndex()
- this.setFocusAt(this.focusedItemIndex)
+ this.setFocusAt(this.focusedIndex)
}
public moveNext(): void {
@@ -42,10 +42,10 @@ export class ContainerFocusHandler {
return
}
- this.focusedItemIndex += 1
- this.constrainFocusedItemIndex()
+ this.focusedIndex += 1
+ this.constrainFocusedIndex()
- this.setFocusAt(this.focusedItemIndex)
+ this.setFocusAt(this.focusedIndex)
}
public moveFirst(): void {
@@ -53,8 +53,8 @@ export class ContainerFocusHandler {
return
}
- this.focusedItemIndex = 0
- this.setFocusAt(this.focusedItemIndex)
+ this.focusedIndex = 0
+ this.setFocusAt(this.focusedIndex)
}
public moveLast(): void {
@@ -62,7 +62,7 @@ export class ContainerFocusHandler {
return
}
- this.focusedItemIndex = this.getItemsCount() - 1
- this.setFocusAt(this.focusedItemIndex)
+ this.focusedIndex = this.getItemsCount() - 1
+ this.setFocusAt(this.focusedIndex)
}
}
diff --git a/src/themes/teams/components/List/listItemStyles.ts b/src/themes/teams/components/List/listItemStyles.ts
index 4131c9e584..34721950c3 100644
--- a/src/themes/teams/components/List/listItemStyles.ts
+++ b/src/themes/teams/components/List/listItemStyles.ts
@@ -2,9 +2,9 @@ import { pxToRem } from '../../utils'
import { ComponentSlotStylesInput, ICSSInJSStyle } from '../../../types'
import { ListItemProps } from '../../../../components/List/ListItem'
-const hoverStyle = variables => ({
- background: variables.selectionHoverBackgroundColor,
- color: variables.selectionHoverColor,
+const hoverFocusStyle = variables => ({
+ background: variables.selectableFocusHoverBackgroundColor,
+ color: variables.selectableFocusHoverColor,
cursor: 'pointer',
'& .ui-item-layout__header': { color: 'inherit' },
@@ -18,16 +18,22 @@ const hoverStyle = variables => ({
'& .ui-item-layout__endMedia': { display: 'block', color: 'inherit' },
})
+const selectedStyle = variables => ({
+ background: variables.selectedBackgroundColor,
+ color: variables.selectedColor,
+})
+
const listItemStyles: ComponentSlotStylesInput = {
- root: ({ props: { selection, important }, variables }): ICSSInJSStyle => ({
- ...(selection && {
+ root: ({ props: { selectable, selected, important }, variables }): ICSSInJSStyle => ({
+ ...(selectable && {
position: 'relative',
// hide the end media by default
'& .ui-item-layout__endMedia': { display: 'none' },
- '&:hover': hoverStyle(variables),
- '&:focus': hoverStyle(variables),
+ '&:hover': hoverFocusStyle(variables),
+ '&:focus': hoverFocusStyle(variables),
+ ...(selected && selectedStyle(variables)),
}),
...(important && {
fontWeight: 'bold',
diff --git a/src/themes/teams/components/List/listItemVariables.ts b/src/themes/teams/components/List/listItemVariables.ts
index 5edc096d03..e7a622765c 100644
--- a/src/themes/teams/components/List/listItemVariables.ts
+++ b/src/themes/teams/components/List/listItemVariables.ts
@@ -13,7 +13,9 @@ export default siteVariables => ({
contentFontSize: siteVariables.fontSizes.small,
contentLineHeight: siteVariables.lineHeightSmall,
- // Selection
- selectionHoverColor: siteVariables.white,
- selectionHoverBackgroundColor: siteVariables.brand08,
+ // Selectable
+ selectableFocusHoverColor: siteVariables.white,
+ selectableFocusHoverBackgroundColor: siteVariables.brand08,
+ selectedColor: siteVariables.black,
+ selectedBackgroundColor: siteVariables.gray10,
})
diff --git a/test/specs/behaviors/listBehavior-test.tsx b/test/specs/behaviors/listBehavior-test.tsx
index faed2115ef..eada8135d1 100644
--- a/test/specs/behaviors/listBehavior-test.tsx
+++ b/test/specs/behaviors/listBehavior-test.tsx
@@ -1,15 +1,15 @@
import { listBehavior } from 'src/lib/accessibility'
describe('ListBehavior.ts', () => {
- test('use SelectableListBehavior if selection prop is defined', () => {
+ test('use SelectableListBehavior if selectable prop is defined', () => {
const property = {
- selection: true,
+ selectable: true,
}
const expectedResult = listBehavior(property)
expect(expectedResult.attributes.root.role).toEqual('listbox')
})
- test('use BasicListItemBehavior if selection prop is NOT defined', () => {
+ test('use BasicListItemBehavior if selectable prop is NOT defined', () => {
const property = {}
const expectedResult = listBehavior(property)
expect(expectedResult.attributes.root.role).toEqual('list')
diff --git a/test/specs/behaviors/listItemBehavior-test.tsx b/test/specs/behaviors/listItemBehavior-test.tsx
index 261ba29fe3..752000a625 100644
--- a/test/specs/behaviors/listItemBehavior-test.tsx
+++ b/test/specs/behaviors/listItemBehavior-test.tsx
@@ -1,15 +1,15 @@
import { listItemBehavior } from 'src/lib/accessibility'
describe('ListItemBehavior.ts', () => {
- test('use SelectableListItemBehavior if selection prop is defined', () => {
+ test('use SelectableListItemBehavior if selectable prop is defined', () => {
const property = {
- selection: true,
+ selectable: true,
}
const expectedResult = listItemBehavior(property)
expect(expectedResult.attributes.root.role).toEqual('option')
})
- test('use BasicListBehavior if selection prop is NOT defined', () => {
+ test('use BasicListBehavior if selectable prop is NOT defined', () => {
const property = {}
const expectedResult = listItemBehavior(property)
expect(expectedResult.attributes.root.role).toEqual('listitem')
diff --git a/test/specs/components/List/List-test.ts b/test/specs/components/List/List-test.ts
deleted file mode 100644
index 60f4471d91..0000000000
--- a/test/specs/components/List/List-test.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { isConformant, handlesAccessibility } from 'test/specs/commonTests'
-
-import List from 'src/components/List/List'
-import implementsCollectionShorthandProp from '../../commonTests/implementsCollectionShorthandProp'
-import ListItem from 'src/components/List/ListItem'
-
-const listImplementsCollectionShorthandProp = implementsCollectionShorthandProp(List)
-
-describe('List', () => {
- isConformant(List)
- handlesAccessibility(List, { defaultRootRole: 'list' })
- listImplementsCollectionShorthandProp('items', ListItem, { mapsValueToProp: 'content' })
-})
diff --git a/test/specs/components/List/List-test.tsx b/test/specs/components/List/List-test.tsx
new file mode 100644
index 0000000000..ae7fe610bf
--- /dev/null
+++ b/test/specs/components/List/List-test.tsx
@@ -0,0 +1,76 @@
+import * as React from 'react'
+
+import { isConformant, handlesAccessibility } from 'test/specs/commonTests'
+import { mountWithProvider } from 'test/utils'
+
+import List from 'src/components/List/List'
+import implementsCollectionShorthandProp from '../../commonTests/implementsCollectionShorthandProp'
+import ListItem from 'src/components/List/ListItem'
+
+const listImplementsCollectionShorthandProp = implementsCollectionShorthandProp(List)
+
+describe('List', () => {
+ isConformant(List)
+ handlesAccessibility(List, { defaultRootRole: 'list' })
+ listImplementsCollectionShorthandProp('items', ListItem, { mapsValueToProp: 'content' })
+
+ const getItems = () => [
+ { key: 'irving', content: 'Irving', onClick: jest.fn() },
+ { key: 'skyler', content: 'Skyler' },
+ { key: 'dante', content: 'Dante' },
+ ]
+
+ describe('items', () => {
+ it('renders children', () => {
+ const listItems = mountWithProvider(
).find('ListItem')
+ expect(listItems.length).toBe(3)
+ expect(listItems.first().props().content).toBe('Irving')
+ expect(listItems.last().props().content).toBe('Dante')
+ })
+
+ it('calls onClick handler for item', () => {
+ const items = getItems()
+ const listItems = mountWithProvider(
).find('ListItem')
+
+ listItems
+ .first()
+ .find('li')
+ .first()
+ .simulate('click')
+ expect(items[0].onClick).toHaveBeenCalled()
+ })
+ })
+
+ describe('selectedIndex', () => {
+ it('should not be set by default', () => {
+ const listItems = mountWithProvider(
).find('ListItem')
+ expect(listItems.everyWhere(item => !item.props().selected)).toBe(true)
+ })
+
+ it('can be set a default value', () => {
+ const listItems = mountWithProvider(
+
,
+ ).find('ListItem')
+ expect(listItems.first().props().selected).toBe(true)
+ })
+
+ it('should be set when item is clicked', () => {
+ const wrapper = mountWithProvider(
+
,
+ )
+ const listItems = wrapper.find('ListItem')
+ expect(listItems.at(0).props().selected).toBe(true)
+
+ listItems
+ .at(1)
+ .find('li')
+ .first()
+ .simulate('click')
+
+ const updatedItems = wrapper.find('ListItem')
+
+ expect(updatedItems.at(0).props().selected).toBe(false)
+ expect(updatedItems.at(1).props().selected).toBe(true)
+ })
+ })
+})
diff --git a/test/specs/lib/accessibility/FocusContainer-test.ts b/test/specs/lib/accessibility/FocusContainer-test.ts
index bed2ab14da..58c98d18ff 100644
--- a/test/specs/lib/accessibility/FocusContainer-test.ts
+++ b/test/specs/lib/accessibility/FocusContainer-test.ts
@@ -11,22 +11,22 @@ describe('Focus Container', () => {
const focusContainer = createFocusContainer()
expect(focusContainer).toBeDefined()
- expect(focusContainer.getFocusedItemIndex()).toBe(0)
+ expect(focusContainer.getFocusedIndex()).toBe(0)
})
describe('sync item index', () => {
test('should set focus item index', () => {
const focusContainer = createFocusContainer({ itemsCount: 5 })
- focusContainer.syncFocusedItemIndex(4)
+ focusContainer.syncFocusedIndex(4)
- expect(focusContainer.getFocusedItemIndex()).toBe(4)
+ expect(focusContainer.getFocusedIndex()).toBe(4)
})
test('should not set focus index function', () => {
const setFocusAt = jest.fn()
const focusContainer = createFocusContainer({ itemsCount: 5, setFocusAtFn: setFocusAt })
- focusContainer.syncFocusedItemIndex(4)
+ focusContainer.syncFocusedIndex(4)
expect(setFocusAt).not.toBeCalled()
})
})
@@ -34,10 +34,10 @@ describe('Focus Container', () => {
describe('move previous', () => {
test('should decrement index of focused item', () => {
const focusContainer = createFocusContainer({ itemsCount: 5 })
- focusContainer.syncFocusedItemIndex(4)
+ focusContainer.syncFocusedIndex(4)
focusContainer.movePrevious()
- expect(focusContainer.getFocusedItemIndex()).toBe(3)
+ expect(focusContainer.getFocusedIndex()).toBe(3)
})
test('should call set focus index function if there are any items', () => {
@@ -58,21 +58,21 @@ describe('Focus Container', () => {
test('focused item index should not ever become less than 0', () => {
const focusContainer = createFocusContainer({ itemsCount: 5 })
- focusContainer.syncFocusedItemIndex(0)
+ focusContainer.syncFocusedIndex(0)
focusContainer.movePrevious()
- expect(focusContainer.getFocusedItemIndex()).toBe(0)
+ expect(focusContainer.getFocusedIndex()).toBe(0)
})
})
describe('move next', () => {
test('should increment index of focused item', () => {
const focusContainer = createFocusContainer({ itemsCount: 5 })
- focusContainer.syncFocusedItemIndex(3)
+ focusContainer.syncFocusedIndex(3)
focusContainer.moveNext()
- expect(focusContainer.getFocusedItemIndex()).toBe(4)
+ expect(focusContainer.getFocusedIndex()).toBe(4)
})
test('should call set focus index function if there are any items', () => {
@@ -93,21 +93,21 @@ describe('Focus Container', () => {
test('focused item index should not exceed range of valid indexes', () => {
const focusContainer = createFocusContainer({ itemsCount: 5 })
- focusContainer.syncFocusedItemIndex(4)
+ focusContainer.syncFocusedIndex(4)
focusContainer.moveNext()
- expect(focusContainer.getFocusedItemIndex()).toBe(4)
+ expect(focusContainer.getFocusedIndex()).toBe(4)
})
})
describe('move first', () => {
test('should set focused item index to 0', () => {
const focusContainer = createFocusContainer({ itemsCount: 5 })
- focusContainer.syncFocusedItemIndex(3)
+ focusContainer.syncFocusedIndex(3)
focusContainer.moveFirst()
- expect(focusContainer.getFocusedItemIndex()).toBe(0)
+ expect(focusContainer.getFocusedIndex()).toBe(0)
})
test('should call set focus index function if there are any items', () => {
@@ -130,10 +130,10 @@ describe('Focus Container', () => {
describe('move last', () => {
test('should set focused item index to last index of valid range', () => {
const focusContainer = createFocusContainer({ itemsCount: 5 })
- focusContainer.syncFocusedItemIndex(2)
+ focusContainer.syncFocusedIndex(2)
focusContainer.moveLast()
- expect(focusContainer.getFocusedItemIndex()).toBe(4)
+ expect(focusContainer.getFocusedIndex()).toBe(4)
})
test('should call set focus index function if there are any items', () => {