diff --git a/.changeset/eighty-melons-exist.md b/.changeset/eighty-melons-exist.md new file mode 100644 index 0000000000..c31dffeab5 --- /dev/null +++ b/.changeset/eighty-melons-exist.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/fuselage-toastbar": minor +--- + +feat(fuselage-toastbar): Add RTL support diff --git a/packages/fuselage-toastbar/package.json b/packages/fuselage-toastbar/package.json index 4de1cf9b6c..a0da109551 100644 --- a/packages/fuselage-toastbar/package.json +++ b/packages/fuselage-toastbar/package.json @@ -96,7 +96,11 @@ "testMatch": [ "/src/**/*.spec.[jt]s?(x)" ], - "testEnvironment": "jsdom" + "testEnvironment": "jsdom", + "setupFilesAfterEnv": [ + "@testing-library/jest-dom/extend-expect", + "testing-utils/setup/noErrorsLogged" + ] }, "volta": { "extends": "../../package.json" diff --git a/packages/fuselage-toastbar/src/ToastBar.spec.tsx b/packages/fuselage-toastbar/src/ToastBar.spec.tsx index bc83157c79..9e9e4000cb 100644 --- a/packages/fuselage-toastbar/src/ToastBar.spec.tsx +++ b/packages/fuselage-toastbar/src/ToastBar.spec.tsx @@ -1,12 +1,65 @@ import { composeStories } from '@storybook/testing-react'; -import { render } from '@testing-library/react'; +import { render, getByRole, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; -import * as stories from './stories'; +import * as stories from './ToastBar.stories'; +import ToastBarProvider from './ToastBarProvider'; -const { ToastBarWithData } = composeStories(stories); +const { Default, TopEnd } = composeStories(stories, { + decorators: [ + (Story) => ( + + + + ), + ], +}); + +const topEndStyle = { + top: '0', + right: '0', +}; + +const topStartStyle = { + top: '0', + left: '0', +}; + +describe('[fuselage-toastbar rendering]', () => { + test('should display ToastBar on the top right of the screen by default', async () => { + render(); + const toast = screen.queryByRole('alert'); + const toastContainer = toast?.parentElement?.parentElement?.parentElement; + + expect(toastContainer).toHaveStyle(topEndStyle); + }); + + test('should display ToastBar on the top right of the screen', async () => { + document.body.setAttribute('dir', 'ltr'); + render(); + const toast = screen.queryByRole('alert'); + const toastContainer = toast?.parentElement?.parentElement?.parentElement; + + expect(toastContainer).toHaveStyle(topEndStyle); + }); + + test('should display ToastBar on the top left of the screen', async () => { + document.body.setAttribute('dir', 'rtl'); + render(); + const toast = screen.queryByRole('alert'); + const toastContainer = toast?.parentElement?.parentElement?.parentElement; + + expect(toastContainer).toHaveStyle(topStartStyle); + }); +}); + +describe('[fuselage-toastbar interacting]', () => { + test('should dispatch the ToastBar on click', async () => { + const { container } = render(); + const button = getByRole(container, 'button'); -describe('[ToastBarWithData Component]', () => { - it('renders without crashing', () => { - render(); + userEvent.click(button); + const toasts = screen.queryAllByRole('alert'); + toasts.forEach((toast) => expect(toast).toBeInTheDocument()); }); }); diff --git a/packages/fuselage-toastbar/src/ToastBar.stories.tsx b/packages/fuselage-toastbar/src/ToastBar.stories.tsx new file mode 100644 index 0000000000..66f7c9fd01 --- /dev/null +++ b/packages/fuselage-toastbar/src/ToastBar.stories.tsx @@ -0,0 +1,137 @@ +import { Button } from '@rocket.chat/fuselage'; +import type { Meta, Story } from '@storybook/react'; +import { useEffect, useState } from 'react'; + +import { useToastBarDispatch } from './ToastBarContext'; + +export default { + title: 'view/ToastBar', + parameters: { + layout: 'centered', + actions: { argTypesRegex: '^on.*' }, + }, +} as Meta; + +const DEFAULT_MESSAGE = 'Lorem Ipsum'; + +export const Default: Story = () => { + const [counter, setCounter] = useState(0); + const dispatchToastMessage = useToastBarDispatch(); + + const messageArray = [ + 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ipsam nihi Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ipsam nihi', + 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ipsam nihi Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ipsam nihi', + 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ipsam nihi Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ipsam nihi', + DEFAULT_MESSAGE, + 'Lorem ipsum dolor, sit amet consectetur adipisicing elit.', + ]; + + const handleToast = () => { + dispatchToastMessage({ + type: 'success', + message: messageArray[counter], + }); + + dispatchToastMessage({ + type: 'error', + message: messageArray[counter], + time: 10, + position: 'bottom-start', + }); + + if (counter === messageArray.length - 1) { + return setCounter(0); + } + + return setCounter((prevState) => prevState + 1); + }; + + return ( + + ); +}; + +export const TopStart: Story = () => { + const dispatchToastMessage = useToastBarDispatch(); + + const handleDispatch = () => + dispatchToastMessage({ + type: 'success', + message: DEFAULT_MESSAGE, + position: 'top-start', + }); + + useEffect(() => { + handleDispatch(); + }, []); + + return ( + + ); +}; + +export const TopEnd: Story = () => { + const dispatchToastMessage = useToastBarDispatch(); + + const handleDispatch = () => + dispatchToastMessage({ + type: 'success', + message: DEFAULT_MESSAGE, + }); + + useEffect(() => { + handleDispatch(); + }, []); + + return ( + + ); +}; + +export const BottomStart: Story = () => { + const dispatchToastMessage = useToastBarDispatch(); + + const handleDispatch = () => + dispatchToastMessage({ + type: 'success', + message: DEFAULT_MESSAGE, + position: 'bottom-start', + }); + + useEffect(() => { + handleDispatch(); + }, []); + + return ( + + ); +}; + +export const BottomEnd: Story = () => { + const dispatchToastMessage = useToastBarDispatch(); + + const handleDispatch = () => + dispatchToastMessage({ + type: 'success', + message: DEFAULT_MESSAGE, + position: 'bottom-end', + }); + + useEffect(() => { + handleDispatch(); + }, []); + + return ( + + ); +}; diff --git a/packages/fuselage-toastbar/src/ToastBarZone.tsx b/packages/fuselage-toastbar/src/ToastBarZone.tsx index 4938c22404..fc31812f77 100644 --- a/packages/fuselage-toastbar/src/ToastBarZone.tsx +++ b/packages/fuselage-toastbar/src/ToastBarZone.tsx @@ -4,10 +4,22 @@ import type { ReactNode, ReactElement } from 'react'; import type { ToastBarPayload } from './ToastBarContext'; const positionProps = { - 'top-start': 'top: 0; left: 0;', - 'top-end': 'top: 0; right: 0;', - 'bottom-start': 'bottom: 0; left: 0;', - 'bottom-end': 'bottom: 0; right: 0;', + 'top-start': { + ltr: 'top: 0; left: 0; right: unset; bottom: unset;', + rtl: 'top: 0; right: 0; left: unset; bottom: unset;', + }, + 'top-end': { + ltr: 'top: 0; right: 0; left: unset; bottom: unset;', + rtl: 'top: 0; left: 0; right: unset; bottom: unset;', + }, + 'bottom-start': { + ltr: 'bottom: 0; left: 0; right: unset; top: unset;', + rtl: 'bottom: 0; right: 0; left: unset; top: unset;', + }, + 'bottom-end': { + ltr: 'bottom: 0; right: 0; left: unset; top: unset;', + rtl: 'bottom: 0; left: 0; right: unset; top: unset;', + }, }; export const ToastBarContainer = styled( @@ -20,7 +32,10 @@ export const ToastBarContainer = styled( display: flex; flex-direction: column; margin: 1rem; - ${(p) => (p.position ? positionProps[p.position] : '')} + ${(p) => (p.position ? positionProps[p.position].ltr : '')} + [dir='rtl'] & { + ${(p) => (p.position ? positionProps[p.position].rtl : '')} + } `; type ToastBarZoneProps = { diff --git a/packages/fuselage-toastbar/src/stories.tsx b/packages/fuselage-toastbar/src/stories.tsx deleted file mode 100644 index 1ce4e9809d..0000000000 --- a/packages/fuselage-toastbar/src/stories.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Button, Box } from '@rocket.chat/fuselage'; -import type { Meta, Story } from '@storybook/react'; -import { useState } from 'react'; - -import { useToastBarDispatch } from './ToastBarContext'; - -export default { - title: 'view/ToastBar', - parameters: { - layout: 'fullscreen', - actions: { argTypesRegex: '^on.*' }, - }, -} as Meta; - -export const ToastBarWithData: Story = () => { - const [counter, setCounter] = useState(0); - const dispatchToastMessage = useToastBarDispatch(); - - const messageArray = [ - 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ipsam nihi Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ipsam nihi', - 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ipsam nihi Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ipsam nihi', - 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ipsam nihi Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ipsam nihi', - 'Lorem Ipsum', - 'Lorem ipsum dolor, sit amet consectetur adipisicing elit.', - ]; - - const handleToast = () => { - dispatchToastMessage({ - type: 'success', - message: messageArray[counter], - }); - - dispatchToastMessage({ - type: 'error', - message: messageArray[counter], - time: 10, - position: 'bottom-start', - }); - - if (counter === messageArray.length - 1) { - return setCounter(0); - } - - return setCounter((prevState) => prevState + 1); - }; - - return ( - - - - ); -};