mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-21 07:32:02 +00:00
refactor(Dropdown): clean up
This commit is contained in:
parent
75bb1b0489
commit
f2490ee775
4 changed files with 59 additions and 51 deletions
|
|
@ -7,7 +7,7 @@ import classNames from 'classnames';
|
||||||
import Option from './Option';
|
import Option from './Option';
|
||||||
import Icon from '@stremio/stremio-icons/react';
|
import Icon from '@stremio/stremio-icons/react';
|
||||||
import styles from './Dropdown.less';
|
import styles from './Dropdown.less';
|
||||||
import interfaceLanguages from '../../../common/interfaceLanguages.json';
|
import useLanguageSorting from './useLanguageSorting';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
options: MultiselectMenuOption[];
|
options: MultiselectMenuOption[];
|
||||||
|
|
@ -18,29 +18,9 @@ type Props = {
|
||||||
onSelect: (value: any) => void;
|
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 Dropdown = ({ level, setLevel, options, onSelect, value, menuOpen }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { isLanguageDropdown, sortedOptions } = useLanguageSorting(options);
|
||||||
const optionsRef = useRef(new Map());
|
const optionsRef = useRef(new Map());
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
|
|
||||||
|
|
@ -70,28 +50,6 @@ const Dropdown = ({ level, setLevel, options, onSelect, value, menuOpen }: Props
|
||||||
}
|
}
|
||||||
}, [menuOpen, selectedOption]);
|
}, [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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(styles['dropdown'], { [styles['open']]: menuOpen })}
|
className={classNames(styles['dropdown'], { [styles['open']]: menuOpen })}
|
||||||
|
|
@ -105,10 +63,9 @@ const Dropdown = ({ level, setLevel, options, onSelect, value, menuOpen }: Props
|
||||||
</Button>
|
</Button>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
{(isLanguageDropdown ? sortedOptions : options)
|
||||||
{sortedOptions.map((option: MultiselectMenuOption) => (
|
?.filter((option: MultiselectMenuOption) => !option.hidden)
|
||||||
<div
|
.map((option: MultiselectMenuOption) => (
|
||||||
key={`${String(option.label)}-${String(option.value)}`}>
|
|
||||||
<Option
|
<Option
|
||||||
key={option.value}
|
key={option.value}
|
||||||
ref={handleSetOptionRef(option.value)}
|
ref={handleSetOptionRef(option.value)}
|
||||||
|
|
@ -116,8 +73,8 @@ const Dropdown = ({ level, setLevel, options, onSelect, value, menuOpen }: Props
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
selectedValue={value}
|
selectedValue={value}
|
||||||
/>
|
/>
|
||||||
</div>
|
))
|
||||||
))}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,10 @@
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.separator {
|
||||||
|
border-bottom: thin solid var(--overlay-color);
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.15);
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import classNames from 'classnames';
|
||||||
import { Button } from 'stremio/components';
|
import { Button } from 'stremio/components';
|
||||||
import styles from './Option.less';
|
import styles from './Option.less';
|
||||||
import Icon from '@stremio/stremio-icons/react';
|
import Icon from '@stremio/stremio-icons/react';
|
||||||
|
import useLanguageSorting from '../useLanguageSorting';
|
||||||
|
import interfaceLanguages from 'stremio/common/interfaceLanguages.json';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
option: MultiselectMenuOption;
|
option: MultiselectMenuOption;
|
||||||
|
|
@ -13,8 +15,15 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const Option = forwardRef<HTMLButtonElement, Props>(({ option, selectedValue, onSelect }, ref) => {
|
const Option = forwardRef<HTMLButtonElement, Props>(({ option, selectedValue, onSelect }, ref) => {
|
||||||
|
const { userLangCode } = useLanguageSorting();
|
||||||
|
|
||||||
const selected = useMemo(() => option?.value === selectedValue, [option, selectedValue]);
|
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(() => {
|
const handleClick = useCallback(() => {
|
||||||
onSelect(option.value);
|
onSelect(option.value);
|
||||||
}, [onSelect, option.value]);
|
}, [onSelect, option.value]);
|
||||||
|
|
@ -22,7 +31,7 @@ const Option = forwardRef<HTMLButtonElement, Props>(({ option, selectedValue, on
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={classNames(styles['option'], { [styles['selected']]: selected })}
|
className={classNames(styles['option'], { [styles['selected']]: selected }, { [styles['separator']]: separator })}
|
||||||
key={option.id}
|
key={option.id}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
aria-selected={selected}
|
aria-selected={selected}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
Loading…
Reference in a new issue