From e8a6e72b13297bdd77ea1ce3cd877d77fedfbeb1 Mon Sep 17 00:00:00 2001 From: Botzy Date: Fri, 7 Feb 2025 17:06:26 +0200 Subject: [PATCH] feat(NumberInput): added NumberInput common component --- src/components/NumberInput/NumberInput.tsx | 84 ++++++++++++++++++++++ src/components/NumberInput/index.ts | 5 ++ src/components/NumberInput/styles.less | 75 +++++++++++++++++++ src/components/index.ts | 2 + 4 files changed, 166 insertions(+) create mode 100644 src/components/NumberInput/NumberInput.tsx create mode 100644 src/components/NumberInput/index.ts create mode 100644 src/components/NumberInput/styles.less diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx new file mode 100644 index 000000000..fe6b84806 --- /dev/null +++ b/src/components/NumberInput/NumberInput.tsx @@ -0,0 +1,84 @@ +// Copyright (C) 2017-2025 Smart code 203358507 + +import React, { forwardRef, useCallback, useState } from 'react'; +import { type KeyboardEvent, type InputHTMLAttributes } from 'react'; +import classnames from 'classnames'; +import styles from './styles.less'; +import Button from '../Button'; +import Icon from '@stremio/stremio-icons/react'; + +type Props = InputHTMLAttributes & { + containerClassName?: string; + className?: string; + disabled?: boolean; + showButtons?: boolean; + defaultValue?: number; + label?: string; + min?: number; + max?: number; + onKeyDown?: (event: KeyboardEvent) => void; + onSubmit?: (event: KeyboardEvent) => void; +}; + +const NumberInput = forwardRef(({ defaultValue, ...props }, ref) => { + const [value, setValue] = useState(defaultValue || 1); + const onKeyDown = useCallback((event: KeyboardEvent) => { + props.onKeyDown && props.onKeyDown(event); + + if (event.key === 'Enter' ) { + props.onSubmit && props.onSubmit(event); + } + }, [props.onKeyDown, props.onSubmit]); + + const handleIncrease = () => { + const { max } = props; + if (max) { + return setValue((prevVal) => + prevVal + 1 > max ? max : prevVal + 1 + ); + } + setValue((prevVal) => prevVal + 1); + }; + + const handleDecrease = () => { + const { min } = props; + if (min) { + return setValue((prevVal) => + prevVal - 1 < min ? min : prevVal - 1 + ); + } + setValue((prevVal) => prevVal - 1); + }; + + return ( +
+ {props.showButtons ? : null} +
+ {props.label &&
{props.label}
} + setValue(event.value)} + onKeyDown={onKeyDown} + /> +
+ {props.showButtons ? : null} +
+ ); +}); + +NumberInput.displayName = 'NumberInput'; + +export default NumberInput; diff --git a/src/components/NumberInput/index.ts b/src/components/NumberInput/index.ts new file mode 100644 index 000000000..4a25f86df --- /dev/null +++ b/src/components/NumberInput/index.ts @@ -0,0 +1,5 @@ +// Copyright (C) 2017-2025 Smart code 203358507 + +import NumberInput from './NumberInput'; + +export default NumberInput; diff --git a/src/components/NumberInput/styles.less b/src/components/NumberInput/styles.less new file mode 100644 index 000000000..0fe8e8c81 --- /dev/null +++ b/src/components/NumberInput/styles.less @@ -0,0 +1,75 @@ +// Copyright (C) 2017-2025 Smart code 203358507 + +.number-input { + user-select: text; + display: flex; + max-width: 12rem; + margin-bottom: 1rem; + color: var(--primary-foreground-color); + background: var(--overlay-color); + border-radius: 100rem; + + .btn { + width: 2.875rem; + height: 2.875rem; + background: var(--overlay-color); + border: none; + border-radius: 50%; + color: white; + 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 { + border: none; + } + + &.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: white; + text-align: center; + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + -moz-appearance: textfield; + margin: 0; + } + } + + } diff --git a/src/components/index.ts b/src/components/index.ts index f65d66f81..ca3561bcd 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -17,6 +17,7 @@ import ModalDialog from './ModalDialog'; import Multiselect from './Multiselect'; import MultiselectMenu from './MultiselectMenu'; import { HorizontalNavBar, VerticalNavBar } from './NavBar'; +import NumberInput from './NumberInput'; import Popup from './Popup'; import RadioButton from './RadioButton'; import SearchBar from './SearchBar'; @@ -48,6 +49,7 @@ export { MultiselectMenu, HorizontalNavBar, VerticalNavBar, + NumberInput, Popup, RadioButton, SearchBar,