Skip to content

Commit

Permalink
Merge branch 'main' into CORE-675-filter-sections-component
Browse files Browse the repository at this point in the history
  • Loading branch information
jomcarvajal authored Jan 30, 2025
2 parents 152ca39 + b13e71e commit 3659d18
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 22 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openstax/ui-components",
"version": "1.11.2",
"version": "1.12.0",
"license": "MIT",
"source": "./src/index.ts",
"types": "./dist/index.d.ts",
Expand Down
34 changes: 28 additions & 6 deletions src/components/ToastContainer.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { render, act } from '@testing-library/react';
import { ToastContainer } from './ToastContainer';
import { act, render } from '@testing-library/react';
import { BodyPortalToastContainer, ToastContainer } from './ToastContainer';
import { ToastData } from '../../src/types';

jest.useFakeTimers();
Expand All @@ -10,7 +10,7 @@ const toasts: ToastData[] = [
{ id: '3', title: 'Success', message: 'message', variant: 'success', dismissAfterMs: 2000 },
];

describe('ToastContainer', () => {
describe('BodyPortalToastContainer', () => {
let root: HTMLElement;

beforeEach(() => {
Expand All @@ -20,18 +20,40 @@ describe('ToastContainer', () => {
});

it('matches snapshot', () => {
render(<ToastContainer toasts={toasts} />, { container: root });
render(<BodyPortalToastContainer toasts={toasts} />, { container: root });
expect(document.body).toMatchSnapshot();
});

it('uses inline prop', () => {
render(<BodyPortalToastContainer toasts={toasts} inline={true} />, { container: root });
expect(document.body).toMatchSnapshot();
});

it('runs callback', () => {
const callback = jest.fn();
render(<BodyPortalToastContainer toasts={toasts} onDismissToast={callback} />, { container: root });
act(() => {
jest.advanceTimersByTime(10000);
expect(callback).toHaveBeenCalledWith('3');
expect(callback).toHaveBeenCalledWith('2');
});
});
});

describe('ToastContainer', () => {
it('matches snapshot', () => {
render(<ToastContainer toasts={toasts} />);
expect(document.body).toMatchSnapshot();
});

it('uses inline prop', () => {
render(<ToastContainer toasts={toasts} inline={true} />, { container: root });
render(<ToastContainer toasts={toasts} inline={true} />);
expect(document.body).toMatchSnapshot();
});

it('runs callback', () => {
const callback = jest.fn();
render(<ToastContainer toasts={toasts.splice(0)} onDismissToast={callback} />, { container: root });
render(<ToastContainer toasts={toasts} onDismissToast={callback} />);
act(() => {
jest.advanceTimersByTime(10000);
expect(callback).toHaveBeenCalledWith('3');
Expand Down
12 changes: 10 additions & 2 deletions src/components/ToastContainer.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import styled from 'styled-components';
import { ToastData } from '../../src/types';
import { ToastContainer } from './ToastContainer';
import { BodyPortalToastContainer, ToastContainer } from './ToastContainer';

const StyledBodyPortalToastContainer = styled(BodyPortalToastContainer)`
top: 2rem;
left: 2rem;
right: unset;
`;

const StyledToastContainer = styled(ToastContainer)`
top: 2rem;
Expand All @@ -13,4 +19,6 @@ const toasts: ToastData[] = [
{ title: 'Error', message: 'message', variant: 'failure', dismissAfterMs: 4000 },
{ title: 'Success', message: 'message', variant: 'success', dismissAfterMs: 2000 },
];
export const Default = () => <StyledToastContainer toasts={toasts} />;

export const UsingBodyPortal = () => <StyledBodyPortalToastContainer toasts={toasts} />;
export const WithoutBodyPortal = () => <StyledToastContainer toasts={toasts} />;
44 changes: 35 additions & 9 deletions src/components/ToastContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import styled, { css } from 'styled-components';
import styled, { css, ThemedStyledFunction } from 'styled-components';
import { BodyPortal } from './BodyPortal';
import { Toast } from './Toast';
import { zIndex } from '../../src/theme';
import { ToastData } from '../../src/types';
import { ComponentType } from 'react';

const StyledToastContainer = styled(BodyPortal)`
const makeStyledToastContainer = <T extends keyof JSX.IntrinsicElements | ComponentType<any>>(
func: ThemedStyledFunction<T, object>
) => func`
${(props: {inline: boolean}) => !props.inline && css`
position: fixed;
right: 2rem;
Expand All @@ -16,15 +19,38 @@ const StyledToastContainer = styled(BodyPortal)`
gap: 1vh;
`;

export const ToastContainer = ({ toasts, onDismissToast, inline = false, className }: {
toasts: ToastData[], onDismissToast?: ToastData['onDismiss'], inline?: boolean, className?: string
}) => {
return <StyledToastContainer inline={inline} aria-live="polite" slot='toast' className={className}>
{toasts.map((toast, index) => <Toast
const StyledToastContainer = makeStyledToastContainer(styled.div);

const StyledBodyPortalToastContainer = makeStyledToastContainer(styled(BodyPortal));

export type ToastContainerParams = {
toasts: ToastData[];
onDismissToast?: ToastData['onDismiss'];
inline?: boolean;
className?: string;
};

export type ToastContainerComponent = (params: ToastContainerParams) => JSX.Element;

const makeToasts = (toasts: ToastData[], inline: boolean, onDismissToast?: (id: string | undefined) => void) => (
toasts.map((toast, index) =>
<Toast
key={`toast-${index}`}
onDismiss={onDismissToast}
inline={inline}
{...toast}
>{toast.message}</Toast>)}
>{toast.message}</Toast>
)
);

export const ToastContainer: ToastContainerComponent = ({ toasts, onDismissToast, inline = false, className }) => (
<StyledToastContainer inline={inline} aria-live='polite' className={className}>
{makeToasts(toasts, inline, onDismissToast)}
</StyledToastContainer>
}
);

export const BodyPortalToastContainer: ToastContainerComponent = ({ toasts, onDismissToast, inline = false, className }) => (
<StyledBodyPortalToastContainer inline={inline} aria-live='polite' slot='toast' className={className}>
{makeToasts(toasts, inline, onDismissToast)}
</StyledBodyPortalToastContainer>
);
140 changes: 136 additions & 4 deletions src/components/__snapshots__/ToastContainer.spec.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ToastContainer matches snapshot 1`] = `
exports[`BodyPortalToastContainer matches snapshot 1`] = `
<body>
<main
id="root"
/>
<div
class="sc-gsnTZi cA-dmZH"
class="sc-dkzDqf cDpjbc"
data-portal-slot="toast"
>
<div
Expand Down Expand Up @@ -67,13 +67,13 @@ exports[`ToastContainer matches snapshot 1`] = `
</body>
`;

exports[`ToastContainer uses inline prop 1`] = `
exports[`BodyPortalToastContainer uses inline prop 1`] = `
<body>
<main
id="root"
/>
<div
class="sc-gsnTZi ejWjjm"
class="sc-dkzDqf iRjmjx"
data-portal-slot="toast"
>
<div
Expand Down Expand Up @@ -133,3 +133,135 @@ exports[`ToastContainer uses inline prop 1`] = `
</div>
</body>
`;

exports[`ToastContainer matches snapshot 1`] = `
<body>
<div>
<div
aria-live="polite"
class="sc-gsnTZi cA-dmZH"
>
<div
class="sc-bczRLJ jAZNhq"
>
<div
class="neutral"
>
<div
class="title"
>
Neutral
</div>
<div
class="body"
>
message
</div>
</div>
</div>
<div
class="sc-bczRLJ cWrNHg"
>
<div
class="failure"
>
<div
class="title"
>
Error
</div>
<div
class="body"
>
message
</div>
</div>
</div>
<div
class="sc-bczRLJ loazPe"
>
<div
class="success"
>
<div
class="title"
>
Success
</div>
<div
class="body"
>
message
</div>
</div>
</div>
</div>
</div>
</body>
`;

exports[`ToastContainer uses inline prop 1`] = `
<body>
<div>
<div
aria-live="polite"
class="sc-gsnTZi ejWjjm"
>
<div
class="sc-bczRLJ jSNerL"
>
<div
class="neutral"
>
<div
class="title"
>
Neutral
</div>
<div
class="body"
>
message
</div>
</div>
</div>
<div
class="sc-bczRLJ fgCwHl"
>
<div
class="failure"
>
<div
class="title"
>
Error
</div>
<div
class="body"
>
message
</div>
</div>
</div>
<div
class="sc-bczRLJ dGEkoL"
>
<div
class="success"
>
<div
class="title"
>
Success
</div>
<div
class="body"
>
message
</div>
</div>
</div>
</div>
</div>
</body>
`;

0 comments on commit 3659d18

Please sign in to comment.