Skip to content

Commit 18a24be

Browse files
authored
feat(ActionSheet): add keyboard navigation for PageUp/Down, Home, End (#5009)
Closes #3875
1 parent ca48bcb commit 18a24be

File tree

2 files changed

+84
-12
lines changed

2 files changed

+84
-12
lines changed

packages/main/src/components/ActionSheet/ActionSheet.cy.tsx

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ interface TestCompProptypes extends ActionSheetPropTypes {
88
}
99

1010
const TestComp = (props: TestCompProptypes) => {
11-
const { onBtnClick, open, children } = props;
11+
const { onBtnClick, open = true, children, ...rest } = props;
1212
return (
1313
<>
1414
<button id="opener">Opener</button>
15-
<ActionSheet className="myCustomClass" open={open} opener="opener">
15+
<ActionSheet className="myCustomClass" open={open} opener="opener" {...rest}>
1616
{children || (
1717
<>
1818
<Button onClick={onBtnClick}>Accept</Button>
@@ -28,7 +28,7 @@ const TestComp = (props: TestCompProptypes) => {
2828
describe('ActionSheet', () => {
2929
it('Click Action', () => {
3030
const onBtnClick = cy.spy().as('onBtnClick');
31-
cy.mount(<TestComp onBtnClick={onBtnClick} open />);
31+
cy.mount(<TestComp onBtnClick={onBtnClick} />);
3232

3333
cy.get('[ui5-responsive-popover]').should('be.visible');
3434

@@ -39,10 +39,55 @@ describe('ActionSheet', () => {
3939

4040
it('does not crash with other component', () => {
4141
cy.mount(
42-
<TestComp open>
42+
<TestComp>
4343
<Label>I should not crash</Label>
4444
</TestComp>
4545
);
4646
cy.findByText('I should not crash').should('be.visible');
4747
});
48+
49+
it('keyboard navigation', () => {
50+
cy.mount(
51+
<TestComp>
52+
{new Array(15).fill('O.o').map((_, index) => (
53+
<Button key={index}>{`Button${index}`}</Button>
54+
))}
55+
</TestComp>
56+
);
57+
cy.focused().should('have.text', 'Button0');
58+
cy.realPress('ArrowDown');
59+
cy.focused().should('have.text', 'Button1');
60+
cy.realPress('ArrowRight');
61+
cy.realPress('ArrowRight');
62+
cy.focused().should('have.text', 'Button3');
63+
cy.realPress('PageUp');
64+
cy.focused().should('have.text', 'Button0');
65+
cy.realPress('PageDown');
66+
cy.focused().should('have.text', 'Button5');
67+
cy.realPress('End');
68+
cy.focused().should('have.text', 'Button14');
69+
cy.realPress('ArrowUp');
70+
cy.focused().should('have.text', 'Button13');
71+
cy.realPress('ArrowLeft');
72+
cy.realPress('ArrowLeft');
73+
cy.focused().should('have.text', 'Button11');
74+
cy.realPress('PageDown');
75+
cy.focused().should('have.text', 'Button14');
76+
cy.realPress('Home');
77+
cy.focused().should('have.text', 'Button0');
78+
79+
// todo: rtl detection of wcr and ui5wc doesn't work for some reason in cypress
80+
// cy.mount(
81+
// <TestComp dir="rtl">
82+
// {new Array(15).fill('O.o').map((_, index) => (
83+
// <Button key={index}>{`Button${index}`}</Button>
84+
// ))}
85+
// </TestComp>
86+
// );
87+
// cy.focused().should('have.text', 'Button0');
88+
// cy.realPress('ArrowLeft');
89+
// cy.focused().should('have.text', 'Button1');
90+
// cy.realPress('ArrowRight');
91+
// cy.focused().should('have.text', 'Button0');
92+
});
4893
});

packages/main/src/components/ActionSheet/index.tsx

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import { isPhone } from '@ui5/webcomponents-base/dist/Device.js';
4-
import { useI18nBundle, useSyncRef } from '@ui5/webcomponents-react-base';
4+
import { useI18nBundle, useIsRTL, useSyncRef } from '@ui5/webcomponents-react-base';
55
import { clsx } from 'clsx';
66
import type { ReactElement } from 'react';
77
import React, { forwardRef, useReducer, useRef } from 'react';
@@ -164,6 +164,7 @@ const ActionSheet = forwardRef<ResponsivePopoverDomRef, ActionSheetPropTypes>((p
164164
const childrenToRender = flattenFragments(children);
165165
const childrenArrayLength = childrenToRender.length;
166166
const childrenLength = isPhone() && showCancelButton ? childrenArrayLength + 1 : childrenArrayLength;
167+
const isRtl = useIsRTL(popoverRef);
167168

168169
const canRenderPortal = useCanRenderPortal();
169170
if (!canRenderPortal) {
@@ -209,13 +210,39 @@ const ActionSheet = forwardRef<ResponsivePopoverDomRef, ActionSheetPropTypes>((p
209210

210211
const handleKeyDown = (e) => {
211212
const currentIndex = parseInt(e.target.dataset.actionBtnIndex);
212-
if (e.key === 'ArrowDown' && currentIndex + 1 < childrenLength) {
213-
e.preventDefault();
214-
actionBtnsRef.current.querySelector(`[data-action-btn-index="${currentIndex + 1}"]`).focus();
215-
}
216-
if (e.key === 'ArrowUp' && currentIndex > 0) {
217-
e.preventDefault();
218-
actionBtnsRef.current.querySelector(`[data-action-btn-index="${currentIndex - 1}"]`).focus();
213+
switch (e.key) {
214+
case 'ArrowDown':
215+
case isRtl ? 'ArrowLeft' : 'ArrowRight':
216+
if (currentIndex + 1 < childrenLength) {
217+
e.preventDefault();
218+
actionBtnsRef.current.querySelector(`[data-action-btn-index="${currentIndex + 1}"]`).focus();
219+
}
220+
break;
221+
case 'ArrowUp':
222+
case isRtl ? 'ArrowRight' : 'ArrowLeft':
223+
if (currentIndex > 0) {
224+
e.preventDefault();
225+
actionBtnsRef.current.querySelector(`[data-action-btn-index="${currentIndex - 1}"]`).focus();
226+
}
227+
break;
228+
case 'PageUp':
229+
e.preventDefault();
230+
actionBtnsRef.current.querySelector(`[data-action-btn-index="${Math.max(currentIndex - 5, 0)}"]`).focus();
231+
break;
232+
case 'PageDown':
233+
e.preventDefault();
234+
actionBtnsRef.current
235+
.querySelector(`[data-action-btn-index="${Math.min(currentIndex + 5, childrenLength - 1)}"]`)
236+
.focus();
237+
break;
238+
case 'Home':
239+
e.preventDefault();
240+
actionBtnsRef.current.querySelector(`[data-action-btn-index="0"]`).focus();
241+
break;
242+
case 'End':
243+
e.preventDefault();
244+
actionBtnsRef.current.querySelector(`[data-action-btn-index="${childrenLength - 1}"]`).focus();
245+
break;
219246
}
220247
};
221248

0 commit comments

Comments
 (0)