Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/soft-sliders-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/fuselage': patch
---

fix(fuselage): Fix Slider track fill when minValue is not zero and mirror fill direction in RTL locales
81 changes: 81 additions & 0 deletions packages/fuselage/src/components/Slider/Slider.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,46 @@
import { composeStories } from '@storybook/react-webpack5';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import type { ReactElement } from 'react';
import { I18nProvider } from 'react-aria';

import { render } from '../../testing';

import Slider from './Slider';
import * as stories from './Slider.stories';

const { Default, WithLabel, MultiThumb, WithDefaultValue } =
composeStories(stories);

const getInjectedGradients = (): string[] => {
const fromStyleTags = Array.from(document.querySelectorAll('style')).map(
(style) => style.textContent ?? '',
);
const fromStyleSheets = Array.from(document.styleSheets).flatMap((sheet) =>
Array.from(sheet.cssRules).map((rule) => rule.cssText),
);

return (
[...fromStyleTags, ...fromStyleSheets]
.join('\n')
.match(/linear-gradient\([^;]*\)/g) ?? []
);
};

// The css-in-js stylesheet is shared across tests, so only gradients that
// appear after a render are attributable to it.
const renderAndCollectGradients = (ui: ReactElement) => {
const before = new Set(getInjectedGradients());
const result = render(ui);

return {
result,
gradients: getInjectedGradients().filter(
(gradient) => !before.has(gradient),
),
};
};

describe('[Slider Component]', () => {
it('renders without crashing', () => {
render(<Default />);
Expand Down Expand Up @@ -46,4 +78,53 @@ describe('[Slider Component]', () => {

expect(slider['value']).toBe('4');
});

it('should position the track fill relative to minValue', () => {
const { gradients } = renderAndCollectGradients(
<Slider
aria-label='range'
minValue={50}
maxValue={150}
defaultValue={100}
/>,
);

const fillGradient = gradients.find((gradient) =>
gradient.includes('to right'),
);

expect(fillGradient).toBeDefined();
expect(fillGradient).toMatch(/50%(?!\d)/);
});

it('should mirror the track fill direction in RTL locales', () => {
const { gradients } = renderAndCollectGradients(
<I18nProvider locale='ar-AE'>
<Slider aria-label='range' defaultValue={25} />
</I18nProvider>,
);

expect(gradients.some((gradient) => gradient.includes('to left'))).toBe(
true,
);
expect(gradients.some((gradient) => gradient.includes('to right'))).toBe(
false,
);
});

it('should keep the multi-thumb band ordered in RTL locales', () => {
const { gradients } = renderAndCollectGradients(
<I18nProvider locale='ar-AE'>
<Slider aria-label='range' multiThumb defaultValue={[25, 75]} />
</I18nProvider>,
);

const bandGradient = gradients.find((gradient) =>
gradient.includes('to left'),
);

expect(bandGradient).toBeDefined();
expect(bandGradient).toMatch(/25%/);
expect(bandGradient).toMatch(/75%/);
});
});
33 changes: 12 additions & 21 deletions packages/fuselage/src/components/Slider/SliderTrack.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { css } from '@rocket.chat/css-in-js';
import type { DOMAttributes, RefObject, ReactNode } from 'react';
import { useMemo } from 'react';
import { useLocale } from 'react-aria';
import type { SliderState } from 'react-stately';

import { Palette } from '../../Theme';
Expand Down Expand Up @@ -34,37 +35,27 @@ export const SliderTrack = ({
);

const getThumbPosition = useMemo(
() => (value: number) => {
const maxValue = state.getThumbMaxValue(1) || state.getThumbMaxValue(0);
const minValue = state.getThumbMinValue(0);
return (value / (maxValue - minValue)) * 100;
},
() => (index: number) => state.getThumbPercent(index) * 100,
[state],
);

const { direction } = useLocale();

const getTrackGradient = () => {
const gradientDirection = direction === 'rtl' ? 'to left' : 'to right';

if (isHorizontal) {
return multiThumb
? `to right, ${light} ${getThumbPosition(
state.values[0],
)}%, ${highlight} 0, ${highlight} ${getThumbPosition(
state.values[1],
)}%, ${light} 0`
: `to right, ${highlight} ${getThumbPosition(
state.values[0],
)}%, ${light} 0%`;
? `${gradientDirection}, ${light} ${getThumbPosition(
0,
)}%, ${highlight} 0, ${highlight} ${getThumbPosition(1)}%, ${light} 0`
: `${gradientDirection}, ${highlight} ${getThumbPosition(0)}%, ${light} 0`;
}

if (isVertical) {
return multiThumb
? `to top, ${light} ${getThumbPosition(
state.values[0],
)}%, ${highlight} 0, ${highlight} ${getThumbPosition(
state.values[1],
)}%, ${light} 0`
: `to top, ${highlight} ${getThumbPosition(
state.values[0],
)}%, ${light} 0%`;
? `to top, ${light} ${getThumbPosition(0)}%, ${highlight} 0, ${highlight} ${getThumbPosition(1)}%, ${light} 0`
: `to top, ${highlight} ${getThumbPosition(0)}%, ${light} 0`;
}

return undefined;
Expand Down