diff --git a/packages/main/__karma_snapshots__/Carousel.md b/packages/main/__karma_snapshots__/Carousel.md index fecee60eb7e..5758be2d752 100644 --- a/packages/main/__karma_snapshots__/Carousel.md +++ b/packages/main/__karma_snapshots__/Carousel.md @@ -7,75 +7,75 @@ -
+
-
+

Carousel 1

-
+

Carousel 2

-
+

Carousel 3

-
+

Carousel 4

-
+

Carousel 5

-
+

Carousel 6

-
+

Carousel 7

-
-
+
+
-
- +
+ 1 - + 2 - + 3 - + 4 - + 5 - + 6 - + 7
-
+
@@ -96,74 +96,74 @@ -
+
-
+

Carousel 1

-
+

Carousel 2

-
+

Carousel 3

-
+

Carousel 4

-
+

Carousel 5

-
+

Carousel 6

-
+

Carousel 7

-
+

Carousel 8

-
+

Carousel 9

-
+

Carousel 10

-
-
+
+
-
+
-
+
@@ -184,75 +184,75 @@ -
+
-
+

Carousel 1

-
+

Carousel 2

-
+

Carousel 3

-
+

Carousel 4

-
+

Carousel 5

-
+

Carousel 6

-
+

Carousel 7

-
-
+
+
-
- +
+ 1 - + 2 - + 3 - + 4 - + 5 - + 6 - + 7
-
+
@@ -273,7 +273,7 @@ -
+

@@ -312,36 +312,36 @@

-
-
+
+
-
- +
+ 1 - + 2 - + 3 - + 4 - + 5 - + 6 - + 7
-
+
@@ -362,38 +362,38 @@ -
+
-
-
+
+
-
- +
+ 1 - + 2 - + 3 - + 4 - + 5 - + 6 - + 7
-
+
@@ -449,38 +449,38 @@ -
+
-
-
+
+
-
- +
+ 1 - + 2 - + 3 - + 4 - + 5 - + 6 - + 7
-
+
@@ -538,75 +538,75 @@ -
+
-
+

Carousel 1

-
+

Carousel 2

-
+

Carousel 3

-
+

Carousel 4

-
+

Carousel 5

-
+

Carousel 6

-
+

Carousel 7

-
-
+
+
-
- +
+ 1 - + 2 - + 3 - + 4 - + 5 - + 6 - + 7
-
+
diff --git a/packages/main/src/components/Carousel/Carousel.jss.ts b/packages/main/src/components/Carousel/Carousel.jss.ts index 89c1edae6da..6eb47988ea4 100644 --- a/packages/main/src/components/Carousel/Carousel.jss.ts +++ b/packages/main/src/components/Carousel/Carousel.jss.ts @@ -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' } } }, @@ -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)' + } } }); diff --git a/packages/main/src/components/Carousel/Carousel.karma.tsx b/packages/main/src/components/Carousel/Carousel.karma.tsx index fcc5fad95ee..ecfdacc8a5a 100644 --- a/packages/main/src/components/Carousel/Carousel.karma.tsx +++ b/packages/main/src/components/Carousel/Carousel.karma.tsx @@ -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); + }); }); diff --git a/packages/main/src/components/Carousel/CarouselPagination.jss.ts b/packages/main/src/components/Carousel/CarouselPagination.jss.ts index 08b86b71d5f..144e129b6d1 100644 --- a/packages/main/src/components/Carousel/CarouselPagination.jss.ts +++ b/packages/main/src/components/Carousel/CarouselPagination.jss.ts @@ -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}`, @@ -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, @@ -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)' } } } diff --git a/packages/main/src/components/Carousel/CarouselPagination.tsx b/packages/main/src/components/Carousel/CarouselPagination.tsx index 23202422d3a..743e8485585 100644 --- a/packages/main/src/components/Carousel/CarouselPagination.tsx +++ b/packages/main/src/components/Carousel/CarouselPagination.tsx @@ -8,7 +8,7 @@ import { Label } from '../../lib/Label'; import { PlacementType } from '../../lib/PlacementType'; import styles from './CarouselPagination.jss'; -const useStyles = createUseStyles>(styles); +const useStyles = createUseStyles>(styles, { name: 'CarouselPagination' }); export interface CarouselPaginationPropTypes { /** @@ -56,17 +56,6 @@ const CarouselPagination: FC = (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]); @@ -84,13 +73,20 @@ const CarouselPagination: FC = (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 (
-
+
@@ -102,7 +98,7 @@ const CarouselPagination: FC = (props) => { Children.map(children, (item, index) => ( {index + 1} @@ -110,11 +106,7 @@ const CarouselPagination: FC = (props) => { ))}
-
+
diff --git a/packages/main/src/components/Carousel/demo.stories.tsx b/packages/main/src/components/Carousel/demo.stories.tsx index a47e2ee408f..efd4a445888 100644 --- a/packages/main/src/components/Carousel/demo.stories.tsx +++ b/packages/main/src/components/Carousel/demo.stories.tsx @@ -10,6 +10,7 @@ import { PlacementType } from '../../lib/PlacementType'; function renderCarousel() { return ( = 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( @@ -96,27 +96,64 @@ const Carousel: FC = 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 (
= forwardRef((props: CarouselPropTypes, re slot={props['slot']} ref={ref} role="list" + tabIndex={0} + onKeyDown={onKeyDown} > {childElementCount > 1 && pageIndicatorPlacement === PlacementType.Top && ( = forwardRef((props: CarouselPropTypes, re
{Children.map(children, (item, index) => (