Skip to content

Commit

Permalink
[#2201] Toast Component added with locales (#2210)
Browse files Browse the repository at this point in the history
* Toast Component added with locales

* null check added

* eslint issues fixed

* eslint issues fixed

* minor fix

* nokogiri version updated

* reviewed changes

* test added

* reviewed changes

* gem version updated

* package dependencies updated

* Update client/app/components/Toast/Toast.scss

Co-authored-by: Julia Nguyen <[email protected]>

* tests added

* issue fixed

* eslint issue fixed

* eslint issue fixed

* eslint issue fixed

* eslint issue fixed

* changes reverted

* Re-run yarn lint

* test script fixed

* test fixed

Co-authored-by: Julia Nguyen <[email protected]>
Co-authored-by: Julia Nguyen <[email protected]>
  • Loading branch information
3 people authored Jan 7, 2023
1 parent 39f213c commit e171b11
Show file tree
Hide file tree
Showing 29 changed files with 306 additions and 26 deletions.
4 changes: 0 additions & 4 deletions app/assets/stylesheets/core/alerts.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
text-align: center;
border-radius: $size-4;

&Dashboard {
@include setMargin($size-0, $size-0, $size-26, $size-0);
}

&Text {
color: $carmine;
}
Expand Down
4 changes: 0 additions & 4 deletions app/assets/stylesheets/core/notices.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,4 @@
padding: $size-10;
text-align: center;
border-radius: $size-4;

&Dashboard {
@include setMargin($size-0, $size-0, $size-26, $size-0);
}
}
5 changes: 4 additions & 1 deletion app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@
<%= react_component('SkipToContent', props: { id: 'mainContent' })%>
<%= react_component('Header', props: header_props) %>
<main>
<%= render partial: 'shared/alerts' %>
<%= react_component('Toast', props: {
notice: notice.present? ? notice : '',
alert: alert.present? ? alert : '' ,
appendDashboardClass: (user_signed_in? && !static_page?) || secret_share_path? }) %>
<% if user_signed_in? && !static_page? %>
<div class="dashboardContent">
<div class="dashboardNav" role="navigation" aria-label="<%= t('navigation.user_menu') %>">
Expand Down
8 changes: 0 additions & 8 deletions app/views/shared/_alerts.html.erb

This file was deleted.

1 change: 1 addition & 0 deletions client/app/components/Input/Input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
&Label {
@include setMargin($size-0, $size-0, $size-0, $size-8);

color: $white;
text-transform: capitalize;
}
}
Expand Down
4 changes: 3 additions & 1 deletion client/app/components/Input/InputCheckbox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ export const InputCheckbox = (props: Props): Node => {
{typeof uncheckedValue !== 'undefined'
&& displayUnchecked(name, uncheckedValue)}
{displayCheckbox(id, name, value, checked, onChange, label)}
<div className={css.checkboxLabel}>{renderHTML(label)}</div>
<label className={`${css.checkboxLabel}`} htmlFor={id}>
{renderHTML(label)}
</label>
</div>
{displayInfo(info)}
</div>
Expand Down
4 changes: 3 additions & 1 deletion client/app/components/Story/StoryActions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ const tooltipElement = (
) => {
const {
link, dataMethod, dataConfirm, name, onClick, commentBy,
} = actions[item];
} = actions[
item
];

const ariaLabel = commentBy || `${name} ${storyName || ''}`;

Expand Down
28 changes: 28 additions & 0 deletions client/app/components/Toast/Toast.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@import "~styles/_global.scss";

.toast {
display: flex;
justify-content: space-between;
width: fit-content;
margin: 0 auto;

button {
background: transparent;
color: $white;
opacity: 1;
border: none;

&:hover {
border: none;
opacity: 0.8;
}
}
}

.toastElementHidden {
visibility: hidden;
}

.toastElementVisible {
visibility: visible;
}
96 changes: 96 additions & 0 deletions client/app/components/Toast/__tests__/Toast.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// @flow
import React from 'react';
import {
render, screen, fireEvent, waitFor,
} from '@testing-library/react';
import { Toast } from 'components/Toast';

describe('Toast', () => {
describe('Toast Type: Alert', () => {
it('renders correctly', () => {
render(
<Toast
alert="Invalid username or password."
appendDashboardClass="true"
/>,
);
expect(screen).not.toBeNull();
});

it('closes correctly on button click', () => {
const { getByRole, container } = render(
<Toast
alert="Invalid username or password."
appendDashboardClass="true"
/>,
);

const toastContent = getByRole('alert');
const toastBtn = container.querySelector('#btn-close-toast-alert');

expect(toastContent).toHaveClass('toastElementVisible');
fireEvent.click(toastBtn);
expect(toastContent).toHaveClass('toastElementHidden');
});

it('closes automatically after 7 seconds', async () => {
const { getByRole } = render(
<Toast
alert="Invalid username or password."
appendDashboardClass="true"
/>,
);

const toastContent = getByRole('alert');
expect(toastContent).toHaveClass('toastElementVisible');
await waitFor(
() => {
expect(toastContent).toHaveClass('toastElementHidden');
},
{
timeout: 7000,
},
);
}, 30000);
});

describe('Toast Type: Notice', () => {
it('renders correctly', () => {
render(<Toast notice="Login successful." />);
expect(screen).not.toBeNull();
});

it('closes correctly on button click', () => {
const { getByRole, container } = render(<Toast notice="Login successful." />);

const toastContent = getByRole('region');
const toastBtn = container.querySelector('#btn-close-toast-notice');

expect(toastContent).toHaveClass('toastElementVisible');
fireEvent.click(toastBtn);
expect(toastContent).toHaveClass('toastElementHidden');
});

it('closes automatically after 7 seconds', async () => {
const { getByRole } = render(<Toast notice="Login successful." />);

const toastContent = getByRole('region');
expect(toastContent).toHaveClass('toastElementVisible');
await waitFor(
() => {
expect(toastContent).toHaveClass('toastElementHidden');
},
{
timeout: 7000,
},
);
}, 30000);
});

describe('Toast Type: Notice', () => {
it('renders correctly', () => {
render(<Toast notice="Signed out successfully." />);
expect(screen).not.toBeNull();
});
});
});
103 changes: 103 additions & 0 deletions client/app/components/Toast/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// @flow
import React, { useState } from 'react';
import type { Node } from 'react';
import { I18n } from 'libs/i18n';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import css from './Toast.scss';

type Props = {
alert?: string,
notice?: string,
appendDashboardClass?: boolean,
};
export type State = {
showToast: boolean,
};

export const Toast = ({ alert, notice, appendDashboardClass }: Props): Node => {
const [showAlert, setShowAlert] = useState<boolean>(
alert !== null
&& alert !== ''
&& !document.documentElement?.hasAttribute('data-turbolinks-preview'),
);
const [showNotice, setShowNotice] = useState<boolean>(
notice !== null
&& notice !== ''
&& !document.documentElement?.hasAttribute('data-turbolinks-preview'),
);
const hideNotice = () => {
setShowNotice(false);
};
const hideAlert = () => {
setShowAlert(false);
};
if (showAlert || showNotice) {
setTimeout(() => {
hideNotice();
hideAlert();
}, 7000);
}
return (
<>
<div
id="toast-notice"
aria-label={showNotice ? I18n.t('alert_auto_hide') : ''}
role="region"
aria-live="polite"
aria-atomic="true"
className={`${
showNotice ? 'notice toastElementVisible' : 'toastElementHidden'
} ${css.toast} ${
showNotice && (showAlert || appendDashboardClass)
? 'smallMarginBottom'
: ''
}`}
>
{showNotice && (
<>
<div>
{notice}
</div>
<button id="btn-close-toast-notice" type="button" onClick={hideNotice} aria-label={I18n.t('close')}>
<span aria-hidden="true">
<FontAwesomeIcon icon={faTimes} />
</span>
</button>
</>
)}
</div>
<div
id="toast-alert"
aria-label={showAlert ? I18n.t('alert_auto_hide') : ''}
role="alert"
className={`${
showAlert ? 'alert toastElementVisible' : 'toastElementHidden'
} ${css.toast} ${
showAlert && appendDashboardClass ? 'smallMarginBottom' : ''
}`}
>
{showAlert && (
<>
<div>
{alert}
</div>
<button id="btn-close-toast-alert" type="button" onClick={hideAlert} aria-label={I18n.t('close')}>
<span aria-hidden="true">
<FontAwesomeIcon icon={faTimes} />
</span>
</button>
</>
)}
</div>
</>
);
};

export default ({ alert, notice, appendDashboardClass }: Props): Node => (
<Toast
alert={alert}
notice={notice}
appendDashboardClass={appendDashboardClass}
/>
);
2 changes: 2 additions & 0 deletions client/app/startup/registration.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { Tag } from 'components/Tag';
import { Tooltip } from 'components/Tooltip';
import Input from 'components/Input';
import OAuthButton from 'components/OAuthButton';
import Toast from 'components/Toast';
import Comments from 'widgets/Comments';
import { ToggleLocale } from 'widgets/ToggleLocale';
import Resources from 'widgets/Resources';
Expand Down Expand Up @@ -65,6 +66,7 @@ ReactOnRails.register({
Tag,
ToggleLocale,
Tooltip,
Toast,
CrisisPrevention,
CarePlanContacts,
OAuthButton,
Expand Down
25 changes: 25 additions & 0 deletions client/app/stories/Toast.stories.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* eslint-disable react/jsx-props-no-spreading */
import React from 'react';
import { Toast } from 'components/Toast';

export default {
title: 'Components/Toast',
component: Toast,
};

const Template = (args) => <Toast {...args} />;

export const noticeToast = Template.bind({});

noticeToast.args = {
notice: 'Login successful.',
appendDashboardClass: 'true',
};
noticeToast.storyName = 'Toast Type: Notice';

export const alertToast = Template.bind({});

alertToast.args = {
alert: 'Login failed.',
};
alertToast.storyName = 'Toast Type: Alert';
8 changes: 6 additions & 2 deletions client/app/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import axios from 'axios';
import renderHTML from 'react-render-html';
import { sanitize } from 'dompurify';

const randomString = (): string => Math.random().toString(36).substring(2, 15)
+ Math.random().toString(36).substring(2, 15);
const randomString = (): string => Math.random()
.toString(36)
.substring(2, 15)
+ Math.random()
.toString(36)
.substring(2, 15);

const setCsrfToken = (): void => {
const tokenDom = document.querySelector('meta[name=csrf-token]');
Expand Down
8 changes: 6 additions & 2 deletions client/app/widgets/CarePlanContacts/__tests__/index.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ describe('CarePlanContacts', () => {
});
const { container } = render(component);

const editLink = container.querySelector('a[aria-label="Edit Test1 Lastname"]');
const editLink = container.querySelector(
'a[aria-label="Edit Test1 Lastname"]',
);
expect(screen.queryByText('Edit Contact')).not.toBeInTheDocument();

await userEvent.click(editLink);
Expand All @@ -72,7 +74,9 @@ describe('CarePlanContacts', () => {
const axiosPostSpy = jest.spyOn(axios, 'patch').mockRejectedValue(error);
const { container } = render(component);

const editLink = container.querySelector('a[aria-label="Edit Test1 Lastname"]');
const editLink = container.querySelector(
'a[aria-label="Edit Test1 Lastname"]',
);
expect(screen.queryByText('Edit Contact')).not.toBeInTheDocument();

await userEvent.click(editLink);
Expand Down
Loading

0 comments on commit e171b11

Please sign in to comment.