@madie/madie-design-system 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. package/.babelrc +10 -0
  2. package/.editorconfig +22 -0
  3. package/.eslintignore +5 -0
  4. package/.eslintrc.js +31 -0
  5. package/.storybook/.babelrc +4 -0
  6. package/.storybook/main.js +89 -0
  7. package/.storybook/preview-head.html +9 -0
  8. package/.storybook/preview.js +5 -0
  9. package/README.md +5 -0
  10. package/components/Accordion/Accordion.stories.js +20 -0
  11. package/components/Accordion/index.jsx +93 -0
  12. package/components/Alert/Alert.stories.js +61 -0
  13. package/components/Alert/index.js +46 -0
  14. package/components/Breadcrumb/Breadcrumb.md +28 -0
  15. package/components/Breadcrumb/Breadcrumb.stories.js +76 -0
  16. package/components/Breadcrumb/index.js +83 -0
  17. package/components/Button/Button.stories.js +121 -0
  18. package/components/Button/index.js +90 -0
  19. package/components/CalloutBox/CalloutBox.stories.js +94 -0
  20. package/components/CalloutBox/index.js +109 -0
  21. package/components/Card/Card.stories.js +163 -0
  22. package/components/Card/index.js +137 -0
  23. package/components/Dropdown/Dropdown.stories.js +94 -0
  24. package/components/Dropdown/index.js +85 -0
  25. package/components/Error/Collapsible.jsx +88 -0
  26. package/components/Error/ErrorUI.jsx +31 -0
  27. package/components/Error/error.js +26 -0
  28. package/components/FlashNotification/FlashNotificationUI.jsx +91 -0
  29. package/components/FlashNotification/index.js +30 -0
  30. package/components/Footer/Footer.stories.js +20 -0
  31. package/components/Footer/FooterUI.jsx +269 -0
  32. package/components/Footer/LegacyFooterUI.jsx +194 -0
  33. package/components/Footer/SocialLinks.jsx +60 -0
  34. package/components/Footer/Subscribe.jsx +34 -0
  35. package/components/Footer/footer.js +30 -0
  36. package/components/Header/Header.md +111 -0
  37. package/components/Header/Header.stories.js +32 -0
  38. package/components/Header/HeaderAccountMenu.jsx +169 -0
  39. package/components/Header/HeaderCancel.jsx +16 -0
  40. package/components/Header/HeaderContainer.jsx +63 -0
  41. package/components/Header/HeaderLogo.jsx +42 -0
  42. package/components/Header/HeaderMenuButton.js +50 -0
  43. package/components/Header/HeaderMenuItem.jsx +232 -0
  44. package/components/Header/HeaderMenuLink.js +50 -0
  45. package/components/Header/HeaderMenuSignOutButton.js +62 -0
  46. package/components/Header/HeaderMobileButton.js +37 -0
  47. package/components/Header/HeaderUI.jsx +167 -0
  48. package/components/Header/HelpIcon.jsx +32 -0
  49. package/components/Header/ImpersonatorBanner.jsx +57 -0
  50. package/components/Header/NavigationButtonIcon.jsx +27 -0
  51. package/components/Header/default-content.json +280 -0
  52. package/components/Header/header.js +40 -0
  53. package/components/Header/hooks.js +52 -0
  54. package/components/Header/utag-helpers.js +9 -0
  55. package/components/Infotip/Infotip.jsx +75 -0
  56. package/components/Infotip/Infotip.stories.js +50 -0
  57. package/components/Infotip/InfotipIcon.jsx +41 -0
  58. package/components/Infotip/index.js +3 -0
  59. package/components/Link/Link.stories.js +81 -0
  60. package/components/Link/index.js +61 -0
  61. package/components/Modal/LegacyModal.jsx +72 -0
  62. package/components/Modal/Modal.jsx +140 -0
  63. package/components/Modal/Modal.stories.js +159 -0
  64. package/components/Modal/index.jsx +22 -0
  65. package/components/NotificationBanner/NotificationBanner.stories.js +23 -0
  66. package/components/NotificationBanner/index.js +265 -0
  67. package/components/SanitizedContent/index.jsx +200 -0
  68. package/components/Search/Search.stories.js +21 -0
  69. package/components/Search/index.js +85 -0
  70. package/components/SessionDialog/sessionDialog.js +26 -0
  71. package/components/SessionDialogUI.jsx +261 -0
  72. package/components/SideNav/AnimationGroup/AnimationGroup.jsx +27 -0
  73. package/components/SideNav/Chart/ScoreChart.jsx +268 -0
  74. package/components/SideNav/Chart/__tests__/ScoreChart.test.js +22 -0
  75. package/components/SideNav/Chart/__tests__/__snapshots__/ScoreChart.test.js.snap +173 -0
  76. package/components/SideNav/Chart/index.js +3 -0
  77. package/components/SideNav/Content/LevelOneContent.jsx +208 -0
  78. package/components/SideNav/Content/LevelTwoContent.jsx +216 -0
  79. package/components/SideNav/Content/index.js +4 -0
  80. package/components/SideNav/Details/IndividualDetails.jsx +18 -0
  81. package/components/SideNav/Details/PracticeDetails.jsx +45 -0
  82. package/components/SideNav/Details/index.js +4 -0
  83. package/components/SideNav/Links/CmsSwitchLink.jsx +41 -0
  84. package/components/SideNav/Links/NavItemInline.jsx +54 -0
  85. package/components/SideNav/Links/NavLinkContainer.jsx +38 -0
  86. package/components/SideNav/Links/NavLinkDrawer.jsx +249 -0
  87. package/components/SideNav/Links/NavLinkInline.jsx +107 -0
  88. package/components/SideNav/Links/NavLinkToggle.jsx +34 -0
  89. package/components/SideNav/Links/index.js +13 -0
  90. package/components/SideNav/SideNav.md +50 -0
  91. package/components/SideNav/SideNav.stories.js +379 -0
  92. package/components/SideNav/UI/SideNavUI.jsx +333 -0
  93. package/components/SideNav/UI/default-content.json +99 -0
  94. package/components/SideNav/UI/index.js +3 -0
  95. package/components/SideNav/helpers.js +49 -0
  96. package/components/SideNav/index.js +53 -0
  97. package/components/Spinner/index.js +9 -0
  98. package/components/Tabs/Tab.js +26 -0
  99. package/components/Tabs/TabPanel.js +34 -0
  100. package/components/Tabs/Tabs.stories.js +120 -0
  101. package/components/Tabs/index.js +95 -0
  102. package/components/TextInput/TextInput.stories.js +118 -0
  103. package/components/TextInput/index.js +137 -0
  104. package/components/Tooltip/Tooltip.jsx +88 -0
  105. package/components/Tooltip/Tooltip.stories.js +80 -0
  106. package/components/Tooltip/index.js +3 -0
  107. package/components/Tooltip/position.js +68 -0
  108. package/components/UnwrappedSpinner/index.js +8 -0
  109. package/components/hooks/useGetConfig.js +74 -0
  110. package/components/index.js +126 -0
  111. package/dist/browser.js +3 -0
  112. package/dist/browser.js.LICENSE.txt +54 -0
  113. package/dist/browser.js.map +1 -0
  114. package/dist/index.js +3 -0
  115. package/dist/index.js.LICENSE.txt +54 -0
  116. package/dist/index.js.map +1 -0
  117. package/dist/react/index.js +3 -0
  118. package/dist/react/index.js.LICENSE.txt +71 -0
  119. package/dist/react/index.js.map +1 -0
  120. package/fonts/FontAwesome.otf +0 -0
  121. package/fonts/OFL.txt +92 -0
  122. package/fonts/Rubik-Black.ttf +0 -0
  123. package/fonts/Rubik-BlackItalic.ttf +0 -0
  124. package/fonts/Rubik-Bold.ttf +0 -0
  125. package/fonts/Rubik-BoldItalic.ttf +0 -0
  126. package/fonts/Rubik-Italic.ttf +0 -0
  127. package/fonts/Rubik-Light.ttf +0 -0
  128. package/fonts/Rubik-LightItalic.ttf +0 -0
  129. package/fonts/Rubik-Medium.ttf +0 -0
  130. package/fonts/Rubik-MediumItalic.ttf +0 -0
  131. package/fonts/Rubik-Regular.ttf +0 -0
  132. package/fonts/fontawesome-webfont.eot +0 -0
  133. package/fonts/fontawesome-webfont.svg +2671 -0
  134. package/fonts/fontawesome-webfont.ttf +0 -0
  135. package/fonts/fontawesome-webfont.woff +0 -0
  136. package/fonts/fontawesome-webfont.woff2 +0 -0
  137. package/images/favicon/android-chrome-192x192.png +0 -0
  138. package/images/favicon/android-chrome-256x256.png +0 -0
  139. package/images/favicon/apple-touch-icon.png +0 -0
  140. package/images/favicon/browserconfig.xml +9 -0
  141. package/images/favicon/favicon-16x16.png +0 -0
  142. package/images/favicon/favicon-32x32.png +0 -0
  143. package/images/favicon/favicon.ico +0 -0
  144. package/images/favicon/manifest.json +18 -0
  145. package/images/favicon/mstile-150x150.png +0 -0
  146. package/images/favicon/safari-pinned-tab.svg +32 -0
  147. package/images/hhs-logo-black.svg +10 -0
  148. package/images/hhs-logo-white.svg +10 -0
  149. package/images/icons/close-x.svg +6 -0
  150. package/images/icons/help-headset.svg +21 -0
  151. package/images/icons/svg/arrow-down.svg +1 -0
  152. package/images/icons/svg/arrow-download.svg +1 -0
  153. package/images/icons/svg/arrow-right.svg +1 -0
  154. package/images/icons/svg/calendar.svg +1 -0
  155. package/images/icons/svg/close.svg +1 -0
  156. package/images/icons/svg/download.svg +1 -0
  157. package/images/icons/svg/external.svg +1 -0
  158. package/images/icons/svg/file-upload.svg +1 -0
  159. package/images/icons/svg/print.svg +1 -0
  160. package/images/icons/svg/search.svg +7 -0
  161. package/images/icons/svg/tooltip-question.svg +6 -0
  162. package/images/icons/svg/trash.svg +1 -0
  163. package/images/icons/svg/upload.svg +1 -0
  164. package/images/qpp_logo_reversed.png +0 -0
  165. package/images/qpp_logo_rgb_color.png +0 -0
  166. package/index.js +31 -0
  167. package/jest/mediaFileTransformer.js +9 -0
  168. package/jest.config.js +188 -0
  169. package/lib/Chevron.jsx +32 -0
  170. package/lib/SvgComponents.jsx +539 -0
  171. package/lib/svg-definitions.svg +125 -0
  172. package/package.json +125 -0
  173. package/session/index.js +5 -0
  174. package/session/logout.js +65 -0
  175. package/session/refresh.js +49 -0
  176. package/session/ttl.js +37 -0
  177. package/styles/_global.scss +106 -0
  178. package/styles/_main.scss +15 -0
  179. package/styles/_paths.scss +1 -0
  180. package/styles/_qpp-style.scss +31 -0
  181. package/styles/components/_accordion.scss +289 -0
  182. package/styles/components/_alerts.scss +54 -0
  183. package/styles/components/_autocomplete.scss +47 -0
  184. package/styles/components/_breadcrumbs.scss +64 -0
  185. package/styles/components/_buttons.scss +580 -0
  186. package/styles/components/_error-page.scss +56 -0
  187. package/styles/components/_flash-notification.scss +98 -0
  188. package/styles/components/_fonts.scss +42 -0
  189. package/styles/components/_forms.scss +277 -0
  190. package/styles/components/_grid.scss +37 -0
  191. package/styles/components/_info-modal.scss +139 -0
  192. package/styles/components/_links.scss +12 -0
  193. package/styles/components/_modals.scss +18 -0
  194. package/styles/components/_notification-banner.scss +165 -0
  195. package/styles/components/_panels.scss +61 -0
  196. package/styles/components/_pie-chart.scss +13 -0
  197. package/styles/components/_score-chart.scss +164 -0
  198. package/styles/components/_spinner.scss +7 -0
  199. package/styles/components/_tables.scss +111 -0
  200. package/styles/components/_tabs.scss +69 -0
  201. package/styles/components/_text.scss +150 -0
  202. package/styles/components/_toggles.scss +3 -0
  203. package/styles/components/_tooltip.scss +23 -0
  204. package/styles/components/_variables.scss +157 -0
  205. package/styles/components/_wells.scss +92 -0
  206. package/styles/qppds/README.md +0 -0
  207. package/styles/qppds/base/_backgrounds.scss +7 -0
  208. package/styles/qppds/base/_fonts.scss +26 -0
  209. package/styles/qppds/base/_icon.scss +23 -0
  210. package/styles/qppds/base/_layout.scss +5 -0
  211. package/styles/qppds/base/_typography.scss +85 -0
  212. package/styles/qppds/base/index.scss +8 -0
  213. package/styles/qppds/components/_alert.scss +95 -0
  214. package/styles/qppds/components/_breadcrumbs.scss +70 -0
  215. package/styles/qppds/components/_button.scss +292 -0
  216. package/styles/qppds/components/_card.scss +175 -0
  217. package/styles/qppds/components/_dropdown.scss +55 -0
  218. package/styles/qppds/components/_footer.scss +597 -0
  219. package/styles/qppds/components/_header.scss +892 -0
  220. package/styles/qppds/components/_link.scss +52 -0
  221. package/styles/qppds/components/_modal.scss +118 -0
  222. package/styles/qppds/components/_page-header.scss +67 -0
  223. package/styles/qppds/components/_pagination.scss +227 -0
  224. package/styles/qppds/components/_search.scss +112 -0
  225. package/styles/qppds/components/_sidebar.scss +9 -0
  226. package/styles/qppds/components/_table.scss +139 -0
  227. package/styles/qppds/components/_tabs.scss +197 -0
  228. package/styles/qppds/components/_text-input.scss +60 -0
  229. package/styles/qppds/components/index.scss +16 -0
  230. package/styles/qppds/components/sidebar/_animations.scss +38 -0
  231. package/styles/qppds/components/sidebar/_cms.scss +61 -0
  232. package/styles/qppds/components/sidebar/_details.scss +58 -0
  233. package/styles/qppds/components/sidebar/_links.scss +444 -0
  234. package/styles/qppds/components/sidebar/_sidebar-animation.scss +125 -0
  235. package/styles/qppds/components/sidebar/_sidebar-tooltip.scss +33 -0
  236. package/styles/qppds/components/sidebar/_sidebar.scss +146 -0
  237. package/styles/qppds/components/sidebar/project-specific/_wi.scss +42 -0
  238. package/styles/qppds/index.scss +4 -0
  239. package/styles/qppds/settings/_functions.scss +4 -0
  240. package/styles/qppds/settings/_index.scss +3 -0
  241. package/styles/qppds/settings/mixins/_borders.scss +6 -0
  242. package/styles/qppds/settings/mixins/_focus.scss +28 -0
  243. package/styles/qppds/settings/mixins/_icons.scss +33 -0
  244. package/styles/qppds/settings/mixins/_index.scss +8 -0
  245. package/styles/qppds/settings/mixins/_layout.scss +74 -0
  246. package/styles/qppds/settings/mixins/_lists.scss +12 -0
  247. package/styles/qppds/settings/mixins/_table.scss +91 -0
  248. package/styles/qppds/settings/mixins/_type.scss +65 -0
  249. package/styles/qppds/settings/mixins/_visually-hidden.scss +30 -0
  250. package/styles/qppds/settings/variables/_color.scss +86 -0
  251. package/styles/qppds/settings/variables/_index.scss +3 -0
  252. package/styles/qppds/settings/variables/_layout.scss +67 -0
  253. package/styles/qppds/settings/variables/_type.scss +132 -0
  254. package/styles/qppds/utilities/_background-color.scss +134 -0
  255. package/styles/qppds/utilities/_color.scss +32 -0
  256. package/styles/qppds/utilities/_display-visibility.scss +83 -0
  257. package/styles/qppds/utilities/_flexbox.scss +240 -0
  258. package/styles/qppds/utilities/_font-family.scss +9 -0
  259. package/styles/qppds/utilities/_font-size.scss +8 -0
  260. package/styles/qppds/utilities/_font-weight.scss +8 -0
  261. package/styles/qppds/utilities/_height.scss +38 -0
  262. package/styles/qppds/utilities/_margin.scss +136 -0
  263. package/styles/qppds/utilities/_padding.scss +76 -0
  264. package/styles/qppds/utilities/_position.scss +24 -0
  265. package/styles/qppds/utilities/_text-align.scss +40 -0
  266. package/styles/qppds/utilities/_text-transform.scss +7 -0
  267. package/styles/qppds/utilities/_truncate.scss +8 -0
  268. package/styles/qppds/utilities/_width.scss +79 -0
  269. package/styles/qppds/utilities/index.scss +15 -0
  270. package/styles/qppds.scss +2 -0
  271. package/styles/third-party/_legacy-bootstrap.scss +19 -0
  272. package/styles/third-party/legacy-bootstrap/_buttons.scss +126 -0
  273. package/styles/third-party/legacy-bootstrap/_dropdowns.scss +211 -0
  274. package/styles/third-party/legacy-bootstrap/_forms.scss +510 -0
  275. package/styles/third-party/legacy-bootstrap/_grid.scss +73 -0
  276. package/styles/third-party/legacy-bootstrap/_mixins.scss +27 -0
  277. package/styles/third-party/legacy-bootstrap/_navbar.scss +644 -0
  278. package/styles/third-party/legacy-bootstrap/_navs.scss +193 -0
  279. package/styles/third-party/legacy-bootstrap/_normalize.scss +385 -0
  280. package/styles/third-party/legacy-bootstrap/_scaffolding.scss +116 -0
  281. package/styles/third-party/legacy-bootstrap/_variables.scss +858 -0
  282. package/styles/third-party/legacy-bootstrap/mixins/_buttons.scss +70 -0
  283. package/styles/third-party/legacy-bootstrap/mixins/_clearfix.scss +22 -0
  284. package/styles/third-party/legacy-bootstrap/mixins/_forms.scss +108 -0
  285. package/styles/third-party/legacy-bootstrap/mixins/_grid-framework.scss +86 -0
  286. package/styles/third-party/legacy-bootstrap/mixins/_grid.scss +112 -0
  287. package/styles/third-party/legacy-bootstrap/mixins/_labels.scss +12 -0
  288. package/styles/third-party/legacy-bootstrap/mixins/_nav-divider.scss +10 -0
  289. package/styles/third-party/legacy-bootstrap/mixins/_nav-vertical-align.scss +9 -0
  290. package/styles/third-party/legacy-bootstrap/mixins/_opacity.scss +7 -0
  291. package/styles/third-party/legacy-bootstrap/mixins/_panels.scss +30 -0
  292. package/styles/third-party/legacy-bootstrap/mixins/_reset-filter.scss +7 -0
  293. package/styles/third-party/legacy-bootstrap/mixins/_reset-text.scss +18 -0
  294. package/styles/third-party/legacy-bootstrap/mixins/_resize.scss +6 -0
  295. package/styles/third-party/legacy-bootstrap/mixins/_responsive-visibility.scss +25 -0
  296. package/styles/third-party/legacy-bootstrap/mixins/_size.scss +10 -0
  297. package/styles/third-party/legacy-bootstrap/mixins/_tab-focus.scss +9 -0
  298. package/styles/third-party/legacy-bootstrap/mixins/_table-row.scss +28 -0
  299. package/styles/third-party/legacy-bootstrap/mixins/_text-emphasis.scss +12 -0
  300. package/styles/third-party/legacy-bootstrap/mixins/_text-overflow.scss +8 -0
  301. package/styles/third-party/legacy-bootstrap/mixins/_vendor-prefixes.scss +182 -0
  302. package/styles/utility/_mixins.scss +46 -0
  303. package/styles/utility/_utilities.scss +73 -0
  304. package/test/components/Accordion.test.js +12 -0
  305. package/test/components/ErrorUI.test.js +33 -0
  306. package/test/components/FooterUI.test.js +90 -0
  307. package/test/components/HeaderUI.test.js +45 -0
  308. package/test/components/Infotip.test.js +73 -0
  309. package/test/components/SessionDialogUI.test.js +339 -0
  310. package/test/components/SideNavUI.test.js +23 -0
  311. package/test/components/Tooltip.test.js +147 -0
  312. package/test/index.test.js +8 -0
  313. package/test/session/session.test.js +221 -0
  314. package/test-setup.js +9 -0
  315. package/webpack.config.js +147 -0
  316. package/webpack.config.react.js +86 -0
@@ -0,0 +1,72 @@
1
+ import React, { Component } from 'react';
2
+ import { render } from 'react-dom';
3
+ import ReactModal from 'react-modal';
4
+ import { CloseXIcon } from '../../lib/SvgComponents.jsx';
5
+ import SanitizedContent from '../SanitizedContent';
6
+ import PropTypes from 'prop-types';
7
+
8
+ class Modal extends Component {
9
+ constructor() {
10
+ super();
11
+
12
+ this.state = {
13
+ showModal: true,
14
+ };
15
+
16
+ this.handleOpenModal = this.handleOpenModal.bind(this);
17
+ this.handleCloseModal = this.handleCloseModal.bind(this);
18
+
19
+ ReactModal.setAppElement('body > *:not(.ReactModalPortal)');
20
+ }
21
+
22
+ handleOpenModal() {
23
+ this.setState({ showModal: true });
24
+ }
25
+
26
+ handleCloseModal() {
27
+ this.setState({ showModal: false });
28
+ }
29
+
30
+ UNSAFE_componentWillReceiveProps(newProps) {
31
+ this.setState({
32
+ showModal: newProps.showModal,
33
+ });
34
+ }
35
+
36
+ render() {
37
+ return (
38
+ <div>
39
+ <ReactModal
40
+ role="alertdialog"
41
+ contentLabel={this.props.contentLabel || 'Informational Modal Dialog'}
42
+ isOpen={this.state.showModal}
43
+ className="info-modal-content"
44
+ overlayClassName="info-modal-overlay"
45
+ onRequestClose={this.handleCloseModal}
46
+ >
47
+ <button
48
+ className="closeModal"
49
+ aria-label="Close modal"
50
+ onClick={this.handleCloseModal}
51
+ >
52
+ <svg className="close-icon" aria-hidden="true">
53
+ <CloseXIcon />
54
+ </svg>
55
+ </button>
56
+ <SanitizedContent html={this.props.content} />
57
+ </ReactModal>
58
+ </div>
59
+ );
60
+ }
61
+ }
62
+
63
+ const displayModal = ({ content, main }) => {
64
+ render(<Modal content={content} showModal={true} />, main);
65
+ };
66
+
67
+ Modal.propTypes = {
68
+ contentLabel: PropTypes.string,
69
+ content: PropTypes.string,
70
+ };
71
+
72
+ export default displayModal;
@@ -0,0 +1,140 @@
1
+ import React, { useRef, useState, useEffect } from 'react';
2
+ import ReactModal from 'react-modal';
3
+ import PropTypes from 'prop-types';
4
+
5
+ import { CloseXIcon2 } from '../../lib/SvgComponents.jsx';
6
+
7
+ const Modal = ({
8
+ children,
9
+ onRequestClose,
10
+ title,
11
+ primary,
12
+ secondary,
13
+ appElement,
14
+ isOpen,
15
+ ...props
16
+ }) => {
17
+ const [overflowSeparator, setOverflowSeparator] = useState(false);
18
+ const closeButtonRef = useRef(null);
19
+ const contentRef = useRef(null);
20
+
21
+ useEffect(() => {
22
+ ReactModal.setAppElement(appElement);
23
+ }, []);
24
+
25
+ return (
26
+ <ReactModal
27
+ onRequestClose={onRequestClose}
28
+ portalClassName="qpp-c-modal"
29
+ className="qpp-c-modal__content"
30
+ overlayClassName="qpp-c-modal__overlay"
31
+ bodyOpenClassName="qpp-c-modal__body-open"
32
+ isOpen={isOpen}
33
+ onAfterOpen={() => {
34
+ // Add heading separator based on overflow
35
+ if (
36
+ contentRef.current?.scrollHeight >
37
+ document.documentElement.clientHeight * 0.6
38
+ ) {
39
+ setOverflowSeparator(true);
40
+ } else {
41
+ setOverflowSeparator(false);
42
+ }
43
+
44
+ // Set default focus
45
+ if (closeButtonRef?.current) {
46
+ closeButtonRef.current.focus();
47
+ }
48
+ }}
49
+ aria={{
50
+ labelledby: 'modal-heading',
51
+ describedby: 'modal-body',
52
+ }}
53
+ {...props}
54
+ >
55
+ <div
56
+ className={`qpp-c-modal__header ${
57
+ overflowSeparator ? 'qpp-c-modal__header-sep' : ''
58
+ }`}
59
+ >
60
+ <h3 id="modal-heading">{title}</h3>
61
+ <button
62
+ className="closex qpp-c-button qpp-c-button--text qpp-u-color--black"
63
+ aria-label="close modal"
64
+ data-testid="modal-close-btn"
65
+ onClick={onRequestClose}
66
+ ref={closeButtonRef}
67
+ >
68
+ <CloseXIcon2 />
69
+ </button>
70
+ </div>
71
+ <div
72
+ id="modal-body"
73
+ className="qpp-c-modal__body"
74
+ ref={contentRef}
75
+ tabIndex={overflowSeparator ? "0" : undefined}
76
+ >
77
+ {children}
78
+ </div>
79
+ {(primary || secondary) && (
80
+ <div className="qpp-c-modal__footer">
81
+ {secondary && (
82
+ <button
83
+ className={`qpp-c-button qpp-c-button--secondary ${
84
+ primary ? 'qpp-u-margin-right--16' : ''
85
+ }`}
86
+ loading={secondary.loading}
87
+ onClick={secondary.onClick || onRequestClose}
88
+ aria-label={secondary.title}
89
+ data-testid="modal-secondary-btn"
90
+ >
91
+ {secondary.title}
92
+ </button>
93
+ )}
94
+ {primary && (
95
+ <button
96
+ className="qpp-c-button"
97
+ loading={primary.loading}
98
+ onClick={primary.onClick}
99
+ data-testid="modal-primary-btn"
100
+ aria-label={primary.title}
101
+ >
102
+ {primary.title}
103
+ </button>
104
+ )}
105
+ </div>
106
+ )}
107
+ </ReactModal>
108
+ );
109
+ };
110
+
111
+ Modal.propTypes = {
112
+ title: PropTypes.string,
113
+ children: PropTypes.node.isRequired,
114
+ primary: PropTypes.shape({
115
+ title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
116
+ onClick: PropTypes.func,
117
+ loading: PropTypes.bool,
118
+ loadingText: PropTypes.string,
119
+ }),
120
+ secondary: PropTypes.shape({
121
+ title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
122
+ onClick: PropTypes.func,
123
+ loading: PropTypes.bool,
124
+ loadingText: PropTypes.string,
125
+ }),
126
+ onRequestClose: PropTypes.func,
127
+ appElement: PropTypes.string,
128
+ isOpen: PropTypes.bool,
129
+ };
130
+
131
+ Modal.defaultProps = {
132
+ onRequestClose: () => {},
133
+ title: undefined,
134
+ primary: null,
135
+ secondary: null,
136
+ appElement: 'body > *:not(.qpp-c-modal)',
137
+ isOpen: false,
138
+ };
139
+
140
+ export default Modal;
@@ -0,0 +1,159 @@
1
+ import React, { useState } from 'react';
2
+ import Modal from './index';
3
+ import { withKnobs, boolean, text } from '@storybook/addon-knobs';
4
+
5
+ export default {
6
+ title: 'Modal',
7
+ component: Modal,
8
+ decorators: [withKnobs],
9
+ };
10
+
11
+ const useModal = () => {
12
+ const [open, setOpen] = useState(false);
13
+ const openModal = () => setOpen(true);
14
+ const closeModal = () => setOpen(false);
15
+ return [open, openModal, closeModal];
16
+ };
17
+
18
+ export const WithDesignSystem = () => {
19
+ const [open, openModal, closeModal] = useModal();
20
+ const knobIsOpen = boolean('isOpen', false);
21
+ return (
22
+ <div className="qpp-u-padding--12">
23
+ <button className="qpp-c-button" onClick={openModal}>
24
+ Open Modal
25
+ </button>
26
+ <Modal
27
+ useDesignSystem
28
+ isOpen={open || knobIsOpen}
29
+ title={text('Title text', 'Title Text')}
30
+ onRequestClose={closeModal}
31
+ >
32
+ <p>
33
+ {text(
34
+ 'bodyText',
35
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus ac pulvinar massa, in mattis ligula. Vestibulum enim velit, ullamcorper vitae lorem ullamcorper, blandit fringilla mauris.'
36
+ )}
37
+ </p>
38
+ </Modal>
39
+ </div>
40
+ );
41
+ };
42
+
43
+ export const WithDesignSystemDialog = () => {
44
+ const [open, openModal, closeModal] = useModal();
45
+ const knobIsOpen = boolean('isOpen', false);
46
+ return (
47
+ <div className="qpp-u-padding--12">
48
+ <button className="qpp-c-button" onClick={openModal}>
49
+ Open Modal
50
+ </button>
51
+ <Modal
52
+ useDesignSystem
53
+ isOpen={open || knobIsOpen}
54
+ title={text('Title text', 'Title Text')}
55
+ onRequestClose={closeModal}
56
+ primary={{
57
+ title: 'Yes, go for it',
58
+ onClick: () => {
59
+ console.log('submit');
60
+ closeModal();
61
+ },
62
+ }}
63
+ secondary={{
64
+ title: 'No, take me back',
65
+ }}
66
+ >
67
+ <p>
68
+ {text(
69
+ 'bodyText',
70
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus ac pulvinar massa, in mattis ligula. Vestibulum enim velit, ullamcorper vitae lorem ullamcorper, blandit fringilla mauris.'
71
+ )}
72
+ </p>
73
+ </Modal>
74
+ </div>
75
+ );
76
+ };
77
+
78
+ export const WithDesignSystemOverflow = () => {
79
+ const [open, openModal, closeModal] = useModal();
80
+ const knobIsOpen = boolean('isOpen', false);
81
+ return (
82
+ <div className="qpp-u-padding--12">
83
+ <button className="qpp-c-button" onClick={openModal}>
84
+ Open Modal
85
+ </button>
86
+ <Modal
87
+ useDesignSystem
88
+ isOpen={open || knobIsOpen}
89
+ title={text('Title text', 'Title Text')}
90
+ onRequestClose={closeModal}
91
+ primary={{
92
+ title: 'Yes, go for it',
93
+ onClick: () => {
94
+ console.log('submit');
95
+ closeModal();
96
+ },
97
+ }}
98
+ secondary={{
99
+ title: 'No, take me back',
100
+ }}
101
+ >
102
+ <p>
103
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus ac
104
+ pulvinar massa, in mattis ligula. Vestibulum enim velit, ullamcorper
105
+ vitae lorem ullamcorper, blandit fringilla mauris. Aenean odio nulla,
106
+ dapibus convallis est ac, tempor congue lorem. Aliquam facilisis
107
+ tortor eget lectus gravida bibendum. Suspendisse vel posuere risus.
108
+ Etiam condimentum sollicitudin est, eget auctor lectus suscipit nec.
109
+ Duis efficitur vitae risus at malesuada. Mauris tempus turpis sed
110
+ lacus lacinia, et mattis tortor eleifend. In sit amet felis vel quam
111
+ egestas ultricies ac ac nisl. Morbi elementum dui libero, non
112
+ convallis risus ornare vel. Maecenas posuere ipsum ut sem aliquet,
113
+ quis maximus dolor cursus. Praesent vulputate orci tellus, sed
114
+ fringilla arcu mollis sit amet. Phasellus eros turpis, consequat sit
115
+ amet placerat sed, mollis ac justo.
116
+ </p>
117
+ <p>
118
+ Cras quis pharetra elit. Mauris lacinia quam quis metus mattis
119
+ lobortis vel nec arcu. Fusce nec tellus vel nunc cursus vestibulum.
120
+ Quisque pretium, arcu at sagittis posuere, leo magna tempus nunc, vel
121
+ malesuada nunc mauris ac ante. Donec arcu velit, imperdiet vitae purus
122
+ nec, imperdiet auctor nisi. Cras ornare, elit sit amet viverra
123
+ bibendum, justo dolor congue mi, quis pretium erat orci a lorem. Sed
124
+ rhoncus quam ut tellus hendrerit accumsan.
125
+ </p>
126
+ <p>
127
+ Donec varius elit vitae elit efficitur fermentum. Vestibulum ante
128
+ ipsum primis in faucibus orci luctus et ultrices posuere cubilia
129
+ curae; Nulla facilisi. Ut malesuada venenatis varius. Nunc libero
130
+ massa, luctus sagittis sapien ultrices, pulvinar venenatis purus. In
131
+ nec orci ac nibh sagittis lobortis vel sed massa. Morbi pretium odio
132
+ vitae malesuada ullamcorper. Pellentesque facilisis posuere
133
+ consectetur. Nam maximus finibus ante, non eleifend neque euismod et.
134
+ </p>
135
+ <p>
136
+ Pellentesque vehicula nec massa nec accumsan. Pellentesque non odio eu
137
+ felis hendrerit pellentesque non sed neque. Morbi dui nisl, facilisis
138
+ id gravida non, vehicula at dui. In eleifend suscipit imperdiet.
139
+ Quisque facilisis augue et mi lobortis tristique. Praesent auctor
140
+ dictum erat, non mollis ante tempus id. Integer et libero iaculis,
141
+ imperdiet neque at, pharetra nisl. Suspendisse diam massa, vehicula eu
142
+ varius ullamcorper, semper vel velit. Morbi consequat mattis nisi, id
143
+ dignissim est bibendum non. Suspendisse semper laoreet neque, ac
144
+ tincidunt tortor. Ut posuere urna a convallis consectetur. Mauris
145
+ varius cursus libero sed molestie. Duis id dignissim arcu. Mauris
146
+ tincidunt egestas congue.
147
+ </p>
148
+ <p>
149
+ Donec rutrum erat vitae nulla ultricies, quis laoreet orci consequat.
150
+ Nunc et justo volutpat dolor feugiat convallis. Vivamus efficitur nisi
151
+ quis risus eleifend, ut tincidunt turpis volutpat. Nam commodo turpis
152
+ dolor, dapibus rutrum erat facilisis a. Ut a dictum urna. Maecenas vel
153
+ egestas odio. Ut venenatis est dolor, eget ullamcorper diam finibus
154
+ et. Proin tincidunt porta pellentesque.
155
+ </p>
156
+ </Modal>
157
+ </div>
158
+ );
159
+ };
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import LegacyModal from './LegacyModal';
5
+ import DsModal from './Modal';
6
+
7
+ const Modal = ({ useDesignSystem, ...rest }) => {
8
+ if (useDesignSystem) {
9
+ return <DsModal {...rest} />;
10
+ }
11
+ return <LegacyModal {...rest} />;
12
+ };
13
+
14
+ Modal.propTypes = {
15
+ useDesignSystem: PropTypes.bool,
16
+ };
17
+ Modal.defaultProps = {
18
+ useDesignSystem: false,
19
+ };
20
+ Modal.displayName = 'Modal';
21
+
22
+ export default Modal;
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import NotificationBanner from './index';
3
+ import { withKnobs, boolean } from '@storybook/addon-knobs';
4
+
5
+ export default {
6
+ title: 'NotificationBanner',
7
+ component: NotificationBanner,
8
+ decorators: [withKnobs],
9
+ };
10
+
11
+ export const ExampleNotificationBanner = () => (
12
+ <NotificationBanner
13
+ result={{
14
+ content: 'CONTENT',
15
+ label: 'Label',
16
+ name: 'name',
17
+ color: 'blue',
18
+ dismissable: boolean('dismissable', false),
19
+ }}
20
+ />
21
+ );
22
+
23
+ ExampleNotificationBanner.storyName = 'example NotificationBanner';
@@ -0,0 +1,265 @@
1
+ import React, { Component } from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import SanitizedContent from '../SanitizedContent';
5
+ import { CloseXIcon, BellOutline } from '../../lib/SvgComponents.jsx';
6
+ import { withGetConfig } from '../hooks/useGetConfig';
7
+
8
+ class NotificationBanner extends Component {
9
+ constructor(props) {
10
+ super(props);
11
+
12
+ this.state = {
13
+ height: '100%',
14
+ expanded: true,
15
+ };
16
+
17
+ this._renderCloseButton = this.renderCloseButton.bind(this);
18
+ this._collapseNotification = this.collapseNotification.bind(this);
19
+ this._expandNotification = this.expandNotification.bind(this);
20
+ this._setBannerHeight = this.setBannerHeight.bind(this);
21
+ }
22
+
23
+ componentDidMount() {
24
+ if (window.innerWidth <= 767) {
25
+ this.setState({
26
+ expanded: false,
27
+ });
28
+ }
29
+
30
+ window.addEventListener('load', this._setBannerHeight);
31
+ window.addEventListener('resize', this._setBannerHeight);
32
+ }
33
+
34
+ // Populate state with notification from localStorage
35
+ componentDidUpdate(prevProps) {
36
+ if (prevProps.result !== this.props?.result) {
37
+ const { result: notifications } = this.props;
38
+ if (Object.keys(notifications).length > 0) {
39
+ this.setState({
40
+ expanded: this.isNotificationExpanded(notifications.content),
41
+ });
42
+ }
43
+ }
44
+ }
45
+
46
+ setBannerHeight() {
47
+ const {
48
+ result: {
49
+ content: { content },
50
+ },
51
+ } = this.props;
52
+ if (content) {
53
+ this.bannerContainer.style.transition = 'none';
54
+
55
+ this.state.expanded
56
+ ? this.collapsedBanner.setAttribute('tabIndex', '-1')
57
+ : this.collapsedBanner.setAttribute('tabIndex', ' ');
58
+
59
+ this.setState({
60
+ height: this.state.expanded
61
+ ? '100%'
62
+ : this.collapsedWrapper.offsetHeight + 'px',
63
+ });
64
+ }
65
+ }
66
+
67
+ isNotificationExpanded(notification) {
68
+ const dateDismissed = new Date(notification.dateDismissed);
69
+ const dateUpdated = new Date(notification.dateUpdated);
70
+ const notDismissed =
71
+ (this.isDateValid(dateUpdated) &&
72
+ this.isDateValid(dateDismissed) &&
73
+ dateUpdated >= dateDismissed) ||
74
+ !this.isDateValid(dateDismissed);
75
+
76
+ return notDismissed;
77
+ }
78
+
79
+ /**
80
+ * Whether the date is valid, i.e. not null
81
+ *
82
+ * @param {Date} date
83
+ * @return {Boolean}
84
+ */
85
+ isDateValid(date) {
86
+ return !isNaN(Date.parse(date));
87
+ }
88
+
89
+ /**
90
+ * Try to find this NotificationBanner instance in localStorage, or else add
91
+ * a minimal version of it, and mark it with a dateDismissed, then update
92
+ * localStorage.
93
+ */
94
+ markNotificationClosed() {
95
+ const { result } = this.props;
96
+ const dateDismissed = new Date().toISOString();
97
+ const storageNotification = result || {};
98
+
99
+ if (storageNotification && Object.keys(storageNotification).length > 0) {
100
+ window.localStorage.setItem(
101
+ 'notifications',
102
+ JSON.stringify({
103
+ ...storageNotification,
104
+ content: {
105
+ ...storageNotification.content,
106
+ dateDismissed,
107
+ },
108
+ })
109
+ );
110
+ } else {
111
+ window.localStorage.setItem(
112
+ 'notifications',
113
+ JSON.stringify({
114
+ ...storageNotification,
115
+ content: {
116
+ name: 'header-notification',
117
+ dateDismissed,
118
+ },
119
+ })
120
+ );
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Callback when the user dimisses a notification. Mark it as dismissed in
126
+ * localStorage, empty the contents, and dispatch a custom event to notify
127
+ * any observers that it has been dismissed.
128
+ */
129
+ collapseNotification(e) {
130
+ e.stopPropagation();
131
+ this.markNotificationClosed();
132
+ this.collapsedBanner.setAttribute('tabIndex', ' ');
133
+ this.bannerContainer.style.transition = 'height .2s ease-out';
134
+ this.setState({
135
+ expanded: false,
136
+ height: this.collapsedWrapper.offsetHeight + 'px',
137
+ });
138
+ }
139
+
140
+ expandNotification() {
141
+ if (!this.state.expanded) {
142
+ this.collapsedBanner.setAttribute('tabIndex', '-1');
143
+ this.bannerContainer.style.transition = 'height .2s ease-out';
144
+ this.setState({
145
+ expanded: true,
146
+ height: '100%',
147
+ });
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Renders the close button if the notification instance is dismissable.
153
+ *
154
+ * @return {String} HTML content
155
+ */
156
+ renderCloseButton() {
157
+ const {
158
+ result: {
159
+ content: { dismissable },
160
+ },
161
+ } = this.props;
162
+ if (dismissable) {
163
+ return (
164
+ <button
165
+ onClick={this._collapseNotification}
166
+ type="button"
167
+ className="notification-banner-close"
168
+ >
169
+ <svg className="close-icon" aria-hidden="true" focusable="false">
170
+ <CloseXIcon />
171
+ </svg>
172
+ <span className="sr-only">Close</span>
173
+ </button>
174
+ );
175
+ } else {
176
+ return '';
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Render the notification if it has any contents.
182
+ *
183
+ * @return {String} HTML content
184
+ */
185
+ render() {
186
+ const { expanded, height } = this.state;
187
+ const { result: { content: { enabled, content } = {} } = {} } = this.props;
188
+ if (!content || !enabled) {
189
+ return <div id="notification-banner" />;
190
+ } else {
191
+ const notificationStatus = expanded ? 'expanded' : 'collapsed';
192
+ const { color = 'blue', label = 'Update' } = content;
193
+ const className = `notification-banner notification-banner-${color} ${notificationStatus}`;
194
+
195
+ return (
196
+ <div
197
+ style={{ height }}
198
+ ref={(elem) => (this.bannerContainer = elem)}
199
+ className={className}
200
+ >
201
+ <div
202
+ ref={(elem) => (this.expandedWrapper = elem)}
203
+ className={`notification-banner-wrapper expanded-view`}
204
+ >
205
+ <div className="notification-banner-label">{label}</div>
206
+ <div className="notification-banner-content">
207
+ <SanitizedContent
208
+ html={expanded ? content : '<p>Nothing to see here</p>'}
209
+ />
210
+ </div>
211
+ {expanded && this._renderCloseButton()}
212
+ </div>
213
+
214
+ <div
215
+ ref={(elem) => (this.collapsedWrapper = elem)}
216
+ className={`notification-banner-wrapper collapsed-view`}
217
+ onClick={this._expandNotification}
218
+ >
219
+ <button
220
+ ref={(elem) => (this.collapsedBanner = elem)}
221
+ className={`notification-banner-label`}
222
+ >
223
+ {label}
224
+ <span className="bell-icon">
225
+ <svg aria-hidden="true" focusable="false">
226
+ <BellOutline />
227
+ </svg>
228
+ </span>
229
+ </button>
230
+ </div>
231
+ </div>
232
+ );
233
+ }
234
+ }
235
+ }
236
+
237
+ NotificationBanner.propTypes = {
238
+ result: PropTypes.shape({
239
+ content: PropTypes.shape({
240
+ content: PropTypes.string,
241
+ label: PropTypes.string,
242
+ color: PropTypes.string,
243
+ dismissable: PropTypes.bool,
244
+ }),
245
+ }),
246
+ };
247
+
248
+ NotificationBanner.defaultProps = {
249
+ result: {
250
+ content: {
251
+ content: null,
252
+ label: 'Update',
253
+ color: 'blue',
254
+ dismissable: true,
255
+ },
256
+ },
257
+ };
258
+
259
+ export default withGetConfig(NotificationBanner, {
260
+ timeout: 5,
261
+ url: '/config/notification',
262
+ localStorageName: 'active_notification',
263
+ equalityCheckExclude: ['dateDismissed', 'dateUpdated'],
264
+ defaultContent: {},
265
+ });