From d06d75cbc0cdf9918e4eae6566e9f9a3c21f3fcc Mon Sep 17 00:00:00 2001 From: Krivonos Aleksandr Date: Mon, 13 May 2024 10:05:40 +0300 Subject: [PATCH] feat(plasma-new-hope): add react-draggable --- .../plasma-web Slider -- focus.snap.png | Bin 0 -> 3082 bytes .../plasma-web Slider -- focus.snap.png | Bin 0 -> 3470 bytes packages/plasma-b2c/api/plasma-b2c.api.md | 12 +- .../src/components/Slider/Slider.stories.tsx | 23 +- packages/plasma-new-hope/package-lock.json | 13 +- packages/plasma-new-hope/package.json | 1 + .../src/components/Slider/Slider.tsx | 10 +- .../Slider/components/Double/Double.tsx | 269 +++++++++--------- .../Slider/components/Single/Single.tsx | 80 +++--- .../components/SliderBase/SliderBase.tsx | 25 +- .../components/Slider/ui/Handle/Handle.tsx | 185 ------------ .../Handler.styles.ts} | 2 +- .../components/Slider/ui/Handler/Handler.tsx | 166 +++++++++++ .../Handler.types.ts} | 2 +- .../src/components/Slider/ui/index.ts | 4 +- .../components/Slider/Slider.stories.tsx | 23 +- .../components/Slider/Slider.stories.tsx | 24 +- packages/plasma-web/api/plasma-web.api.md | 12 +- .../Slider/Slider.component-test.tsx | 13 + .../src/components/Slider/Slider.stories.tsx | 24 +- packages/sdds-serv/api/sdds-serv.api.md | 26 +- .../Slider/Slider.component-test.tsx | 96 +++++++ .../src/components/Slider/Slider.config.ts | 241 ++++++++++++++++ .../src/components/Slider/Slider.stories.tsx | 166 +++++++++++ .../src/components/Slider/Slider.tsx | 12 + .../sdds-serv/src/components/Slider/index.ts | 2 + packages/sdds-serv/src/index.ts | 1 + .../docs/components/Slider.mdx | 36 ++- .../sdds-serv-docs/docs/components/Slider.mdx | 78 +++++ 29 files changed, 1127 insertions(+), 419 deletions(-) create mode 100644 cypress/snapshots/b2c/components/Slider/Slider.component-test.tsx/plasma-web Slider -- focus.snap.png create mode 100644 cypress/snapshots/web/components/Slider/Slider.component-test.tsx/plasma-web Slider -- focus.snap.png delete mode 100644 packages/plasma-new-hope/src/components/Slider/ui/Handle/Handle.tsx rename packages/plasma-new-hope/src/components/Slider/ui/{Handle/Handle.styles.ts => Handler/Handler.styles.ts} (94%) create mode 100644 packages/plasma-new-hope/src/components/Slider/ui/Handler/Handler.tsx rename packages/plasma-new-hope/src/components/Slider/ui/{Handle/Handle.types.ts => Handler/Handler.types.ts} (95%) create mode 100644 packages/sdds-serv/src/components/Slider/Slider.component-test.tsx create mode 100644 packages/sdds-serv/src/components/Slider/Slider.config.ts create mode 100644 packages/sdds-serv/src/components/Slider/Slider.stories.tsx create mode 100644 packages/sdds-serv/src/components/Slider/Slider.tsx create mode 100644 packages/sdds-serv/src/components/Slider/index.ts create mode 100644 website/sdds-serv-docs/docs/components/Slider.mdx diff --git a/cypress/snapshots/b2c/components/Slider/Slider.component-test.tsx/plasma-web Slider -- focus.snap.png b/cypress/snapshots/b2c/components/Slider/Slider.component-test.tsx/plasma-web Slider -- focus.snap.png new file mode 100644 index 0000000000000000000000000000000000000000..98289f6b42eb01f9fccbd50bb503659360433c02 GIT binary patch literal 3082 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 literal 0 HcmV?d00001 diff --git a/packages/plasma-b2c/api/plasma-b2c.api.md b/packages/plasma-b2c/api/plasma-b2c.api.md index 76cd0b5701..b8d14ba525 100644 --- a/packages/plasma-b2c/api/plasma-b2c.api.md +++ b/packages/plasma-b2c/api/plasma-b2c.api.md @@ -775,19 +775,11 @@ l: string; view: { default: string; }; -<<<<<<< HEAD -}> & ((Omit, "type" | "target" | "onChange" | "size" | "value" | "checked" | "maxLength" | "minLength"> & CustomComboboxProps & { +}> & ((Omit, "onChange" | "type" | "target" | "size" | "value" | "checked" | "maxLength" | "minLength"> & CustomComboboxProps & { valueType?: "single" | undefined; value?: ComboboxPrimitiveValue | undefined; onChangeValue?: ((value?: ComboboxPrimitiveValue | undefined) => void) | undefined; -} & RefAttributes) | (Omit, "type" | "target" | "onChange" | "size" | "value" | "checked" | "maxLength" | "minLength"> & CustomComboboxProps & { -======= -}> & ((Omit, "onChange" | "type" | "target" | "size" | "value" | "checked" | "minLength" | "maxLength"> & CustomComboboxProps & { -valueType?: "single" | undefined; -value?: ComboboxPrimitiveValue | undefined; -onChangeValue?: ((value?: ComboboxPrimitiveValue | undefined) => void) | undefined; -} & RefAttributes) | (Omit, "onChange" | "type" | "target" | "size" | "value" | "checked" | "minLength" | "maxLength"> & CustomComboboxProps & { ->>>>>>> a61c59f64 (Update package-lock.json files) +} & RefAttributes) | (Omit, "onChange" | "type" | "target" | "size" | "value" | "checked" | "maxLength" | "minLength"> & CustomComboboxProps & { valueType: "multiple"; value?: ComboboxPrimitiveValue[] | undefined; onChangeValue?: ((value?: ComboboxPrimitiveValue[] | undefined) => void) | undefined; diff --git a/packages/plasma-b2c/src/components/Slider/Slider.stories.tsx b/packages/plasma-b2c/src/components/Slider/Slider.stories.tsx index 276bdd7eea..69a1095f65 100644 --- a/packages/plasma-b2c/src/components/Slider/Slider.stories.tsx +++ b/packages/plasma-b2c/src/components/Slider/Slider.stories.tsx @@ -61,9 +61,13 @@ const StoryDefault = (args: StorySingleProps) => { setValue(values); }; + const onChangeHandle = (values) => { + setValue(values); + }; + return ( - + ); }; @@ -103,13 +107,27 @@ export const Default: StorySingle = { const StoryMultipleValues = (args: StoryProps) => { const [value, setValue] = useState([10, 80]); const sortValues = (values) => { - return values.sort((a, b) => a - b); + return values + .map((val) => { + if (val < args.min) { + return args.min; + } + if (val > args.max) { + return args.max; + } + return val; + }) + .sort((a, b) => a - b); }; const onChangeHandle = (values) => { setValue(sortValues(values)); }; + const onChangeCommitedHandle = (values) => { + setValue(sortValues(values)); + }; + const onBlurTextField = (values) => { setValue(sortValues(values)); }; @@ -126,6 +144,7 @@ const StoryMultipleValues = (args: StoryProps) => { value={value} onKeyDownTextField={onKeyDownTextField} onBlurTextField={onBlurTextField} + onChangeCommitted={onChangeCommitedHandle} onChange={onChangeHandle} {...args} /> diff --git a/packages/plasma-new-hope/package-lock.json b/packages/plasma-new-hope/package-lock.json index 2371dd81f6..9b8e0b634d 100644 --- a/packages/plasma-new-hope/package-lock.json +++ b/packages/plasma-new-hope/package-lock.json @@ -14,6 +14,7 @@ "@popperjs/core": "2.11.8", "@salutejs/plasma-core": "1.160.0-dev.0", "focus-visible": "5.2.0", + "react-draggable": "4.4.3", "react-popper": "2.3.0", "storeon": "3.1.5" }, @@ -7210,9 +7211,9 @@ } }, "node_modules/classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, "node_modules/clean-stack": { "version": "2.2.0", @@ -19639,9 +19640,9 @@ } }, "classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, "clean-stack": { "version": "2.2.0", diff --git a/packages/plasma-new-hope/package.json b/packages/plasma-new-hope/package.json index 44264bfadf..1300116368 100644 --- a/packages/plasma-new-hope/package.json +++ b/packages/plasma-new-hope/package.json @@ -99,6 +99,7 @@ "@popperjs/core": "2.11.8", "@salutejs/plasma-core": "1.160.0-dev.0", "focus-visible": "5.2.0", + "react-draggable": "4.4.3", "react-popper": "2.3.0", "storeon": "3.1.5" } diff --git a/packages/plasma-new-hope/src/components/Slider/Slider.tsx b/packages/plasma-new-hope/src/components/Slider/Slider.tsx index 2d71997db0..55131eee16 100644 --- a/packages/plasma-new-hope/src/components/Slider/Slider.tsx +++ b/packages/plasma-new-hope/src/components/Slider/Slider.tsx @@ -13,16 +13,10 @@ const isSingleValueProps = (props: SliderProps): props is SingleSliderProps => t export const sliderRoot = (Root: RootPropsOmitOnChange) => forwardRef((props, ref) => { - if (isSingleValueProps(props)) { - return ( - - - - ); - } return ( - + {isSingleValueProps(props) && } + {!isSingleValueProps(props) && } ); }); diff --git a/packages/plasma-new-hope/src/components/Slider/components/Double/Double.tsx b/packages/plasma-new-hope/src/components/Slider/components/Double/Double.tsx index 84fe67bdb7..4c1f49642a 100644 --- a/packages/plasma-new-hope/src/components/Slider/components/Double/Double.tsx +++ b/packages/plasma-new-hope/src/components/Slider/components/Double/Double.tsx @@ -2,8 +2,8 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import type { FC, ChangeEvent, KeyboardEvent, FocusEvent } from 'react'; import { SliderBase } from '../SliderBase/SliderBase'; -import { Handle } from '../../ui'; -import type { HandleProps } from '../../ui'; +import { Handler } from '../../ui'; +import type { HandlerProps } from '../../ui'; import { sizeData } from '../../utils'; import { cx, isNumber } from '../../../../utils'; import { classes } from '../../Slider.tokens'; @@ -57,15 +57,15 @@ export const DoubleSlider: FC = ({ const [firstInputActive, setFirstInputActive] = useState(false); const [secondInputActive, setSecondInputActive] = useState(false); + const [firstValue, setFirstValue] = useState(value[0]); + const [secondValue, setSecondValue] = useState(value[1]); + const firstHandleRef = useRef(null); const secondHandleRef = useRef(null); const firstHandleValue = useRef(value[0]); const secondHandleValue = useRef(value[1]); - const [firstValue, setFirstValue] = useState(value[0]); - const [secondValue, setSecondValue] = useState(value[1]); - const { stepSize } = state; const hasLabelContent = label || labelContentLeft; @@ -98,148 +98,159 @@ export const DoubleSlider: FC = ({ [setState], ); - const onFirstHandleChange = useCallback>( - (handleValue, data) => { - if (!secondHandleRef?.current) { - return; - } - const newHandleXPosition = data.x; - const secondHandleXPosition = getXCenterHandle(secondHandleRef.current); - const fillWidth = secondHandleXPosition - newHandleXPosition; + const onFirstHandleChange: NonNullable = (handleValue, data) => { + if (!secondHandleRef?.current) { + return; + } + const newHandleXPosition = data.x; + const secondHandleXPosition = getXCenterHandle(secondHandleRef.current); + const fillWidth = secondHandleXPosition - newHandleXPosition; - firstHandleValue.current = handleValue; + firstHandleValue.current = handleValue; - setFirstValue(handleValue); + setFirstValue(handleValue); - setState((prevState) => ({ - ...prevState, - firstHandleZIndex: 101, - secondHandleZIndex: 100, - railFillWidth: fillWidth < 0 ? 0 : fillWidth, - railFillXPosition: newHandleXPosition, - })); - if (onChange) { - onChange([handleValue, value[1]]); - } - }, - [onChange, value], - ); - - const onFirstHandleChangeCommitted = useCallback>( - (handleValue, data) => { - setFirstValue(handleValue); - onChangeCommitted && onChangeCommitted([handleValue, value[1]]); + setState((prevState) => ({ + ...prevState, + firstHandleZIndex: 101, + secondHandleZIndex: 100, + railFillWidth: fillWidth < 0 ? 0 : fillWidth, + railFillXPosition: newHandleXPosition, + })); + if (onChange) { + onChange([handleValue, value[1]]); + } + }; + + const onFirstHandleChangeCommitted: NonNullable = (handleValue, data) => { + if (!secondHandleRef?.current) { + return; + } + const newHandleXPosition = data.x; + const secondHandleXPosition = getXCenterHandle(secondHandleRef.current); + const fillWidth = secondHandleXPosition - newHandleXPosition; + + firstHandleValue.current = handleValue; + + setFirstValue(handleValue); + if (onChangeCommitted) { + onChangeCommitted([handleValue, value[1]]); + } - setState((prevState) => ({ - ...prevState, - firstValue: handleValue, - xFirstHandle: data.lastX, - })); - }, - [onChangeCommitted, value], - ); + setState((prevState) => ({ + ...prevState, + firstValue: handleValue, + xFirstHandle: data.x, + railFillWidth: fillWidth < 0 ? 0 : fillWidth, + railFillXPosition: newHandleXPosition, + })); + }; - const onFirstTextfieldChange = useCallback( - (event: ChangeEvent) => { - if (!isNumber(event.target.value)) { - return; - } + const onFirstTextfieldChange = (event: ChangeEvent) => { + if (!isNumber(event.target.value)) { + return; + } - const handleValue = Number(event.target.value); + const handleValue = Number(event.target.value); - setFirstValue(handleValue); - onChangeTextField && onChangeTextField([handleValue, secondValue], event); - }, - [isNumber, setFirstValue, secondValue], - ); + setFirstValue(handleValue); + if (onChangeTextField) { + onChangeTextField([handleValue, secondValue], event); + } + }; - const onFirstTextfieldBlur = useCallback( - (event: FocusEvent) => { - if (!isNumber(event.target.value)) { - return; - } + const onFirstTextfieldBlur = (event: FocusEvent) => { + if (!isNumber(event.target.value)) { + return; + } - const handleValue = Number(event.target.value); + const handleValue = Number(event.target.value); - setFirstValue(handleValue); + setFirstValue(handleValue); + if (onBlurTextField) { onBlurTextField && onBlurTextField([handleValue, secondValue], event); - }, - [isNumber, setSecondValue, onBlurTextField, secondValue], - ); - - const onSecondHandleChange = useCallback>( - (handleValue, data) => { - if (!firstHandleRef?.current) { - return; - } - const firstXHandleXPosition = getXCenterHandle(firstHandleRef.current); + } + }; - const newHandleXPosition = data.x; - const fillWidth = newHandleXPosition - firstXHandleXPosition; + const onSecondHandleChange: NonNullable = (handleValue, data) => { + if (!firstHandleRef?.current) { + return; + } + const firstXHandleXPosition = getXCenterHandle(firstHandleRef.current); - secondHandleValue.current = handleValue; + const newHandleXPosition = data.x; + const fillWidth = newHandleXPosition - firstXHandleXPosition; - setSecondValue(handleValue); - setState((prevState) => ({ - ...prevState, - firstHandleZIndex: 100, - secondHandleZIndex: 101, - railFillWidth: fillWidth < 0 ? 0 : fillWidth, - railFillXPosition: firstXHandleXPosition, - })); - if (onChange) { - onChange([value[0], handleValue]); - } - }, - [onChange, value], - ); - - const onSecondHandleChangeCommitted = useCallback>( - (handleValue, data) => { - onChangeCommitted && onChangeCommitted([value[0], handleValue]); - setSecondValue(handleValue); - setState((prevState) => ({ - ...prevState, - secondValue: handleValue, - xSecondHandle: data.lastX, - })); - }, - [onChangeCommitted, value], - ); + secondHandleValue.current = handleValue; - const onSecondTextfieldChange = useCallback( - (event: ChangeEvent) => { - if (!isNumber(event.target.value)) { - return; - } - const handleValue = Number(event.target.value); + setSecondValue(handleValue); + setState((prevState) => ({ + ...prevState, + firstHandleZIndex: 100, + secondHandleZIndex: 101, + railFillWidth: fillWidth < 0 ? 0 : fillWidth, + railFillXPosition: firstXHandleXPosition, + })); + if (onChange) { + onChange([value[0], handleValue]); + } + }; - setSecondValue(handleValue); - onChangeTextField && onChangeTextField([firstValue, handleValue], event); - }, - [isNumber, setSecondValue, onChangeTextField, firstValue], - ); + const onSecondHandleChangeCommitted: NonNullable = (handleValue, data) => { + if (!firstHandleRef?.current) { + return; + } + const firstXHandleXPosition = getXCenterHandle(firstHandleRef.current); - const onSecondTextfieldBlur = useCallback( - (event: FocusEvent) => { - if (!isNumber(event.target.value)) { - return; - } + const newHandleXPosition = data.x; + const fillWidth = newHandleXPosition - firstXHandleXPosition; - const handleValue = Number(event.target.value); + secondHandleValue.current = handleValue; - setSecondValue(handleValue); - onBlurTextField && onBlurTextField([firstValue, handleValue], event); - }, - [isNumber, setSecondValue, onBlurTextField, firstValue], - ); + if (onChangeCommitted) { + onChangeCommitted([value[0], handleValue]); + } - const onTextfieldKeyDown = useCallback( - (event: ChangeEvent & KeyboardEvent) => { - onKeyDownTextField && onKeyDownTextField([firstValue, secondValue], event); - }, - [[isNumber, setSecondValue, onKeyDownTextField], firstValue, secondValue], - ); + setSecondValue(handleValue); + setState((prevState) => ({ + ...prevState, + secondValue: handleValue, + xSecondHandle: data.x, + railFillWidth: fillWidth < 0 ? 0 : fillWidth, + railFillXPosition: firstXHandleXPosition, + })); + }; + + const onSecondTextfieldChange = (event: ChangeEvent) => { + if (!isNumber(event.target.value)) { + return; + } + const handleValue = Number(event.target.value); + + setSecondValue(handleValue); + if (onChangeTextField) { + onChangeTextField([firstValue, handleValue], event); + } + }; + + const onSecondTextfieldBlur = (event: FocusEvent) => { + if (!isNumber(event.target.value)) { + return; + } + + const handleValue = Number(event.target.value); + + setSecondValue(handleValue); + if (onBlurTextField) { + onBlurTextField([firstValue, handleValue], event); + } + }; + + const onTextfieldKeyDown = (event: ChangeEvent & KeyboardEvent) => { + if (onKeyDownTextField) { + onKeyDownTextField([firstValue, secondValue], event); + } + }; const [ariaLabelLeft, ariaLabelRight] = ariaLabel || []; const currentFirstSliderValue = Math.max(state.firstValue, min); @@ -263,7 +274,7 @@ export const DoubleSlider: FC = ({ railFillXPosition={state.railFillXPosition} {...rest} > - = ({ onMouseEnter={() => setFirstInputActive(true)} onMouseLeave={() => setFirstInputActive(false)} /> - = ({ railFillWidth: 0, }); + const [startOffset, setStartOffset] = useState(0); + const [endOffset, setEndOffset] = useState(0); + + const [dragValue, setDragValue] = useState(value); + const { stepSize } = state; const hasLabelContent = label || labelContentLeft; @@ -56,11 +61,6 @@ export const SingleSlider: FC = ({ const startLabelRef = useRef(null); const endLabelRef = useRef(null); - const [startOffset, setStartOffset] = useState(0); - const [endOffset, setEndOffset] = useState(0); - - const [dragValue, setDragValue] = useState(value); - const activeFirstValue = dragValue === min ? classes.activeRangeValue : undefined; const activeSecondValue = dragValue === max ? classes.activeRangeValue : undefined; @@ -89,43 +89,47 @@ export const SingleSlider: FC = ({ })); }, [value, labelPlacement, stepSize, rangeValuesPlacement, min, max, setStartOffset, setEndOffset]); - const setStepSize = useCallback((newStepSize: number) => { + const setStepSize = useCallback( + (newStepSize: number) => { + setState((prevState) => ({ + ...prevState, + stepSize: newStepSize, + })); + }, + [setState], + ); + + const onHandleChange: NonNullable = (handleValue, data) => { + const newHandleXPosition = data.x; + const newValue = Math.round(handleValue); + setState((prevState) => ({ ...prevState, - stepSize: newStepSize, + railFillWidth: newHandleXPosition, })); - }, []); - const onHandleChange = useCallback>( - (handleValue, data) => { - const newHandleXPosition = data.x; - setState((prevState) => ({ - ...prevState, - railFillWidth: newHandleXPosition, - })); + if (onChange) { + onChange(newValue); + } - if (onChange) { - onChange(handleValue); - } + setDragValue(newValue); + }; - setDragValue(handleValue); - }, - [onChange, setDragValue], - ); + const onHandleChangeCommitted: NonNullable = (handleValue, data) => { + const newValue = Math.round(handleValue); - const onHandleChangeCommitted = useCallback>( - (handleValue, data) => { - onChangeCommitted && onChangeCommitted(handleValue); - setState((prevState) => ({ - ...prevState, - xHandle: data.lastX, - railFillWidth: data.lastX, - })); + if (onChangeCommitted) { + onChangeCommitted(newValue); + } - setDragValue(handleValue); - }, - [onChangeCommitted, setDragValue], - ); + setState((prevState) => ({ + ...prevState, + xHandle: data.lastX, + railFillWidth: data.lastX, + })); + + setDragValue(newValue); + }; return ( @@ -153,7 +157,7 @@ export const SingleSlider: FC = ({ rangeValuesPlacement={rangeValuesPlacement} {...rest} > - = ({ max={max} startOffset={startOffset} endOffset={endOffset} - value={value} + value={dragValue} disabled={disabled} ariaLabel={ariaLabel} multipleStepSize={multipleStepSize} diff --git a/packages/plasma-new-hope/src/components/Slider/components/SliderBase/SliderBase.tsx b/packages/plasma-new-hope/src/components/Slider/components/SliderBase/SliderBase.tsx index 42b839c21b..25e4f9518e 100644 --- a/packages/plasma-new-hope/src/components/Slider/components/SliderBase/SliderBase.tsx +++ b/packages/plasma-new-hope/src/components/Slider/components/SliderBase/SliderBase.tsx @@ -1,4 +1,4 @@ -import React, { PropsWithChildren, useCallback, useRef, MouseEventHandler, useEffect } from 'react'; +import React, { PropsWithChildren, useRef, MouseEventHandler, useEffect } from 'react'; import { DraggableData } from 'react-draggable'; import { useIsomorphicLayoutEffect } from '../../../../hooks'; @@ -37,23 +37,20 @@ export const SliderBase: React.FC> = ({ resizeHandler(); }, [labelPlacement, rangeValuesPlacement, ref.current]); - const onHandleChange: MouseEventHandler = useCallback( - (e) => { - if (!onChange || disabled) { - return; - } + const onHandleChange: MouseEventHandler = (e) => { + if (!onChange || disabled) { + return; + } - const { x, width } = e.currentTarget.getBoundingClientRect(); + const { x, width } = e.currentTarget.getBoundingClientRect(); - const lastX = e.clientX - x; + const lastX = e.clientX - x; - const position = min + (lastX / (width - gap)) * (max - min); - const result = Math.max(min, Math.min(max, position)); + const position = min + (lastX / (width - gap)) * (max - min); + const result = Math.max(min, Math.min(max, position)); - onChange(result, { lastX } as DraggableData); - }, - [onChange, disabled, min, gap, max, settings], - ); + onChange(result, { lastX } as DraggableData); + }; useIsomorphicLayoutEffect(() => { const resizeHandler = () => { diff --git a/packages/plasma-new-hope/src/components/Slider/ui/Handle/Handle.tsx b/packages/plasma-new-hope/src/components/Slider/ui/Handle/Handle.tsx deleted file mode 100644 index 69be8f3c56..0000000000 --- a/packages/plasma-new-hope/src/components/Slider/ui/Handle/Handle.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import React, { useState, useRef, useCallback, forwardRef, KeyboardEvent } from 'react'; -import Draggable, { DraggableEventHandler } from 'react-draggable'; -import type { DraggableData } from 'react-draggable'; - -import { getSliderThumbValue, getOffsets } from '../../utils'; -import { Thumb } from '../Thumb/Thumb'; - -import type { HandleProps } from './Handle.types'; -import { HandleStyled, StyledValue } from './Handle.styles'; - -// TODO: PLASMA-1707 -declare module 'react-draggable' { - export interface DraggableProps { - children: React.ReactNode; - } -} - -const KeyboardSupport = { - PageUp: 33, - PageDown: 34, - End: 35, - Home: 36, - ArrowLeft: 37, - ArrowUp: 38, - ArrowRight: 39, - ArrowDown: 40, -}; - -export const Handle = forwardRef( - ( - { - stepSize, - onChangeCommitted, - onChange, - xPosition = 0, - min, - max, - bounds = [], - zIndex, - disabled, - side, - showCurrentValue = false, - startOffset = 0, - endOffset = 0, - ...rest - }, - ref, - ) => { - const lastOnChangeValue = useRef(); - const currentSliderValue = lastOnChangeValue?.current ?? rest.value; - - const [offsetLeft, offsetRight] = getOffsets(ref, side); - - const [leftValueBound, rightValueBound] = bounds; - const leftPositionBound = leftValueBound ? (leftValueBound - min) * stepSize : null; - const rightPositionBound = rightValueBound ? (rightValueBound - min) * stepSize : null; - - const position = typeof xPosition === 'number' ? { x: xPosition, y: 0 } : undefined; - const tabIndex = disabled ? -1 : 0; - - const [positionX, setPositionX] = useState(position?.x ?? 0); - - const computedBounds = { - left: (leftPositionBound ?? 0) + offsetLeft, - right: (rightPositionBound ?? stepSize * (max - min)) - offsetRight, - }; - - const showCurrentValueCondition = - showCurrentValue && positionX >= startOffset && positionX <= max * stepSize - endOffset; - - const onDrag = useCallback( - (_, data) => { - const newValue = getSliderThumbValue(data.x, stepSize, min, max); - if (lastOnChangeValue.current !== newValue) { - onChange?.(newValue, data); - setPositionX(data.x); - lastOnChangeValue.current = newValue; - } - }, - [onChange, setPositionX, stepSize, min, max], - ); - - const onStop = useCallback( - (_, data) => { - const newValue = getSliderThumbValue(data.x, stepSize, min, max); - setPositionX(data.x); - onChangeCommitted && onChangeCommitted(newValue, data); - }, - [onChangeCommitted, setPositionX, stepSize, min, max], - ); - - const onKeyPress = useCallback( - (event: KeyboardEvent) => { - event.persist(); - - const { keyCode, target } = event; - - if (!Object.values(KeyboardSupport).includes(keyCode)) { - return; - } - - const { ArrowUp, ArrowRight, ArrowDown, ArrowLeft, Home, End, PageDown, PageUp } = KeyboardSupport; - - const computedMultipleSteps = stepSize * ((rest.multipleStepSize / 100) * max); - - const data: DraggableData = { - x: 0, - deltaX: stepSize, - lastX: xPosition, - y: 0, - deltaY: 0, - lastY: 0, - node: target as HTMLDivElement, - }; - - switch (keyCode) { - case ArrowUp: - case ArrowRight: - data.x = xPosition + stepSize; - break; - case ArrowDown: - case ArrowLeft: - data.x = xPosition - stepSize; - data.deltaX = -stepSize; - break; - case PageUp: - data.x = xPosition + computedMultipleSteps; - data.deltaX = computedMultipleSteps; - break; - case PageDown: - data.x = xPosition - computedMultipleSteps; - data.deltaX = -computedMultipleSteps; - break; - case End: - data.x = max * stepSize; - break; - case Home: - data.x = 0; - break; - default: - data.x = 0; - } - - const { left, right } = computedBounds; - - /* - * INFO: Находим значение в диапазоне между указанными левой и правой границами. - * Необходимо для правильного расчета положения SliderThumb. - * см. функция clamp - */ - const boundedValue = Math.max(Math.min(right, data.x), left); - - const computedValue = getSliderThumbValue(boundedValue, stepSize, min, max); - lastOnChangeValue.current = computedValue; - - onChangeCommitted && onChangeCommitted(computedValue, data); - }, - [onChangeCommitted, bounds, stepSize, rest.multipleStepSize, min, max, xPosition], - ); - - return ( - - - - {showCurrentValueCondition && {currentSliderValue}} - - - ); - }, -); diff --git a/packages/plasma-new-hope/src/components/Slider/ui/Handle/Handle.styles.ts b/packages/plasma-new-hope/src/components/Slider/ui/Handler/Handler.styles.ts similarity index 94% rename from packages/plasma-new-hope/src/components/Slider/ui/Handle/Handle.styles.ts rename to packages/plasma-new-hope/src/components/Slider/ui/Handler/Handler.styles.ts index 9e46a7b619..d7d0631599 100644 --- a/packages/plasma-new-hope/src/components/Slider/ui/Handle/Handle.styles.ts +++ b/packages/plasma-new-hope/src/components/Slider/ui/Handler/Handler.styles.ts @@ -2,7 +2,7 @@ import { styled } from '@linaria/react'; import { tokens } from '../../Slider.tokens'; -export const HandleStyled = styled.div` +export const HandlerStyled = styled.div` cursor: pointer; position: absolute; z-index: 1; diff --git a/packages/plasma-new-hope/src/components/Slider/ui/Handler/Handler.tsx b/packages/plasma-new-hope/src/components/Slider/ui/Handler/Handler.tsx new file mode 100644 index 0000000000..330c74c758 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Slider/ui/Handler/Handler.tsx @@ -0,0 +1,166 @@ +import React, { useRef, forwardRef, KeyboardEvent } from 'react'; +import Draggable, { DraggableEventHandler } from 'react-draggable'; +import type { DraggableData } from 'react-draggable'; + +import { getSliderThumbValue, getOffsets } from '../../utils'; +import { Thumb } from '../Thumb/Thumb'; + +import type { HandlerProps } from './Handler.types'; +import { HandlerStyled, StyledValue } from './Handler.styles'; + +// TODO: PLASMA-1707 +declare module 'react-draggable' { + export interface DraggableProps { + children: React.ReactNode; + } +} + +const KeyboardSupport = { + PageUp: 33, + PageDown: 34, + End: 35, + Home: 36, + ArrowLeft: 37, + ArrowUp: 38, + ArrowRight: 39, + ArrowDown: 40, +}; + +export const Handler = forwardRef( + ( + { + stepSize, + onChangeCommitted, + onChange, + xPosition = 0, + min, + max, + bounds = [], + zIndex, + disabled, + side, + showCurrentValue = false, + startOffset = 0, + endOffset = 0, + value, + ...rest + }, + ref, + ) => { + const lastOnChangeValue = useRef(); + + const [offsetLeft, offsetRight] = getOffsets(ref, side); + + const [leftValueBound, rightValueBound] = bounds; + const leftPositionBound = leftValueBound ? (leftValueBound - min) * stepSize : null; + const rightPositionBound = rightValueBound ? (rightValueBound - min) * stepSize : null; + + const position = typeof xPosition === 'number' ? { x: xPosition, y: 0 } : undefined; + const tabIndex = disabled ? -1 : 0; + + const computedBounds = { + left: (leftPositionBound ?? 0) + offsetLeft, + right: (rightPositionBound ?? stepSize * (max - min)) - offsetRight, + }; + + const showCurrentValueCondition = + showCurrentValue && + ((xPosition >= startOffset && xPosition <= max * stepSize - endOffset) || (xPosition === 0 && value !== 0)); + + const onDrag: DraggableEventHandler = (_, data) => { + const newValue = getSliderThumbValue(data.x, stepSize, min, max); + if (lastOnChangeValue.current !== newValue) { + onChange?.(newValue, data); + lastOnChangeValue.current = newValue; + } + }; + + const onStop: DraggableEventHandler = (_, data) => { + const newValue = getSliderThumbValue(data.x, stepSize, min, max); + onChangeCommitted && onChangeCommitted(newValue, data); + }; + + const onKeyPress = (event: KeyboardEvent) => { + event.persist(); + + const { keyCode, target } = event; + + if (!Object.values(KeyboardSupport).includes(keyCode)) { + return; + } + + const { ArrowUp, ArrowRight, ArrowDown, ArrowLeft, Home, End, PageDown, PageUp } = KeyboardSupport; + + const computedMultipleSteps = stepSize * ((rest.multipleStepSize / 100) * max); + + const data: DraggableData = { + x: 0, + deltaX: stepSize, + lastX: xPosition, + y: 0, + deltaY: 0, + lastY: 0, + node: target as HTMLDivElement, + }; + + switch (keyCode) { + case ArrowUp: + case ArrowRight: + data.x = xPosition + stepSize; + break; + case ArrowDown: + case ArrowLeft: + data.x = xPosition - stepSize; + data.deltaX = -stepSize; + break; + case PageUp: + data.x = xPosition + computedMultipleSteps; + data.deltaX = computedMultipleSteps; + break; + case PageDown: + data.x = xPosition - computedMultipleSteps; + data.deltaX = -computedMultipleSteps; + break; + case End: + data.x = max * stepSize; + break; + case Home: + data.x = 0; + break; + default: + data.x = 0; + } + + const { left, right } = computedBounds; + + /* + * INFO: Находим значение в диапазоне между указанными левой и правой границами. + * Необходимо для правильного расчета положения SliderThumb. + * см. функция clamp + */ + const boundedValue = Math.max(Math.min(right, data.x), left); + + const computedValue = getSliderThumbValue(boundedValue, stepSize, min, max); + lastOnChangeValue.current = computedValue; + + onChangeCommitted && onChangeCommitted(computedValue, data); + }; + + return ( + + + + {showCurrentValueCondition && {value}} + + + ); + }, +); diff --git a/packages/plasma-new-hope/src/components/Slider/ui/Handle/Handle.types.ts b/packages/plasma-new-hope/src/components/Slider/ui/Handler/Handler.types.ts similarity index 95% rename from packages/plasma-new-hope/src/components/Slider/ui/Handle/Handle.types.ts rename to packages/plasma-new-hope/src/components/Slider/ui/Handler/Handler.types.ts index 55e430ff2a..3195770286 100644 --- a/packages/plasma-new-hope/src/components/Slider/ui/Handle/Handle.types.ts +++ b/packages/plasma-new-hope/src/components/Slider/ui/Handler/Handler.types.ts @@ -1,6 +1,6 @@ import type { DraggableData } from 'react-draggable'; -export interface HandleProps { +export interface HandlerProps { stepSize: number; min: number; max: number; diff --git a/packages/plasma-new-hope/src/components/Slider/ui/index.ts b/packages/plasma-new-hope/src/components/Slider/ui/index.ts index 3b3067aab0..e2d80532d9 100644 --- a/packages/plasma-new-hope/src/components/Slider/ui/index.ts +++ b/packages/plasma-new-hope/src/components/Slider/ui/index.ts @@ -1,7 +1,7 @@ -export * from './Handle/Handle'; +export * from './Handler/Handler'; export * from './Thumb/Thumb'; export { ThumbBase } from './Thumb/Thumb.styles'; -export * from './Handle/Handle.types'; +export * from './Handler/Handler.types'; export * from './Thumb/Thumb.types'; diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Slider/Slider.stories.tsx b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Slider/Slider.stories.tsx index 383be1d734..99854b6086 100644 --- a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Slider/Slider.stories.tsx +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Slider/Slider.stories.tsx @@ -63,9 +63,13 @@ const StoryDefault = (args: StorySingleProps) => { setValue(values); }; + const onChangeHandle = (values) => { + setValue(values); + }; + return ( - + ); }; @@ -105,13 +109,27 @@ export const Default: StorySingle = { const StoryMultipleValues = (args: StoryProps) => { const [value, setValue] = useState([10, 80]); const sortValues = (values) => { - return values.sort((a, b) => a - b); + return values + .map((val) => { + if (val < args.min) { + return args.min; + } + if (val > args.max) { + return args.max; + } + return val; + }) + .sort((a, b) => a - b); }; const onChangeHandle = (values) => { setValue(sortValues(values)); }; + const onChangeCommitedHandle = (values) => { + setValue(sortValues(values)); + }; + const onBlurTextField = (values) => { setValue(sortValues(values)); }; @@ -128,6 +146,7 @@ const StoryMultipleValues = (args: StoryProps) => { value={value} onKeyDownTextField={onKeyDownTextField} onBlurTextField={onBlurTextField} + onChangeCommitted={onChangeCommitedHandle} onChange={onChangeHandle} {...args} /> diff --git a/packages/plasma-new-hope/src/examples/plasma_web/components/Slider/Slider.stories.tsx b/packages/plasma-new-hope/src/examples/plasma_web/components/Slider/Slider.stories.tsx index 2ffbd8589e..49a64af494 100644 --- a/packages/plasma-new-hope/src/examples/plasma_web/components/Slider/Slider.stories.tsx +++ b/packages/plasma-new-hope/src/examples/plasma_web/components/Slider/Slider.stories.tsx @@ -3,7 +3,6 @@ import type { ComponentProps } from 'react'; import styled from 'styled-components'; import { disableProps } from '@salutejs/plasma-sb-utils'; import type { StoryObj, Meta } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; import { WithTheme } from '../../../_helpers'; @@ -63,9 +62,13 @@ const StoryDefault = (args: StorySingleProps) => { setValue(values); }; + const onChangeHandle = (values) => { + setValue(values); + }; + return ( - + ); }; @@ -105,13 +108,27 @@ export const Default: StorySingle = { const StoryMultipleValues = (args: StoryProps) => { const [value, setValue] = useState([10, 80]); const sortValues = (values) => { - return values.sort((a, b) => a - b); + return values + .map((val) => { + if (val < args.min) { + return args.min; + } + if (val > args.max) { + return args.max; + } + return val; + }) + .sort((a, b) => a - b); }; const onChangeHandle = (values) => { setValue(sortValues(values)); }; + const onChangeCommitedHandle = (values) => { + setValue(sortValues(values)); + }; + const onBlurTextField = (values) => { setValue(sortValues(values)); }; @@ -128,6 +145,7 @@ const StoryMultipleValues = (args: StoryProps) => { value={value} onKeyDownTextField={onKeyDownTextField} onBlurTextField={onBlurTextField} + onChangeCommitted={onChangeCommitedHandle} onChange={onChangeHandle} {...args} /> diff --git a/packages/plasma-web/api/plasma-web.api.md b/packages/plasma-web/api/plasma-web.api.md index 534fb8ce5b..8eeb3f60b2 100644 --- a/packages/plasma-web/api/plasma-web.api.md +++ b/packages/plasma-web/api/plasma-web.api.md @@ -776,19 +776,11 @@ l: string; view: { default: string; }; -<<<<<<< HEAD -}> & ((Omit, "type" | "target" | "onChange" | "size" | "value" | "checked" | "maxLength" | "minLength"> & CustomComboboxProps & { +}> & ((Omit, "onChange" | "type" | "target" | "size" | "value" | "checked" | "maxLength" | "minLength"> & CustomComboboxProps & { valueType?: "single" | undefined; value?: ComboboxPrimitiveValue | undefined; onChangeValue?: ((value?: ComboboxPrimitiveValue | undefined) => void) | undefined; -} & RefAttributes) | (Omit, "type" | "target" | "onChange" | "size" | "value" | "checked" | "maxLength" | "minLength"> & CustomComboboxProps & { -======= -}> & ((Omit, "onChange" | "type" | "target" | "size" | "value" | "checked" | "minLength" | "maxLength"> & CustomComboboxProps & { -valueType?: "single" | undefined; -value?: ComboboxPrimitiveValue | undefined; -onChangeValue?: ((value?: ComboboxPrimitiveValue | undefined) => void) | undefined; -} & RefAttributes) | (Omit, "onChange" | "type" | "target" | "size" | "value" | "checked" | "minLength" | "maxLength"> & CustomComboboxProps & { ->>>>>>> a61c59f64 (Update package-lock.json files) +} & RefAttributes) | (Omit, "onChange" | "type" | "target" | "size" | "value" | "checked" | "maxLength" | "minLength"> & CustomComboboxProps & { valueType: "multiple"; value?: ComboboxPrimitiveValue[] | undefined; onChangeValue?: ((value?: ComboboxPrimitiveValue[] | undefined) => void) | undefined; diff --git a/packages/plasma-web/src/components/Slider/Slider.component-test.tsx b/packages/plasma-web/src/components/Slider/Slider.component-test.tsx index 3388c31a2f..e7530b5805 100644 --- a/packages/plasma-web/src/components/Slider/Slider.component-test.tsx +++ b/packages/plasma-web/src/components/Slider/Slider.component-test.tsx @@ -7,6 +7,7 @@ const StandardTypoStyle = createGlobalStyle(standardTypo); describe('plasma-web: Slider', () => { const Slider = getComponent('Slider'); + const sliderThumbSelector = 'div > div + div > div'; const CypressTestDecoratorWithTypo: FC = ({ children }) => ( @@ -26,6 +27,18 @@ describe('plasma-web: Slider', () => { cy.matchImageSnapshot(); }); + it('focus', () => { + mount( + + + , + ); + + cy.get(sliderThumbSelector).focus(); + + cy.matchImageSnapshot(); + }); + it('_view', () => { mount( diff --git a/packages/plasma-web/src/components/Slider/Slider.stories.tsx b/packages/plasma-web/src/components/Slider/Slider.stories.tsx index 276bdd7eea..d8f5c0265a 100644 --- a/packages/plasma-web/src/components/Slider/Slider.stories.tsx +++ b/packages/plasma-web/src/components/Slider/Slider.stories.tsx @@ -3,7 +3,6 @@ import type { ComponentProps } from 'react'; import styled from 'styled-components'; import { InSpacingDecorator, disableProps } from '@salutejs/plasma-sb-utils'; import type { StoryObj, Meta } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; import { Slider } from './Slider'; @@ -61,9 +60,13 @@ const StoryDefault = (args: StorySingleProps) => { setValue(values); }; + const onChangeHandle = (values) => { + setValue(values); + }; + return ( - + ); }; @@ -103,13 +106,27 @@ export const Default: StorySingle = { const StoryMultipleValues = (args: StoryProps) => { const [value, setValue] = useState([10, 80]); const sortValues = (values) => { - return values.sort((a, b) => a - b); + return values + .map((val) => { + if (val < args.min) { + return args.min; + } + if (val > args.max) { + return args.max; + } + return val; + }) + .sort((a, b) => a - b); }; const onChangeHandle = (values) => { setValue(sortValues(values)); }; + const onChangeCommitedHandle = (values) => { + setValue(sortValues(values)); + }; + const onBlurTextField = (values) => { setValue(sortValues(values)); }; @@ -126,6 +143,7 @@ const StoryMultipleValues = (args: StoryProps) => { value={value} onKeyDownTextField={onKeyDownTextField} onBlurTextField={onBlurTextField} + onChangeCommitted={onChangeCommitedHandle} onChange={onChangeHandle} {...args} /> diff --git a/packages/sdds-serv/api/sdds-serv.api.md b/packages/sdds-serv/api/sdds-serv.api.md index f18b37aa69..6563e5b363 100644 --- a/packages/sdds-serv/api/sdds-serv.api.md +++ b/packages/sdds-serv/api/sdds-serv.api.md @@ -48,6 +48,7 @@ import { CustomPopoverProps } from '@salutejs/plasma-new-hope/types/components/P import { CustomToastProps } from '@salutejs/plasma-new-hope/types/components/Toast/Toast.types'; import { DividerProps } from '@salutejs/plasma-new-hope/styled-components'; import { dividerTokens } from '@salutejs/plasma-new-hope/styled-components'; +import { DoubleSliderProps } from '@salutejs/plasma-new-hope/styled-components'; import { DrawerContentProps } from '@salutejs/plasma-new-hope/styled-components'; import { DrawerFooterProps } from '@salutejs/plasma-new-hope/styled-components'; import { DrawerHeaderProps } from '@salutejs/plasma-new-hope/styled-components'; @@ -118,6 +119,8 @@ import { SegmentProvider } from '@salutejs/plasma-new-hope/styled-components'; import { SelectPrimitiveValue } from '@salutejs/plasma-new-hope/styled-components'; import { SelectProps } from '@salutejs/plasma-new-hope/styled-components'; import { ShowToastArgs } from '@salutejs/plasma-new-hope/styled-components'; +import { SingleSliderProps } from '@salutejs/plasma-new-hope/styled-components'; +import { SliderProps } from '@salutejs/plasma-new-hope/styled-components'; import { SpacingProps } from '@salutejs/plasma-new-hope/styled-components'; import { SSRProvider } from '@salutejs/plasma-new-hope/styled-components'; import { StatusLabels } from '@salutejs/plasma-new-hope/types/components/Avatar/Avatar.types'; @@ -1026,6 +1029,25 @@ export { SelectProps } export { ShowToastArgs } +// @public +export const Slider: FunctionComponent & ((SingleSliderProps & RefAttributes) | (DoubleSliderProps & RefAttributes))>; + +export { SliderProps } + // @public export const Spinner: StyledComponent> | undefined; contentRight?: ReactElement> | undefined; @@ -1203,7 +1225,7 @@ readOnly?: boolean | undefined; disabled?: boolean | undefined; } & { label?: string | undefined; -labelPlacement?: "outer" | "inner" | undefined; +labelPlacement?: "inner" | "outer" | undefined; leftHelper?: string | undefined; contentLeft?: ReactElement> | undefined; contentRight?: ReactElement> | undefined; diff --git a/packages/sdds-serv/src/components/Slider/Slider.component-test.tsx b/packages/sdds-serv/src/components/Slider/Slider.component-test.tsx new file mode 100644 index 0000000000..3388c31a2f --- /dev/null +++ b/packages/sdds-serv/src/components/Slider/Slider.component-test.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { mount, CypressTestDecorator, getComponent, PadMe } from '@salutejs/plasma-cy-utils'; +import { createGlobalStyle } from 'styled-components'; +import { standard as standardTypo } from '@salutejs/plasma-typo'; + +const StandardTypoStyle = createGlobalStyle(standardTypo); + +describe('plasma-web: Slider', () => { + const Slider = getComponent('Slider'); + + const CypressTestDecoratorWithTypo: FC = ({ children }) => ( + + + {children} + + ); + + it('simple', () => { + mount( + + + + + , + ); + cy.matchImageSnapshot(); + }); + + it('_view', () => { + mount( + + + + + + + , + ); + + cy.matchImageSnapshot(); + }); + + it('_size', () => { + mount( + + + + + + + , + ); + cy.matchImageSnapshot(); + }); + + it('_disabled', () => { + mount( + + + + + , + ); + cy.matchImageSnapshot(); + }); + + it('_placement', () => { + mount( + + + + + + + + + + + + + + + + + + + + + + + + , + ); + cy.matchImageSnapshot(); + }); +}); diff --git a/packages/sdds-serv/src/components/Slider/Slider.config.ts b/packages/sdds-serv/src/components/Slider/Slider.config.ts new file mode 100644 index 0000000000..065c9212f8 --- /dev/null +++ b/packages/sdds-serv/src/components/Slider/Slider.config.ts @@ -0,0 +1,241 @@ +import { css, sliderTokens } from '@salutejs/plasma-new-hope/styled-components'; + +export const config = { + defaults: { + view: 'default', + size: 'm', + }, + variations: { + view: { + default: css` + ${sliderTokens.labelColor}: var(--text-primary); + + ${sliderTokens.rangeValueColor}: var(--text-secondary); + + ${sliderTokens.thumbBorderColor}: var(--surface-solid-tertiary); + ${sliderTokens.thumbBackgroundColor}: var(--on-light-surface-solid-card); + ${sliderTokens.thumbFocusBorderColor}: var(--surface-solid-default); + + ${sliderTokens.railBackgroundColor}: var(--surface-solid-tertiary); + + ${sliderTokens.fillColor}: var(--surface-solid-default); + + ${sliderTokens.textFieldColor}: var(--text-secondary); + ${sliderTokens.textFieldBackgroundColor}: var(--surface-transparent-primary); + ${sliderTokens.textFieldCaretColor}: var(--text-primary); + ${sliderTokens.textFieldPlaceholderColor}: var(--text-secondary); + ${sliderTokens.textFiledFocusColor}: var(--text-primary); + ${sliderTokens.textFieldActiveColor}: var(--text-primary); + `, + accent: css` + ${sliderTokens.labelColor}: var(--text-primary); + + ${sliderTokens.rangeValueColor}: var(--text-secondary); + + ${sliderTokens.thumbBorderColor}: var(--surface-solid-tertiary); + ${sliderTokens.thumbBackgroundColor}: var(--on-light-surface-solid-card); + ${sliderTokens.thumbFocusBorderColor}: var(--surface-accent); + + ${sliderTokens.railBackgroundColor}: var(--surface-solid-tertiary); + + ${sliderTokens.fillColor}: var(--surface-accent); + + ${sliderTokens.textFieldColor}: var(--text-secondary); + ${sliderTokens.textFieldBackgroundColor}: var(--surface-transparent-primary); + ${sliderTokens.textFieldCaretColor}: var(--text-primary); + ${sliderTokens.textFieldPlaceholderColor}: var(--text-secondary); + ${sliderTokens.textFiledFocusColor}: var(--text-primary); + ${sliderTokens.textFieldActiveColor}: var(--text-primary); + `, + gradient: css` + ${sliderTokens.labelColor}: var(--text-primary); + + ${sliderTokens.rangeValueColor}: var(--text-secondary); + + ${sliderTokens.thumbBorderColor}: var(--surface-solid-tertiary); + ${sliderTokens.thumbBackgroundColor}: var(--on-light-surface-solid-card); + ${sliderTokens.thumbFocusBorderColor}: var(--surface-accent-gradient); + + ${sliderTokens.railBackgroundColor}: var(--surface-solid-tertiary); + + ${sliderTokens.fillColor}: var(--surface-accent-gradient); + + ${sliderTokens.textFieldColor}: var(--text-secondary); + ${sliderTokens.textFieldBackgroundColor}: var(--surface-transparent-primary); + ${sliderTokens.textFieldCaretColor}: var(--text-primary); + ${sliderTokens.textFieldPlaceholderColor}: var(--text-secondary); + ${sliderTokens.textFiledFocusColor}: var(--text-primary); + ${sliderTokens.textFieldActiveColor}: var(--text-primary); + `, + }, + size: { + l: css` + ${sliderTokens.height}: 1.5rem; + ${sliderTokens.doubleWrapperGap}: 0.375rem; + + ${sliderTokens.labelWrapperGap}: 0.25rem; + ${sliderTokens.labelWrapperMarginBottom}: 0.25rem; + ${sliderTokens.labelWrapperMarginRight}: 0.875rem; + + ${sliderTokens.labelFontFamily}: var(--plasma-typo-body-l-font-family); + ${sliderTokens.labelFontSize}: var(--plasma-typo-body-l-font-size); + ${sliderTokens.labelFontStyle}: var(--plasma-typo-body-l-font-style); + ${sliderTokens.labelFontWeight}: var(--plasma-typo-body-l-font-weight); + ${sliderTokens.labelLetterSpacing}: var(--plasma-typo-body-l-letter-spacing); + ${sliderTokens.labelLineHeight}: var(--plasma-typo-body-l-line-height); + + ${sliderTokens.rangeMinValueMargin}: 0.75rem; + ${sliderTokens.rangeMaxValueMargin}: 0.75rem; + ${sliderTokens.rangeValueBottomOffset}: -1.25rem; + + ${sliderTokens.rangeValueFontFamily}: var(--plasma-typo-body-s-font-family); + ${sliderTokens.rangeValueFontSize}: var(--plasma-typo-body-s-font-size); + ${sliderTokens.rangeValueFontStyle}: var(--plasma-typo-body-s-font-style); + ${sliderTokens.rangeValueFontWeight}: var(--plasma-typo-body-s-font-weight); + ${sliderTokens.rangeValueLetterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${sliderTokens.rangeValueLineHeight}: var(--plasma-typo-body-s-line-height); + + ${sliderTokens.thumbSize}: 1.5rem; + ${sliderTokens.thumbBorder}: 0.125rem solid; + + ${sliderTokens.currentValueTopOffset}: 1.625rem; + + ${sliderTokens.currentValueFontFamily}: var(--plasma-typo-text-s-font-family); + ${sliderTokens.currentValueFontSize}: var(--plasma-typo-text-s-font-size); + ${sliderTokens.currentValueFontStyle}: var(--plasma-typo-text-s-font-style); + ${sliderTokens.currentValueFontWeight}: var(--plasma-typo-text-s-font-weight); + ${sliderTokens.currentValueLetterSpacing}: var(--plasma-typo-text-s-letter-spacing); + ${sliderTokens.currentValueLineHeight}: var(--plasma-typo-text-s-line-height); + + ${sliderTokens.railHeight}: 0.25rem; + ${sliderTokens.railBorderRadius}: 0.125rem; + ${sliderTokens.railIndent}: 0.75rem; + + ${sliderTokens.textFieldWrapperGap}: 0.125rem; + + ${sliderTokens.textFieldHeight}: 3.5rem; + ${sliderTokens.textFieldPadding}: 1.25rem 1rem 1.25rem 1rem; + ${sliderTokens.textFieldBorderRadius}: 0.75rem; + ${sliderTokens.textFieldFontFamily}: var(--plasma-typo-body-l-font-family); + ${sliderTokens.textFieldFontSize}: var(--plasma-typo-body-l-font-size); + ${sliderTokens.textFieldFontStyle}: var(--plasma-typo-body-l-font-style); + ${sliderTokens.textFieldFontWeight}: var(--plasma-typo-body-l-font-weight); + ${sliderTokens.textFieldLetterSpacing}: var(--plasma-typo-body-l-letter-spacing); + ${sliderTokens.textFieldLineHeight}: var(--plasma-typo-body-l-line-height); + `, + m: css` + ${sliderTokens.height}: 1.5rem; + ${sliderTokens.doubleWrapperGap}: 0.375rem; + + ${sliderTokens.labelWrapperGap}: 0.25rem; + ${sliderTokens.labelWrapperMarginBottom}: 0.25rem; + ${sliderTokens.labelWrapperMarginRight}: 0.875rem; + + ${sliderTokens.labelFontFamily}: var(--plasma-typo-body-m-font-family); + ${sliderTokens.labelFontSize}: var(--plasma-typo-body-m-font-size); + ${sliderTokens.labelFontStyle}: var(--plasma-typo-body-m-font-style); + ${sliderTokens.labelFontWeight}: var(--plasma-typo-body-m-font-weight); + ${sliderTokens.labelLetterSpacing}: var(--plasma-typo-body-m-letter-spacing); + ${sliderTokens.labelLineHeight}: var(--plasma-typo-body-m-line-height); + + ${sliderTokens.rangeMinValueMargin}: 0.75rem; + ${sliderTokens.rangeMaxValueMargin}: 0.75rem; + ${sliderTokens.rangeValueBottomOffset}: -1.25rem; + + ${sliderTokens.rangeValueFontFamily}: var(--plasma-typo-body-s-font-family); + ${sliderTokens.rangeValueFontSize}: var(--plasma-typo-body-s-font-size); + ${sliderTokens.rangeValueFontStyle}: var(--plasma-typo-body-s-font-style); + ${sliderTokens.rangeValueFontWeight}: var(--plasma-typo-body-s-font-weight); + ${sliderTokens.rangeValueLetterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${sliderTokens.rangeValueLineHeight}: var(--plasma-typo-body-s-line-height); + + ${sliderTokens.thumbSize}: 1.5rem; + ${sliderTokens.thumbBorder}: 0.125rem solid; + + ${sliderTokens.currentValueTopOffset}: 1.75rem; + + ${sliderTokens.currentValueFontFamily}: var(--plasma-typo-text-xs-font-family); + ${sliderTokens.currentValueFontSize}: var(--plasma-typo-text-xs-font-size); + ${sliderTokens.currentValueFontStyle}: var(--plasma-typo-text-xs-font-style); + ${sliderTokens.currentValueFontWeight}: var(--plasma-typo-text-xs-font-weight); + ${sliderTokens.currentValueLetterSpacing}: var(--plasma-typo-text-xs-letter-spacing); + ${sliderTokens.currentValueLineHeight}: var(--plasma-typo-text-xs-line-height); + + ${sliderTokens.railHeight}: 0.25rem; + ${sliderTokens.railBorderRadius}: 0.125rem; + ${sliderTokens.railIndent}: 0.75rem; + + ${sliderTokens.textFieldWrapperGap}: 0.125rem; + + ${sliderTokens.textFieldHeight}: 3rem; + ${sliderTokens.textFieldPadding}: 0.875rem 1rem 0.875rem 1rem; + ${sliderTokens.textFieldBorderRadius}: 0.75rem; + ${sliderTokens.textFieldFontFamily}: var(--plasma-typo-body-m-font-family); + ${sliderTokens.textFieldFontSize}: var(--plasma-typo-body-m-font-size); + ${sliderTokens.textFieldFontStyle}: var(--plasma-typo-body-m-font-style); + ${sliderTokens.textFieldFontWeight}: var(--plasma-typo-body-m-font-weight); + ${sliderTokens.textFieldLetterSpacing}: var(--plasma-typo-body-m-letter-spacing); + ${sliderTokens.textFieldLineHeight}: var(--plasma-typo-body-m-line-height); + `, + s: css` + ${sliderTokens.height}: 1rem; + ${sliderTokens.doubleWrapperGap}: 0.375rem; + + ${sliderTokens.labelWrapperGap}: 0.25rem; + ${sliderTokens.labelWrapperMarginBottom}: 0.25rem; + ${sliderTokens.labelWrapperMarginRight}: 0.875rem; + + ${sliderTokens.labelFontFamily}: var(--plasma-typo-body-s-font-family); + ${sliderTokens.labelFontSize}: var(--plasma-typo-body-s-font-size); + ${sliderTokens.labelFontStyle}: var(--plasma-typo-body-s-font-style); + ${sliderTokens.labelFontWeight}: var(--plasma-typo-body-s-font-weight); + ${sliderTokens.labelLetterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${sliderTokens.labelLineHeight}: var(--plasma-typo-body-s-line-height); + + ${sliderTokens.rangeMinValueMargin}: 0.5rem; + ${sliderTokens.rangeMaxValueMargin}: 0.5rem; + ${sliderTokens.rangeValueBottomOffset}: -1.25rem; + + ${sliderTokens.rangeValueFontFamily}: var(--plasma-typo-body-xs-font-family); + ${sliderTokens.rangeValueFontSize}: var(--plasma-typo-body-xs-font-size); + ${sliderTokens.rangeValueFontStyle}: var(--plasma-typo-body-xs-font-style); + ${sliderTokens.rangeValueFontWeight}: var(--plasma-typo-body-xs-font-weight); + ${sliderTokens.rangeValueLetterSpacing}: var(--plasma-typo-body-xs-letter-spacing); + ${sliderTokens.rangeValueLineHeight}: var(--plasma-typo-body-xs-line-height); + + ${sliderTokens.thumbSize}: 1rem; + ${sliderTokens.thumbBorder}: 0.125rem solid; + + ${sliderTokens.currentValueTopOffset}: 1.25rem; + + ${sliderTokens.currentValueFontFamily}: var(--plasma-typo-text-xs-font-family); + ${sliderTokens.currentValueFontSize}: var(--plasma-typo-text-xs-font-size); + ${sliderTokens.currentValueFontStyle}: var(--plasma-typo-text-xs-font-style); + ${sliderTokens.currentValueFontWeight}: var(--plasma-typo-text-xs-font-weight); + ${sliderTokens.currentValueLetterSpacing}: var(--plasma-typo-text-xs-letter-spacing); + ${sliderTokens.currentValueLineHeight}: var(--plasma-typo-text-xs-line-height); + + ${sliderTokens.railHeight}: 0.25rem; + ${sliderTokens.railBorderRadius}: 0.125rem; + ${sliderTokens.railIndent}: 0.75rem; + + ${sliderTokens.textFieldWrapperGap}: 0.125rem; + + ${sliderTokens.textFieldHeight}: 2.5rem; + ${sliderTokens.textFieldPadding}: 0.5rem 1rem 0.5rem 1rem; + ${sliderTokens.textFieldBorderRadius}: 0.625rem; + ${sliderTokens.textFieldFontFamily}: var(--plasma-typo-body-s-font-family); + ${sliderTokens.textFieldFontSize}: var(--plasma-typo-body-s-font-size); + ${sliderTokens.textFieldFontStyle}: var(--plasma-typo-body-s-font-style); + ${sliderTokens.textFieldFontWeight}: var(--plasma-typo-body-s-font-weight); + ${sliderTokens.textFieldLetterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${sliderTokens.textFieldLineHeight}: var(--plasma-typo-body-s-line-height); + `, + }, + disabled: { + true: css` + ${sliderTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/packages/sdds-serv/src/components/Slider/Slider.stories.tsx b/packages/sdds-serv/src/components/Slider/Slider.stories.tsx new file mode 100644 index 0000000000..d8f5c0265a --- /dev/null +++ b/packages/sdds-serv/src/components/Slider/Slider.stories.tsx @@ -0,0 +1,166 @@ +import React, { useState } from 'react'; +import type { ComponentProps } from 'react'; +import styled from 'styled-components'; +import { InSpacingDecorator, disableProps } from '@salutejs/plasma-sb-utils'; +import type { StoryObj, Meta } from '@storybook/react'; + +import { Slider } from './Slider'; + +const sizes = ['l', 'm', 's']; +const views = ['default', 'accent', 'gradient']; +const labelPlacements = ['outer', 'inner']; +const rangeValuesPlacement = ['outer', 'inner']; + +const meta: Meta = { + title: 'Controls/Slider', + component: Slider, + decorators: [InSpacingDecorator], + argTypes: { + view: { + options: views, + control: { + type: 'select', + }, + }, + size: { + options: sizes, + control: { + type: 'inline-radio', + }, + }, + ...disableProps([ + 'value', + 'onChangeCommitted', + 'ariaLabel', + 'onChange', + 'fontSizeMultiplier', + 'gap', + 'settings', + 'hasHoverAnimation', + ]), + }, +}; + +export default meta; + +type StoryProps = Omit, 'value' | 'onChangeCommitted'>; +type StorySingleProps = StoryProps; + +type StorySingle = StoryObj; +type StoryDouble = StoryObj; + +const SliderWrapper = styled.div` + width: 25rem; +`; + +const StoryDefault = (args: StorySingleProps) => { + const [value, setValue] = useState(30); + + const onChangeCommittedHandle = (values) => { + setValue(values); + }; + + const onChangeHandle = (values) => { + setValue(values); + }; + + return ( + + + + ); +}; + +export const Default: StorySingle = { + argTypes: { + labelPlacement: { + options: labelPlacements, + control: { + type: 'inline-radio', + }, + }, + rangeValuesPlacement: { + options: rangeValuesPlacement, + control: { + type: 'inline-radio', + }, + }, + }, + args: { + view: 'default', + size: 'm', + min: 0, + max: 100, + disabled: false, + ariaLabel: 'Цена товара', + multipleStepSize: 10, + label: 'Цена товара', + labelPlacement: 'outer', + rangeValuesPlacement: 'outer', + showRangeValues: true, + showCurrentValue: false, + }, + render: (args) => , +}; + +const StoryMultipleValues = (args: StoryProps) => { + const [value, setValue] = useState([10, 80]); + const sortValues = (values) => { + return values + .map((val) => { + if (val < args.min) { + return args.min; + } + if (val > args.max) { + return args.max; + } + return val; + }) + .sort((a, b) => a - b); + }; + + const onChangeHandle = (values) => { + setValue(sortValues(values)); + }; + + const onChangeCommitedHandle = (values) => { + setValue(sortValues(values)); + }; + + const onBlurTextField = (values) => { + setValue(sortValues(values)); + }; + + const onKeyDownTextField = (values, event) => { + if (event.key === 'Enter') { + setValue(sortValues(values)); + } + }; + + return ( + + + + ); +}; + +export const MultipleValues: StoryDouble = { + args: { + view: 'default', + size: 'm', + min: 0, + max: 100, + disabled: false, + label: 'Цена товара', + ariaLabel: ['Минимальная цена товара', 'Максимальная цена товара'], + multipleStepSize: 10, + }, + render: (args) => , +}; diff --git a/packages/sdds-serv/src/components/Slider/Slider.tsx b/packages/sdds-serv/src/components/Slider/Slider.tsx new file mode 100644 index 0000000000..9fbafc706c --- /dev/null +++ b/packages/sdds-serv/src/components/Slider/Slider.tsx @@ -0,0 +1,12 @@ +import { sliderConfig, component, mergeConfig } from '@salutejs/plasma-new-hope/styled-components'; + +import { config } from './Slider.config'; + +const mergedConfig = mergeConfig(sliderConfig, config); +const SliderComponent = component(mergedConfig); + +/** + * Слайдер позволяет определить числовое значение в пределах указанного промежутка. + * Можно указать два значения. + */ +export const Slider = SliderComponent; diff --git a/packages/sdds-serv/src/components/Slider/index.ts b/packages/sdds-serv/src/components/Slider/index.ts new file mode 100644 index 0000000000..737e414a4b --- /dev/null +++ b/packages/sdds-serv/src/components/Slider/index.ts @@ -0,0 +1,2 @@ +export { Slider } from './Slider'; +export type { SliderProps } from '@salutejs/plasma-new-hope/styled-components'; diff --git a/packages/sdds-serv/src/index.ts b/packages/sdds-serv/src/index.ts index c4ec934223..75763345cf 100644 --- a/packages/sdds-serv/src/index.ts +++ b/packages/sdds-serv/src/index.ts @@ -24,6 +24,7 @@ export * from './components/Pagination'; export * from './components/Radiobox'; export * from './components/Segment'; export * from './components/Spinner'; +export * from './components/Slider'; export * from './components/SSRProvider'; export * from './components/Switch'; export * from './components/Tabs'; diff --git a/website/plasma-web-docs/docs/components/Slider.mdx b/website/plasma-web-docs/docs/components/Slider.mdx index ee59d0741a..b56fd4e2f4 100644 --- a/website/plasma-web-docs/docs/components/Slider.mdx +++ b/website/plasma-web-docs/docs/components/Slider.mdx @@ -36,15 +36,45 @@ import { Slider } from '@salutejs/plasma-web'; export function App() { const [value, setValue] = useState([10, 80]); + const sortValues = (values) => { + return values + .map((val) => { + if (val < 0) { + return 0; + } + if (val > 100) { + return 100; + } + return val; + }) + .sort((a, b) => a - b); + }; + + const onChangeHandle = (values) => { + setValue(sortValues(values)); + }; + const onChangeCommitedHandle = (values) => { + setValue(sortValues(values)); + }; + + const onBlurTextField = (values) => { + setValue(sortValues(values)); + }; - const onChangeCommittedHandle = (values) => { - setValue(values); + const onKeyDownTextField = (values, event) => { + if (event.key === 'Enter') { + setValue(sortValues(values)); + } }; return (
- +
); } diff --git a/website/sdds-serv-docs/docs/components/Slider.mdx b/website/sdds-serv-docs/docs/components/Slider.mdx new file mode 100644 index 0000000000..8a98e3a6d6 --- /dev/null +++ b/website/sdds-serv-docs/docs/components/Slider.mdx @@ -0,0 +1,78 @@ +--- +id: slider +title: Slider +--- + +import { PropsTable, Description } from '@site/src/components'; + +# Slider + + + + +## Использование + +```tsx live +import React from 'react'; +import { Slider } from '@salutejs/sdds-serv'; + +export function App() { + return ( +
+ {}} min={0} max={100} value={30} /> +
+ ); +} +``` + +Можно использовать диапазон значений. + +```tsx live +import React, { useState } from 'react'; +import { Slider } from '@salutejs/plasma-web'; + +export function App() { + const [value, setValue] = useState([10, 80]); + const sortValues = (values) => { + return values + .map((val) => { + if (val < 0) { + return 0; + } + if (val > 100) { + return 100; + } + return val; + }) + .sort((a, b) => a - b); + }; + + const onChangeHandle = (values) => { + setValue(sortValues(values)); + }; + + const onChangeCommitedHandle = (values) => { + setValue(sortValues(values)); + }; + + const onBlurTextField = (values) => { + setValue(sortValues(values)); + }; + + const onKeyDownTextField = (values, event) => { + if (event.key === 'Enter') { + setValue(sortValues(values)); + } + }; + + return ( +
+ +
+ ); +} +``` \ No newline at end of file