Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#2201] Toast Component added with locales #2210

Merged
merged 24 commits into from
Jan 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ GEM
addressable (~> 2.7)
letter_opener (1.7.0)
launchy (~> 2.2)
loofah (2.19.0)
loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
Expand All @@ -241,7 +241,7 @@ GEM
ruby2_keywords (~> 0.0.1)
netrc (0.11.0)
nio4r (2.5.8)
nokogiri (1.13.9)
nokogiri (1.13.10)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
oauth2 (1.4.9)
Expand Down Expand Up @@ -338,8 +338,8 @@ GEM
activesupport (>= 4.2)
choice (~> 0.2.0)
ruby-graphviz (~> 1.2)
rails-html-sanitizer (1.4.3)
loofah (~> 2.3)
rails-html-sanitizer (1.4.4)
loofah (~> 2.19, >= 2.19.1)
rails-i18n (6.0.0)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 7)
Expand Down
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;
julianguyen marked this conversation as resolved.
Show resolved Hide resolved
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;
julianguyen marked this conversation as resolved.
Show resolved Hide resolved
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', () => {
zeeshansarwar38 marked this conversation as resolved.
Show resolved Hide resolved
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();
});
});
});
zeeshansarwar38 marked this conversation as resolved.
Show resolved Hide resolved
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 (
<>
zeeshansarwar38 marked this conversation as resolved.
Show resolved Hide resolved
<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({});

zeeshansarwar38 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading