diff --git a/packages/design-system-dashboard-cli/coverage/lcov-report/write-react-owners-to-csv.js.html b/packages/design-system-dashboard-cli/coverage/lcov-report/write-react-owners-to-csv.js.html index 7bf63a620..ad96289e8 100644 --- a/packages/design-system-dashboard-cli/coverage/lcov-report/write-react-owners-to-csv.js.html +++ b/packages/design-system-dashboard-cli/coverage/lcov-report/write-react-owners-to-csv.js.html @@ -330,14 +330,13 @@

All files write-react-owners-to-csv.js

const today = require('./today');   const componentsToKeep = [ - 'AlertBox', 'ExpandingGroup', 'IconSearch', 'LoadingIndicator', 'TextInput', ];   -const hasMigrationScript = ['AlertBox', 'LoadingIndicator']; +const hasMigrationScript = ['LoadingIndicator'];   function cleanPath(pathToClean) { const cwd = process.cwd(); diff --git a/packages/react-components/index.js b/packages/react-components/index.js index f5ed5bc3b..a3074f667 100644 --- a/packages/react-components/index.js +++ b/packages/react-components/index.js @@ -1,24 +1,19 @@ -import AlertBox, { ALERT_TYPE } from './AlertBox'; import Breadcrumbs from './Breadcrumbs'; import ExpandingGroup from './ExpandingGroup'; import IconBase from './IconBase'; import IconSearch from './IconSearch'; import LoadingIndicator from './LoadingIndicator'; -import MaintenanceBanner from './MaintenanceBanner'; import Modal from './Modal'; import TextInput from './TextInput'; import './i18n-setup'; export { - AlertBox, - ALERT_TYPE, Breadcrumbs, ExpandingGroup, IconBase, IconSearch, LoadingIndicator, - MaintenanceBanner, Modal, TextInput, }; diff --git a/packages/react-components/src/components/AlertBox/AlertBox.jsx b/packages/react-components/src/components/AlertBox/AlertBox.jsx deleted file mode 100644 index b8a91e8e3..000000000 --- a/packages/react-components/src/components/AlertBox/AlertBox.jsx +++ /dev/null @@ -1,203 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import classNames from 'classnames'; -import dispatchAnalyticsEvent from '../../helpers/analytics'; - -// Enum used to set the AlertBox's `status` prop -export const ALERT_TYPE = Object.freeze({ - INFO: 'info', // Blue border, black circled 'i' - ERROR: 'error', // Red border, red circled exclamation - SUCCESS: 'success', // Green border, green checkmark - WARNING: 'warning', // Yellow border, black triangle exclamation - CONTINUE: 'continue', // Green border, green lock -}); - -/** - * This component is no longer supported, please use `` instead. - */ -class AlertBox extends Component { - constructor(props) { - super(props); - this.handleAlertBodyClick = this.handleAlertBodyClick.bind(this); - } - - componentDidMount() { - this.scrollToAlert(); - } - - componentWillUnmount() { - clearTimeout(this.scrollToAlertTimeout); - } - - shouldComponentUpdate(nextProps) { - const visibilityChanged = this.props.isVisible !== nextProps.isVisible; - const contentChanged = this.props.content !== nextProps.content; - const statusChanged = this.props.status !== nextProps.status; - return visibilityChanged || contentChanged || statusChanged; - } - - componentDidUpdate() { - this.scrollToAlert(); - } - - scrollToAlert = () => { - if (!this._ref || !this._ref.scrollIntoView) { - return; - } - - // Without using the setTimeout, React has not added the element - // to the DOM when it calls scrollIntoView() - if (this.props.isVisible && this.props.scrollOnShow) { - clearTimeout(this.scrollToAlertTimeout); - this.scrollToAlertTimeout = setTimeout(() => { - this._ref.scrollIntoView({ - block: this.props.scrollPosition, - behavior: 'smooth', - }); - }, 0); - } - }; - - handleAlertBodyClick(e) { - if (!this.props.disableAnalytics) { - // If it's a link being clicked, dispatch an analytics event - if (e.target?.tagName === 'A') { - dispatchAnalyticsEvent({ - componentName: 'AlertBox', - action: 'linkClick', - details: { - clickLabel: e.target.innerText, - headline: this.props.headline, - status: this.props.status, - backgroundOnly: this.props.backgroundOnly, - closeable: !!this.props.onCloseAlert, - }, - }); - } - } - } - - render() { - if (!this.props.isVisible) return
; - - const alertClass = classNames( - 'usa-alert', - `usa-alert-${this.props.status}`, - { 'background-color-only': this.props.backgroundOnly }, - this.props.className, - ); - - const closeButton = this.props.onCloseAlert && ( - - ); - - const alertHeading = this.props.headline; - const alertText = this.props.content || this.props.children; - const H = `h${this.props.level}`; - - return ( -
{ - this._ref = ref; - }} - > -
- {alertHeading && {alertHeading}} - {alertText &&
{alertText}
} -
- {closeButton} -
- ); - } -} - -/* eslint-disable consistent-return */ -AlertBox.propTypes = { - /** - * Determines the color and icon of the alert box. - */ - status: PropTypes.oneOf(Object.values(ALERT_TYPE)).isRequired, - - /** - * Show or hide the alert. Useful for alerts triggered by app interaction. - */ - isVisible: PropTypes.bool, - /** - * Child elements (content) - */ - children: PropTypes.node, - /** - * Body content of the alert, which can also be passed via children. - */ - content: PropTypes.node, - - /** - * Optional headline. - */ - headline: PropTypes.node, - - /** - * Optional Close button aria-label. - */ - closeBtnAriaLabel: PropTypes.string, - - /** - * Close event handler if the alert can be dismissed or closed. - */ - onCloseAlert: PropTypes.func, - - /** - * If true, page scrolls to alert when it is shown. - */ - scrollOnShow: PropTypes.bool, - - /** - * Defaults to 'start' but customizable. - */ - scrollPosition: PropTypes.string, - - /** - * Optional class name to add to the alert box. - */ - className: PropTypes.string, - - /** - * If true, renders an AlertBox with only a background color, without an - * accented left edge or an icon - */ - backgroundOnly: PropTypes.bool, - - /** - * The header level to use with the headline prop, must be a number 1-6 - */ - level(props, propName) { - const level = parseInt(props[propName], 10); - if (Number.isNaN(level) || level < 1 || level > 6) { - return new Error( - `Invalid prop: AlertBox level must be a number from 1-6, was passed ${props[propName]}`, - ); - } - }, - /** - * Analytics tracking function(s) will not be called - */ - disableAnalytics: PropTypes.bool, -}; -/* eslint-enable consistent-return */ - -AlertBox.defaultProps = { - scrollPosition: 'start', - isVisible: true, - backgroundOnly: false, - closeBtnAriaLabel: 'Close notification', - level: 3, -}; - -export default AlertBox; diff --git a/packages/react-components/src/components/AlertBox/AlertBox.mdx b/packages/react-components/src/components/AlertBox/AlertBox.mdx deleted file mode 100644 index 2f2cb1263..000000000 --- a/packages/react-components/src/components/AlertBox/AlertBox.mdx +++ /dev/null @@ -1,102 +0,0 @@ ---- -title: AlertBox -name: AlertBox -tags: informational, warning, error, success, continue, dismissable ---- - -import AlertBox, { ALERT_TYPE } from './AlertBox' - - -### Code: -```javascript -import AlertBox, { ALERT_TYPE } from '@department-of-veterans-affairs/component-library/AlertBox' - -
- - - - - - {}}/> - - Content without heading.

} - status={ALERT_TYPE.INFO}/> - -
-``` - -### Rendered Component -
-
- - - - - - {}}/> - - Content without heading.

} - status={ALERT_TYPE.INFO}/> - -
-
diff --git a/packages/react-components/src/components/AlertBox/AlertBox.unit.spec.jsx b/packages/react-components/src/components/AlertBox/AlertBox.unit.spec.jsx deleted file mode 100644 index 030ca79b4..000000000 --- a/packages/react-components/src/components/AlertBox/AlertBox.unit.spec.jsx +++ /dev/null @@ -1,221 +0,0 @@ -import React from 'react'; -import { shallow, mount } from 'enzyme'; -import { axeCheck } from '../../helpers/test-helpers'; -import { expect } from 'chai'; -import sinon from 'sinon'; - -import AlertBox from './AlertBox.jsx'; - -// Placeholder for required "content" element -const Content =

; -const Headline = 'Headline'; -const CloseBtnAriaLabelOptional = 'Close notification optional'; -function closeAlert() { - // -} - -describe('', () => { - it('should be an empty div if invisible', () => { - const wrapper = shallow( - , - ); - expect(wrapper.html()).to.equal('

'); - wrapper.unmount(); - }); - - it('should have the expected classnames', () => { - const wrapper = shallow( - , - ); - expect(wrapper.find('.usa-alert').hasClass('usa-alert-info')).to.equal( - true, - ); - expect( - wrapper.find('.usa-alert').hasClass('background-color-only'), - ).to.equal(false); - wrapper.unmount(); - }); - - it('should have have `background-color-only` class added when `backgroundOnly` is `true`', () => { - const wrapper = shallow( - , - ); - expect( - wrapper.find('.usa-alert').hasClass('background-color-only'), - ).to.equal(true); - wrapper.unmount(); - }); - - it('should apply classes set via `className`', () => { - const wrapper = shallow( - , - ); - expect(wrapper.find('.usa-alert').hasClass('foo')).to.equal(true); - wrapper.unmount(); - }); - - it('should use level prop for headline element', () => { - const wrapper = shallow( - , - ); - expect(wrapper.find('.usa-alert-heading').is('h4')).to.equal(true); - wrapper.unmount(); - }); - - it('should pass aXe check when visible', () => - axeCheck()); - - it('should pass aXe check when not visible', () => - axeCheck()); - - it('should pass aXe check without a headline', () => - axeCheck()); - - it('should pass aXe check with a headline', () => - axeCheck( - , - )); - - it('should pass aXe check when it has a close button', () => - axeCheck( - , - )); - - it('should pass aXe check when it has a close button with optional aria-label', () => - axeCheck( - , - )); - - it('should pass aXe check when `backgroundOnly` is `true`', () => - axeCheck( - , - )); - - describe('analytics event', function () { - let wrapper; - - const HeadlineWithLink = ( - <> - Headline with a link - - ); - - beforeEach(() => { - wrapper = mount( - -
- This is content - - with a link - - . -
-
, - ); - }); - - afterEach(() => { - wrapper.unmount(); - }); - - it('should be triggered when link in AlertBox content is clicked', () => { - const handleAnalyticsEvent = sinon.spy(); - - global.document.body.addEventListener( - 'component-library-analytics', - handleAnalyticsEvent, - ); - - // Click link in content - const testLink = wrapper.find('.usa-alert-text a'); - testLink.simulate('click'); - - global.document.body.removeEventListener( - 'component-library-analytics', - handleAnalyticsEvent, - ); - - expect( - handleAnalyticsEvent.calledWith( - sinon.match.has('detail', { - componentName: 'AlertBox', - action: 'linkClick', - details: { - clickLabel: 'with a link', - headline: HeadlineWithLink, - status: 'info', - backgroundOnly: true, - closeable: false, - }, - version: sinon.match.string, - }), - ), - ).to.be.true; - }); - - it('should be triggered when link in AlertBox headline is clicked', () => { - const handleAnalyticsEvent = sinon.spy(); - - global.document.body.addEventListener( - 'component-library-analytics', - handleAnalyticsEvent, - ); - - // Click link in headline - const testLink = wrapper.find('.usa-alert-heading a'); - testLink.simulate('click'); - - global.document.body.removeEventListener( - 'component-library-analytics', - handleAnalyticsEvent, - ); - - expect( - handleAnalyticsEvent.calledWith( - sinon.match.has('detail', { - componentName: 'AlertBox', - action: 'linkClick', - details: { - clickLabel: 'with a link', - headline: HeadlineWithLink, - status: 'info', - backgroundOnly: true, - closeable: false, - }, - version: sinon.match.string, - }), - ), - ).to.be.true; - }); - }); -}); diff --git a/packages/react-components/src/components/MaintenanceBanner/MaintenanceBanner.jsx b/packages/react-components/src/components/MaintenanceBanner/MaintenanceBanner.jsx deleted file mode 100644 index 856dd04e1..000000000 --- a/packages/react-components/src/components/MaintenanceBanner/MaintenanceBanner.jsx +++ /dev/null @@ -1,185 +0,0 @@ -// Node modules. -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - -// Relative imports. -import AlertBox from '../AlertBox/AlertBox'; -import { - formatDate, - isDateAfter, - isDateBefore, - isDateSameDay, -} from '../../helpers/format-date'; - -export const MAINTENANCE_BANNER = 'MAINTENANCE_BANNER'; - -// @WARNING: This is currently only used once in vets-website. -/** - * Display a maintenance banner for a given time window. - */ -export class MaintenanceBanner extends Component { - static propTypes = { - /** - * The content of the banner for downtime. - */ - content: PropTypes.string.isRequired, - /** - * A Date object used when downtime expires. - */ - expiresAt: PropTypes.instanceOf(Date).isRequired, - /** - * A unique ID that will be used for conditionally rendering the banner based on if the user has dismissed it already. - */ - id: PropTypes.string.isRequired, - /** - * Usually this is just window.localStorage - */ - localStorage: PropTypes.shape({ - getItem: PropTypes.func.isRequired, - setItem: PropTypes.func.isRequired, - }), - /** - * A Date object used when downtime starts. - */ - startsAt: PropTypes.instanceOf(Date).isRequired, - /** - * The title of the banner for downtime. - */ - title: PropTypes.string.isRequired, - /** - * The content of the banner for pre-downtime. - */ - warnContent: PropTypes.string, - /** - * A Date object used when pre-downtime starts. - */ - warnStartsAt: PropTypes.instanceOf(Date), - /** - * The title of the banner for pre-downtime. - */ - warnTitle: PropTypes.string, - }; - - constructor(props) { - super(props); - this.state = { - dismissed: - props.localStorage && - props.localStorage.getItem(MAINTENANCE_BANNER) === this.props.id, - }; - } - - derivePostContent = () => { - const { startsAt, expiresAt } = this.props; - - if (isDateSameDay(startsAt, expiresAt)) { - return ( - <> -

- Date: {formatDate(startsAt, 'dateFull')} -

-

- Start/End time: {formatDate(startsAt, 'timeShort')}{' '} - to {formatDate(expiresAt, 'timeShort')} ET -

- - ); - } - - return ( - <> -

- Start: {formatDate(startsAt)} ET -

-

- End: {formatDate(expiresAt)} ET -

- - ); - }; - - onCloseAlert = () => { - if (this.props.localStorage) { - this.props.localStorage.setItem(MAINTENANCE_BANNER, this.props.id); - } - this.setState({ dismissed: true }); - }; - - render() { - const { derivePostContent, onCloseAlert } = this; - const { dismissed } = this.state; - const { - content, - expiresAt, - id, - startsAt, - title, - warnContent, - warnStartsAt, - warnTitle, - } = this.props; - - // Derive dates. - const now = new Date(); - const postContent = derivePostContent(); - - // Escape early if the banner is dismissed. - if (dismissed) { - return null; - } - - // Escape early if it's before when it should show. - if (isDateBefore(now, warnStartsAt)) { - return null; - } - - // Escape early if it's after when it should show. - if (isDateAfter(now, expiresAt)) { - return null; - } - - // Show pre-downtime. - if (isDateBefore(now, startsAt)) { - return ( -
- -

{warnContent}

- {postContent} - - } - headline={warnTitle} - onCloseAlert={onCloseAlert} - status="warning" - /> -
- ); - } - - // Show downtime. - return ( -
- -

{content}

- {postContent} - - } - headline={title} - onCloseAlert={onCloseAlert} - status="error" - /> -
- ); - } -} - -export default MaintenanceBanner; diff --git a/packages/react-components/src/components/MaintenanceBanner/MaintenanceBanner.mdx b/packages/react-components/src/components/MaintenanceBanner/MaintenanceBanner.mdx deleted file mode 100644 index cceb7bc48..000000000 --- a/packages/react-components/src/components/MaintenanceBanner/MaintenanceBanner.mdx +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: MaintenanceBanner -name: MaintenanceBanner ---- - -import MaintenanceBanner from './MaintenanceBanner'; - -### Code: - -```javascript -import MaintenanceBanner from '@department-of-veterans-affairs/component-library/MaintenanceBanner'; - -// Derive startsAt, expiresAt and warnStartsAt -const startsAt = new Date(); -const expiresAt = new Date(); -const warnStartsAt = new Date(); -expiresAt.setHours(expiresAt.getHours() + 24); -warnStartsAt.setHours(warnStartsAt.getHours() - 12); - -; -``` - -### Rendered Component - -const startsAt = new Date(); -const expiresAt = new Date(); -const warnStartsAt = new Date(); -expiresAt.setHours(expiresAt.getHours() + 24); -warnStartsAt.setHours(warnStartsAt.getHours() - 12); - -
- -
diff --git a/packages/react-components/src/components/MaintenanceBanner/MaintenanceBanner.unit.spec.jsx b/packages/react-components/src/components/MaintenanceBanner/MaintenanceBanner.unit.spec.jsx deleted file mode 100644 index 359973e4f..000000000 --- a/packages/react-components/src/components/MaintenanceBanner/MaintenanceBanner.unit.spec.jsx +++ /dev/null @@ -1,64 +0,0 @@ -// Node modules. -import React from 'react'; -import { mount } from 'enzyme'; -import { expect } from 'chai'; - -// Relative imports. -import MaintenanceBanner from './MaintenanceBanner.jsx'; -import { formatDate } from '../../helpers/format-date.js'; - -const deriveDefaultProps = (startsAt = new Date()) => { - const expiresAt = new Date(startsAt); - const warnStartsAt = new Date(startsAt); - expiresAt.setHours(expiresAt.getHours() + 2); - warnStartsAt.setHours(warnStartsAt.getHours() - 12); - - const formattedStartsAt = formatDate(startsAt); - const formattedExpiresAt = formatDate(expiresAt); - - return { - id: '1', - startsAt, - expiresAt, - title: 'DS Logon is down for maintenance.', - content: - 'DS Logon is down for maintenance. Please use ID.me or MyHealtheVet to sign in or use online tools.', - warnStartsAt, - warnTitle: 'DS Logon will be down for maintenance', - warnContent: `DS Logon will be unavailable from ${formattedStartsAt} to ${formattedExpiresAt} Please use ID.me or MyHealtheVet to sign in or use online tools during this time.`, - }; -}; - -describe('', () => { - it("Escapes early if it's before when it should show.", () => { - const date = new Date(); - date.setHours(date.getHours() + 13); - const wrapper = mount(); - expect(wrapper.html()).to.equal(null); - wrapper.unmount(); - }); - - it('Shows pre-downtime.', () => { - const date = new Date(); - date.setHours(date.getHours() + 2); - const wrapper = mount(); - expect(wrapper.html()).to.not.equal(null); - expect(wrapper.html()).to.include('vads-u-border-color--warning-message'); - wrapper.unmount(); - }); - - it('Shows downtime.', () => { - const wrapper = mount(); - expect(wrapper.html()).to.not.equal(null); - expect(wrapper.html()).to.include('vads-u-border-color--secondary'); - wrapper.unmount(); - }); - - it("Escapes early if it's after when it should show.", () => { - const date = new Date(); - date.setHours(date.getHours() - 3); - const wrapper = mount(); - expect(wrapper.html()).to.equal(null); - wrapper.unmount(); - }); -}); diff --git a/packages/react-components/src/index.js b/packages/react-components/src/index.js index 96e9d8960..cfa2887cd 100644 --- a/packages/react-components/src/index.js +++ b/packages/react-components/src/index.js @@ -1,22 +1,17 @@ -import AlertBox, { ALERT_TYPE } from './components/AlertBox/AlertBox'; import Breadcrumbs from './components/Breadcrumbs/Breadcrumbs'; import ExpandingGroup from './components/ExpandingGroup/ExpandingGroup'; import IconBase from './components/IconBase/IconBase'; import IconSearch from './components/IconSearch/IconSearch'; import LoadingIndicator from './components/LoadingIndicator/LoadingIndicator'; -import MaintenanceBanner from './components/MaintenanceBanner/MaintenanceBanner'; import Modal from './components/Modal/Modal'; import TextInput from './components/TextInput/TextInput'; export { - AlertBox, - ALERT_TYPE, Breadcrumbs, ExpandingGroup, IconBase, IconSearch, LoadingIndicator, - MaintenanceBanner, Modal, TextInput, }; diff --git a/packages/storybook/stories/MaintenanceBanner.stories.jsx b/packages/storybook/stories/MaintenanceBanner.stories.jsx deleted file mode 100644 index 81c7175d9..000000000 --- a/packages/storybook/stories/MaintenanceBanner.stories.jsx +++ /dev/null @@ -1,58 +0,0 @@ -/* eslint-disable no-console */ -import React from 'react'; -import { formatDate } from '../../react-components/src/helpers/format-date'; -import MaintenanceBanner from '../../react-components/src/components/MaintenanceBanner/MaintenanceBanner'; -import { StoryDocs } from './wc-helpers'; - -export default { - title: 'Components/Banner - Maintenance (React)', - component: MaintenanceBanner, - id: 'components/banners-maintenancebanner', - argTypes: { - startsAt: { control: { type: 'date' } }, - expiresAt: { control: { type: 'date' } }, - warnStartsAt: { control: { type: 'date' } }, - }, - parameters: { - componentSubtitle: 'Maintenance banner React component', - docs: { - page: () => , - }, - }, -}; - -const Template = args => { - const startTime = args.startsAt; - const endTime = args.expiresAt; - const warnStart = args.warnStartsAt; - console.group('Times passed to Template'); - console.log('startTime:', formatDate(startTime, 'timeShort')); - console.log('endTime', formatDate(endTime, 'timeShort')); - console.log('warnStart', formatDate(warnStart, 'timeShort')); - console.groupEnd(); - return ; -}; - -const defaultArgs = { - id: 'maintenence-banner-id', - title: 'Site maintenance', - warnTitle: 'Upcoming site maintenance', - content: - 'We’re working on VA.gov right now. If you have trouble signing in or using tools, check back after we’re finished. Thank you for your patience.', - warnContent: - 'We’ll be doing some work on VA.gov. The maintenance will last X hours. During that time, you won’t be able to sign in or use tools.', - startsAt: new Date(), - expiresAt: new Date(new Date().setHours(new Date().getHours() + 2)), - warnStartsAt: new Date(new Date().setHours(new Date().getHours() - 1)), -}; - -export const DuringMaintenance = Template.bind(null); -DuringMaintenance.args = { ...defaultArgs }; - -export const BeforeMaintenance = Template.bind(null); -BeforeMaintenance.args = { - ...defaultArgs, - startsAt: new Date(new Date().setHours(new Date().getHours() + 1)), - expiresAt: new Date(new Date().setHours(new Date().getHours() + 2)), - warnStartsAt: new Date(), -};