@madie/madie-design-system 2.0.0 → 2.0.1
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.
- package/.babelrc +14 -2
- package/.storybook/preview.js +1 -1
- package/components/Accordion/index.jsx +32 -36
- package/components/AutoComplete/AutoComplete.jsx +1 -16
- package/components/Button/Button.stories.js +0 -5
- package/components/Button/index.js +34 -44
- package/components/DateField/DateField.jsx +1 -14
- package/components/DateTimeField/DateTimeField.jsx +1 -9
- package/components/InputLabel/index.jsx +1 -6
- package/components/Instant/index.jsx +0 -16
- package/components/Link/index.js +42 -0
- package/components/MadieAlert/index.jsx +1 -40
- package/components/MadieConfirmDialog/MadieConfirmDialog.stories.js +0 -5
- package/components/MadieConfirmDialog/index.jsx +0 -8
- package/components/MadieDeleteDialog/MadieDeleteDialog.stories.js +0 -6
- package/components/MadieDeleteDialog/index.jsx +0 -7
- package/components/MadieDialog/MadieDialog.stories.js +0 -5
- package/components/MadieDialog/index.jsx +0 -21
- package/components/MadieDiscardDialog/MadieDiscardDialog.stories.js +0 -5
- package/components/MadieDiscardDialog/index.jsx +0 -7
- package/components/MadieSpinner/MadieSpinner.stories.js +0 -6
- package/components/MadieSpinner/index.jsx +0 -5
- package/components/MadieTooltip/index.jsx +0 -6
- package/components/MadieTooltipIcon/index.jsx +0 -5
- package/components/Modal/Modal.jsx +31 -43
- package/components/Modal/index.jsx +6 -8
- package/components/NumberInput/NumberInput.stories.js +0 -5
- package/components/NumberInput/index.jsx +0 -13
- package/components/Pagination/index.jsx +0 -17
- package/components/Popover/index.js +0 -23
- package/components/RadioButton/RadioButton.jsx +1 -13
- package/components/ReadOnlyTextField/ReadOnlyTextField.stories.js +0 -6
- package/components/ReadOnlyTextField/index.jsx +0 -8
- package/components/RichTextEditor/index.jsx +0 -15
- package/components/SanitizedContent/index.jsx +0 -8
- package/components/Search/index.js +26 -40
- package/components/Select/Select.stories.js +0 -4
- package/components/Select/index.jsx +1 -16
- package/components/Tabs/Tab.js +29 -25
- package/components/Tabs/TabPanel.js +15 -19
- package/components/Tabs/Tabs.stories.js +14 -50
- package/components/Tabs/index.js +10 -28
- package/components/TextArea/TextArea.stories.js +0 -6
- package/components/TextArea/index.jsx +1 -13
- package/components/TextField/TextField.stories.js +0 -6
- package/components/TextField/index.jsx +1 -16
- package/components/TimeField/TimeField.jsx +0 -10
- package/components/Toast/index.jsx +0 -14
- package/components/TruncateText/index.js +0 -8
- package/components/{index.js → index.mjs} +37 -33
- package/dist/index.js +3 -1
- package/dist/index.js.LICENSE.txt +16 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.html +1 -1
- package/dist/react/index.mjs +103 -0
- package/dist/react/{index.js.LICENSE.txt → index.mjs.LICENSE.txt} +0 -52
- package/dist/react/index.mjs.map +1 -0
- package/dist/react/package.json +1 -0
- package/{eslint.config.js → eslint.config.cjs} +3 -0
- package/jest.config.mjs +10 -0
- package/lib/Chevron.jsx +2 -10
- package/package.json +17 -10
- package/styles/_main.scss +4 -3
- package/test/components/Button.test.js +6 -16
- package/test/components/Search.test.js +1 -1
- package/test/components/TruncateText.test.js +1 -1
- package/webpack.config.mjs +79 -0
- package/webpack.config.react.mjs +72 -0
- package/components/NotificationBanner/NotificationBanner.stories.js +0 -29
- package/components/NotificationBanner/index.js +0 -279
- package/components/UnwrappedSpinner/index.js +0 -8
- package/dist/browser.js +0 -1
- package/dist/react/index.js +0 -316
- package/dist/react/index.js.map +0 -1
- package/jest.config.js +0 -17
- package/webpack.config.js +0 -123
- package/webpack.config.react.js +0 -104
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "type": "module" }
|
|
@@ -28,6 +28,7 @@ module.exports = defineConfig([{
|
|
|
28
28
|
ecmaFeatures: {
|
|
29
29
|
jsx: true,
|
|
30
30
|
},
|
|
31
|
+
ecmaVersion: "latest",
|
|
31
32
|
},
|
|
32
33
|
|
|
33
34
|
globals: {
|
|
@@ -46,6 +47,8 @@ module.exports = defineConfig([{
|
|
|
46
47
|
"linebreak-style": ["error", "unix"],
|
|
47
48
|
"no-console": 0,
|
|
48
49
|
"no-prototype-builtins": [0],
|
|
50
|
+
"react/prop-types": "off"
|
|
51
|
+
|
|
49
52
|
},
|
|
50
53
|
|
|
51
54
|
settings: {
|
package/jest.config.mjs
ADDED
package/lib/Chevron.jsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import PropTypes from "prop-types";
|
|
3
2
|
|
|
4
|
-
export const Chevron = (props) => {
|
|
3
|
+
export const Chevron = ({ className = "accordion-icon", ...props }) => {
|
|
4
|
+
|
|
5
5
|
return (
|
|
6
6
|
<svg
|
|
7
7
|
width="10px"
|
|
@@ -22,11 +22,3 @@ export const Chevron = (props) => {
|
|
|
22
22
|
</svg>
|
|
23
23
|
);
|
|
24
24
|
};
|
|
25
|
-
|
|
26
|
-
Chevron.propTypes = {
|
|
27
|
-
className: PropTypes.string,
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
Chevron.defaultProps = {
|
|
31
|
-
className: "accordion-icon",
|
|
32
|
-
};
|
package/package.json
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@madie/madie-design-system",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Shared style guide across the Madie program.",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
5
|
"homepage": "https://github.com/MeasureAuthoringTool/madie-design-system",
|
|
6
|
+
"main": "./components/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
"import": "./dist/react/index.mjs",
|
|
9
|
+
"require": "./components/index.js",
|
|
10
|
+
"default": "./components/index.js"
|
|
11
|
+
},
|
|
7
12
|
"scripts": {
|
|
8
13
|
"prepublishOnly": "npm run build",
|
|
9
14
|
"build": "npm run clean && webpack-cli --progress --color --env production && npm run build-react",
|
|
10
15
|
"dev": "npm run clean && webpack-cli --progress --color --watch --env production=false",
|
|
11
16
|
"clean": "rimraf build && rimraf dist",
|
|
12
|
-
"build-react": "webpack --progress --config webpack.config.react.
|
|
13
|
-
"build-react-watch": "webpack --progress --watch --config webpack.config.react.
|
|
17
|
+
"build-react": "webpack --progress --config webpack.config.react.mjs && echo '{ \"type\": \"module\" }' > dist/react/package.json",
|
|
18
|
+
"build-react-watch": "webpack --progress --watch --config webpack.config.react.mjs",
|
|
14
19
|
"start": "npm run build",
|
|
15
|
-
"test": "jest",
|
|
20
|
+
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
|
|
16
21
|
"watch-tests": "cross-env BABEL_ENV=test jest --watch",
|
|
17
22
|
"coverage": "cross-env BABEL_ENV=test jest --coverage",
|
|
18
23
|
"precommit": "npm run format && npm run lint && npm run format-styles",
|
|
@@ -25,7 +30,8 @@
|
|
|
25
30
|
"storybook": "storybook dev -p 6007",
|
|
26
31
|
"build-storybook": "storybook build",
|
|
27
32
|
"predeploy": "npm run build-storybook",
|
|
28
|
-
"deploy": "gh-pages -d storybook-static"
|
|
33
|
+
"deploy": "gh-pages -d storybook-static",
|
|
34
|
+
"check-es": "es-check es2020 './dist/react/index.mjs' --module"
|
|
29
35
|
},
|
|
30
36
|
"devDependencies": {
|
|
31
37
|
"@babel/eslint-parser": "^7.27.5",
|
|
@@ -45,7 +51,8 @@
|
|
|
45
51
|
"d3-selection": "^1.4.2",
|
|
46
52
|
"d3-shape": "^1.3.7",
|
|
47
53
|
"enzyme": "^3.11.0",
|
|
48
|
-
"
|
|
54
|
+
"es-check": "^9.1.4",
|
|
55
|
+
"eslint": "^9.30.1",
|
|
49
56
|
"eslint-plugin-react": "^7.33.2",
|
|
50
57
|
"globals": "^16.2.0",
|
|
51
58
|
"jest": "^30.0.0",
|
|
@@ -77,15 +84,16 @@
|
|
|
77
84
|
"url": "https://github.com/MeasureAuthoringTool/madie-design-system/issues"
|
|
78
85
|
},
|
|
79
86
|
"dependencies": {
|
|
87
|
+
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
80
88
|
"@babel/plugin-transform-class-properties": "^7.27.1",
|
|
81
89
|
"@babel/plugin-transform-modules-umd": "^7.27.1",
|
|
82
90
|
"@babel/preset-env": "^7.27.2",
|
|
83
91
|
"@babel/preset-react": "^7.27.1",
|
|
84
|
-
"@cmsgov/design-system": "^
|
|
92
|
+
"@cmsgov/design-system": "^12.4.0",
|
|
85
93
|
"@emotion/react": "^11.11.1",
|
|
86
94
|
"@emotion/styled": "^11.11.0",
|
|
87
95
|
"@mui/icons-material": "^7.1.1",
|
|
88
|
-
"@mui/material": "7.
|
|
96
|
+
"@mui/material": "^7.2.0",
|
|
89
97
|
"@mui/system": "^7.1.1",
|
|
90
98
|
"@mui/x-date-pickers": "7.29.4",
|
|
91
99
|
"@tiptap/extension-color": "^2.11.7",
|
|
@@ -106,7 +114,6 @@
|
|
|
106
114
|
"jwt-decode": "^3.1.2",
|
|
107
115
|
"lodash": "^4.17.21",
|
|
108
116
|
"preact": "^10.6.4",
|
|
109
|
-
"prop-types": "^15.8.1",
|
|
110
117
|
"react": "19.1.0",
|
|
111
118
|
"react-dom": "19.1.0",
|
|
112
119
|
"react-draggable": "^4.4.6",
|
package/styles/_main.scss
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
@charset "utf-8";
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// CMS DS v12 uses `/src/styles/index` for SCSS sources not `dist/scss`
|
|
4
|
+
// @import "~@cmsgov/design-system/src/styles/index"; // way that npm module says to do it
|
|
5
|
+
@import '@cmsgov/design-system/dist/css/index.css'; // precompiled css instead to make it work since there's some extra webpack stuff going on.
|
|
6
|
+
|
|
4
7
|
$font-path: "~@cmsgov/design-system/dist/fonts";
|
|
5
8
|
$image-path: "~@cmsgov/design-system/dist/images";
|
|
6
9
|
|
|
7
|
-
@import "@cmsgov/design-system/dist/scss/index";
|
|
8
|
-
|
|
9
10
|
@import "paths";
|
|
10
11
|
|
|
11
12
|
@import "third-party/legacy-bootstrap";
|
|
@@ -56,21 +56,11 @@ describe("Button", () => {
|
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
it("Default props are as expected", async () => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
expect(
|
|
63
|
-
|
|
64
|
-
expect(className).
|
|
65
|
-
const href = Button.defaultProps.href;
|
|
66
|
-
expect(href).toBe("");
|
|
67
|
-
const loading = Button.defaultProps.loading;
|
|
68
|
-
expect(loading).toBe(false);
|
|
69
|
-
const size = Button.defaultProps.size;
|
|
70
|
-
expect(size).toBe(null);
|
|
71
|
-
const type = Button.defaultProps.type;
|
|
72
|
-
expect(type).toBe("button");
|
|
73
|
-
const variant = Button.defaultProps.variant;
|
|
74
|
-
expect(variant).toBe(null);
|
|
59
|
+
render(<Button>Click me</Button>);
|
|
60
|
+
const button = screen.getByRole("button", { name: /click me/i });
|
|
61
|
+
expect(button).toHaveAttribute("type", "button");
|
|
62
|
+
expect(button).not.toBeDisabled();
|
|
63
|
+
expect(button).toHaveTextContent("Click me");
|
|
64
|
+
expect(button.className).toContain("");
|
|
75
65
|
});
|
|
76
66
|
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
|
4
|
+
import TerserPlugin from "terser-webpack-plugin";
|
|
5
|
+
// import pkg from "./package.json" assert { type: "json" };
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
|
|
10
|
+
const entry = ["./index.js"];
|
|
11
|
+
|
|
12
|
+
const _module = {
|
|
13
|
+
rules: [
|
|
14
|
+
{
|
|
15
|
+
test: /\.(js|jsx)?$/,
|
|
16
|
+
exclude: /node_modules/,
|
|
17
|
+
use: {
|
|
18
|
+
loader: "babel-loader"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
test: /\.scss$/,
|
|
23
|
+
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
|
|
24
|
+
exclude: /node_modules/,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
test: /\.(ttf|eot|woff|otf|woff2)$/,
|
|
28
|
+
loader: "file-loader",
|
|
29
|
+
options: {
|
|
30
|
+
name: "fonts/[name].[ext]",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
test: /\.svg$/,
|
|
35
|
+
use: ["raw-loader", "svgo-loader"],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const plugins = [
|
|
41
|
+
new MiniCssExtractPlugin({ filename: "style.css" })
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const resolve = {
|
|
45
|
+
extensions: [".js", ".jsx", ".json", ".svg"],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const optimization = {
|
|
49
|
+
minimize: true,
|
|
50
|
+
minimizer: [new TerserPlugin()],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export default {
|
|
54
|
+
mode: "production",
|
|
55
|
+
entry,
|
|
56
|
+
output: {
|
|
57
|
+
path: path.resolve(__dirname, "dist"),
|
|
58
|
+
filename: "index.js",
|
|
59
|
+
module: true,
|
|
60
|
+
library: {
|
|
61
|
+
type: "module",
|
|
62
|
+
},
|
|
63
|
+
environment: {
|
|
64
|
+
module: true,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
experiments: {
|
|
68
|
+
outputModule: true,
|
|
69
|
+
},
|
|
70
|
+
devtool: "source-map",
|
|
71
|
+
module: _module,
|
|
72
|
+
plugins,
|
|
73
|
+
resolve,
|
|
74
|
+
optimization,
|
|
75
|
+
externals: {
|
|
76
|
+
react: "react",
|
|
77
|
+
"react-dom": "react-dom"
|
|
78
|
+
}
|
|
79
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
|
3
|
+
import TerserPlugin from "terser-webpack-plugin";
|
|
4
|
+
import HtmlWebpackPlugin from "html-webpack-plugin";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
|
|
10
|
+
const _module = {
|
|
11
|
+
rules: [
|
|
12
|
+
{
|
|
13
|
+
test: /\.(js|jsx)?$/,
|
|
14
|
+
exclude: /node_modules/,
|
|
15
|
+
use: {
|
|
16
|
+
loader: "babel-loader"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
test: /\.scss$/,
|
|
21
|
+
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
|
|
22
|
+
exclude: /node_modules/,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
test: /\.(ttf|eot|woff|otf|woff2)$/,
|
|
26
|
+
loader: "file-loader",
|
|
27
|
+
options: {
|
|
28
|
+
name: "fonts/[name].[ext]",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
test: /\.svg$/,
|
|
33
|
+
use: ["raw-loader", "svgo-loader"],
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default {
|
|
39
|
+
mode: "production",
|
|
40
|
+
entry: "./components/index.mjs",
|
|
41
|
+
output: {
|
|
42
|
+
filename: "index.mjs",
|
|
43
|
+
path: path.resolve(__dirname, "dist/react"),
|
|
44
|
+
module: true,
|
|
45
|
+
library: {
|
|
46
|
+
type: "module",
|
|
47
|
+
},
|
|
48
|
+
environment: {
|
|
49
|
+
module: true,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
experiments: {
|
|
53
|
+
outputModule: true,
|
|
54
|
+
},
|
|
55
|
+
devtool: "source-map",
|
|
56
|
+
module: _module,
|
|
57
|
+
plugins: [
|
|
58
|
+
new MiniCssExtractPlugin({ filename: "style.css" }),
|
|
59
|
+
new HtmlWebpackPlugin(),
|
|
60
|
+
],
|
|
61
|
+
resolve: {
|
|
62
|
+
extensions: [".js", ".jsx", ".json", ".svg"],
|
|
63
|
+
},
|
|
64
|
+
optimization: {
|
|
65
|
+
minimize: true,
|
|
66
|
+
minimizer: [new TerserPlugin()],
|
|
67
|
+
},
|
|
68
|
+
externals: {
|
|
69
|
+
react: "react",
|
|
70
|
+
"react-dom": "react-dom"
|
|
71
|
+
}
|
|
72
|
+
};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import NotificationBanner from "./index";
|
|
3
|
-
|
|
4
|
-
export default {
|
|
5
|
-
title: "NotificationBanner",
|
|
6
|
-
component: NotificationBanner,
|
|
7
|
-
args: {
|
|
8
|
-
result: {
|
|
9
|
-
content: "CONTENT",
|
|
10
|
-
label: "Label",
|
|
11
|
-
name: "name",
|
|
12
|
-
color: "blue",
|
|
13
|
-
dismissable: false,
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
argTypes: {
|
|
17
|
-
result: {
|
|
18
|
-
control: "object",
|
|
19
|
-
},
|
|
20
|
-
"result.dismissable": {
|
|
21
|
-
control: "boolean",
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const Template = (args) => <NotificationBanner {...args} />;
|
|
27
|
-
|
|
28
|
-
export const ExampleNotificationBanner = Template.bind({});
|
|
29
|
-
ExampleNotificationBanner.storyName = "example NotificationBanner";
|
|
@@ -1,279 +0,0 @@
|
|
|
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(
|
|
41
|
-
notifications.content,
|
|
42
|
-
),
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
setBannerHeight() {
|
|
49
|
-
const {
|
|
50
|
-
result: {
|
|
51
|
-
content: { content },
|
|
52
|
-
},
|
|
53
|
-
} = this.props;
|
|
54
|
-
if (content) {
|
|
55
|
-
this.bannerContainer.style.transition = "none";
|
|
56
|
-
|
|
57
|
-
this.state.expanded
|
|
58
|
-
? this.collapsedBanner.setAttribute("tabIndex", "-1")
|
|
59
|
-
: this.collapsedBanner.setAttribute("tabIndex", " ");
|
|
60
|
-
|
|
61
|
-
this.setState({
|
|
62
|
-
height: this.state.expanded
|
|
63
|
-
? "100%"
|
|
64
|
-
: this.collapsedWrapper.offsetHeight + "px",
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
isNotificationExpanded(notification) {
|
|
70
|
-
const dateDismissed = new Date(notification.dateDismissed);
|
|
71
|
-
const dateUpdated = new Date(notification.dateUpdated);
|
|
72
|
-
const notDismissed =
|
|
73
|
-
(this.isDateValid(dateUpdated) &&
|
|
74
|
-
this.isDateValid(dateDismissed) &&
|
|
75
|
-
dateUpdated >= dateDismissed) ||
|
|
76
|
-
!this.isDateValid(dateDismissed);
|
|
77
|
-
|
|
78
|
-
return notDismissed;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Whether the date is valid, i.e. not null
|
|
83
|
-
*
|
|
84
|
-
* @param {Date} date
|
|
85
|
-
* @return {Boolean}
|
|
86
|
-
*/
|
|
87
|
-
isDateValid(date) {
|
|
88
|
-
return !isNaN(Date.parse(date));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Try to find this NotificationBanner instance in localStorage, or else add
|
|
93
|
-
* a minimal version of it, and mark it with a dateDismissed, then update
|
|
94
|
-
* localStorage.
|
|
95
|
-
*/
|
|
96
|
-
markNotificationClosed() {
|
|
97
|
-
const { result } = this.props;
|
|
98
|
-
const dateDismissed = new Date().toISOString();
|
|
99
|
-
const storageNotification = result || {};
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
storageNotification &&
|
|
103
|
-
Object.keys(storageNotification).length > 0
|
|
104
|
-
) {
|
|
105
|
-
window.localStorage.setItem(
|
|
106
|
-
"notifications",
|
|
107
|
-
JSON.stringify({
|
|
108
|
-
...storageNotification,
|
|
109
|
-
content: {
|
|
110
|
-
...storageNotification.content,
|
|
111
|
-
dateDismissed,
|
|
112
|
-
},
|
|
113
|
-
}),
|
|
114
|
-
);
|
|
115
|
-
} else {
|
|
116
|
-
window.localStorage.setItem(
|
|
117
|
-
"notifications",
|
|
118
|
-
JSON.stringify({
|
|
119
|
-
...storageNotification,
|
|
120
|
-
content: {
|
|
121
|
-
name: "header-notification",
|
|
122
|
-
dateDismissed,
|
|
123
|
-
},
|
|
124
|
-
}),
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Callback when the user dimisses a notification. Mark it as dismissed in
|
|
131
|
-
* localStorage, empty the contents, and dispatch a custom event to notify
|
|
132
|
-
* any observers that it has been dismissed.
|
|
133
|
-
*/
|
|
134
|
-
collapseNotification(e) {
|
|
135
|
-
e.stopPropagation();
|
|
136
|
-
this.markNotificationClosed();
|
|
137
|
-
this.collapsedBanner.setAttribute("tabIndex", " ");
|
|
138
|
-
this.bannerContainer.style.transition = "height .2s ease-out";
|
|
139
|
-
this.setState({
|
|
140
|
-
expanded: false,
|
|
141
|
-
height: this.collapsedWrapper.offsetHeight + "px",
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
expandNotification() {
|
|
146
|
-
if (!this.state.expanded) {
|
|
147
|
-
this.collapsedBanner.setAttribute("tabIndex", "-1");
|
|
148
|
-
this.bannerContainer.style.transition = "height .2s ease-out";
|
|
149
|
-
this.setState({
|
|
150
|
-
expanded: true,
|
|
151
|
-
height: "100%",
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Renders the close button if the notification instance is dismissable.
|
|
158
|
-
*
|
|
159
|
-
* @return {String} HTML content
|
|
160
|
-
*/
|
|
161
|
-
renderCloseButton() {
|
|
162
|
-
const {
|
|
163
|
-
result: {
|
|
164
|
-
content: { dismissable },
|
|
165
|
-
},
|
|
166
|
-
} = this.props;
|
|
167
|
-
if (dismissable) {
|
|
168
|
-
return (
|
|
169
|
-
<button
|
|
170
|
-
onClick={this._collapseNotification}
|
|
171
|
-
type="button"
|
|
172
|
-
className="notification-banner-close"
|
|
173
|
-
>
|
|
174
|
-
<svg
|
|
175
|
-
className="close-icon"
|
|
176
|
-
aria-hidden="true"
|
|
177
|
-
focusable="false"
|
|
178
|
-
>
|
|
179
|
-
<CloseXIcon />
|
|
180
|
-
</svg>
|
|
181
|
-
<span className="sr-only">Close</span>
|
|
182
|
-
</button>
|
|
183
|
-
);
|
|
184
|
-
} else {
|
|
185
|
-
return "";
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Render the notification if it has any contents.
|
|
191
|
-
*
|
|
192
|
-
* @return {String} HTML content
|
|
193
|
-
*/
|
|
194
|
-
render() {
|
|
195
|
-
const { expanded, height } = this.state;
|
|
196
|
-
const { result: { content: { enabled, content } = {} } = {} } =
|
|
197
|
-
this.props;
|
|
198
|
-
if (!content || !enabled) {
|
|
199
|
-
return <div id="notification-banner" />;
|
|
200
|
-
} else {
|
|
201
|
-
const notificationStatus = expanded ? "expanded" : "collapsed";
|
|
202
|
-
const { color = "blue", label = "Update" } = content;
|
|
203
|
-
const className = `notification-banner notification-banner-${color} ${notificationStatus}`;
|
|
204
|
-
|
|
205
|
-
return (
|
|
206
|
-
<div
|
|
207
|
-
style={{ height }}
|
|
208
|
-
ref={(elem) => (this.bannerContainer = elem)}
|
|
209
|
-
className={className}
|
|
210
|
-
>
|
|
211
|
-
<div
|
|
212
|
-
ref={(elem) => (this.expandedWrapper = elem)}
|
|
213
|
-
className={`notification-banner-wrapper expanded-view`}
|
|
214
|
-
>
|
|
215
|
-
<div className="notification-banner-label">{label}</div>
|
|
216
|
-
<div className="notification-banner-content">
|
|
217
|
-
<SanitizedContent
|
|
218
|
-
html={
|
|
219
|
-
expanded
|
|
220
|
-
? content
|
|
221
|
-
: "<p>Nothing to see here</p>"
|
|
222
|
-
}
|
|
223
|
-
/>
|
|
224
|
-
</div>
|
|
225
|
-
{expanded && this._renderCloseButton()}
|
|
226
|
-
</div>
|
|
227
|
-
|
|
228
|
-
<div
|
|
229
|
-
ref={(elem) => (this.collapsedWrapper = elem)}
|
|
230
|
-
className={`notification-banner-wrapper collapsed-view`}
|
|
231
|
-
onClick={this._expandNotification}
|
|
232
|
-
>
|
|
233
|
-
<button
|
|
234
|
-
ref={(elem) => (this.collapsedBanner = elem)}
|
|
235
|
-
className={`notification-banner-label`}
|
|
236
|
-
>
|
|
237
|
-
{label}
|
|
238
|
-
<span className="bell-icon">
|
|
239
|
-
<svg aria-hidden="true" focusable="false">
|
|
240
|
-
<BellOutline />
|
|
241
|
-
</svg>
|
|
242
|
-
</span>
|
|
243
|
-
</button>
|
|
244
|
-
</div>
|
|
245
|
-
</div>
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
NotificationBanner.propTypes = {
|
|
252
|
-
result: PropTypes.shape({
|
|
253
|
-
content: PropTypes.shape({
|
|
254
|
-
content: PropTypes.string,
|
|
255
|
-
label: PropTypes.string,
|
|
256
|
-
color: PropTypes.string,
|
|
257
|
-
dismissable: PropTypes.bool,
|
|
258
|
-
}),
|
|
259
|
-
}),
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
NotificationBanner.defaultProps = {
|
|
263
|
-
result: {
|
|
264
|
-
content: {
|
|
265
|
-
content: null,
|
|
266
|
-
label: "Update",
|
|
267
|
-
color: "blue",
|
|
268
|
-
dismissable: true,
|
|
269
|
-
},
|
|
270
|
-
},
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
export default withGetConfig(NotificationBanner, {
|
|
274
|
-
timeout: 5,
|
|
275
|
-
url: "/config/notification",
|
|
276
|
-
localStorageName: "active_notification",
|
|
277
|
-
equalityCheckExclude: ["dateDismissed", "dateUpdated"],
|
|
278
|
-
defaultContent: {},
|
|
279
|
-
});
|