Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

feat(carousel): adding actionable items into carousel #2271

Merged
merged 13 commits into from
Jan 29, 2020
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import * as React from 'react'
import { Carousel, Image, Flex, Text, Button, Toolbar, Header } from '@fluentui/react'

const imageAltTags = {
ade: 'Portrait of Ade',
elliot: 'Portrait of Elliot',
kristy: 'Portrait of Kristy',
nan: 'Portrait of Nan',
}

const tabAriaLabel = {
ade: 'Ade',
elliot: 'Elliot',
kristy: 'Kristy',
nan: 'Nan',
}

const carouselTextContent = (
<Text>
<Header as="h3"> Card </Header>
text or any other text 1 , text or any other text 2, text or any other text 3 text or any other
text 4, text or any other text 5, text or any other text 6
</Text>
)

const buttonStyles = { margin: '40px 0px 10px 10px' }
const imageStyles = { maxWidth: '70px', maxHeight: '70px', margin: '15px 0px 0px 5px' }

const carouselToolbarContent = (
<Toolbar
aria-label="Actions"
styles={{ marginTop: '40px' }}
items={[
{
key: 'custom-button-1',
kind: 'custom',
content: <Button content="First" />,
fitted: 'horizontally',
},
{
key: 'custom-button-2',
kind: 'custom',
content: <Button content="Second" />,
fitted: 'horizontally',
},
{
key: 'custom-button-3',
kind: 'custom',
content: <Button content="Third" />,
fitted: 'horizontally',
},
]}
/>
)

const carouselItems = [
{
key: 'ade',
id: 'ade',
content: (
<div>
<Flex gap="gap.medium">
<Image
styles={imageStyles}
src="public/images/avatar/large/ade.jpg"
fluid
alt={imageAltTags.ade}
/>
{carouselTextContent}
</Flex>
<Button content="Open" styles={buttonStyles} />
</div>
),
'aria-label': 'Ade card',
},
{
key: 'elliot',
id: 'elliot',
content: (
<div>
<Flex gap="gap.medium">
<Image
styles={imageStyles}
src="public/images/avatar/large/elliot.jpg"
fluid
alt={imageAltTags.elliot}
/>
{carouselTextContent}
</Flex>
{carouselToolbarContent}
</div>
),
'aria-label': 'Elliot card',
},
{
key: 'kristy',
id: 'kristy',
content: (
<div>
<Flex gap="gap.medium">
<Image
styles={imageStyles}
src="public/images/avatar/large/kristy.png"
fluid
alt={imageAltTags.kristy}
/>
{carouselTextContent}
</Flex>
<Flex gap="gap.medium" styles={buttonStyles}>
<Button content="Call" />
<Button content="Video call" />
</Flex>
</div>
),
'aria-label': 'Kristy card',
},
]

const CarouselExample = () => (
<Carousel
ariaRoleDescription="carousel"
navigation={{
'aria-label': 'people cards',
items: carouselItems.map((item, index) => ({
key: item.id,
'aria-label': tabAriaLabel[item.id],
'aria-controls': item.id,
})),
}}
items={carouselItems}
getItemPositionText={(index: number, size: number) => `${index + 1} of ${size}`}
/>
)

export default CarouselExample
6 changes: 6 additions & 0 deletions docs/src/examples/components/Carousel/Variations/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ const Variations = () => (
description="A Carousel's items navigation can be circular."
examplePath="components/Carousel/Variations/CarouselCircularExample"
/>

<ComponentExample
title="Carousel with actionable elements"
description="A Carousel can have actionable elements inside."
examplePath="components/Carousel/Variations/CarouselExampleWithFocusableElements"
/>
</ExampleSection>
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Accessibility } from '../../types'
import * as keyboardKey from 'keyboard-key'

/**
* @specification
Expand All @@ -16,7 +17,11 @@ const carouselItemBehavior: Accessibility<CarouselItemProps> = props => ({
},

keyActions: {
root: {},
root: {
arrowKeysNavigationStopPropagation: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a unit test for this.

It should check that left/right arrows sent to a child of the container (the visible item for instance, or a child of that visible item) should not move the slides.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks :) for this catch. Tests added, as well a specification line in the top of file.

keyCombinations: [{ keyCode: keyboardKey.ArrowRight }, { keyCode: keyboardKey.ArrowLeft }],
},
},
},
})

Expand Down
9 changes: 9 additions & 0 deletions packages/react/src/components/Carousel/CarouselItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ class CarouselItem extends UIComponent<WithAsProp<CarouselItemProps>> {
itemPositionText: `${CarouselItem.className}__itemPositionText`,
}

actionHandlers = {
arrowKeysNavigationStopPropagation: e => {
// let event propagate, when it was invoke on the element where arrow keys should rotate carousel
if (e.currentTarget !== e.target) {
e.stopPropagation()
}
},
}

renderComponent({ ElementType, classes, styles, accessibility, unhandledProps }) {
const { children, content, itemPositionText } = this.props
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@ import * as React from 'react'

import { isConformant } from 'test/specs/commonTests'
import Carousel, { CarouselProps } from 'src/components/Carousel/Carousel'
import Button from 'src/components/Button/Button'
import CarouselItem from 'src/components/Carousel/CarouselItem'
import CarouselNavigation from 'src/components/Carousel/CarouselNavigation'
import CarouselNavigationItem from 'src/components/Carousel/CarouselNavigationItem'
import Text from 'src/components/Text/Text'
import { ReactWrapper, CommonWrapper } from 'enzyme'
import { findIntrinsicElement, mountWithProvider } from 'test/utils'

const buttonName = 'button-to-test'

const items = [
{
key: 'item1',
content: <Text content={'item1'} />,
content: (
<div>
<Text content={'item1'} /> <Button id={buttonName} content={buttonName} />
</div>
),
},
{
key: 'item2',
Expand Down Expand Up @@ -54,6 +61,8 @@ const getNavigationNavigationItemAtIndexWrapper = (
): CommonWrapper => findIntrinsicElement(wrapper, `.${CarouselNavigationItem.className}`).at(index)
const getItemAtIndexWrapper = (wrapper: ReactWrapper, index: number): CommonWrapper =>
findIntrinsicElement(wrapper, `.${CarouselItem.className}`).at(index)
const getButtonWrapper = (wrapper: ReactWrapper): CommonWrapper =>
findIntrinsicElement(wrapper, `#${buttonName}`)

jest.useFakeTimers()

Expand Down Expand Up @@ -150,6 +159,26 @@ describe('Carousel', () => {

expect(pagination.getDOMNode().textContent).toBe(`1 of ${items.length}`)
})

it('should not change at arrow left if event is invoked on child element', () => {
kolaps33 marked this conversation as resolved.
Show resolved Hide resolved
const wrapper = renderCarousel()
const button = getButtonWrapper(wrapper)
const pagination = getPaginationWrapper(wrapper)

button.simulate('keydown', { key: 'ArrowLeft' })

expect(pagination.getDOMNode().textContent).toBe(`1 of ${items.length}`)
})

it('should not change at arrow right if event is invoked on child element', () => {
const wrapper = renderCarousel()
const button = getButtonWrapper(wrapper)
const pagination = getPaginationWrapper(wrapper)

button.simulate('keydown', { key: 'ArrowRight' })

expect(pagination.getDOMNode().textContent).toBe(`1 of ${items.length}`)
})
})

describe('paddle', () => {
Expand Down