diff --git a/src/ProgressBar/index.jsx b/src/ProgressBar/index.jsx index 1fe9cc5850..606f06f032 100644 --- a/src/ProgressBar/index.jsx +++ b/src/ProgressBar/index.jsx @@ -3,7 +3,7 @@ import ProgressBarBase from 'react-bootstrap/ProgressBar'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import Annotation from '../Annotation'; -import { placeInfoAtZero } from './utils'; +import { getOffsetStyles, placeInfoAtZero } from './utils'; export const ANNOTATION_CLASS = 'pgn__annotation'; const HINT_SWAP_PERCENT = 50; @@ -38,11 +38,12 @@ function ProgressBarAnnotated({ const isThresholdHintAfter = threshold < HINT_SWAP_PERCENT; const progressColor = VARIANTS.includes(variant) ? variant : PROGRESS_DEFAULT_VARIANT; const thresholdColor = VARIANTS.includes(thresholdVariant) ? thresholdVariant : THRESHOLD_DEFAULT_VARIANT; + const direction = window.getComputedStyle(document.body).getPropertyValue('direction'); const positionAnnotations = useCallback(() => { - placeInfoAtZero(progressInfoRef, isProgressHintAfter, ANNOTATION_CLASS); - placeInfoAtZero(thresholdInfoRef, isThresholdHintAfter, ANNOTATION_CLASS); - }, [isProgressHintAfter, isThresholdHintAfter]); + placeInfoAtZero(progressInfoRef, direction, isProgressHintAfter, ANNOTATION_CLASS); + placeInfoAtZero(thresholdInfoRef, direction, isThresholdHintAfter, ANNOTATION_CLASS); + }, [direction, isProgressHintAfter, isThresholdHintAfter]); useEffect(() => { positionAnnotations(); @@ -65,7 +66,7 @@ function ProgressBarAnnotated({ {!!label && (
{!isProgressHintAfter && getHint(progressHint)} @@ -96,7 +97,7 @@ function ProgressBarAnnotated({ {(!!threshold && !!thresholdLabel) && (
{!isThresholdHintAfter && getHint(thresholdHint)} diff --git a/src/ProgressBar/index.scss b/src/ProgressBar/index.scss index 5b3532d62f..c323002a91 100644 --- a/src/ProgressBar/index.scss +++ b/src/ProgressBar/index.scss @@ -13,6 +13,7 @@ width: 100%; position: relative; overflow: visible; + padding: 3.125rem 0; .progress { overflow: visible; @@ -58,6 +59,11 @@ height: $progress-threshold-circle; border-radius: calc($progress-threshold-circle / 2); z-index: 1; + + [dir="rtl"] & { + left: -(calc($progress-threshold-circle / 2)); + right: auto; + } } } } @@ -77,8 +83,13 @@ } .pgn__progress-info { - display: inline-block; - position: relative; + position: absolute; + display: flex; + align-items: baseline; + + &:first-child { + top: 0; + } } .pgn__progress-hint { diff --git a/src/ProgressBar/tests/ProgressBar.test.jsx b/src/ProgressBar/tests/ProgressBar.test.jsx index 8e372a517b..e6a7d79824 100644 --- a/src/ProgressBar/tests/ProgressBar.test.jsx +++ b/src/ProgressBar/tests/ProgressBar.test.jsx @@ -1,5 +1,6 @@ import React from 'react'; import { mount } from 'enzyme'; +import { render } from '@testing-library/react'; import renderer from 'react-test-renderer'; import ProgressBar, { ANNOTATION_CLASS } from '..'; @@ -47,9 +48,8 @@ describe('', () => { expect(tree).toMatchSnapshot(); }); it('renders info blocks with calculated margins', () => { - const useReferenceSpy = jest.spyOn(React, 'useRef').mockReturnValue(ref); - mount(); - expect(useReferenceSpy).toHaveBeenCalledTimes(2); + jest.spyOn(React, 'useRef').mockReturnValue(ref); + render(); expect(ref.current.style.marginLeft).not.toBeFalsy(); }); it('renders correct variant for progress bar and annotation', () => { @@ -85,5 +85,14 @@ describe('', () => { expect(wrapper.find('.pgn__progress-info').get(0).props.children[2]).toEqual(false); expect(wrapper.find('.pgn__progress-info').get(1).props.children[2]).toEqual(false); }); + it('should apply styles based on direction for threshold', () => { + window.getComputedStyle = jest.fn().mockReturnValue({ getPropertyValue: () => 'rtl' }); + const { container } = render(); + const progressInfo = container.querySelector('.pgn__progress-info'); + const computedStyles = window.getComputedStyle(progressInfo); + + expect(computedStyles.getPropertyValue('directory')).toBe('rtl'); + window.getComputedStyle.mockRestore(); + }); }); }); diff --git a/src/ProgressBar/tests/utils.test.js b/src/ProgressBar/tests/utils.test.js index 740fb3790d..e6443e29cb 100644 --- a/src/ProgressBar/tests/utils.test.js +++ b/src/ProgressBar/tests/utils.test.js @@ -49,24 +49,42 @@ describe('utils', () => { expect(actualMarginLeft).toEqual(expectedMarginLeft); }); - it('correctly calculates left margin when annotationOnly equals to true', () => { - placeInfoAtZero(ref, false); + it('correctly calculates left margin when annotationOnly equals to true and dir equal ltr', () => { + placeInfoAtZero(ref, 'ltr', false); const { children } = ref.current; - let marginLeft = 0.0; + let horizontalMargin = 0.0; for (let i = 0; i < children.length || 0; i++) { const elementParams = children[i].getBoundingClientRect(); if (children[i].className.includes(ANNOTATION_CLASS)) { - marginLeft += elementParams.width / 2; + horizontalMargin += elementParams.width / 2; } else { - marginLeft += elementParams.width; + horizontalMargin += elementParams.width; } } - const expectedMarginLeft = `${-marginLeft}px`; + const expectedMarginLeft = `${-horizontalMargin}px`; const actualMarginLeft = ref.current.style.marginLeft; expect(actualMarginLeft).toEqual(expectedMarginLeft); }); + it('correctly calculates right margin when annotationOnly equals to true and dir equal rtl', () => { + placeInfoAtZero(ref, 'rtl', false); + + const { children } = ref.current; + let horizontalMargin = 0.0; + for (let i = 0; i < children.length || 0; i++) { + const elementParams = children[i].getBoundingClientRect(); + if (children[i].className.includes(ANNOTATION_CLASS)) { + horizontalMargin += elementParams.width / 2; + } else { + horizontalMargin += elementParams.width; + } + } + const expectedHorizontalMargin = `${-horizontalMargin}px`; + const actualMarginRight = ref.current.style.marginRight; + + expect(actualMarginRight).toEqual(expectedHorizontalMargin); + }); it('returns false if reference is wrong', () => { const wrongRef1 = {}; const wrongRef2 = { current: {} }; diff --git a/src/ProgressBar/utils.js b/src/ProgressBar/utils.js index 4d5f602224..451c49e60f 100644 --- a/src/ProgressBar/utils.js +++ b/src/ProgressBar/utils.js @@ -3,24 +3,42 @@ * that the annotation pointer indicates on zero of the ProgressBar. * * @param {object} ref reference to the info block + * @param {string} direction direction of the page ("ltr" or "rtl") * @param {boolean} annotationOnly ignores width of the hint * @param {string} annotationClass is used to identify the annotation element */ -// eslint-disable-next-line import/prefer-default-export -export const placeInfoAtZero = (ref, annotationOnly = true, annotationClass = 'pgn__annotation') => { +export const placeInfoAtZero = ( + ref, + direction = 'ltr', + annotationOnly = true, + annotationClass = 'pgn__annotation', +) => { if (!ref.current || !ref.current.style) { return false; } const { children } = ref.current; - let marginLeft = 0.0; + let horizontalMargin = 0.0; for (let i = 0; i < children.length || 0; i++) { const elementParams = children[i].getBoundingClientRect(); if (children[i].className.includes(annotationClass)) { - marginLeft += elementParams.width / 2; + horizontalMargin += elementParams.width / 2; } else { - marginLeft += annotationOnly ? 0.0 : elementParams.width; + horizontalMargin += annotationOnly ? 0.0 : elementParams.width; } } // eslint-disable-next-line no-param-reassign - ref.current.style.marginLeft = `${-marginLeft}px`; + ref.current.style[direction === 'rtl' ? 'marginRight' : 'marginLeft'] = `${-horizontalMargin}px`; return true; }; + +/** + * Retrieves offset styles based on the page direction. + * + * @param {number} value - The offset value in percentages. + * @param {string} direction - The offset direction ('rtl' for rightward offset, 'ltr' for leftward offset). + * @returns {Object} An object containing offset styles with either the 'left' or 'right' property, + * depending on the direction. + */ +export const getOffsetStyles = ( + value, + direction, +) => (direction === 'rtl' ? { right: `${value}%` } : { left: `${value}%` });