From fd4c9e73c82d4d66bab2fb95490f5968f38607dd Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Tue, 3 Jun 2025 12:34:14 +0300 Subject: [PATCH] refactor(MultiselectMenu): use value only --- .../MultiselectMenu/Dropdown/Dropdown.tsx | 16 +++---- .../Dropdown/Option/Option.tsx | 9 ++-- .../MultiselectMenu/MultiselectMenu.less | 8 +++- .../MultiselectMenu/MultiselectMenu.tsx | 16 ++++--- src/routes/Addons/useSelectableInputs.js | 14 +------ src/routes/Discover/Discover.js | 8 ++-- src/routes/Discover/useSelectableInputs.js | 25 ++++------- src/routes/Library/Library.js | 6 +-- src/routes/Library/useSelectableInputs.js | 5 +-- .../MetaDetails/StreamsList/StreamsList.js | 5 +-- .../VideosList/SeasonsBar/SeasonsBar.js | 4 +- .../Settings/useProfileSettingsInputs.js | 42 ++++--------------- .../useStreamingServerSettingsInputs.js | 22 ++-------- 13 files changed, 63 insertions(+), 117 deletions(-) diff --git a/src/components/MultiselectMenu/Dropdown/Dropdown.tsx b/src/components/MultiselectMenu/Dropdown/Dropdown.tsx index 438ced13c..5f1ee4fa0 100644 --- a/src/components/MultiselectMenu/Dropdown/Dropdown.tsx +++ b/src/components/MultiselectMenu/Dropdown/Dropdown.tsx @@ -10,23 +10,25 @@ import styles from './Dropdown.less'; type Props = { options: MultiselectMenuOption[]; - selectedOption?: MultiselectMenuOption | null; + value?: string | number | null; menuOpen: boolean | (() => void); level: number; setLevel: (level: number) => void; - onSelect: (value: number) => void; + onSelect: (value: string | number | null) => void; }; -const Dropdown = ({ level, setLevel, options, onSelect, selectedOption, menuOpen }: Props) => { +const Dropdown = ({ level, setLevel, options, onSelect, value, menuOpen }: Props) => { const { t } = useTranslation(); const optionsRef = useRef(new Map()); const containerRef = useRef(null); - const handleSetOptionRef = useCallback((value: number) => (node: HTMLButtonElement | null) => { + const selectedOption = options.find(opt => opt.value === value) || null; + + const handleSetOptionRef = useCallback((optionValue: string | number) => (node: HTMLButtonElement | null) => { if (node) { - optionsRef.current.set(value, node); + optionsRef.current.set(optionValue, node); } else { - optionsRef.current.delete(value); + optionsRef.current.delete(optionValue); } }, []); @@ -67,7 +69,7 @@ const Dropdown = ({ level, setLevel, options, onSelect, selectedOption, menuOpen ref={handleSetOptionRef(option.value)} option={option} onSelect={onSelect} - selectedOption={selectedOption} + selectedValue={value} /> )) } diff --git a/src/components/MultiselectMenu/Dropdown/Option/Option.tsx b/src/components/MultiselectMenu/Dropdown/Option/Option.tsx index 91aa173f7..444e1876e 100644 --- a/src/components/MultiselectMenu/Dropdown/Option/Option.tsx +++ b/src/components/MultiselectMenu/Dropdown/Option/Option.tsx @@ -8,13 +8,12 @@ import Icon from '@stremio/stremio-icons/react'; type Props = { option: MultiselectMenuOption; - selectedOption?: MultiselectMenuOption | null; - onSelect: (value: number) => void; + selectedValue?: string | number | null; + onSelect: (value: string | number | null) => void; }; -const Option = forwardRef(({ option, selectedOption, onSelect }, ref) => { - // consider using option.id === selectedOption?.id instead - const selected = useMemo(() => option?.value === selectedOption?.value, [option, selectedOption]); +const Option = forwardRef(({ option, selectedValue, onSelect }, ref) => { + const selected = useMemo(() => option?.value === selectedValue, [option, selectedValue]); const handleClick = useCallback(() => { onSelect(option.value); diff --git a/src/components/MultiselectMenu/MultiselectMenu.less b/src/components/MultiselectMenu/MultiselectMenu.less index c26c2480e..4aee1a4a8 100644 --- a/src/components/MultiselectMenu/MultiselectMenu.less +++ b/src/components/MultiselectMenu/MultiselectMenu.less @@ -14,7 +14,6 @@ } .multiselect-button { - color: var(--primary-foreground-color); padding: 0.75rem 1.5rem; display: flex; flex: 1; @@ -23,6 +22,13 @@ gap: 0 0.5rem; border-radius: @border-radius; + .label { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: var(--primary-foreground-color); + } + .icon { width: 1rem; color: var(--primary-foreground-color); diff --git a/src/components/MultiselectMenu/MultiselectMenu.tsx b/src/components/MultiselectMenu/MultiselectMenu.tsx index 8f41278e7..9a84cb8eb 100644 --- a/src/components/MultiselectMenu/MultiselectMenu.tsx +++ b/src/components/MultiselectMenu/MultiselectMenu.tsx @@ -13,17 +13,19 @@ type Props = { className?: string, title?: string | (() => string); options: MultiselectMenuOption[]; - selectedOption?: MultiselectMenuOption; - onSelect: (value: number) => void; + value?: string | number | null; + onSelect: (value: string | number | null) => void; }; -const MultiselectMenu = ({ className, title, options, selectedOption, onSelect }: Props) => { +const MultiselectMenu = ({ className, title, options, value, onSelect }: Props) => { const [menuOpen, , closeMenu, toggleMenu] = useBinaryState(false); const multiselectMenuRef = useOutsideClick(() => closeMenu()); const [level, setLevel] = React.useState(0); - const onOptionSelect = (value: number) => { - level ? setLevel(level + 1) : onSelect(value), closeMenu(); + const selectedOption = options.find(opt => opt.value === value); + + const onOptionSelect = (selectedValue: string | number | null) => { + level ? setLevel(level + 1) : onSelect(selectedValue), closeMenu(); }; return ( @@ -35,11 +37,13 @@ const MultiselectMenu = ({ className, title, options, selectedOption, onSelect } aria-haspopup='listbox' aria-expanded={menuOpen} > +
{ typeof title === 'function' ? title() : title ?? selectedOption?.label } +
{ @@ -50,7 +54,7 @@ const MultiselectMenu = ({ className, title, options, selectedOption, onSelect } options={options} onSelect={onOptionSelect} menuOpen={menuOpen} - selectedOption={selectedOption} + value={value} /> : null } diff --git a/src/routes/Addons/useSelectableInputs.js b/src/routes/Addons/useSelectableInputs.js index c201b3976..a8af37fbe 100644 --- a/src/routes/Addons/useSelectableInputs.js +++ b/src/routes/Addons/useSelectableInputs.js @@ -13,12 +13,7 @@ const mapSelectableInputs = (installedAddons, remoteAddons, t) => { label: t.stringWithPrefix(name, 'ADDON_'), title: t.stringWithPrefix(name, 'ADDON_'), })), - selectedOption: selectedCatalog - ? { - label: t.stringWithPrefix(selectedCatalog.name, 'ADDON_'), - value: selectedCatalog.deepLinks.addons, - } - : undefined, + value: selectedCatalog ? selectedCatalog.deepLinks.addons : undefined, title: remoteAddons.selected !== null ? () => { const selectableCatalog = remoteAddons.selectable.catalogs @@ -44,12 +39,7 @@ const mapSelectableInputs = (installedAddons, remoteAddons, t) => { value: deepLinks.addons, label: t.stringWithPrefix(type, 'TYPE_') })), - selectedOption: selectedType - ? { - label: selectedType.type !== null ? t.stringWithPrefix(selectedType.type, 'TYPE_') : t.string('TYPE_ALL'), - value: selectedType.deepLinks.addons - } - : undefined, + value: selectedType ? selectedType.deepLinks.addons : undefined, title: () => { return installedAddons.selected !== null ? installedAddons.selected.request.type === null ? diff --git a/src/routes/Discover/Discover.js b/src/routes/Discover/Discover.js index 695822ecb..1c2b6f122 100644 --- a/src/routes/Discover/Discover.js +++ b/src/routes/Discover/Discover.js @@ -100,13 +100,13 @@ const Discover = ({ urlParams, queryParams }) => {
- {selectInputs.map(({ title, options, selectedOption, onSelect }, index) => ( + {selectInputs.map(({ title, options, value, onSelect }, index) => ( ))} @@ -202,13 +202,13 @@ const Discover = ({ urlParams, queryParams }) => { { inputsModalOpen ? - {selectInputs.map(({ title, options, selectedOption, onSelect }, index) => ( + {selectInputs.map(({ title, options, value, onSelect }, index) => ( ))} diff --git a/src/routes/Discover/useSelectableInputs.js b/src/routes/Discover/useSelectableInputs.js index 459e095cf..2c476c804 100644 --- a/src/routes/Discover/useSelectableInputs.js +++ b/src/routes/Discover/useSelectableInputs.js @@ -11,11 +11,8 @@ const mapSelectableInputs = (discover, t) => { value: deepLinks.discover, label: t.stringWithPrefix(type, 'TYPE_') })), - selectedOption: selectedType - ? { - label: t.stringWithPrefix(selectedType.type, 'TYPE_'), - value: selectedType.deepLinks.discover, - } + value: selectedType + ? selectedType.deepLinks.discover : undefined, title: discover.selected !== null ? () => t.stringWithPrefix(discover.selected.request.path.type, 'TYPE_') @@ -32,11 +29,8 @@ const mapSelectableInputs = (discover, t) => { label: t.catalogTitle({ addon, id, name }), title: `${name} (${addon.manifest.name})` })), - selectedOption: discover.selected?.request.path.id - ? { - label: t.catalogTitle({ addon: selectedCatalog.addon, id: selectedCatalog.id, name: selectedCatalog.name }), - value: selectedCatalog.deepLinks.discover - } + value: discover.selected?.request.path.id + ? selectedCatalog.deepLinks.discover : undefined, title: discover.selected !== null ? () => { @@ -61,13 +55,10 @@ const mapSelectableInputs = (discover, t) => { value }) })), - selectedOption: { - label: typeof selectedExtra.value === 'string' ? t.stringWithPrefix(selectedExtra.value) : t.string('NONE'), - value: JSON.stringify({ - href: selectedExtra.deepLinks.discover, - value: selectedExtra.value, - }) - }, + value: JSON.stringify({ + href: selectedExtra.deepLinks.discover, + value: selectedExtra.value, + }), title: options.some(({ selected, value }) => selected && value === null) ? () => t.stringWithPrefix(name, 'SELECT_') : t.stringWithPrefix(selectedExtra.value), diff --git a/src/routes/Library/Library.js b/src/routes/Library/Library.js index 6b12afe08..2fd81cde2 100644 --- a/src/routes/Library/Library.js +++ b/src/routes/Library/Library.js @@ -64,10 +64,10 @@ const Library = ({ model, urlParams, queryParams }) => { } }, [profile.auth, library.selected]); React.useEffect(() => { - if (!library.selected?.type && typeSelect.selectedOption) { - window.location = typeSelect.selectedOption.value; + if (!library.selected?.type && typeSelect.value) { + window.location = typeSelect.value; } - }, [typeSelect.selectedOption, library.selected]); + }, [typeSelect.value, library.selected]); return ( { diff --git a/src/routes/Library/useSelectableInputs.js b/src/routes/Library/useSelectableInputs.js index 426359a10..84816b646 100644 --- a/src/routes/Library/useSelectableInputs.js +++ b/src/routes/Library/useSelectableInputs.js @@ -10,10 +10,7 @@ const mapSelectableInputs = (library, t) => { value: deepLinks.library, label: type === null ? t.string('TYPE_ALL') : t.stringWithPrefix(type, 'TYPE_') })), - selectedOption: { - label: selectedType?.type === null ? t.string('TYPE_ALL') : t.stringWithPrefix(selectedType?.type, 'TYPE_'), - value: selectedType?.deepLinks.library - }, + value: selectedType?.deepLinks.library, onSelect: (value) => { window.location = value; } diff --git a/src/routes/MetaDetails/StreamsList/StreamsList.js b/src/routes/MetaDetails/StreamsList/StreamsList.js index 3e1b1339c..627b41857 100644 --- a/src/routes/MetaDetails/StreamsList/StreamsList.js +++ b/src/routes/MetaDetails/StreamsList/StreamsList.js @@ -89,10 +89,7 @@ const StreamsList = ({ className, video, type, onEpisodeSearch, ...props }) => { title: streamsByAddon[transportUrl].addon.manifest.name, })) ], - selectedOption: { - label: selectedAddon === ALL_ADDONS_KEY ? t('ALL_ADDONS') : streamsByAddon[selectedAddon]?.addon.manifest.name, - value: selectedAddon - }, + value: selectedAddon, onSelect: onAddonSelected }; }, [streamsByAddon, selectedAddon]); diff --git a/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBar.js b/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBar.js index 29637a24c..51b51f0e7 100644 --- a/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBar.js +++ b/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBar.js @@ -17,7 +17,7 @@ const SeasonsBar = ({ className, seasons, season, onSelect }) => { })); }, [seasons]); const selectedSeason = React.useMemo(() => { - return { label: String(season), value: String(season) }; + return String(season); }, [season]); const prevNextButtonOnClick = React.useCallback((event) => { if (typeof onSelect === 'function') { @@ -64,7 +64,7 @@ const SeasonsBar = ({ className, seasons, season, onSelect }) => { className={styles['seasons-popup-label-container']} options={options} title={season > 0 ? `${t('SEASON')} ${season}` : t('SPECIAL')} - selectedOption={selectedSeason} + value={selectedSeason} onSelect={seasonOnSelect} />