diff --git a/.github/workflows/auto_assign.yml b/.github/workflows/auto_assign.yml new file mode 100644 index 000000000..dfbddb6a4 --- /dev/null +++ b/.github/workflows/auto_assign.yml @@ -0,0 +1,65 @@ +name: PR and Issue Workflow +on: + pull_request: + types: [opened, reopened] + issues: + types: [opened] +jobs: + auto-assign-and-label: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + # Auto assign PR to author + - name: Auto Assign PR to Author + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const pr = context.payload.pull_request; + if (pr) { + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + assignees: [pr.user.login] + }); + console.log(`Assigned PR #${pr.number} to author @${pr.user.login}`); + } + + # Dynamic labeling based on PR/Issue title + - name: Label PRs and Issues + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const prTitle = context.payload.pull_request ? context.payload.pull_request.title : context.payload.issue.title; + const issueNumber = context.payload.pull_request ? context.payload.pull_request.number : context.payload.issue.number; + const isIssue = context.payload.issue !== undefined; + const labelMappings = [ + { pattern: /^feat(ure)?/i, label: 'feature' }, + { pattern: /^fix/i, label: 'bug' }, + { pattern: /^refactor/i, label: 'refactor' }, + { pattern: /^chore/i, label: 'chore' }, + { pattern: /^docs?/i, label: 'documentation' }, + { pattern: /^perf(ormance)?/i, label: 'performance' }, + { pattern: /^test/i, label: 'testing' } + ]; + let labelsToAdd = []; + for (const mapping of labelMappings) { + if (mapping.pattern.test(prTitle)) { + labelsToAdd.push(mapping.label); + } + } + if (labelsToAdd.length > 0) { + github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + labels: labelsToAdd + }); + } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 79e66d642..39a17e5aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "react-i18next": "^15.1.3", "react-is": "18.3.1", "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", - "stremio-translations": "github:Stremio/stremio-translations#a6be0425573917c2e82b66d28968c1a4d444cb96", + "stremio-translations": "github:Stremio/stremio-translations#8212fa77c4febd22ddb611590e9fb574dc845416", "url": "0.11.4", "use-long-press": "^3.2.0" }, @@ -48,6 +48,8 @@ "@stylistic/eslint-plugin": "^2.11.0", "@stylistic/eslint-plugin-jsx": "^2.11.0", "@types/hat": "^0.0.4", + "@types/lodash.isequal": "^4.5.8", + "@types/lodash.throttle": "^4.1.9", "@types/react": "^18.3.13", "@types/react-dom": "^18.3.1", "babel-loader": "9.2.1", @@ -66,6 +68,7 @@ "mini-css-extract-plugin": "2.9.2", "postcss-loader": "8.1.1", "readdirp": "4.0.2", + "recast": "0.23.11", "terser-webpack-plugin": "5.3.10", "thread-loader": "^4.0.4", "ts-loader": "^9.5.1", @@ -3806,6 +3809,33 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/lodash": { + "version": "4.17.18", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.18.tgz", + "integrity": "sha512-KJ65INaxqxmU6EoCiJmRPZC9H9RVWCRd349tXM2M3O5NA7cY6YL7c0bHAHQ93NOfTObEQ004kd2QVHs/r0+m4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash.isequal": { + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.8.tgz", + "integrity": "sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/lodash.throttle": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.9.tgz", + "integrity": "sha512-PCPVfpfueguWZQB7pJQK890F2scYKoDUL3iM522AptHWn7d5NQmeS/LTEHIcLr5PaTzl3dK2Z0xSUHHTHwaL5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -4786,6 +4816,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -12497,6 +12540,23 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -13373,9 +13433,9 @@ } }, "node_modules/stremio-translations": { - "version": "1.44.10", - "resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#a6be0425573917c2e82b66d28968c1a4d444cb96", - "integrity": "sha512-77kVE/eos/SA16kzeK7TTWmqoLF0mLPCJXjITwVIVzMHr8XyBPZFOfmiVEg4M6W1W7qYqA+dHhzicyLs7hJhlw==", + "version": "1.44.12", + "resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#8212fa77c4febd22ddb611590e9fb574dc845416", + "integrity": "sha512-5DladLUsghLlVRsZh2bBnb7UMqU8NEYMHc+YbzBvb1llgMk9elXFSHtAjInepZlC5zWx2pJYOQ8lQzzqogQdFw==", "license": "MIT" }, "node_modules/string_decoder": { @@ -13976,6 +14036,13 @@ "dev": true, "license": "MIT" }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinycolor2": { "version": "1.6.0", "dev": true, diff --git a/package.json b/package.json index 5ed9368ab..9599f3587 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "start-prod": "webpack serve --mode production", "build": "webpack --mode production", "test": "jest", - "lint": "eslint src" + "lint": "eslint src", + "scan-translations": "npx jest ./tests/i18nScan.test.js" }, "dependencies": { "@babel/runtime": "7.26.0", @@ -40,7 +41,7 @@ "react-i18next": "^15.1.3", "react-is": "18.3.1", "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", - "stremio-translations": "github:Stremio/stremio-translations#a6be0425573917c2e82b66d28968c1a4d444cb96", + "stremio-translations": "github:Stremio/stremio-translations#8212fa77c4febd22ddb611590e9fb574dc845416", "url": "0.11.4", "use-long-press": "^3.2.0" }, @@ -52,6 +53,8 @@ "@stylistic/eslint-plugin": "^2.11.0", "@stylistic/eslint-plugin-jsx": "^2.11.0", "@types/hat": "^0.0.4", + "@types/lodash.isequal": "^4.5.8", + "@types/lodash.throttle": "^4.1.9", "@types/react": "^18.3.13", "@types/react-dom": "^18.3.1", "babel-loader": "9.2.1", @@ -70,6 +73,7 @@ "mini-css-extract-plugin": "2.9.2", "postcss-loader": "8.1.1", "readdirp": "4.0.2", + "recast": "0.23.11", "terser-webpack-plugin": "5.3.10", "thread-loader": "^4.0.4", "ts-loader": "^9.5.1", diff --git a/src/App/App.js b/src/App/App.js index 38be271ac..3e816be9f 100644 --- a/src/App/App.js +++ b/src/App/App.js @@ -102,12 +102,18 @@ const App = () => { // Handle shell events React.useEffect(() => { const onOpenMedia = (data) => { - if (data.startsWith('stremio:///')) return; - if (data.startsWith('stremio://')) { - const transportUrl = data.replace('stremio://', 'https://'); - if (URL.canParse(transportUrl)) { - window.location.href = `#/addons?addon=${encodeURIComponent(transportUrl)}`; + try { + const { protocol, hostname, pathname, searchParams } = new URL(data); + if (protocol === CONSTANTS.PROTOCOL) { + if (hostname.length) { + const transportUrl = `https://${hostname}${pathname}`; + window.location.href = `#/addons?addon=${encodeURIComponent(transportUrl)}`; + } else { + window.location.href = `#${pathname}?${searchParams.toString()}`; + } } + } catch (e) { + console.error('Failed to open media:', e); } }; diff --git a/src/common/CONSTANTS.js b/src/common/CONSTANTS.js index 8e4e3efdc..92d009895 100644 --- a/src/common/CONSTANTS.js +++ b/src/common/CONSTANTS.js @@ -106,6 +106,8 @@ const EXTERNAL_PLAYERS = [ const WHITELISTED_HOSTS = ['stremio.com', 'strem.io', 'stremio.zendesk.com', 'google.com', 'youtube.com', 'twitch.tv', 'twitter.com', 'x.com', 'netflix.com', 'adex.network', 'amazon.com', 'forms.gle']; +const PROTOCOL = 'stremio:'; + module.exports = { CHROMECAST_RECEIVER_APP_ID, DEFAULT_STREAMING_SERVER_URL, @@ -127,4 +129,5 @@ module.exports = { SUPPORTED_LOCAL_SUBTITLES, EXTERNAL_PLAYERS, WHITELISTED_HOSTS, + PROTOCOL, }; diff --git a/src/common/Platform/Platform.tsx b/src/common/Platform/Platform.tsx index 2212303e4..0da1881ef 100644 --- a/src/common/Platform/Platform.tsx +++ b/src/common/Platform/Platform.tsx @@ -1,6 +1,5 @@ import React, { createContext, useContext } from 'react'; import { WHITELISTED_HOSTS } from 'stremio/common/CONSTANTS'; -import useShell from 'stremio/common/useShell'; import { name, isMobile } from './device'; interface PlatformContext { @@ -16,19 +15,13 @@ type Props = { }; const PlatformProvider = ({ children }: Props) => { - const shell = useShell(); - const openExternal = (url: string) => { try { const { hostname } = new URL(url); const isWhitelisted = WHITELISTED_HOSTS.some((host: string) => hostname.endsWith(host)); const finalUrl = !isWhitelisted ? `https://www.stremio.com/warning#${encodeURIComponent(url)}` : url; - if (shell.active) { - shell.send('open-external', finalUrl); - } else { - window.open(finalUrl, '_blank'); - } + window.open(finalUrl, '_blank'); } catch (e) { console.error('Failed to parse external url:', e); } diff --git a/src/common/Toast/ToastItem/ToastItem.js b/src/common/Toast/ToastItem/ToastItem.js index 94b5a98b4..3a317ab1b 100644 --- a/src/common/Toast/ToastItem/ToastItem.js +++ b/src/common/Toast/ToastItem/ToastItem.js @@ -1,6 +1,7 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useTranslation } = require('react-i18next'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const { default: Icon } = require('@stremio/stremio-icons/react'); @@ -8,6 +9,7 @@ const { Button } = require('stremio/components'); const styles = require('./styles'); const ToastItem = ({ title, message, dataset, onSelect, onClose, ...props }) => { + const { t } = useTranslation(); const type = React.useMemo(() => { return ['success', 'alert', 'info', 'error'].includes(props.type) ? props.type @@ -74,7 +76,7 @@ const ToastItem = ({ title, message, dataset, onSelect, onClose, ...props }) => null } - diff --git a/src/common/Toast/useToast.d.ts b/src/common/Toast/useToast.d.ts new file mode 100644 index 000000000..e74d7ade8 --- /dev/null +++ b/src/common/Toast/useToast.d.ts @@ -0,0 +1,11 @@ +type ToastOptions = { + type: string, + title: string, + timeout: number, +}; + +declare const useToast: () => { + show: (options: ToastOptions) => void, +}; + +export = useToast; diff --git a/src/common/animations.less b/src/common/animations.less index 91dbe386d..3b9815f14 100644 --- a/src/common/animations.less +++ b/src/common/animations.less @@ -82,6 +82,19 @@ transform: translateY(100%); } +.fade-enter { + opacity: 0; +} + +.fade-active { + opacity: 1; + transition: opacity 0.3s cubic-bezier(0.32, 0, 0.67, 0); +} + +.fade-exit { + opacity: 0; +} + @keyframes fade-in-no-motion { 0% { opacity: 0; diff --git a/src/common/routesRegexp.js b/src/common/routesRegexp.js index 3903da44b..b9989b4b5 100644 --- a/src/common/routesRegexp.js +++ b/src/common/routesRegexp.js @@ -6,7 +6,7 @@ const routesRegexp = { urlParamsNames: [] }, board: { - regexp: /^\/?$/, + regexp: /^\/?(?:board)?$/, urlParamsNames: [] }, discover: { diff --git a/src/common/useFullscreen.ts b/src/common/useFullscreen.ts index 5f0975fb8..9bd5d0fc5 100644 --- a/src/common/useFullscreen.ts +++ b/src/common/useFullscreen.ts @@ -46,6 +46,10 @@ const useFullscreen = () => { exitFullscreen(); } + if (event.code === 'KeyF') { + toggleFullscreen(); + } + if (event.code === 'F11' && shell.active) { toggleFullscreen(); } @@ -60,7 +64,7 @@ const useFullscreen = () => { document.removeEventListener('keydown', onKeyDown); document.removeEventListener('fullscreenchange', onFullscreenChange); }; - }, [settings.escExitFullscreen]); + }, [settings.escExitFullscreen, toggleFullscreen]); return [fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen]; }; diff --git a/src/components/AddonDetailsModal/AddonDetails/AddonDetails.js b/src/components/AddonDetailsModal/AddonDetails/AddonDetails.js index da5fde198..78c744b96 100644 --- a/src/components/AddonDetailsModal/AddonDetails/AddonDetails.js +++ b/src/components/AddonDetailsModal/AddonDetails/AddonDetails.js @@ -1,6 +1,7 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useTranslation } = require('react-i18next'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const { default: Icon } = require('@stremio/stremio-icons/react'); @@ -8,6 +9,7 @@ const { default: Image } = require('stremio/components/Image'); const styles = require('./styles'); const AddonDetails = ({ className, id, name, version, logo, description, types, transportUrl, official }) => { + const { t } = useTranslation(); const renderLogoFallback = React.useCallback(() => ( ), []); @@ -24,7 +26,7 @@ const AddonDetails = ({ className, id, name, version, logo, description, types, {typeof name === 'string' && name.length > 0 ? name : id} { typeof version === 'string' && version.length > 0 ? - v. {version} + {t('ADDON_VERSION_SHORT', {version})} : null } @@ -41,7 +43,7 @@ const AddonDetails = ({ className, id, name, version, logo, description, types, { typeof transportUrl === 'string' && transportUrl.length > 0 ?
- URL: + {`${t('URL')}:`} {transportUrl}
: @@ -50,7 +52,7 @@ const AddonDetails = ({ className, id, name, version, logo, description, types, { Array.isArray(types) && types.length > 0 ?
- Supported types: + {`${t('ADDON_SUPPORTED_TYPES')}:`} { types.length === 1 ? @@ -66,7 +68,7 @@ const AddonDetails = ({ className, id, name, version, logo, description, types, { !official ?
-
Using third-party add-ons will always be subject to your responsibility and the governing law of the jurisdiction you are located.
+
{t('ADDON_DISCLAIMER')}
: null diff --git a/src/components/AddonDetailsModal/AddonDetailsModal.js b/src/components/AddonDetailsModal/AddonDetailsModal.js index 332eab364..a89a68b77 100644 --- a/src/components/AddonDetailsModal/AddonDetailsModal.js +++ b/src/components/AddonDetailsModal/AddonDetailsModal.js @@ -1,6 +1,7 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useTranslation } = require('react-i18next'); const PropTypes = require('prop-types'); const ModalDialog = require('stremio/components/ModalDialog'); const { withCoreSuspender } = require('stremio/common/CoreSuspender'); @@ -43,13 +44,14 @@ function withRemoteAndLocalAddon(AddonDetails) { } const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { + const { t } = useTranslation(); const { core } = useServices(); const platform = usePlatform(); const addonDetails = useAddonDetails(transportUrl); const modalButtons = React.useMemo(() => { const cancelButton = { className: styles['cancel-button'], - label: 'Cancel', + label: t('BUTTON_CANCEL'), props: { onClick: (event) => { if (typeof onCloseRequest === 'function') { @@ -67,7 +69,7 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { addonDetails.remoteAddon.content.content.manifest.behaviorHints.configurable ? { className: styles['configure-button'], - label: 'Configure', + label: t('ADDON_CONFIGURE'), props: { onClick: (event) => { platform.openExternal(transportUrl.replace('manifest.json', 'configure')); @@ -86,7 +88,7 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { const toggleButton = addonDetails.localAddon !== null ? { className: styles['uninstall-button'], - label: 'Uninstall', + label: t('ADDON_UNINSTALL'), props: { onClick: (event) => { core.transport.dispatch({ @@ -113,7 +115,7 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { { className: styles['install-button'], - label: 'Install', + label: t('ADDON_INSTALL'), props: { onClick: (event) => { core.transport.dispatch({ @@ -141,21 +143,21 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { return addonDetails.remoteAddon?.content.type === 'Ready' ? addonDetails.remoteAddon.content.content.manifest.background : null; }, [addonDetails.remoteAddon]); return ( - + { addonDetails.selected === null ?
- Loading addon manifest + {t('ADDON_LOADING_MANIFEST')}
: addonDetails.remoteAddon === null || addonDetails.remoteAddon.content.type === 'Loading' ?
- Loading addon manifest from {addonDetails.selected.transportUrl} + {t('ADDON_LOADING_MANIFEST_FROM', { origin: addonDetails.selected.transportUrl})}
: addonDetails.remoteAddon.content.type === 'Err' && addonDetails.localAddon === null ?
- Failed to get addon manifest from {addonDetails.selected.transportUrl} + {t('ADDON_LOADING_MANIFEST_FAILED', {origin: addonDetails.selected.transportUrl})}
{addonDetails.remoteAddon.content.content.message}
: @@ -174,17 +176,18 @@ AddonDetailsModal.propTypes = { onCloseRequest: PropTypes.func }; -const AddonDetailsModalFallback = ({ onCloseRequest }) => ( - { + const { t } = useTranslation(); + return
- Loading addon manifest + {t('ADDON_LOADING_MANIFEST')}
-
-); +
; +}; AddonDetailsModalFallback.propTypes = AddonDetailsModal.propTypes; diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index f97e1ba82..dcce809d2 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -7,6 +7,7 @@ import styles from './Button.less'; type Props = { className?: string, + style?: object, href?: string, target?: string title?: string, diff --git a/src/components/Checkbox/Checkbox.less b/src/components/Checkbox/Checkbox.less index a84244ce9..9276990b3 100644 --- a/src/components/Checkbox/Checkbox.less +++ b/src/components/Checkbox/Checkbox.less @@ -23,6 +23,7 @@ .link { font-size: 0.9rem; color: var(--primary-accent-color); + margin-left: 0.5rem; &:hover { text-decoration: underline; diff --git a/src/components/Checkbox/Checkbox.tsx b/src/components/Checkbox/Checkbox.tsx index da4ae33eb..b252006eb 100644 --- a/src/components/Checkbox/Checkbox.tsx +++ b/src/components/Checkbox/Checkbox.tsx @@ -80,7 +80,6 @@ const Checkbox = React.forwardRef(({ name, disabled, cl
{label} - {' '} { href && link ?
diff --git a/src/components/Multiselect/Multiselect.js b/src/components/Multiselect/Multiselect.js index 0e353eef4..c2bf855d1 100644 --- a/src/components/Multiselect/Multiselect.js +++ b/src/components/Multiselect/Multiselect.js @@ -1,6 +1,7 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useTranslation } = require('react-i18next'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const { default: Icon } = require('@stremio/stremio-icons/react'); @@ -11,6 +12,7 @@ const useBinaryState = require('stremio/common/useBinaryState'); const styles = require('./styles'); const Multiselect = ({ className, mode, direction, title, disabled, dataset, options, renderLabelContent, renderLabelText, onOpen, onClose, onSelect, ...props }) => { + const { t } = useTranslation(); const [menuOpen, , closeMenu, toggleMenu] = useBinaryState(false); const filteredOptions = React.useMemo(() => { return Array.isArray(options) ? @@ -122,7 +124,7 @@ const Multiselect = ({ className, mode, direction, title, disabled, dataset, opt )) :
-
No options available
+
{t('NO_OPTIONS')}
}
diff --git a/src/components/MultiselectMenu/Dropdown/Dropdown.less b/src/components/MultiselectMenu/Dropdown/Dropdown.less index 3bea17d22..2373241f2 100644 --- a/src/components/MultiselectMenu/Dropdown/Dropdown.less +++ b/src/components/MultiselectMenu/Dropdown/Dropdown.less @@ -2,7 +2,7 @@ @import (reference) '~stremio/common/screen-sizes.less'; -@parent-height: 10rem; +@parent-height: 12rem; .dropdown { background: var(--modal-background-color); diff --git a/src/components/MultiselectMenu/Dropdown/Dropdown.tsx b/src/components/MultiselectMenu/Dropdown/Dropdown.tsx index 3da75619b..00fd3ff6a 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?: any; menuOpen: boolean | (() => void); level: number; setLevel: (level: number) => void; - onSelect: (value: number) => void; + onSelect: (value: any) => 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); + + const handleSetOptionRef = useCallback((optionValue: any) => (node: HTMLButtonElement | null) => { if (node) { - optionsRef.current.set(value, node); + optionsRef.current.set(optionValue, node); } else { - optionsRef.current.delete(value); + optionsRef.current.delete(optionValue); } }, []); @@ -63,11 +65,11 @@ const Dropdown = ({ level, setLevel, options, onSelect, selectedOption, menuOpen .filter((option: MultiselectMenuOption) => !option.hidden) .map((option: MultiselectMenuOption) => (