Skip to content

Commit

Permalink
feat(Carousel): Added Keyboard Navigation (#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcusNotheis authored Aug 22, 2019
1 parent 83553c0 commit e1299ed
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 162 deletions.
216 changes: 108 additions & 108 deletions packages/main/__karma_snapshots__/Carousel.md

Large diffs are not rendered by default.

42 changes: 36 additions & 6 deletions packages/main/src/components/Carousel/Carousel.jss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,29 @@ const styles = ({ parameters }: JSSTheme) => ({
minWidth: '15.5rem',
fontFamily: parameters.sapUiFontFamily,
backgroundColor: parameters.sapUiBaseBG,
'&:hover': {
'& [data-value="paginationArrow"]': {
opacity: 1
'&:focus': {
outline: 'none',
'&:before': {
border: '1px solid #000000',
position: 'absolute',
content: '" "',
top: '0',
right: '0',
bottom: '0',
left: '0',
zIndex: '2',
pointerEvents: 'none'
},
'&:after': {
border: '1px dotted #ffffff',
position: 'absolute',
content: '" "',
top: '0',
right: '0',
bottom: '0',
left: '0',
zIndex: '2',
pointerEvents: 'none'
}
}
},
Expand All @@ -34,9 +54,19 @@ const styles = ({ parameters }: JSSTheme) => ({
fontSize: '1rem',
visibility: 'hidden'
},
carouselItemContentIndicator: {
padding: '0 4rem',
width: 'calc(100% - 8rem)'
carouselArrowPlacementContent: {
'&:hover': {
'& [data-value="paginationArrow"]': {
opacity: 1,
'& ui5-icon': {
transform: 'rotate(0deg)'
}
}
},
'& $carouselItem': {
padding: '0 4rem',
width: 'calc(100% - 8rem)'
}
}
});

Expand Down
20 changes: 20 additions & 0 deletions packages/main/src/components/Carousel/Carousel.karma.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,24 @@ describe('Carousel', () => {
);
expect(wrapper.render().find('[data-value="paginationArrow"]')).to.have.length(0);
});

it('Navigation to next page with Keyboard', () => {
const callback = sinon.spy();
const wrapper = mountThemedComponent(renderCarousel({ activePage: 0, onPageChanged: callback }));
wrapper
.find('div[role="list"]')
.last()
.simulate('keydown', { key: 'ArrowRight' });
expect(getEventFromCallback(callback).getParameter('selectedIndex')).to.equal(1);
});

it('Navigation to previous page with Keyboard', () => {
const callback = sinon.spy();
const wrapper = mountThemedComponent(renderCarousel({ activePage: 1, onPageChanged: callback }));
wrapper
.find('div[role="list"]')
.first()
.simulate('keydown', { key: 'ArrowLeft' });
expect(getEventFromCallback(callback).getParameter('selectedIndex')).to.equal(0);
});
});
32 changes: 13 additions & 19 deletions packages/main/src/components/Carousel/CarouselPagination.jss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,14 @@ const styles = ({ parameters }: JSSTheme) => ({
borderRadius: '50%',
alignSelf: 'center',
boxSizing: 'border-box',
backgroundColor: parameters.sapUiContentNonInteractiveIconColor,
'&$paginationIconActive': {
margin: '0 0.25rem',
width: '0.5rem',
height: '0.5rem',
backgroundColor: parameters.sapUiSelected
}
backgroundColor: parameters.sapUiContentNonInteractiveIconColor
},
paginationIconActive: {
margin: '0 0.25rem',
width: '0.5rem',
height: '0.5rem',
backgroundColor: parameters.sapUiSelected
},
paginationIconActive: {},
paginationArrow: {
boxShadow: 'none',
border: `1px solid ${parameters.sapUiButtonBorderColor}`,
Expand All @@ -66,6 +65,9 @@ const styles = ({ parameters }: JSSTheme) => ({
color: parameters.sapUiButtonEmphasizedTextColor
}
},
'@global html[dir="rtl"] div[data-value="paginationArrow"] ui5-icon': {
transform: 'rotate(180deg)'
},
paginationArrowContent: {
'& $paginationArrow': {
boxShadow: parameters.sapUiShadowLevel1,
Expand All @@ -86,21 +88,13 @@ const styles = ({ parameters }: JSSTheme) => ({
}
},
paginationArrowContentNoBar: {
composes: ['$paginationArrowContent'],
'& $paginationArrow': {
boxShadow: parameters.sapUiShadowLevel1,
'&:first-child': {
position: 'absolute',
top: 'calc(50% - 1rem)',
left: '0.5rem',
opacity: 0,
zIndex: ZIndex.InputModal
top: 'calc(50% - 1rem)'
},
'&:last-child': {
position: 'absolute',
top: 'calc(50% - 1rem)',
right: '0.5rem',
opacity: 0,
zIndex: ZIndex.InputModal
top: 'calc(50% - 1rem)'
}
}
}
Expand Down
38 changes: 15 additions & 23 deletions packages/main/src/components/Carousel/CarouselPagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Label } from '../../lib/Label';
import { PlacementType } from '../../lib/PlacementType';
import styles from './CarouselPagination.jss';

const useStyles = createUseStyles<JSSTheme, keyof ReturnType<typeof styles>>(styles);
const useStyles = createUseStyles<JSSTheme, keyof ReturnType<typeof styles>>(styles, { name: 'CarouselPagination' });

export interface CarouselPaginationPropTypes {
/**
Expand Down Expand Up @@ -56,17 +56,6 @@ const CarouselPagination: FC<CarouselPaginationPropTypes> = (props) => {
const numberOfChildren = React.Children.count(children);
const showTextIndicator = numberOfChildren >= TEXT_INDICATOR_THRESHOLD;

const paginationClasses = StyleClassHelper.of(classes.pagination);
if (arrowsPlacement === CarouselArrowsPlacement.Content) {
paginationClasses.put(classes.paginationArrowContent);
}
if (pageIndicatorPlacement === PlacementType.Top) {
paginationClasses.put(classes.paginationTop);
}
if (pageIndicatorPlacement === PlacementType.Bottom) {
paginationClasses.put(classes.paginationBottom);
}

const shouldRenderPaginationBar = useMemo(() => {
return showPageIndicator || arrowsPlacement === CarouselArrowsPlacement.PageIndicator;
}, [showPageIndicator, arrowsPlacement]);
Expand All @@ -84,13 +73,20 @@ const CarouselPagination: FC<CarouselPaginationPropTypes> = (props) => {
);
}

const paginationClasses = StyleClassHelper.of(classes.pagination);
if (arrowsPlacement === CarouselArrowsPlacement.Content) {
paginationClasses.put(classes.paginationArrowContent);
}
if (pageIndicatorPlacement === PlacementType.Top) {
paginationClasses.put(classes.paginationTop);
}
if (pageIndicatorPlacement === PlacementType.Bottom) {
paginationClasses.put(classes.paginationBottom);
}

return (
<div className={paginationClasses.valueOf()}>
<div
data-value={arrowsPlacement === CarouselArrowsPlacement.Content ? 'paginationArrow' : null}
className={classes.paginationArrow}
onClick={goToPreviousPage}
>
<div data-value="paginationArrow" className={classes.paginationArrow} onClick={goToPreviousPage}>
<Icon src="sap-icon://slim-arrow-left" />
</div>

Expand All @@ -102,19 +98,15 @@ const CarouselPagination: FC<CarouselPaginationPropTypes> = (props) => {
Children.map(children, (item, index) => (
<span
key={index}
className={`${activePage === index ? classes.paginationIconActive : null} ${classes.paginationIcon}`}
className={`${classes.paginationIcon}${activePage === index ? ` ${classes.paginationIconActive}` : ''}`}
aria-label={`Item ${index + 1} of ${numberOfChildren} displayed`}
>
{index + 1}
</span>
))}
</div>

<div
data-value={arrowsPlacement === CarouselArrowsPlacement.Content ? 'paginationArrow' : null}
className={classes.paginationArrow}
onClick={goToNextPage}
>
<div data-value="paginationArrow" className={classes.paginationArrow} onClick={goToNextPage}>
<Icon src="sap-icon://slim-arrow-right" />
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions packages/main/src/components/Carousel/demo.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { PlacementType } from '../../lib/PlacementType';
function renderCarousel() {
return (
<Carousel
width="90%"
activePage={number('active', 0)}
onPageChanged={action('onPageChanged')}
arrowsPlacement={select(
Expand Down
51 changes: 45 additions & 6 deletions packages/main/src/components/Carousel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const Carousel: FC<CarouselPropTypes> = forwardRef((props: CarouselPropTypes, re

const carouselItemClasses = StyleClassHelper.of(classes.carouselItem);
if (arrowsPlacement === CarouselArrowsPlacement.Content) {
carouselItemClasses.put(classes.carouselItemContentIndicator);
classNameString.put(classes.carouselArrowPlacementContent);
}

const selectPageAtIndex = useCallback(
Expand All @@ -96,27 +96,64 @@ const Carousel: FC<CarouselPropTypes> = forwardRef((props: CarouselPropTypes, re

const childElementCount = Children.count(children);
const goToNextPage = useCallback(
(e) => {
(e, skipManualInversion = false) => {
if (
document.dir === 'rtl' &&
arrowsPlacement === CarouselArrowsPlacement.Content &&
e.type === 'click' &&
!skipManualInversion
) {
return goToPreviousPage(e, true);
}
if (loop === false && currentlyActivePage === childElementCount - 1) {
return;
}
const nextPage = currentlyActivePage === childElementCount - 1 ? 0 : currentlyActivePage + 1;
selectPageAtIndex(nextPage, e);
},
[loop, currentlyActivePage, selectPageAtIndex, childElementCount]
[loop, currentlyActivePage, selectPageAtIndex, childElementCount, arrowsPlacement]
);

const goToPreviousPage = useCallback(
(e) => {
(e, skipManualInversion = false) => {
if (
document.dir === 'rtl' &&
arrowsPlacement === CarouselArrowsPlacement.Content &&
e.type === 'click' &&
!skipManualInversion
) {
return goToNextPage(e, true);
}
if (loop === false && currentlyActivePage === 0) {
return;
}
const previousPage = currentlyActivePage === 0 ? childElementCount - 1 : currentlyActivePage - 1;
selectPageAtIndex(previousPage, e);
},
[loop, childElementCount, currentlyActivePage, selectPageAtIndex]
[loop, childElementCount, currentlyActivePage, selectPageAtIndex, arrowsPlacement, goToNextPage]
);

const onKeyDown = useCallback(
(e) => {
if (e.key === 'ArrowRight') {
if (document.dir === 'rtl') {
goToPreviousPage(e);
} else {
goToNextPage(e);
}
}
if (e.key === 'ArrowLeft') {
if (document.dir === 'rtl') {
goToNextPage(e);
} else {
goToPreviousPage(e);
}
}
},
[goToPreviousPage, goToNextPage]
);

const translateXPrefix = document.dir === 'rtl' ? '' : '-';
return (
<div
className={classNameString.toString()}
Expand All @@ -125,6 +162,8 @@ const Carousel: FC<CarouselPropTypes> = forwardRef((props: CarouselPropTypes, re
slot={props['slot']}
ref={ref}
role="list"
tabIndex={0}
onKeyDown={onKeyDown}
>
{childElementCount > 1 && pageIndicatorPlacement === PlacementType.Top && (
<CarouselPagination
Expand All @@ -140,7 +179,7 @@ const Carousel: FC<CarouselPropTypes> = forwardRef((props: CarouselPropTypes, re
<div
className={classes.carouselInner}
style={{
transform: `translateX(-${currentlyActivePage * 100}%)`
transform: `translateX(${translateXPrefix}${currentlyActivePage * 100}%)`
}}
>
{Children.map(children, (item, index) => (
Expand Down

0 comments on commit e1299ed

Please sign in to comment.