Skip to content

Commit fe3aade

Browse files
authored
Merge pull request #778 from Stremio/feat/multiselect-menu-scroll-to-view
feat(MultiSelectMenu): scroll into view
2 parents b5d073b + a4dd1e2 commit fe3aade

File tree

2 files changed

+53
-27
lines changed

2 files changed

+53
-27
lines changed

src/common/MultiselectMenu/Dropdown/Dropdown.tsx

+47-23
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright (C) 2017-2024 Smart code 203358507
22

3-
import React from 'react';
3+
import React, { useRef, useEffect, useCallback } from 'react';
44
import Button from 'stremio/common/Button';
55
import { useTranslation } from 'react-i18next';
66
import classNames from 'classnames';
@@ -19,33 +19,57 @@ type Props = {
1919

2020
const Dropdown = ({ level, setLevel, options, onSelect, selectedOption, menuOpen }: Props) => {
2121
const { t } = useTranslation();
22+
const optionsRef = useRef(new Map());
23+
const containerRef = useRef(null);
2224

23-
const onBackButtonClick = () => {
25+
const handleSetOptionRef = useCallback((value: number) => (node: HTMLButtonElement | null) => {
26+
if (node) {
27+
optionsRef.current.set(value, node);
28+
} else {
29+
optionsRef.current.delete(value);
30+
}
31+
}, []);
32+
33+
const handleBackClick = useCallback(() => {
2434
setLevel(level - 1);
25-
};
35+
}, [setLevel, level]);
2636

27-
return (
28-
<div className={classNames(styles['dropdown'], { [styles['open']]: menuOpen })} role={'listbox'}>
29-
{
30-
level > 0 ?
31-
<Button className={styles['back-button']} onClick={onBackButtonClick}>
32-
<Icon name={'caret-left'} className={styles['back-button-icon']} />
33-
{t('BACK')}
34-
</Button>
35-
: null
37+
useEffect(() => {
38+
if (menuOpen && selectedOption && containerRef.current) {
39+
const selectedNode = optionsRef.current.get(selectedOption.value);
40+
if (selectedNode) {
41+
selectedNode.scrollIntoView({
42+
behavior: 'smooth',
43+
block: 'nearest'
44+
});
3645
}
37-
{
38-
options
39-
.filter((option: MultiselectMenuOption) => !option.hidden)
40-
.map((option: MultiselectMenuOption, index) => (
41-
<Option
42-
key={index}
43-
option={option}
44-
onSelect={onSelect}
45-
selectedOption={selectedOption}
46-
/>
47-
))
46+
}
47+
}, [menuOpen, selectedOption]);
4848

49+
return (
50+
<div
51+
className={classNames(styles['dropdown'], { [styles['open']]: menuOpen })}
52+
role={'listbox'}
53+
ref={containerRef}
54+
>
55+
{level > 0 ?
56+
<Button className={styles['back-button']} onClick={handleBackClick}>
57+
<Icon name={'caret-left'} className={styles['back-button-icon']} />
58+
{t('BACK')}
59+
</Button>
60+
: null
61+
}
62+
{options
63+
.filter((option: MultiselectMenuOption) => !option.hidden)
64+
.map((option: MultiselectMenuOption) => (
65+
<Option
66+
key={option.id}
67+
ref={handleSetOptionRef(option.value)}
68+
option={option}
69+
onSelect={onSelect}
70+
selectedOption={selectedOption}
71+
/>
72+
))
4973
}
5074
</div>
5175
);

src/common/MultiselectMenu/Dropdown/Option/Option.tsx

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright (C) 2017-2024 Smart code 203358507
22

3-
import React, { useCallback, useMemo } from 'react';
3+
import React, { useCallback, useMemo, forwardRef } from 'react';
44
import classNames from 'classnames';
55
import Button from 'stremio/common/Button';
66
import styles from './Option.less';
@@ -12,7 +12,7 @@ type Props = {
1212
onSelect: (value: number) => void;
1313
};
1414

15-
const Option = ({ option, selectedOption, onSelect }: Props) => {
15+
const Option = forwardRef<HTMLButtonElement, Props>(({ option, selectedOption, onSelect }, ref) => {
1616
// consider using option.id === selectedOption?.id instead
1717
const selected = useMemo(() => option?.value === selectedOption?.value, [option, selectedOption]);
1818

@@ -22,6 +22,7 @@ const Option = ({ option, selectedOption, onSelect }: Props) => {
2222

2323
return (
2424
<Button
25+
ref={ref}
2526
className={classNames(styles['option'], { [styles['selected']]: selected })}
2627
key={option.id}
2728
onClick={handleClick}
@@ -32,7 +33,6 @@ const Option = ({ option, selectedOption, onSelect }: Props) => {
3233
selected && !option.level ?
3334
<div className={styles['icon']} />
3435
: null
35-
3636
}
3737
{
3838
option.level ?
@@ -41,6 +41,8 @@ const Option = ({ option, selectedOption, onSelect }: Props) => {
4141
}
4242
</Button>
4343
);
44-
};
44+
});
45+
46+
Option.displayName = 'Option';
4547

4648
export default Option;

0 commit comments

Comments
 (0)