Skip to content

Commit

Permalink
feat(Slider): fix rtl view (#8098)
Browse files Browse the repository at this point in the history
  • Loading branch information
EldarMuhamethanov authored Dec 26, 2024
1 parent b658bd5 commit ba6b9c4
Show file tree
Hide file tree
Showing 15 changed files with 153 additions and 36 deletions.
6 changes: 6 additions & 0 deletions packages/vkui/src/components/Slider/Slider.e2e-playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,17 @@ export const SliderPlayground = (props: ComponentPlaygroundProps) => {
min: [-10],
max: [10],
value: [0],
dir: ['ltr', 'rtl'],
},
{
multiple: [true],
defaultValue: [[20, 80]],
},
{
multiple: [true],
defaultValue: [[30, 90]],
dir: ['rtl'],
},
{
defaultValue: [50],
$adaptivity: 'y',
Expand Down
23 changes: 22 additions & 1 deletion packages/vkui/src/components/Slider/Slider.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
position: relative;
cursor: pointer;
block-size: var(--vkui_internal--slider_thumb_size);

--vkui_internal--Slider_start_value: 0;
--vkui_internal--Slider_end_value: 0;
}

.disabled {
Expand All @@ -24,8 +27,14 @@
}

.trackFill {
inline-size: auto;
background: var(--vkui--color_background_accent);
inline-size: calc(var(--vkui_internal--Slider_start_value) * 1%);
}

.multiple .trackFill {
inline-size: auto;
inset-inline-start: calc(var(--vkui_internal--Slider_start_value) * 1%);
inset-inline-end: calc((100 - var(--vkui_internal--Slider_end_value)) * 1%);
}

.thumbs {
Expand All @@ -40,6 +49,18 @@
transform: translate(-50%, -50%);
}

.rtl .thumb {
transform: translate(50%, -50%);
}

.thumbStart {
inset-inline-start: calc(var(--vkui_internal--Slider_start_value) * 1%);
}

.thumbEnd {
inset-inline-start: calc(var(--vkui_internal--Slider_end_value) * 1%);
}

.sizeL {
--vkui_internal--slider_thumb_size: 28px;
}
Expand Down
62 changes: 62 additions & 0 deletions packages/vkui/src/components/Slider/Slider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
baselineComponent,
fakeTimers,
mockRect,
mockRtlDirection,
userEvent,
waitForFloatingPosition,
} from '../../testing/utils';
Expand Down Expand Up @@ -239,6 +240,67 @@ describe(Slider, () => {
});
});

describe('check rtl view', () => {
mockRtlDirection();

it('moves start', async () => {
render(<Slider />);
const slider = screen.getByRole('slider');

fireEvent.mouseDown(slider);

fireEvent.mouseMove(slider, pointerPos(40));
expect(slider).toHaveValue('60');

fireEvent.mouseMove(slider, pointerPos(50));
expect(slider).toHaveValue('50');

fireEvent.mouseUp(slider);
});

it('moves start (multiple)', async () => {
render(
<Slider
multiple
defaultValue={[30, 70]}
startThumbTestId="startSlider"
endThumbTestId="endSlider"
/>,
);
const startSlider = screen.getByTestId('startSlider');
const endSlider = screen.getByTestId('endSlider');

fireEvent.mouseDown(startSlider);

fireEvent.mouseMove(startSlider, pointerPos(40));
expect(startSlider).toHaveValue('60');
expect(endSlider).toHaveValue('70');

fireEvent.mouseMove(startSlider, pointerPos(50));
expect(startSlider).toHaveValue('50');
expect(endSlider).toHaveValue('70');

fireEvent.mouseUp(startSlider);
});

it('moves end (multiple)', async () => {
render(<Slider multiple defaultValue={[30, 70]} />);
const [startSlider, endSlider] = screen.getAllByRole('slider');

fireEvent.mouseDown(endSlider);

fireEvent.mouseMove(endSlider, pointerPos(40));
expect(startSlider).toHaveValue('30');
expect(endSlider).toHaveValue('60');

fireEvent.mouseMove(endSlider, pointerPos(50));
expect(startSlider).toHaveValue('30');
expect(endSlider).toHaveValue('50');

fireEvent.mouseUp(endSlider);
});
});

describe('with tooltip', () => {
fakeTimers();

Expand Down
38 changes: 23 additions & 15 deletions packages/vkui/src/components/Slider/Slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import * as React from 'react';
import { classNames } from '@vkontakte/vkjs';
import { clamp } from '../../helpers/math';
import { mergeStyle } from '../../helpers/mergeStyle';
import { useAdaptivity } from '../../hooks/useAdaptivity';
import { useDirection } from '../../hooks/useDirection';
import { useExternRef } from '../../hooks/useExternRef';
import type { HTMLAttributesWithRootRef } from '../../types';
import type { CSSCustomProperties, HTMLAttributesWithRootRef } from '../../types';
import { type CustomTouchEvent, type CustomTouchEventHandler, Touch } from '../Touch/Touch';
import { SliderThumb } from './SliderThumb/SliderThumb';
import {
Expand Down Expand Up @@ -101,9 +103,12 @@ export const Slider = ({
onChange,
withTooltip,
size = 'l',
style: styleProp,
...restProps
}: SliderProps | SliderMultipleProps): React.ReactNode => {
const { sizeY = 'none' } = useAdaptivity();
const [directionRef, textDirection = 'ltr'] = useDirection();
const isRtl = textDirection === 'rtl';

const isControlled = valueProp !== undefined;
const [localValue, setValue] = React.useState(defaultValue);
Expand Down Expand Up @@ -163,7 +168,10 @@ export const Slider = ({
// @ts-expect-error: TS2345 в VKUITouchEvent плохо описаны типы. `target` это просто `EventTarget`.
const foundDraggingType = getDraggingTypeByTargetDataset(event.originalEvent.target);

const nextStartX = event.startX - nextContainerX;
let nextStartX = event.startX - nextContainerX;
if (isRtl) {
nextStartX = nextContainerWidth - nextStartX;
}
const nextValue = offsetToValue(nextStartX, nextContainerWidth, min, max, step);
const nextDragging = snapDirection(value, nextValue, foundDraggingType);

Expand Down Expand Up @@ -205,7 +213,7 @@ export const Slider = ({
const { startX, containerWidth, dragging } = gesture;

const { shiftX = 0 } = event;
const nextStartX = startX + shiftX;
const nextStartX = startX + (isRtl ? -shiftX : shiftX);
const nextValue = offsetToValue(nextStartX, containerWidth, min, max, step);

changeValue(updateInternalStateValue(value, nextValue, min, max, dragging), event);
Expand All @@ -231,6 +239,11 @@ export const Slider = ({
);
};

const style: CSSCustomProperties = {
'--vkui_internal--Slider_start_value': String(startValueInPercent),
'--vkui_internal--Slider_end_value': String(endReversedValueInPercent),
};

return (
<Touch
data-value={multiple ? `${startValue},${endValue}` : startValue}
Expand All @@ -240,27 +253,23 @@ export const Slider = ({
disabled && styles.disabled,
sizeY !== 'regular' && sizeYClassNames[sizeY],
sizeClassNames[size],
multiple && styles.multiple,
isRtl && styles.rtl,
className,
)}
style={mergeStyle(styleProp, style)}
getRootRef={directionRef}
onStart={disabled ? undefined : handlePointerStart}
onMove={disabled ? undefined : handlePointerMove}
onEnd={disabled ? undefined : handlePointerEnd}
>
<div className={styles.track} />
<div
className={styles.trackFill}
style={
multiple
? { left: `${startValueInPercent}%`, right: `${100 - endReversedValueInPercent}%` }
: { width: `${startValueInPercent}%` }
}
/>
<div className={styles.trackFill} />
<div ref={thumbsContainerRef} className={styles.thumbs}>
<SliderThumb
data-type="start"
className={styles.thumb}
className={classNames(styles.thumb, styles.thumbStart)}
style={{
left: `${startValueInPercent}%`,
// Меняем местами порядок слоёв, иначе, при достижении `start` и `end` 100%, `end` будет перекрывать `start`.
zIndex: multiple && startValueInPercent >= 50 ? 2 : undefined,
}}
Expand All @@ -284,8 +293,7 @@ export const Slider = ({
{multiple && (
<SliderThumb
data-type="end"
className={styles.thumb}
style={{ left: `${endReversedValueInPercent}%` }}
className={classNames(styles.thumb, styles.thumbEnd)}
withTooltip={withTooltip}
inputProps={{
'data-type': 'end',
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions packages/vkui/src/testing/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,26 @@ export function fakeTimers() {
});
}

export function mockRtlDirection() {
const originalGetComputedStyle = window.getComputedStyle;

let getComputedStyleMock: ReturnType<typeof jest.spyOn> | null = null;
beforeEach(() => {
/**
* Мокаем получение direction
*/
getComputedStyleMock = jest.spyOn(window, 'getComputedStyle').mockImplementation((e) => {
return {
...originalGetComputedStyle(e),
direction: 'rtl',
};
});
});
afterEach(() => {
getComputedStyleMock.mockRestore();
});
}

export const imgOnlyAttributes: ImgOnlyAttributes = {
alt: 'test',
crossOrigin: 'anonymous',
Expand Down

0 comments on commit ba6b9c4

Please sign in to comment.