From 50f6a43747decf7cb4a5ab0daf9c7237792e98cd Mon Sep 17 00:00:00 2001 From: Aniruddha Adak Date: Sun, 23 Aug 2026 10:10:11 +0530 Subject: [PATCH 1/2] fix(fuselage): correct Slider track fill for non-zero minValue and RTL locales --- .changeset/soft-sliders-listen.md | 5 ++ .../src/components/Slider/Slider.spec.tsx | 69 +++++++++++++++++++ .../src/components/Slider/SliderTrack.tsx | 33 ++++----- 3 files changed, 86 insertions(+), 21 deletions(-) create mode 100644 .changeset/soft-sliders-listen.md diff --git a/.changeset/soft-sliders-listen.md b/.changeset/soft-sliders-listen.md new file mode 100644 index 0000000000..dd5e2a0eab --- /dev/null +++ b/.changeset/soft-sliders-listen.md @@ -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 diff --git a/packages/fuselage/src/components/Slider/Slider.spec.tsx b/packages/fuselage/src/components/Slider/Slider.spec.tsx index 7344f4a5d7..626883b565 100644 --- a/packages/fuselage/src/components/Slider/Slider.spec.tsx +++ b/packages/fuselage/src/components/Slider/Slider.spec.tsx @@ -1,14 +1,39 @@ 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 * as stories from './Slider.stories'; +import Slider from './Slider'; 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(); @@ -46,4 +71,48 @@ describe('[Slider Component]', () => { expect(slider['value']).toBe('4'); }); + + it('should position the track fill relative to minValue', () => { + const { gradients } = renderAndCollectGradients( + , + ); + + 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( + + + , + ); + + 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( + + + , + ); + + const bandGradient = gradients.find((gradient) => + gradient.includes('to left'), + ); + + expect(bandGradient).toBeDefined(); + expect(bandGradient).toMatch(/25%/); + expect(bandGradient).toMatch(/75%/); + }); }); diff --git a/packages/fuselage/src/components/Slider/SliderTrack.tsx b/packages/fuselage/src/components/Slider/SliderTrack.tsx index 95ab2a559c..c77d95d121 100644 --- a/packages/fuselage/src/components/Slider/SliderTrack.tsx +++ b/packages/fuselage/src/components/Slider/SliderTrack.tsx @@ -1,6 +1,7 @@ import { css } from '@rocket.chat/css-in-js'; import type { DOMAttributes, MutableRefObject, ReactNode } from 'react'; import { useMemo } from 'react'; +import { useLocale } from 'react-aria'; import type { SliderState } from 'react-stately'; import { Palette } from '../../Theme'; @@ -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; From b12ad2e0e2017a44f0e43bc862181993d754aff1 Mon Sep 17 00:00:00 2001 From: Aniruddha Adak Date: Sun, 23 Aug 2026 23:32:35 +0530 Subject: [PATCH 2/2] test(fuselage): fix import order and formatting in Slider spec --- .../src/components/Slider/Slider.spec.tsx | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/fuselage/src/components/Slider/Slider.spec.tsx b/packages/fuselage/src/components/Slider/Slider.spec.tsx index 626883b565..7a9ce4b692 100644 --- a/packages/fuselage/src/components/Slider/Slider.spec.tsx +++ b/packages/fuselage/src/components/Slider/Slider.spec.tsx @@ -6,8 +6,8 @@ import { I18nProvider } from 'react-aria'; import { render } from '../../testing'; -import * as stories from './Slider.stories'; import Slider from './Slider'; +import * as stories from './Slider.stories'; const { Default, WithLabel, MultiThumb, WithDefaultValue } = composeStories(stories); @@ -20,9 +20,11 @@ const getInjectedGradients = (): string[] => { Array.from(sheet.cssRules).map((rule) => rule.cssText), ); - return [...fromStyleTags, ...fromStyleSheets] - .join('\n') - .match(/linear-gradient\([^;]*\)/g) ?? []; + return ( + [...fromStyleTags, ...fromStyleSheets] + .join('\n') + .match(/linear-gradient\([^;]*\)/g) ?? [] + ); }; // The css-in-js stylesheet is shared across tests, so only gradients that @@ -31,7 +33,12 @@ const renderAndCollectGradients = (ui: ReactElement) => { const before = new Set(getInjectedGradients()); const result = render(ui); - return { result, gradients: getInjectedGradients().filter((gradient) => !before.has(gradient)) }; + return { + result, + gradients: getInjectedGradients().filter( + (gradient) => !before.has(gradient), + ), + }; }; describe('[Slider Component]', () => { @@ -74,7 +81,12 @@ describe('[Slider Component]', () => { it('should position the track fill relative to minValue', () => { const { gradients } = renderAndCollectGradients( - , + , ); const fillGradient = gradients.find((gradient) =>