From e4943589833539bb67545075854b5554766c3908 Mon Sep 17 00:00:00 2001 From: Brian Buck Date: Tue, 24 Feb 2026 14:36:42 -0700 Subject: [PATCH] feat: convert ProgressBar component to TypeScript Replace PropTypes and defaultProps with TypeScript interfaces and parameter defaults to support React 19 migration. Resolves #3739. - Rename .jsx/.js files to .tsx/.ts - Add ProgressBarAnnotatedProps interface with JSDoc comments - Type refs, utility functions, and component props - Remove ts-ignore comment from src/index.ts - Export ProgressBarAnnotatedProps type for consumers Co-Authored-By: Claude Opus 4.6 --- src/ProgressBar/{index.jsx => index.tsx} | 93 +++++++++---------- ...gressBar.test.jsx => ProgressBar.test.tsx} | 12 +-- ...est.jsx.snap => ProgressBar.test.tsx.snap} | 0 .../tests/{utils.test.js => utils.test.ts} | 6 +- src/ProgressBar/{utils.js => utils.ts} | 18 ++-- src/index.ts | 2 +- 6 files changed, 65 insertions(+), 66 deletions(-) rename src/ProgressBar/{index.jsx => index.tsx} (67%) rename src/ProgressBar/tests/{ProgressBar.test.jsx => ProgressBar.test.tsx} (94%) rename src/ProgressBar/tests/__snapshots__/{ProgressBar.test.jsx.snap => ProgressBar.test.tsx.snap} (100%) rename src/ProgressBar/tests/{utils.test.js => utils.test.ts} (97%) rename src/ProgressBar/{utils.js => utils.ts} (79%) diff --git a/src/ProgressBar/index.jsx b/src/ProgressBar/index.tsx similarity index 67% rename from src/ProgressBar/index.jsx rename to src/ProgressBar/index.tsx index 85f5e06f03..84e2a90cbf 100644 --- a/src/ProgressBar/index.jsx +++ b/src/ProgressBar/index.tsx @@ -1,6 +1,6 @@ +/* eslint-disable react/require-default-props */ import React, { useCallback, useEffect } from 'react'; import ProgressBarBase from 'react-bootstrap/ProgressBar'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; import Annotation from '../Annotation'; import { getOffsetStyles, placeInfoAtZero } from './utils'; @@ -14,30 +14,53 @@ const VARIANTS = [ 'warning', 'success', 'error', -]; +] as const; -function ProgressBar(props) { +type Variant = typeof VARIANTS[number]; + +export interface ProgressBarAnnotatedProps { + /** Current value of progress. */ + now?: number; + /** Show label that represents visual percentage. */ + label?: React.ReactNode; + /** The `ProgressBar` style variant to use. */ + variant?: Variant; + /** Specifies an additional `className` to add to the base element. */ + className?: string; + /** Threshold current value. */ + threshold?: number; + /** Specifies label for `threshold`. */ + thresholdLabel?: React.ReactNode; + /** Variant for threshold value. */ + thresholdVariant?: Variant; + /** Text near the progress annotation. */ + progressHint?: React.ReactNode; + /** Text near the threshold annotation. */ + thresholdHint?: React.ReactNode; +} + +function ProgressBar(props: React.ComponentPropsWithoutRef) { return ; } function ProgressBarAnnotated({ now, label, - variant, + variant = PROGRESS_DEFAULT_VARIANT, threshold, thresholdLabel, - thresholdVariant, + thresholdVariant = THRESHOLD_DEFAULT_VARIANT, progressHint, thresholdHint, ...props -}) { - const progressInfoRef = React.useRef(); - const thresholdInfoRef = React.useRef(); +}: ProgressBarAnnotatedProps) { + const progressInfoRef = React.useRef(null); + const thresholdInfoRef = React.useRef(null); const thresholdPercent = (threshold || 0) - (now || 0); - const isProgressHintAfter = now < HINT_SWAP_PERCENT; - 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 isProgressHintAfter = (now as number) < HINT_SWAP_PERCENT; + const isThresholdHintAfter = (threshold as number) < 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(() => { @@ -51,11 +74,11 @@ function ProgressBarAnnotated({ positionAnnotations(); }); const progressInfoEl = progressInfoRef.current; - observer.observe(progressInfoEl); - return () => progressInfoEl && observer.unobserve(progressInfoEl); + observer.observe(progressInfoEl!); + return () => { if (progressInfoEl) { observer.unobserve(progressInfoEl); } }; }, [positionAnnotations]); - const getHint = (text) => ( + const getHint = (text: React.ReactNode) => ( {text} @@ -114,38 +137,12 @@ function ProgressBarAnnotated({ ); } -ProgressBarAnnotated.propTypes = { - /** Current value of progress. */ - now: PropTypes.number, - /** Show label that represents visual percentage. */ - label: PropTypes.node, - /** The `ProgressBar` style variant to use. */ - variant: PropTypes.oneOf(VARIANTS), - /** Specifies an additional `className` to add to the base element. */ - className: PropTypes.string, - /** Threshold current value. */ - threshold: PropTypes.number, - /** Specifies label for `threshold`. */ - thresholdLabel: PropTypes.node, - /** Variant for threshold value. */ - thresholdVariant: PropTypes.oneOf(VARIANTS), - /** Text near the progress annotation. */ - progressHint: PropTypes.node, - /** Text near the threshold annotation. */ - thresholdHint: PropTypes.node, -}; +interface ProgressBarComponent { + (props: React.ComponentPropsWithoutRef): React.JSX.Element; + Annotated: React.FC; +} -ProgressBarAnnotated.defaultProps = { - now: undefined, - label: undefined, - variant: PROGRESS_DEFAULT_VARIANT, - className: undefined, - threshold: undefined, - thresholdLabel: undefined, - thresholdVariant: THRESHOLD_DEFAULT_VARIANT, - progressHint: undefined, - thresholdHint: undefined, -}; +const ProgressBarWithAnnotated = ProgressBar as unknown as ProgressBarComponent; +ProgressBarWithAnnotated.Annotated = ProgressBarAnnotated; -ProgressBar.Annotated = ProgressBarAnnotated; -export default ProgressBar; +export default ProgressBarWithAnnotated; diff --git a/src/ProgressBar/tests/ProgressBar.test.jsx b/src/ProgressBar/tests/ProgressBar.test.tsx similarity index 94% rename from src/ProgressBar/tests/ProgressBar.test.jsx rename to src/ProgressBar/tests/ProgressBar.test.tsx index 5744ebeaf0..6e457a39e2 100644 --- a/src/ProgressBar/tests/ProgressBar.test.jsx +++ b/src/ProgressBar/tests/ProgressBar.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import renderer from 'react-test-renderer'; -import ProgressBar, { ANNOTATION_CLASS } from '..'; +import ProgressBar, { ANNOTATION_CLASS, ProgressBarAnnotatedProps } from '..'; const ref = { current: { @@ -24,9 +24,9 @@ const ref = { }, ], }, -}; +} as any; -function ProgressBarElement(props) { +function ProgressBarElement(props: ProgressBarAnnotatedProps) { return ( ', () => { expect(progressHints[1].textContent).toEqual(''); }); it('should apply styles based on direction for threshold', () => { - window.getComputedStyle = jest.fn().mockReturnValue({ getPropertyValue: () => 'rtl' }); + window.getComputedStyle = jest.fn().mockReturnValue({ getPropertyValue: () => 'rtl' }) as any; const { container } = render(); const progressInfo = container.querySelector('.pgn__progress-info'); - const computedStyles = window.getComputedStyle(progressInfo); + const computedStyles = window.getComputedStyle(progressInfo!); expect(computedStyles.getPropertyValue('directory')).toBe('rtl'); - window.getComputedStyle.mockRestore(); + (window.getComputedStyle as jest.Mock).mockRestore(); }); }); }); diff --git a/src/ProgressBar/tests/__snapshots__/ProgressBar.test.jsx.snap b/src/ProgressBar/tests/__snapshots__/ProgressBar.test.tsx.snap similarity index 100% rename from src/ProgressBar/tests/__snapshots__/ProgressBar.test.jsx.snap rename to src/ProgressBar/tests/__snapshots__/ProgressBar.test.tsx.snap diff --git a/src/ProgressBar/tests/utils.test.js b/src/ProgressBar/tests/utils.test.ts similarity index 97% rename from src/ProgressBar/tests/utils.test.js rename to src/ProgressBar/tests/utils.test.ts index e6443e29cb..523976fab6 100644 --- a/src/ProgressBar/tests/utils.test.js +++ b/src/ProgressBar/tests/utils.test.ts @@ -21,7 +21,7 @@ const ref = { }, ], }, -}; +} as any; describe('utils', () => { describe('placeInfoAtZero', () => { @@ -86,8 +86,8 @@ describe('utils', () => { expect(actualMarginRight).toEqual(expectedHorizontalMargin); }); it('returns false if reference is wrong', () => { - const wrongRef1 = {}; - const wrongRef2 = { current: {} }; + const wrongRef1 = {} as any; + const wrongRef2 = { current: {} } as any; expect(placeInfoAtZero(wrongRef1)).toEqual(false); expect(placeInfoAtZero(wrongRef2)).toEqual(false); }); diff --git a/src/ProgressBar/utils.js b/src/ProgressBar/utils.ts similarity index 79% rename from src/ProgressBar/utils.js rename to src/ProgressBar/utils.ts index 30469d62d7..a28012684c 100644 --- a/src/ProgressBar/utils.js +++ b/src/ProgressBar/utils.ts @@ -1,3 +1,5 @@ +import React from 'react'; + /** * Gets the current annotation styles and calculates the left margin so * that the annotation pointer indicates on zero of the ProgressBar. @@ -8,11 +10,11 @@ * @param {string} annotationClass is used to identify the annotation element */ export const placeInfoAtZero = ( - ref, - direction = 'ltr', - annotationOnly = true, - annotationClass = 'pgn__annotation', -) => { + ref: React.RefObject, + direction: string = 'ltr', + annotationOnly: boolean = true, + annotationClass: string = 'pgn__annotation', +): boolean => { if (!ref.current || !ref.current.style) { return false; } const { children } = ref.current; let horizontalMargin = 0.0; @@ -38,6 +40,6 @@ export const placeInfoAtZero = ( * depending on the direction. */ export const getOffsetStyles = ( - value, - direction, -) => (direction === 'rtl' ? { right: `${value}%` } : { left: `${value}%` }); + value: number | undefined, + direction: string, +): { left: string } | { right: string } => (direction === 'rtl' ? { right: `${value}%` } : { left: `${value}%` }); diff --git a/src/index.ts b/src/index.ts index 7e11e26a8c..aaac36c174 100644 --- a/src/index.ts +++ b/src/index.ts @@ -147,8 +147,8 @@ export { } from './Pagination'; // @ts-ignore: has yet to be converted to TypeScript export { default as Popover, PopoverTitle, PopoverContent } from './Popover'; -// @ts-ignore: has yet to be converted to TypeScript export { default as ProgressBar } from './ProgressBar'; +export type { ProgressBarAnnotatedProps } from './ProgressBar'; // @ts-ignore: has yet to be converted to TypeScript export { default as ProductTour } from './ProductTour'; // @ts-ignore: has yet to be converted to TypeScript