From 93fc916c3bc0a0e61734c7af47e1f4925c636f31 Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Tue, 2 Sep 2025 18:21:15 -0400 Subject: [PATCH 01/27] Component compat --- packages/modal/src/Modal/ModalView.tsx | 1 + packages/table/src/ExpandedContent/ExpandedContent.tsx | 9 ++------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/modal/src/Modal/ModalView.tsx b/packages/modal/src/Modal/ModalView.tsx index 80cc00220f..d94545e45b 100644 --- a/packages/modal/src/Modal/ModalView.tsx +++ b/packages/modal/src/Modal/ModalView.tsx @@ -105,6 +105,7 @@ const ModalView = React.forwardRef( * Prevents the default cancel behavior (ESC key) to ensure * the modal only closes via the custom escape key handler logic. */ + // @ts-ignore React17 - `onCancel` is unavailable in React 17 types onCancel={e => e.preventDefault()} > {children} diff --git a/packages/table/src/ExpandedContent/ExpandedContent.tsx b/packages/table/src/ExpandedContent/ExpandedContent.tsx index aebb312807..16d9a90eb8 100644 --- a/packages/table/src/ExpandedContent/ExpandedContent.tsx +++ b/packages/table/src/ExpandedContent/ExpandedContent.tsx @@ -12,10 +12,7 @@ import { baseStyles, expandedContentThemeStyles, } from './ExpandedContent.styles'; -import { - ExpandedContentComponentType, - ExpandedContentProps, -} from './ExpandedContent.types'; +import { ExpandedContentProps } from './ExpandedContent.types'; const ExpandedContentWithRef = ( { row, virtualRow, ...rest }: ExpandedContentProps, @@ -51,9 +48,7 @@ const ExpandedContentWithRef = ( // React.forwardRef can only work with plain function types, i.e. types with a single call signature and no other members. // This assertion has an interface that restores the original function signature to work with generics. -export const ExpandedContent = React.forwardRef( - ExpandedContentWithRef, -) as ExpandedContentComponentType; +export const ExpandedContent = React.forwardRef(ExpandedContentWithRef); ExpandedContent.displayName = 'ExpandedContent'; From 5bea85b5e464dd22fe699354e93f1457a255884f Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Tue, 2 Sep 2025 18:34:50 -0400 Subject: [PATCH 02/27] .ts compat --- .../rich-links/src/RichLink/RichLink.types.ts | 6 ++++-- .../src/DrawerLayout/DrawerLayout.types.ts | 6 ++++-- packages/lib/src/index.ts | 6 +++++- .../src/Polymorphic/Polymorphic.ts | 7 ++++--- .../src/Polymorphic/Polymorphic.types.ts | 11 +++++++++-- .../src/useResizable/useResizable.ts | 5 +++++ packages/testing-lib/src/RTLOverrides.ts | 19 ++++++++++++++++++- packages/testing-lib/src/index.ts | 7 ++++++- tools/storybook-addon/src/main/index.ts | 1 + tools/storybook-utils/src/StoryMeta.types.ts | 6 +++++- 10 files changed, 61 insertions(+), 13 deletions(-) diff --git a/chat/rich-links/src/RichLink/RichLink.types.ts b/chat/rich-links/src/RichLink/RichLink.types.ts index 37682f951d..9db4f5fb37 100644 --- a/chat/rich-links/src/RichLink/RichLink.types.ts +++ b/chat/rich-links/src/RichLink/RichLink.types.ts @@ -1,11 +1,13 @@ -import { DarkModeProps, HTMLElementProps } from '@leafygreen-ui/lib'; +import { ComponentPropsWithoutRef } from 'react'; + +import { DarkModeProps } from '@leafygreen-ui/lib'; import { RichLinkBadgeProps } from './RichLinkBadge/RichLinkBadge.types'; import { RichLinkVariantName } from './richLinkVariants'; export interface BaseRichLinkProps extends DarkModeProps, - HTMLElementProps<'a', never> { + ComponentPropsWithoutRef<'a'> { /** * The text that shows on the rich link */ diff --git a/packages/drawer/src/DrawerLayout/DrawerLayout.types.ts b/packages/drawer/src/DrawerLayout/DrawerLayout.types.ts index aef030026d..e124e67321 100644 --- a/packages/drawer/src/DrawerLayout/DrawerLayout.types.ts +++ b/packages/drawer/src/DrawerLayout/DrawerLayout.types.ts @@ -1,4 +1,6 @@ -import { DarkModeProps, HTMLElementProps } from '@leafygreen-ui/lib'; +import { ComponentPropsWithoutRef } from 'react'; + +import { DarkModeProps } from '@leafygreen-ui/lib'; import { DrawerProps } from '../Drawer/Drawer.types'; import { DrawerToolbarLayoutProps } from '../DrawerToolbarLayout'; @@ -6,7 +8,7 @@ import { DrawerToolbarLayoutProps } from '../DrawerToolbarLayout'; type PickedDrawerProps = Pick; export interface BaseDrawerLayoutPropsWithoutDisplayMode - extends HTMLElementProps<'div'>, + extends ComponentPropsWithoutRef<'div'>, DarkModeProps, PickedDrawerProps { children: React.ReactNode; diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts index b6ff98d302..3485c226ca 100644 --- a/packages/lib/src/index.ts +++ b/packages/lib/src/index.ts @@ -36,7 +36,11 @@ export { isComponentType, validateChildren } from './validateChildren'; export { createUniqueClassName, getNodeTextContent, getTheme, Theme, typeIs }; export type { DarkModeProps, LgIdProps, LgIdString }; -/** Helper type to extract an HTML element's valid props */ +/** + * Helper type to extract an HTML element's valid props + * + * @deprecated - Prefer the built-in React type {@link React.ComponentProps} + * */ export type HTMLElementProps< Element extends keyof JSX.IntrinsicElements, RefType extends HTMLElement = never, diff --git a/packages/polymorphic/src/Polymorphic/Polymorphic.ts b/packages/polymorphic/src/Polymorphic/Polymorphic.ts index 6ec3ff0e7f..eea7a26b63 100644 --- a/packages/polymorphic/src/Polymorphic/Polymorphic.ts +++ b/packages/polymorphic/src/Polymorphic/Polymorphic.ts @@ -29,10 +29,11 @@ export const Polymorphic = < // (i.e. we don't know if `as="button"`, and if `type` is a valid prop) // If no `ref` arg was passed in, we use the plain render function - // Using ts-ignore instead of ts-expect-error since expect throws an unused expect TS error in a React 17 environment - // @ts-ignore FIXME: https://jira.mongodb.org/browse/LG-3410 const PolyComponent: PolymorphicComponentType = - render.length === 1 ? render : forwardRef(render); + render.length === 1 + ? render + : // @ts-expect-error In TS<5.0 this is `ReactElement` but in 5+ it's `ReactNode` + (forwardRef(render) as PolymorphicComponentType); PolyComponent.displayName = displayName ?? render.displayName ?? 'PolymorphicComponent'; diff --git a/packages/polymorphic/src/Polymorphic/Polymorphic.types.ts b/packages/polymorphic/src/Polymorphic/Polymorphic.types.ts index 663d98867d..cd3d22f96f 100644 --- a/packages/polymorphic/src/Polymorphic/Polymorphic.types.ts +++ b/packages/polymorphic/src/Polymorphic/Polymorphic.types.ts @@ -7,6 +7,7 @@ import { ElementType, PropsWithChildren, PropsWithoutRef, + ReactElement, ReactNode, RefAttributes, WeakValidationMap, @@ -93,6 +94,12 @@ export type PolymorphicPropsWithRef< ref?: PolymorphicRef; }; +// TODO: In TS<5.0 this is `ReactElement` but in 5+ it's `ReactNode` +export type PolymorphicRenderFunctionReturnType = + | ReactElement + | ReactNode + | null; + /** * An explicit definition of the component type * @@ -109,7 +116,7 @@ export interface PolymorphicComponentType< ( props: PolymorphicPropsWithRef, ref: PolymorphicRef, - ): ReactNode | null; + ): PolymorphicRenderFunctionReturnType; displayName?: string; propTypes?: | WeakValidationMap< @@ -133,7 +140,7 @@ export interface PolymorphicRenderFunction< ( props: PolymorphicPropsWithRef, ref: PolymorphicRef, - ): ReactNode | null; + ): PolymorphicRenderFunctionReturnType; displayName?: string; propTypes?: never; } diff --git a/packages/resizable/src/useResizable/useResizable.ts b/packages/resizable/src/useResizable/useResizable.ts index 53d41f6faf..b50bdb1bd1 100644 --- a/packages/resizable/src/useResizable/useResizable.ts +++ b/packages/resizable/src/useResizable/useResizable.ts @@ -123,8 +123,10 @@ export const useResizable = ({ */ const setNextKeyboardSize = useCallback( (event: React.KeyboardEvent | KeyboardEvent, position: Position | null) => { + // @ts-ignore React17 KeyboardEvent types differ if (position && event.code in SIZE_GROWTH_KEY_MAPPINGS[position]) { const sizeGrowth = + // @ts-ignore React17 KeyboardEvent types differ SIZE_GROWTH_KEY_MAPPINGS[position][event.code as Arrow]; const nextSize = getNextKeyboardSize({ sizeGrowth, @@ -147,12 +149,15 @@ export const useResizable = ({ (event: React.KeyboardEvent | KeyboardEvent) => { if (position === Position.Left || position === Position.Right) { if ( + // @ts-ignore React17 KeyboardEvent types differ event.code === keyMap.ArrowLeft || + // @ts-ignore React17 KeyboardEvent types differ event.code === keyMap.ArrowRight ) { event.preventDefault(); } } else { + // @ts-ignore React17 KeyboardEvent types differ if (event.code === keyMap.ArrowUp || event.code === keyMap.ArrowDown) { event.preventDefault(); } diff --git a/packages/testing-lib/src/RTLOverrides.ts b/packages/testing-lib/src/RTLOverrides.ts index a93c870d12..ddfe108777 100644 --- a/packages/testing-lib/src/RTLOverrides.ts +++ b/packages/testing-lib/src/RTLOverrides.ts @@ -22,13 +22,30 @@ export type Exists< * * (used when running in a React 17 test environment) */ -export const renderHook: Exists = +export const renderHook: ( + callback: (props: any) => any, + options?: { + wrapper?: React.ComponentType; + } & Record, +) => RenderHookResult = (RTL as any).renderHook ?? (() => { const RHTL = require('@testing-library/react-hooks'); return RHTL.renderHook; })(); +export type RenderHookResult = Exists< + typeof RTL, + 'RenderHookResult', + { + current: TValue; + rerender: (props?: TProps) => void; + unmount: () => void; + result: { + current: TValue; + }; + } +>; /** * Re-exports `act` from `"@testing-library/react"` if it exists, * or from `"@testing-library/react-hooks"` diff --git a/packages/testing-lib/src/index.ts b/packages/testing-lib/src/index.ts index 5a8cba2f7e..0ed6acc6eb 100644 --- a/packages/testing-lib/src/index.ts +++ b/packages/testing-lib/src/index.ts @@ -1,7 +1,12 @@ import * as Context from './context'; import * as jest from './jest'; import * as JestDOM from './jest-dom'; -export { act, renderHook, renderHookServer } from './RTLOverrides'; +export { + act, + renderHook, + type RenderHookResult, + renderHookServer, +} from './RTLOverrides'; export { useTraceUpdate } from './useTraceUpdate'; export { waitForState } from './waitForState'; export { waitForTransition } from './waitForTransition'; diff --git a/tools/storybook-addon/src/main/index.ts b/tools/storybook-addon/src/main/index.ts index 9e1231b8cd..3df7208b52 100644 --- a/tools/storybook-addon/src/main/index.ts +++ b/tools/storybook-addon/src/main/index.ts @@ -19,6 +19,7 @@ export { previewHead } from './preview-head'; const { scopes } = getLGConfig(); const directories = Object.values(scopes); +// @ts-expect-error https://github.com/storybookjs/storybook/issues/23624 export const stories: StorybookConfig['stories'] = findStories({ includePattern: [ `../{${directories.join(',')}}/**/*.stor@(y|ies).@(js|ts)?(x)`, diff --git a/tools/storybook-utils/src/StoryMeta.types.ts b/tools/storybook-utils/src/StoryMeta.types.ts index 047d579409..53df43396e 100644 --- a/tools/storybook-utils/src/StoryMeta.types.ts +++ b/tools/storybook-utils/src/StoryMeta.types.ts @@ -125,4 +125,8 @@ export type PlayFn< T extends React.ElementType, /** Any eXtra Props the story should support */ XP extends Record = {}, -> = PlayFunction & XP>; +> = PlayFunction< + // @ts-ignore React17 - ReactRenderer does not satisfy the constraint Renderer + ReactRenderer, + ComponentProps & XP +>; From 2f4af533092e3b39783f3e07dec5419b9ce0c158 Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Tue, 2 Sep 2025 18:43:26 -0400 Subject: [PATCH 03/27] PolymorphicRenderFunctionReturnType --- packages/polymorphic/src/Polymorphic/Polymorphic.ts | 2 +- packages/polymorphic/src/Polymorphic/Polymorphic.types.ts | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/polymorphic/src/Polymorphic/Polymorphic.ts b/packages/polymorphic/src/Polymorphic/Polymorphic.ts index eea7a26b63..dd177e2b37 100644 --- a/packages/polymorphic/src/Polymorphic/Polymorphic.ts +++ b/packages/polymorphic/src/Polymorphic/Polymorphic.ts @@ -32,7 +32,7 @@ export const Polymorphic = < const PolyComponent: PolymorphicComponentType = render.length === 1 ? render - : // @ts-expect-error In TS<5.0 this is `ReactElement` but in 5+ it's `ReactNode` + : // @ts-ignore React17 In TS<5.0 this is `ReactElement` but in 5+ it's `ReactNode` (forwardRef(render) as PolymorphicComponentType); PolyComponent.displayName = diff --git a/packages/polymorphic/src/Polymorphic/Polymorphic.types.ts b/packages/polymorphic/src/Polymorphic/Polymorphic.types.ts index cd3d22f96f..911a6b0eff 100644 --- a/packages/polymorphic/src/Polymorphic/Polymorphic.types.ts +++ b/packages/polymorphic/src/Polymorphic/Polymorphic.types.ts @@ -7,7 +7,6 @@ import { ElementType, PropsWithChildren, PropsWithoutRef, - ReactElement, ReactNode, RefAttributes, WeakValidationMap, @@ -95,10 +94,7 @@ export type PolymorphicPropsWithRef< }; // TODO: In TS<5.0 this is `ReactElement` but in 5+ it's `ReactNode` -export type PolymorphicRenderFunctionReturnType = - | ReactElement - | ReactNode - | null; +export type PolymorphicRenderFunctionReturnType = ReactNode | null; /** * An explicit definition of the component type From cb8319e1a211059069add33d094f2b2aaf10ad4d Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Wed, 3 Sep 2025 12:41:04 -0400 Subject: [PATCH 04/27] sync spec files --- .../src/ChartContext/ChartContext.spec.tsx | 1 - packages/button/src/Button/Button.spec.tsx | 5 +++- packages/card/src/Card/Card.spec.tsx | 11 ++------ .../CalendarCell/CalendarCell.spec.tsx | 2 +- .../context/SharedDatePickerContext.spec.tsx | 2 +- .../useControlledValue.spec.tsx | 8 ++---- .../src/Descendants/Descendants.spec.tsx | 26 ++++++++++++------- .../DrawerToolbarLayout.spec.tsx | 2 +- packages/form-field/src/FormField.spec.tsx | 2 +- .../useDynamicRefs/useDynamicRefs.spec.tsx | 1 - .../MigrationContext.spec.tsx | 2 +- .../PopoverContext/PopoverContext.spec.tsx | 4 ++- .../PopoverPropsContext.spec.tsx | 2 +- packages/menu/src/Menu.spec.tsx | 12 ++++++--- .../src/PasswordInput/PasswordInput.spec.tsx | 4 +-- packages/popover/src/Popover/Popover.spec.tsx | 15 ++++++----- packages/select/src/Select/Select.spec.tsx | 4 +-- .../src/SideNavItem/SideNavItem.spec.tsx | 1 + .../src/SplitButton/SplitButton.spec.tsx | 12 +++------ .../ExpandedContent/ExpandedContent.spec.tsx | 5 ++-- .../table/src/testing/getTestUtils.spec.tsx | 1 + packages/tooltip/src/Tooltip/Tooltip.spec.tsx | 16 +++++++++--- .../src/Link/BackLink/BackLink.spec.tsx | 1 + .../typography/src/Link/Link/Link.spec.tsx | 1 + 24 files changed, 77 insertions(+), 63 deletions(-) diff --git a/charts/core/src/ChartContext/ChartContext.spec.tsx b/charts/core/src/ChartContext/ChartContext.spec.tsx index 4184dffc24..4f5fde5c3c 100644 --- a/charts/core/src/ChartContext/ChartContext.spec.tsx +++ b/charts/core/src/ChartContext/ChartContext.spec.tsx @@ -46,7 +46,6 @@ describe('lg-chart/core/ChartContext', () => { const { result } = renderHook(useChartContext, { wrapper: ChartProviderMock, }); - // @ts-expect-error - chartOptions is not defined in the ChartContextType const { chartOptions, updateChartOptions, addChartSeries } = result.current; expect(chartOptions).toBe(chartOptions); diff --git a/packages/button/src/Button/Button.spec.tsx b/packages/button/src/Button/Button.spec.tsx index 6f5158e484..a9ea3a2f5a 100644 --- a/packages/button/src/Button/Button.spec.tsx +++ b/packages/button/src/Button/Button.spec.tsx @@ -14,7 +14,10 @@ const title = 'Test button title'; const child = 'Button child'; function renderButton(props: ButtonProps = {}) { - const utils = render(