diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx index 303e04cbf..2add02be5 100644 --- a/src/components/NumberInput/NumberInput.tsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -1,6 +1,6 @@ // Copyright (C) 2017-2025 Smart code 203358507 -import React, { ChangeEvent, forwardRef, useCallback, useState } from 'react'; +import React, { ChangeEvent, forwardRef, useCallback, useEffect, useState } from 'react'; import { type KeyboardEvent, type InputHTMLAttributes } from 'react'; import classnames from 'classnames'; import styles from './styles.less'; @@ -18,10 +18,11 @@ type Props = InputHTMLAttributes & { max?: number; onKeyDown?: (event: KeyboardEvent) => void; onSubmit?: (event: KeyboardEvent) => void; + onUpdate?: (value: number) => void; }; -const NumberInput = forwardRef(({ defaultValue, ...props }, ref) => { - const [value, setValue] = useState(defaultValue || 0); +const NumberInput = forwardRef(({ defaultValue, showButtons, onUpdate, ...props }, ref) => { + const [value, setValue] = useState(defaultValue || 0); const onKeyDown = useCallback((event: KeyboardEvent) => { props.onKeyDown && props.onKeyDown(event); @@ -32,33 +33,63 @@ const NumberInput = forwardRef(({ defaultValue, ...prop const handleIncrease = () => { const { max } = props; - if (typeof max !== 'undefined') { - return setValue((prevVal) => - prevVal + 1 > max ? max : prevVal + 1 - ); + if (max !== undefined) { + return setValue((prevVal) => { + const value = prevVal || 0; + return value + 1 > max ? max : value + 1; + }); } - setValue((prevVal) => prevVal + 1); + setValue((prevVal) => { + const value = prevVal || 0; + return value + 1; + }); }; const handleDecrease = () => { const { min } = props; - if (typeof min !== 'undefined') { - return setValue((prevVal) => - prevVal - 1 < min ? min : prevVal - 1 - ); + if (min !== undefined) { + return setValue((prevVal) => { + const value = prevVal || 0; + return value - 1 < min ? min : value - 1; + }); } - setValue((prevVal) => prevVal - 1); + setValue((prevVal) => { + const value = prevVal || 0; + return value - 1; + }); }; + const handleChange = (event: ChangeEvent) => { + const min = props.min || 0; + let newValue = event.target.valueAsNumber; + if (newValue && newValue < min) { + newValue = min; + } + if (props.max !== undefined && newValue && newValue > props.max) { + newValue = props.max; + } + setValue(newValue); + }; + + useEffect(() => { + if (typeof onUpdate === 'function') { + onUpdate(value); + } + }, [value]); + return (
- {props.showButtons ? : null} -
+ { + showButtons ? + + : null + } +
{props.label &&
{props.label}
} (({ defaultValue, ...prop value={value} {...props} className={classnames(props.className, styles['value'], { 'disabled': props.disabled })} - onChange={(event: ChangeEvent) => { - const newValue = parseInt(event.target.value); - if (props.min !== undefined && newValue < props.min) return props.min; - if (props.max !== undefined && newValue > props.max) return props.max; - setValue(newValue); - }} + onChange={handleChange} onKeyDown={onKeyDown} />
- {props.showButtons ? : null} + { + showButtons ? + + : null + }
); }); diff --git a/src/components/NumberInput/styles.less b/src/components/NumberInput/styles.less index 484f3661a..a88bc6d20 100644 --- a/src/components/NumberInput/styles.less +++ b/src/components/NumberInput/styles.less @@ -3,73 +3,63 @@ .number-input { user-select: text; display: flex; - max-width: 12rem; + max-width: 14rem; + height: 3.5rem; margin-bottom: 1rem; color: var(--primary-foreground-color); background: var(--overlay-color); - border-radius: 100rem; + border-radius: 3.5rem; - .btn { - width: 2.875rem; - height: 2.875rem; - background: var(--overlay-color); - border: none; - border-radius: 50%; - color: var(--primary-foreground-color); - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: background 0.3s; - z-index: 1; - - svg { - width: 1.625rem; - } - } - - .number-display { - height: 2.875rem; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - padding: 0 1rem; - - &::-moz-focus-inner { + .button { + flex: none; + width: 3.5rem; + height: 3.5rem; + padding: 1rem; + background: var(--overlay-color); border: none; + border-radius: 100%; + cursor: pointer; + z-index: 1; + + .icon { + width: 100%; + height: 100%; + } } - &.with-btns { - padding: 0 1.9375rem; - margin-left: -1.4375rem; - margin-right: -1.4375rem; - max-width: 9.125rem; - } - } - - /* Label */ - .number-display .label { - font-size: 0.625rem; - font-weight: 400; - opacity: 0.7; - } - - /* Value */ - .number-display .value { - font-size: 1.3125rem; - display: flex; - width: 100%; - color: var(--primary-foreground-color); - text-align: center; + .number-display { + display: flex; + flex: 1; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + padding: 0 1rem; - &::-webkit-outer-spin-button, - &::-webkit-inner-spin-button { - -webkit-appearance: none; - -moz-appearance: textfield; - margin: 0; + &::-moz-focus-inner { + border: none; + } + + .label { + font-size: 0.8rem; + font-weight: 400; + opacity: 0.7; + } + + .value { + font-size: 1.2rem; + display: flex; + justify-content: center; + width: 100%; + color: var(--primary-foreground-color); + text-align: center; + appearance: none; + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + } } - } - - } +} \ No newline at end of file diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less index f5e1f509c..260eb8ebe 100644 --- a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less @@ -21,7 +21,7 @@ flex: 0 1 auto; font-size: 1rem; font-weight: 700; - max-height: 3.6em; + max-height: 3.5rem; text-align: center; color: var(--primary-foreground-color); margin-bottom: 0; diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx index 44da39bfa..ddb38e353 100644 --- a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx @@ -1,6 +1,6 @@ // Copyright (C) 2017-2025 Smart code 203358507 -import React, { useRef } from 'react'; +import React from 'react'; import { useTranslation } from 'react-i18next'; import { Button, NumberInput } from 'stremio/components'; import styles from './EpisodePicker.less'; @@ -16,33 +16,28 @@ export const EpisodePicker = ({ className, onSubmit }: Props) => { const videoId = decodeURIComponent(splitPath[splitPath.length - 1]); const [, pathSeason, pathEpisode] = videoId ? videoId.split(':') : []; const [season, setSeason] = React.useState(() => { - const initialSeason = isNaN(parseInt(pathSeason)) ? 1 : parseInt(pathSeason); + const initialSeason = isNaN(parseInt(pathSeason)) ? 0 : parseInt(pathSeason); return initialSeason; }); const [episode, setEpisode] = React.useState(() => { const initialEpisode = isNaN(parseInt(pathEpisode)) ? 1 : parseInt(pathEpisode); return initialEpisode; }); - const seasonRef = useRef(null); - const episodeRef = useRef(null); + const handleSeasonChange = (value: number) => setSeason(!isNaN(value) ? value : 1); - const handleSeasonChange = (value?: number) => setSeason(value !== undefined ? value : 1); - - const handleEpisodeChange = (value?: number) => setEpisode(value !== undefined ? value : 1); + const handleEpisodeChange = (value: number) => setEpisode(!isNaN(value) ? value : 1); const handleSubmit = React.useCallback(() => { - const season = parseInt(seasonRef.current?.value || '1'); - const episode = parseInt(episodeRef.current?.value || '1'); if (typeof onSubmit === 'function' && !isNaN(season) && !isNaN(episode)) { onSubmit(season, episode); } - }, [onSubmit, seasonRef, episodeRef]); + }, [onSubmit, season, episode]); const disabled = React.useMemo(() => season === parseInt(pathSeason) && episode === parseInt(pathEpisode), [pathSeason, pathEpisode, season, episode]); return
- - + +