diff --git a/src/Button/index.tsx b/src/Button/index.tsx index 2bd67ac3ce..c22e3c3e28 100644 --- a/src/Button/index.tsx +++ b/src/Button/index.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import BaseButton, { type ButtonProps as BaseButtonProps } from 'react-bootstrap/Button'; import BaseButtonGroup, { type ButtonGroupProps as BaseButtonGroupProps } from 'react-bootstrap/ButtonGroup'; import BaseButtonToolbar, { type ButtonToolbarProps } from 'react-bootstrap/ButtonToolbar'; -import { type BsPrefixRefForwardingComponent as ComponentWithAsProp } from 'react-bootstrap/esm/helpers'; +import type { ComponentWithAsProp } from '../utils/types/bootstrap'; // @ts-ignore - we're not going to bother adding types for the deprecated button import ButtonDeprecated from './deprecated'; diff --git a/src/utils/types/bootstrap.test.tsx b/src/utils/types/bootstrap.test.tsx new file mode 100644 index 0000000000..0346c5d2b4 --- /dev/null +++ b/src/utils/types/bootstrap.test.tsx @@ -0,0 +1,86 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import React from 'react'; +import type { BsPropsWithAs, ComponentWithAsProp } from './bootstrap'; + +// Note: these are type-only tests. They don't actually do much at runtime; the important checks are at transpile time. + +describe('BsPropsWithAs', () => { + interface Props extends BsPropsWithAs { + otherProp?: number; + } + + it('defines optional bsPrefix, className, and as but no other props', () => { + const checkProps = (_props: Props) => {}; + // These are all valid props per the prop definition: + checkProps({ }); + checkProps({ bsPrefix: 'bs' }); + checkProps({ className: 'foo bar' }); + checkProps({ as: 'tr' }); + checkProps({ className: 'foo bar', as: 'button', otherProp: 15 }); + // But these are all invalid: + // @ts-expect-error + checkProps({ newProp: 10 }); + // @ts-expect-error + checkProps({ onClick: () => {} }); + // @ts-expect-error + checkProps({ id: 'id' }); + // @ts-expect-error + checkProps({ children: }); + }); +}); + +describe('ComponentWithAsProp', () => { + interface MyProps extends BsPropsWithAs { + customProp?: string; + } + const MyComponent: ComponentWithAsProp<'div', MyProps> = ( + React.forwardRef( + ({ as: Inner = 'div', ...props }, ref) => , + ) + ); + + // eslint-disable-next-line react/function-component-definition + const CustomComponent: React.FC<{ requiredProp: string }> = () => ; + + it('is defined to wrap a
by default, and accepts related props', () => { + // This is valid - by default it is a DIV so accepts props and ref related to DIV: + const divClick: React.MouseEventHandler = () => {}; + const divRef: React.RefObject = { current: null }; + const valid = ; + }); + + it('is defined to wrap a
by default, and rejects unrelated props', () => { + const btnRef: React.RefObject = { current: null }; + // @ts-expect-error because the ref is to a