Skip to content

Commit

Permalink
feat(ToastContainer): add containerPosition prop (#6316)
Browse files Browse the repository at this point in the history
* Add containerPosition props

* Refactor ToastContainerSpec

* Allow empty string to remove position-*
  • Loading branch information
tkesgar committed May 3, 2022
1 parent 9d1f674 commit 9815957
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 39 deletions.
14 changes: 13 additions & 1 deletion src/ToastContainer.tsx
Expand Up @@ -19,6 +19,7 @@ export interface ToastContainerProps
extends BsPrefixProps,
React.HTMLAttributes<HTMLElement> {
position?: ToastPosition;
containerPosition?: string;
}

const propTypes = {
Expand All @@ -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 = {
Expand All @@ -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',
Expand All @@ -78,7 +87,10 @@ const ToastContainer: BsPrefixRefForwardingComponent<
{...props}
className={classNames(
bsPrefix,
position && `position-absolute ${positionClasses[position]}`,
position && [
containerPosition ? `position-${containerPosition}` : null,
positionClasses[position],
],
className,
)}
/>
Expand Down
107 changes: 69 additions & 38 deletions test/ToastContainerSpec.tsx
@@ -1,53 +1,84 @@
import { render } from '@testing-library/react';
import ToastContainer, { ToastPosition } from '../src/ToastContainer';

const expectedClasses: Record<ToastPosition, Array<string>> = {
'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<ToastPosition, Array<string>> = {
'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(<ToastContainer />);
container.firstElementChild!.classList.contains('toast-container').should.be
.true;
});

Object.keys(expectedClasses).forEach((position: ToastPosition) => {
it(`should render position=${position}`, () => {
const { container } = render(<ToastContainer position={position} />);
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(<ToastContainer position={position} />);
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(<ToastContainer position={position} />);
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(
<ToastContainer
position={position}
containerPosition={containerPosition}
/>,
);
expectedClasses[position].map(
(className) =>
container.firstElementChild!.classList.contains(className)
.should.be.true,
);
});
});
});
},
);
});

0 comments on commit 9815957

Please sign in to comment.