refactor(Dropdown): clean up

This commit is contained in:
Timothy Z. 2025-06-24 09:54:57 +03:00
parent 75bb1b0489
commit f2490ee775
4 changed files with 59 additions and 51 deletions

View file

@ -7,7 +7,7 @@ import classNames from 'classnames';
import Option from './Option';
import Icon from '@stremio/stremio-icons/react';
import styles from './Dropdown.less';
import interfaceLanguages from '../../../common/interfaceLanguages.json';
import useLanguageSorting from './useLanguageSorting';
type Props = {
options: MultiselectMenuOption[];
@ -18,29 +18,9 @@ type Props = {
onSelect: (value: any) => void;
};
function normalizeLanguageCode(langCode: string): string {
const language = interfaceLanguages.find((lang) => lang.codes.includes(langCode));
if (!language) {
console.warn(`Unknown language code: ${langCode}. Falling back to 'eng'.`);
return 'eng';
}
return language.codes[1];
}
function getOptionLanguageCode(option: MultiselectMenuOption) {
if (option.label === 'None') {
return 'None';
}
if (!option || !option.value) {
console.warn('Invalid option or option value:', option);
return 'eng';
}
const optionValue = String(option.value);
return optionValue.length === 3 ? optionValue : normalizeLanguageCode(optionValue) || 'eng';
}
const Dropdown = ({ level, setLevel, options, onSelect, value, menuOpen }: Props) => {
const { t } = useTranslation();
const { isLanguageDropdown, sortedOptions } = useLanguageSorting(options);
const optionsRef = useRef(new Map());
const containerRef = useRef(null);
@ -70,28 +50,6 @@ const Dropdown = ({ level, setLevel, options, onSelect, value, menuOpen }: Props
}
}, [menuOpen, selectedOption]);
const browserLocale = navigator.language || 'eng-US';
const userLanguageCode = normalizeLanguageCode(browserLocale) || 'eng';
const priorityLanguage = userLanguageCode === 'eng'
? ['eng', 'None']
: [userLanguageCode, 'eng', 'None'];
const isPriorityLanguage = (option: MultiselectMenuOption) => {
return priorityLanguage.includes(getOptionLanguageCode(option));
};
const visibleOptions = options.filter((option: MultiselectMenuOption) => !option.hidden);
const sortedOptions = [
...priorityLanguage.flatMap((lang) =>
visibleOptions.filter((option) => getOptionLanguageCode(option) === lang)),
...visibleOptions
.filter((option) => !isPriorityLanguage(option))
.sort((a, b) => a.label.localeCompare(b.label))
];
return (
<div
className={classNames(styles['dropdown'], { [styles['open']]: menuOpen })}
@ -105,10 +63,9 @@ const Dropdown = ({ level, setLevel, options, onSelect, value, menuOpen }: Props
</Button>
: null
}
{sortedOptions.map((option: MultiselectMenuOption) => (
<div
key={`${String(option.label)}-${String(option.value)}`}>
{(isLanguageDropdown ? sortedOptions : options)
?.filter((option: MultiselectMenuOption) => !option.hidden)
.map((option: MultiselectMenuOption) => (
<Option
key={option.value}
ref={handleSetOptionRef(option.value)}
@ -116,8 +73,8 @@ const Dropdown = ({ level, setLevel, options, onSelect, value, menuOpen }: Props
onSelect={onSelect}
selectedValue={value}
/>
</div>
))}
))
}
</div>
);
};

View file

@ -23,6 +23,10 @@
opacity: 1;
}
&.separator {
border-bottom: thin solid var(--overlay-color);
}
&:hover {
background-color: rgba(255, 255, 255, 0.15);
}

View file

@ -5,6 +5,8 @@ import classNames from 'classnames';
import { Button } from 'stremio/components';
import styles from './Option.less';
import Icon from '@stremio/stremio-icons/react';
import useLanguageSorting from '../useLanguageSorting';
import interfaceLanguages from 'stremio/common/interfaceLanguages.json';
type Props = {
option: MultiselectMenuOption;
@ -13,8 +15,15 @@ type Props = {
};
const Option = forwardRef<HTMLButtonElement, Props>(({ option, selectedValue, onSelect }, ref) => {
const { userLangCode } = useLanguageSorting();
const selected = useMemo(() => option?.value === selectedValue, [option, selectedValue]);
const separator = useMemo(() => {
const lang = interfaceLanguages.find((l) => l.name === option?.label);
return lang ? userLangCode.some((code) => lang.codes.includes(code)) : false;
}, [option, userLangCode]);
const handleClick = useCallback(() => {
onSelect(option.value);
}, [onSelect, option.value]);
@ -22,7 +31,7 @@ const Option = forwardRef<HTMLButtonElement, Props>(({ option, selectedValue, on
return (
<Button
ref={ref}
className={classNames(styles['option'], { [styles['selected']]: selected })}
className={classNames(styles['option'], { [styles['selected']]: selected }, { [styles['separator']]: separator })}
key={option.id}
onClick={handleClick}
aria-selected={selected}

View file

@ -0,0 +1,38 @@
import { useMemo } from 'react';
import interfaceLanguages from 'stremio/common/interfaceLanguages.json';
const useLanguageSorting = (options?: MultiselectMenuOption[]) => {
const userLangCode = useMemo(() => {
const lang = interfaceLanguages.find((l) => l.codes.includes(navigator.language || 'en-US'));
if (lang) {
const threeLetter = lang.codes[1] || 'eng';
const fullLocale = navigator.language || 'en-US';
return [threeLetter, fullLocale];
}
return ['eng'];
}, []);
const isLanguageDropdown = useMemo(() => {
return options?.some((opt) => interfaceLanguages.some((l) => l.name === opt.label));
}, [options]);
const sortedOptions = useMemo(() => {
if (!isLanguageDropdown || !options) return options;
const matchingIndex = options.findIndex((opt) => {
const lang = interfaceLanguages.find((l) => l.name === opt.label);
return userLangCode.some((code) => lang?.codes.includes(code));
});
if (matchingIndex === -1) return options;
const matchingOption = options[matchingIndex];
const otherOptions = options.filter((_, index) => index !== matchingIndex);
return [matchingOption, ...otherOptions];
}, [options, userLangCode, isLanguageDropdown]);
return { userLangCode, isLanguageDropdown, sortedOptions };
};
export default useLanguageSorting;