@madgex/design-system 13.0.4 → 13.2.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.
- package/dist/assets/icons.json +1 -1
- package/dist/css/index.css +1 -1
- package/dist/js/components/mds-timeout-dialog-standalone.js +3 -0
- package/dist/js/index-fractal.js +2 -1
- package/dist/js/index.js +1 -1
- package/dist/js/timeout-dialog-DuVVAWa3.js +23 -0
- package/package.json +2 -1
- package/src/components/card/README.md +4 -0
- package/src/components/card/_template.njk +25 -16
- package/src/components/card/card-link.js +24 -0
- package/src/components/card/card.config.js +28 -0
- package/src/components/card/card.njk +4 -3
- package/src/components/card/card.scss +23 -1
- package/src/components/timeout-dialog/README.md +44 -0
- package/src/components/timeout-dialog/mds-timeout-dialog-standalone.js +34 -0
- package/src/components/timeout-dialog/timeout-dialog-standalone.scss +3 -0
- package/src/components/timeout-dialog/timeout-dialog.config.js +6 -0
- package/src/components/timeout-dialog/timeout-dialog.js +189 -0
- package/src/components/timeout-dialog/timeout-dialog.njk +103 -0
- package/src/components/timeout-dialog/timeout-dialog.scss +13 -0
- package/src/js/index-fractal.js +2 -1
- package/src/js/index.js +8 -0
- package/src/scss/components/__index.scss +1 -0
- package/src/scss/core/_defaults.scss +11 -5
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
const ONE_MINUTE_IN_MS = 60000;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Displays a Dialog box when timeout is about to expire. (representing User's Auth session (auth cookie)).
|
|
5
|
+
*
|
|
6
|
+
* This expiry time is based on attribute passed `timeout-in-milliseconds`,
|
|
7
|
+
* ⚠️ We dont actually read any real cookie, but assume the page rendered "timeout time" is the same as the cookie! cookie is not readable by JS ⚠️
|
|
8
|
+
*
|
|
9
|
+
* A warning Dialog is show 2 minutes before expiry (configurable via attribute `warning-in-milliseconds`).
|
|
10
|
+
* The warning dialog has a button which triggers an XHR to try and refresh the auth cookie by making a background request. Assumes 200 was successful auth refresh.
|
|
11
|
+
*
|
|
12
|
+
* On expiry, final Dialog is show displaying your message, e.g. "you have been signed out", with a close button
|
|
13
|
+
*
|
|
14
|
+
*/
|
|
15
|
+
export class MdsTimeoutDialog extends HTMLElement {
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
|
|
19
|
+
/** good practice to "bind" event listener functions, so `this` always works inside them */
|
|
20
|
+
this.onClickSignInButton = this.onClickSignInButton.bind(this);
|
|
21
|
+
this.onSignedOutDialogClose = this.onSignedOutDialogClose.bind(this);
|
|
22
|
+
}
|
|
23
|
+
connectedCallback() {
|
|
24
|
+
this.start();
|
|
25
|
+
}
|
|
26
|
+
disconnectedCallback() {
|
|
27
|
+
this.destroy();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get rootNode() {
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get _document() {
|
|
35
|
+
return this.getRootNode({ composed: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* template when there's still time remaining.
|
|
40
|
+
*
|
|
41
|
+
* closedby="none" means it does not close with `esc` key, we want the user to make a decision via the dialog buttons
|
|
42
|
+
*/
|
|
43
|
+
get template() {
|
|
44
|
+
const template = this._document.createElement('template');
|
|
45
|
+
const titleId = `mds-timeout-dialog-title-${window.crypto.randomUUID()}`;
|
|
46
|
+
template.innerHTML = /* HTML */ `
|
|
47
|
+
<dialog class="mds-timeout-dialog" role="alertdialog" closedby="none" aria-labelledby="${titleId}">
|
|
48
|
+
<h2 class="mds-margin-bottom-b4" id="${titleId}">${this.translations['title']}</h2>
|
|
49
|
+
<p>${this.translations['message']}</p>
|
|
50
|
+
<p class="mds-message mds-message--error mds-display-none">${this.translations['message-error-sign-in']}</p>
|
|
51
|
+
<div class="mds-grid-row mds-grid-center">
|
|
52
|
+
<button class="mds-button mds-margin-right-b4" stay-signed-in-trigger type="button">
|
|
53
|
+
${this.translations['btn-stay-signed-in']}
|
|
54
|
+
</button>
|
|
55
|
+
<a class="mds-button mds-button--neutral" href=${this.logoutUrl}>${this.translations['btn-sign-out']}</a>
|
|
56
|
+
</div>
|
|
57
|
+
</dialog>
|
|
58
|
+
`;
|
|
59
|
+
return template;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* template used when time has run out
|
|
63
|
+
*/
|
|
64
|
+
get templateSignedOut() {
|
|
65
|
+
const template = this._document.createElement('template');
|
|
66
|
+
const titleId = `mds-timeout-dialog-title-${window.crypto.randomUUID()}`;
|
|
67
|
+
template.innerHTML = /* HTML */ `
|
|
68
|
+
<dialog role="alertdialog" class="mds-timeout-dialog" aria-labelledby="${titleId}">
|
|
69
|
+
<h2 class="mds-margin-bottom-b4" id="${titleId}">${this.translations['title-signed-out']}</h2>
|
|
70
|
+
<div class="mds-grid-row mds-grid-center">
|
|
71
|
+
<!-- will "submit" the dialog, e.g. close it without JS -->
|
|
72
|
+
<form method="dialog">
|
|
73
|
+
<button class="mds-button">${this.translations['btn-close']}</button>
|
|
74
|
+
</form>
|
|
75
|
+
</div>
|
|
76
|
+
</dialog>
|
|
77
|
+
`;
|
|
78
|
+
return template;
|
|
79
|
+
}
|
|
80
|
+
get elDialog() {
|
|
81
|
+
return this.rootNode.querySelector('dialog');
|
|
82
|
+
}
|
|
83
|
+
get elSignInButton() {
|
|
84
|
+
return this.rootNode.querySelector('[stay-signed-in-trigger]');
|
|
85
|
+
}
|
|
86
|
+
get elMessageError() {
|
|
87
|
+
return this.rootNode.querySelector('.mds-message--error');
|
|
88
|
+
}
|
|
89
|
+
get translations() {
|
|
90
|
+
return {
|
|
91
|
+
title: this.getAttribute('t-title') || 'You are about to be signed out',
|
|
92
|
+
'title-signed-out': this.getAttribute('t-title-timed-out') || 'You have been signed out',
|
|
93
|
+
message: this.getAttribute('t-message') || 'For your security, we will log you out in 2 minutes.',
|
|
94
|
+
'message-error-sign-in': this.getAttribute('t-message-error-failed-refresh') || 'Failed to sign in - try again',
|
|
95
|
+
'btn-stay-signed-in': this.getAttribute('t-btn-stay-refresh') || 'Stay signed in',
|
|
96
|
+
'btn-sign-out': this.getAttribute('t-btn-sign-out') || 'Sign out',
|
|
97
|
+
'btn-close': this.getAttribute('t-btn-close') || 'close',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** URL to hit to refresh auth cookie, when user clicks to stay signed in */
|
|
101
|
+
get keepAliveUrl() {
|
|
102
|
+
return this.getAttribute('keep-alive-url') || '?continue=true';
|
|
103
|
+
}
|
|
104
|
+
get loginUrl() {
|
|
105
|
+
return this.getAttribute('login-url') || '/login';
|
|
106
|
+
}
|
|
107
|
+
get logoutUrl() {
|
|
108
|
+
return this.getAttribute('logout-url') || '/logoff';
|
|
109
|
+
}
|
|
110
|
+
/** How many minutes until signed out, dialog opens this many minutes before signed out */
|
|
111
|
+
get warningInMilliseconds() {
|
|
112
|
+
return parseInt(this.getAttribute('warning-in-milliseconds'), 10) || 2 * ONE_MINUTE_IN_MS;
|
|
113
|
+
}
|
|
114
|
+
/** matching Platform variable name, number of milliseconds until user is signed out (cookie expires) */
|
|
115
|
+
get timeoutInMilliseconds() {
|
|
116
|
+
const timeoutInMilliseconds = parseInt(this.getAttribute('timeout-in-milliseconds'), 10);
|
|
117
|
+
if (!timeoutInMilliseconds) {
|
|
118
|
+
console.error('no attribute timeout-in-milliseconds supplied');
|
|
119
|
+
}
|
|
120
|
+
return timeoutInMilliseconds;
|
|
121
|
+
}
|
|
122
|
+
/** clear timeouts, event listeners and remove dialog */
|
|
123
|
+
destroy() {
|
|
124
|
+
clearTimeout(this.showWarningDialogTimeout);
|
|
125
|
+
clearTimeout(this.showTimedOutDialogTimeout);
|
|
126
|
+
|
|
127
|
+
if (this.elSignInButton) this.elSignInButton.removeEventListener('click', this.onClickSignInButton);
|
|
128
|
+
if (this.elDialog) this.elDialog.removeEventListener('close', this.onSignedOutDialogClose);
|
|
129
|
+
|
|
130
|
+
if (this.elDialog) this.elDialog.close();
|
|
131
|
+
this.rootNode.replaceChildren('');
|
|
132
|
+
}
|
|
133
|
+
/** add warning dialog to this DOM, initialise timeouts for showing dialogs, and handlers for timeouts */
|
|
134
|
+
start() {
|
|
135
|
+
/** use light DOM so we can access the page CSS, and use template contents */
|
|
136
|
+
this.rootNode.replaceChildren(this.template.content.cloneNode(true));
|
|
137
|
+
this.elSignInButton.addEventListener('click', this.onClickSignInButton);
|
|
138
|
+
|
|
139
|
+
this.showWarningDialogTimeout = setTimeout(() => {
|
|
140
|
+
this.elDialog.showModal();
|
|
141
|
+
}, this.timeoutInMilliseconds - this.warningInMilliseconds);
|
|
142
|
+
|
|
143
|
+
this.showTimedOutDialogTimeout = setTimeout(() => {
|
|
144
|
+
this.setSignedOut();
|
|
145
|
+
}, this.timeoutInMilliseconds);
|
|
146
|
+
}
|
|
147
|
+
/** destroy existing dialog/timeouts, and re-create as if starting from the beginning */
|
|
148
|
+
reset() {
|
|
149
|
+
this.destroy();
|
|
150
|
+
this.start();
|
|
151
|
+
}
|
|
152
|
+
/** end if the line, we are signed out, shut down timeouts and show signed out dialog, on dialog close redirect to log in page */
|
|
153
|
+
setSignedOut() {
|
|
154
|
+
this.destroy();
|
|
155
|
+
/** use light DOM so we can access the page CSS, and use template contents */
|
|
156
|
+
this.rootNode.replaceChildren(this.templateSignedOut.content.cloneNode(true));
|
|
157
|
+
this.elDialog.showModal();
|
|
158
|
+
this.elDialog.addEventListener('close', this.onSignedOutDialogClose);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* hit URL which should hopefully return a new logged in cookie.
|
|
162
|
+
* We assume a 200 response means a cookie was set, as the cookie is usually not readable via javascript
|
|
163
|
+
*/
|
|
164
|
+
async onClickSignInButton() {
|
|
165
|
+
try {
|
|
166
|
+
const res = await fetch(this.keepAliveUrl);
|
|
167
|
+
if (!res.ok) {
|
|
168
|
+
const text = await res.text();
|
|
169
|
+
throw new Error(`Could not stay signed in - ${res.statusText} - ${text}`);
|
|
170
|
+
}
|
|
171
|
+
// assume successful response means successful cookie refresh
|
|
172
|
+
this.reset();
|
|
173
|
+
if (this.elMessageError) this.elMessageError.classList.add('mds-display-none');
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error(error);
|
|
176
|
+
// response is error, assume failed to fetch fresh cookie
|
|
177
|
+
if (this.elMessageError) this.elMessageError.classList.remove('mds-display-none');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* when the 'signed out dialog' gets closed, we want to send the user straight to the log in page.
|
|
183
|
+
* This is because we dont want the user to close the dialog and be left with a visually signed in page.
|
|
184
|
+
* PipelinedPage return URL back to this current page URL after log in
|
|
185
|
+
*/
|
|
186
|
+
onSignedOutDialogClose() {
|
|
187
|
+
window.location.href = `${this.loginUrl}?PipelinedPage=${encodeURIComponent(window.location.href)}`;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
|
|
2
|
+
<style>
|
|
3
|
+
/* Intentionally gross styles to test shadow DOM isolation */
|
|
4
|
+
.blurb {
|
|
5
|
+
margin-bottom: 50px;
|
|
6
|
+
}
|
|
7
|
+
.blurb, .blurb * {
|
|
8
|
+
font-family: 'comic sans ms' !important;
|
|
9
|
+
}
|
|
10
|
+
.blurb button {
|
|
11
|
+
transform: rotate(-2.5deg) !important;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
dialog {
|
|
16
|
+
border: 10px solid red !important;
|
|
17
|
+
background: yellow !important;
|
|
18
|
+
padding: 20px !important;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
dialog h2 {
|
|
22
|
+
color: red !important;
|
|
23
|
+
font-size: 40px !important;
|
|
24
|
+
font-family: 'Comic Sans MS', cursive !important;
|
|
25
|
+
text-decoration: underline !important;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
dialog p {
|
|
29
|
+
color: green !important;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.mds-button {
|
|
33
|
+
background: magenta !important;
|
|
34
|
+
transform: rotate(-2.5deg) !important;
|
|
35
|
+
}
|
|
36
|
+
</style>
|
|
37
|
+
|
|
38
|
+
<nav class="position-relative mds-margin-bottom-b12">
|
|
39
|
+
<div id="inject-container" style="position:relative;">
|
|
40
|
+
{# <template shadowrootmode="open">
|
|
41
|
+
<mds-timeout-dialog-standalone
|
|
42
|
+
login-url="/login"
|
|
43
|
+
logout-url="/logoff"
|
|
44
|
+
keep-alive-url="/"
|
|
45
|
+
timeout-in-milliseconds="24000"
|
|
46
|
+
warning-in-milliseconds="23000"
|
|
47
|
+
t-title="You are about to be signed out"
|
|
48
|
+
t-title-timed-out="You have been signed out"
|
|
49
|
+
t-message="For your security, we will log you out in ?? minutes."
|
|
50
|
+
t-message-error-failed-refresh="Failed to sign in - try again"
|
|
51
|
+
t-btn-stay-refresh="Stay signed in"
|
|
52
|
+
t-btn-sign-out="Sign out"
|
|
53
|
+
t-btn-close="close"></mds-timeout-dialog-standalone>
|
|
54
|
+
</template> #}
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<p class="mds-margin-bottom-b12">
|
|
58
|
+
This page has some gross styles for dialog elements. The LightDOM version will inherit these styles but the
|
|
59
|
+
shadow DOM version should be isolated from these styles & wupply its own.
|
|
60
|
+
</p>
|
|
61
|
+
|
|
62
|
+
<div class="blurb">
|
|
63
|
+
<h2>LightDOM Version</h2>
|
|
64
|
+
<pre><code><mds-timeout-dialog></mds-timeout-dialog></code></pre>
|
|
65
|
+
<p>
|
|
66
|
+
<button onclick="testRegularTimeOutDialog()">Inject TimeOut Dialog</button>
|
|
67
|
+
</p>
|
|
68
|
+
|
|
69
|
+
<br>
|
|
70
|
+
|
|
71
|
+
<h2>Standalone/ShadowDOM Version</h2>
|
|
72
|
+
<pre><code><mds-timeout-dialog-standalone></mds-timeout-dialog-standalone></code></pre>
|
|
73
|
+
<p>
|
|
74
|
+
<button onclick="testStandaloneTimeOutDialog()">Inject TimeOut Dialog</button>
|
|
75
|
+
</p>
|
|
76
|
+
</div>
|
|
77
|
+
</nav>
|
|
78
|
+
|
|
79
|
+
<script>
|
|
80
|
+
function testRegularTimeOutDialog() {
|
|
81
|
+
const regularElement = document.createElement('mds-timeout-dialog');
|
|
82
|
+
regularElement.setAttribute('timeout-in-milliseconds', '6000');
|
|
83
|
+
regularElement.setAttribute('warning-in-milliseconds', '5500');
|
|
84
|
+
regularElement.setAttribute('t-title', 'Regular Version - Light DOM');
|
|
85
|
+
regularElement.setAttribute('t-message', 'This dialog uses LIGHT DOM and will be styled by page CSS');
|
|
86
|
+
|
|
87
|
+
document.getElementById('inject-container').innerHTML = '';
|
|
88
|
+
document.getElementById('inject-container').appendChild(regularElement);
|
|
89
|
+
console.log(regularElement);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function testStandaloneTimeOutDialog() {
|
|
93
|
+
const standaloneElement = document.createElement('mds-timeout-dialog-standalone');
|
|
94
|
+
standaloneElement.setAttribute('timeout-in-milliseconds', '6000');
|
|
95
|
+
standaloneElement.setAttribute('warning-in-milliseconds', '5500');
|
|
96
|
+
standaloneElement.setAttribute('t-title', 'Standalone Version - Shadow DOM');
|
|
97
|
+
standaloneElement.setAttribute('t-message', 'This dialog uses SHADOW DOM and should be isolated from page CSS');
|
|
98
|
+
|
|
99
|
+
document.getElementById('inject-container').innerHTML = '';
|
|
100
|
+
document.getElementById('inject-container').appendChild(standaloneElement);
|
|
101
|
+
console.log(standaloneElement);
|
|
102
|
+
}
|
|
103
|
+
</script>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* making standard browser <dialog> look a bit more like mds-modal - maybe mds-modal should be replaced with <dialog> and this small amount of CSS? */
|
|
2
|
+
.mds-timeout-dialog {
|
|
3
|
+
background-color: #fff;
|
|
4
|
+
margin: ($constant-size-baseline * 10) auto;
|
|
5
|
+
padding: ($constant-size-baseline * 8) ($constant-size-baseline * 12);
|
|
6
|
+
border-radius: $border-radius;
|
|
7
|
+
border: 0;
|
|
8
|
+
max-width: 800px;
|
|
9
|
+
position: relative;
|
|
10
|
+
}
|
|
11
|
+
.mds-timeout-dialog::backdrop {
|
|
12
|
+
background-color: rgba(0, 0, 0, 0.8);
|
|
13
|
+
}
|
package/src/js/index-fractal.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import switchStateScript from './fractal-scripts/switch-state';
|
|
2
2
|
import notificationScript from './fractal-scripts/notification';
|
|
3
3
|
|
|
4
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
4
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
5
5
|
switchStateScript.init();
|
|
6
6
|
notificationScript.init();
|
|
7
|
+
await import('../components/timeout-dialog/mds-timeout-dialog-standalone.js');
|
|
7
8
|
});
|
package/src/js/index.js
CHANGED
|
@@ -10,10 +10,18 @@ import characterCount from '../components/inputs/textarea/character-count';
|
|
|
10
10
|
import button from '../components/button/button';
|
|
11
11
|
import prose from '../helpers/prose/prose';
|
|
12
12
|
import { MdsDropdownNav } from '../components/dropdown-nav/dropdown-nav';
|
|
13
|
+
import { MdsTimeoutDialog } from '../components/timeout-dialog/timeout-dialog';
|
|
14
|
+
import { MdsCardLink } from '../components/card/card-link';
|
|
13
15
|
|
|
14
16
|
if (!window.customElements.get('mds-dropdown-nav')) {
|
|
15
17
|
window.customElements.define('mds-dropdown-nav', MdsDropdownNav);
|
|
16
18
|
}
|
|
19
|
+
if (!window.customElements.get('mds-timeout-dialog')) {
|
|
20
|
+
window.customElements.define('mds-timeout-dialog', MdsTimeoutDialog);
|
|
21
|
+
}
|
|
22
|
+
if (!window.customElements.get('mds-card-link')) {
|
|
23
|
+
window.customElements.define('mds-card-link', MdsCardLink);
|
|
24
|
+
}
|
|
17
25
|
|
|
18
26
|
const initAll = () => {
|
|
19
27
|
tabs.init();
|
|
@@ -16,6 +16,14 @@ pre {
|
|
|
16
16
|
margin-bottom: $constant-size-baseline * 3;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
.mds-link--hover {
|
|
20
|
+
--link-color: #{$link-hover-color};
|
|
21
|
+
|
|
22
|
+
--text-decoration: var(--mds-text-decoration-link-hover, underline);
|
|
23
|
+
--text-decoration-thickness: #{$link-hover-decoration-thickness};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.mds-link,
|
|
19
27
|
a {
|
|
20
28
|
--link-color: #{$link-color};
|
|
21
29
|
--link-font-weight: var(--mds-font-weight-link-base);
|
|
@@ -30,16 +38,14 @@ a {
|
|
|
30
38
|
|
|
31
39
|
&:hover,
|
|
32
40
|
&:focus {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
--text-decoration: var(--mds-text-decoration-link-hover, underline);
|
|
36
|
-
--text-decoration-thickness: #{$link-hover-decoration-thickness};
|
|
41
|
+
@extend .mds-link--hover;
|
|
37
42
|
}
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
.mds-link--no-decoration a,
|
|
41
46
|
a.mds-link--no-decoration {
|
|
42
47
|
--text-decoration: none;
|
|
48
|
+
|
|
43
49
|
&:hover,
|
|
44
50
|
&:focus {
|
|
45
51
|
// mirror regular version
|
|
@@ -57,4 +63,4 @@ h3 {
|
|
|
57
63
|
|
|
58
64
|
img {
|
|
59
65
|
max-width: 100%;
|
|
60
|
-
}
|
|
66
|
+
}
|