diff --git a/src/ToastContainer.tsx b/src/ToastContainer.tsx index 0ca7a1b2e9..b31d591030 100644 --- a/src/ToastContainer.tsx +++ b/src/ToastContainer.tsx @@ -19,6 +19,7 @@ export interface ToastContainerProps extends BsPrefixProps, React.HTMLAttributes { position?: ToastPosition; + containerPosition?: string; } const propTypes = { @@ -41,6 +42,13 @@ const propTypes = { 'bottom-center', 'bottom-end', ]), + + /** + * By default the container is rendered with `position-absolute` utility class. Provide a string to use other `position-*` utility classes, or an empty string to remove it. + * + * @default 'absolute' + */ + containerPosition: PropTypes.string, }; const positionClasses = { @@ -63,6 +71,7 @@ const ToastContainer: BsPrefixRefForwardingComponent< { bsPrefix, position, + containerPosition = 'absolute', className, // Need to define the default "as" during prop destructuring to be compatible with styled-components github.com/react-bootstrap/react-bootstrap/issues/3595 as: Component = 'div', @@ -78,7 +87,10 @@ const ToastContainer: BsPrefixRefForwardingComponent< {...props} className={classNames( bsPrefix, - position && `position-absolute ${positionClasses[position]}`, + position && [ + containerPosition ? `position-${containerPosition}` : null, + positionClasses[position], + ], className, )} /> diff --git a/test/ToastContainerSpec.tsx b/test/ToastContainerSpec.tsx index 670b144afd..4fbf302784 100644 --- a/test/ToastContainerSpec.tsx +++ b/test/ToastContainerSpec.tsx @@ -1,38 +1,26 @@ import { render } from '@testing-library/react'; import ToastContainer, { ToastPosition } from '../src/ToastContainer'; -const expectedClasses: Record> = { - 'top-start': ['position-absolute', 'top-0', 'start-0'], - 'top-center': [ - 'position-absolute', - 'top-0', - 'start-50', - 'translate-middle-x', - ], - 'top-end': ['position-absolute', 'top-0', 'end-0'], - 'middle-start': [ - 'position-absolute', - 'top-50', - 'start-0', - 'translate-middle-y', - ], - 'middle-center': [ - 'position-absolute', - 'top-50', - 'start-50', - 'translate-middle', - ], - 'middle-end': ['position-absolute', 'top-50', 'end-0', 'translate-middle-y'], - 'bottom-start': ['position-absolute', 'bottom-0', 'start-0'], - 'bottom-center': [ - 'position-absolute', - 'bottom-0', - 'start-50', - 'translate-middle-x', - ], - 'bottom-end': ['position-absolute', 'bottom-0', 'end-0'], +const expectedClassesWithoutPosition: Record> = { + 'top-start': ['top-0', 'start-0'], + 'top-center': ['top-0', 'start-50', 'translate-middle-x'], + 'top-end': ['top-0', 'end-0'], + 'middle-start': ['top-50', 'start-0', 'translate-middle-y'], + 'middle-center': ['top-50', 'start-50', 'translate-middle'], + 'middle-end': ['top-50', 'end-0', 'translate-middle-y'], + 'bottom-start': ['bottom-0', 'start-0'], + 'bottom-center': ['bottom-0', 'start-50', 'translate-middle-x'], + 'bottom-end': ['bottom-0', 'end-0'], }; +const createExpectedClasses = (containerPosition = 'absolute') => + Object.fromEntries( + Object.entries(expectedClassesWithoutPosition).map(([key, value]) => [ + key, + containerPosition ? [`position-${containerPosition}`, ...value] : value, + ]), + ); + describe('ToastContainer', () => { it('should render a basic toast container', () => { const { container } = render(); @@ -40,14 +28,57 @@ describe('ToastContainer', () => { .true; }); - Object.keys(expectedClasses).forEach((position: ToastPosition) => { - it(`should render position=${position}`, () => { - const { container } = render(); - expectedClasses[position].map( - (className) => - container.firstElementChild!.classList.contains(className).should.be - .true, - ); + describe('without containerPosition', () => { + const expectedClasses = createExpectedClasses(); + + Object.keys(expectedClasses).forEach((position: ToastPosition) => { + it(`should render classes for position=${position} with position-absolute`, () => { + const { container } = render(); + expectedClasses[position].map( + (className) => + container.firstElementChild!.classList.contains(className).should.be + .true, + ); + }); + }); + }); + + describe('with containerPosition = "" (empty string)', () => { + const expectedClasses = createExpectedClasses(''); + + Object.keys(expectedClasses).forEach((position: ToastPosition) => { + it(`should render classes for position=${position} without position-*`, () => { + const { container } = render(); + expectedClasses[position].map( + (className) => + container.firstElementChild!.classList.contains(className).should.be + .true, + ); + }); }); }); + + ['absolute', 'fixed', 'relative', 'sticky', 'custom'].forEach( + (containerPosition) => { + describe(`with containerPosition=${containerPosition}`, () => { + const expectedClasses = createExpectedClasses(containerPosition); + + Object.keys(expectedClasses).forEach((position: ToastPosition) => { + it(`should render classes for position=${position} with position-${containerPosition}`, () => { + const { container } = render( + , + ); + expectedClasses[position].map( + (className) => + container.firstElementChild!.classList.contains(className) + .should.be.true, + ); + }); + }); + }); + }, + ); });