Skip to content

Commit d3770ea

Browse files
Refactor Session component to use dialog for session timeout overlay, enhancing accessibility and styling. Implement body scroll locking and focus management for improved user experience during session interactions.
1 parent f763960 commit d3770ea

1 file changed

Lines changed: 170 additions & 65 deletions

File tree

resources/js/components/Session.vue

Lines changed: 170 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,70 @@
11
<template>
2-
<b-modal
3-
id="sessionModal"
4-
ref="sessionModal"
5-
:title="title"
6-
modal-class="session-timeout-modal"
7-
backdrop-class="session-timeout-backdrop"
8-
footer-class="pm-modal-footer"
9-
no-close-on-backdrop
10-
centered
11-
no-close-button
12-
>
13-
<template #modal-header>
14-
<h5>{{ title }}</h5>
15-
</template>
16-
<div v-if="!isProcessing">
17-
<span v-html="message" />
18-
<div class="progress">
19-
<div
20-
class="progress-bar progress-bar-striped"
21-
role="progressbar"
22-
:style="{width: percentage + '%'}"
23-
>
24-
<span
25-
align="left"
26-
class="pl-2"
27-
>{{ moment().startOf('day').seconds(time).format('mm:ss') }}</span>
28-
</div>
29-
</div>
30-
</div>
2+
<div>
313
<div
32-
v-else
33-
class="d-flex align-items-center justify-content-center py-3"
4+
v-if="shown"
5+
class="session-timeout-overlay"
6+
role="presentation"
347
>
35-
<output
36-
class="spinner-border spinner-border-sm mr-2"
37-
aria-live="polite"
38-
/>
39-
<span>{{ ("Processing...") }}</span>
40-
</div>
41-
<template #modal-footer>
42-
<button
43-
v-if="!isProcessing"
44-
type="button"
45-
class="btn btn-outline-secondary ml-2"
46-
:disabled="isBusy"
47-
@click="logoutNow"
48-
>
49-
{{ ('LogOut') }}
50-
</button>
51-
<button
52-
v-if="!isProcessing"
53-
type="button"
54-
class="btn btn-secondary ml-2"
55-
:disabled="isBusy"
56-
@click="keepAlive"
8+
<dialog
9+
ref="sessionDialog"
10+
class="session-timeout-dialog"
11+
:aria-label="title"
12+
open
13+
tabindex="-1"
5714
>
58-
{{ ('Stay Connected') }}
59-
</button>
60-
</template>
61-
</b-modal>
15+
<header class="session-timeout-header">
16+
<h5>{{ title }}</h5>
17+
</header>
18+
<div class="session-timeout-body">
19+
<div v-if="!isProcessing">
20+
<span v-html="message" />
21+
<div class="progress">
22+
<div
23+
class="progress-bar progress-bar-striped"
24+
role="progressbar"
25+
:style="{width: percentage + '%'}"
26+
>
27+
<span
28+
align="left"
29+
class="pl-2"
30+
>{{ moment().startOf('day').seconds(time).format('mm:ss') }}</span>
31+
</div>
32+
</div>
33+
</div>
34+
<div
35+
v-else
36+
class="d-flex align-items-center justify-content-center py-3"
37+
>
38+
<output
39+
class="spinner-border spinner-border-sm mr-2"
40+
aria-live="polite"
41+
/>
42+
<span>{{ ("Processing...") }}</span>
43+
</div>
44+
</div>
45+
<footer class="pm-modal-footer session-timeout-footer">
46+
<button
47+
v-if="!isProcessing"
48+
type="button"
49+
class="btn btn-outline-secondary ml-2"
50+
:disabled="isBusy"
51+
@click="logoutNow"
52+
>
53+
{{ ('LogOut') }}
54+
</button>
55+
<button
56+
v-if="!isProcessing"
57+
type="button"
58+
class="btn btn-secondary ml-2"
59+
:disabled="isBusy"
60+
@click="keepAlive"
61+
>
62+
{{ ('Stay Connected') }}
63+
</button>
64+
</footer>
65+
</dialog>
66+
</div>
67+
</div>
6268
</template>
6369

6470
<script>
@@ -70,6 +76,8 @@ export default {
7076
errors: {},
7177
disabled: false,
7278
localRenewing: false,
79+
originalParent: null,
80+
originalNextSibling: null,
7381
};
7482
},
7583
computed: {
@@ -93,18 +101,62 @@ export default {
93101
shown(value) {
94102
if (value) {
95103
this.resetProcessingState();
96-
}
97-
if (value) {
98-
this.$refs.sessionModal.show();
104+
this.lockBodyScroll();
105+
this.focusDialog();
99106
} else {
100-
this.$refs.sessionModal.hide();
107+
this.unlockBodyScroll();
101108
}
102109
},
103110
},
104111
mounted() {
112+
this.mountOverlayInBody();
113+
if (this.shown) {
114+
this.lockBodyScroll();
115+
this.focusDialog();
116+
}
105117
this.$emit("show");
106118
},
119+
beforeDestroy() {
120+
this.unlockBodyScroll();
121+
this.restoreOverlayParent();
122+
},
107123
methods: {
124+
mountOverlayInBody() {
125+
if (!document?.body || this.$el.parentNode === document.body) {
126+
return;
127+
}
128+
129+
this.originalParent = this.$el.parentNode;
130+
this.originalNextSibling = this.$el.nextSibling;
131+
document.body.appendChild(this.$el);
132+
},
133+
restoreOverlayParent() {
134+
if (!this.originalParent || !this.$el.parentNode) {
135+
return;
136+
}
137+
138+
this.$el.remove();
139+
this.originalParent.insertBefore(this.$el, this.originalNextSibling);
140+
},
141+
lockBodyScroll() {
142+
document?.body?.classList.add("session-timeout-open");
143+
},
144+
unlockBodyScroll() {
145+
document?.body?.classList.remove("session-timeout-open");
146+
},
147+
focusDialog() {
148+
this.$nextTick(() => {
149+
globalThis.setTimeout(() => {
150+
const firstButton = this.$refs.sessionDialog?.querySelector("button:not([disabled])");
151+
if (firstButton) {
152+
firstButton.focus();
153+
return;
154+
}
155+
156+
this.$refs.sessionDialog?.focus();
157+
}, 50);
158+
});
159+
},
108160
resetProcessingState() {
109161
this.localRenewing = false;
110162
this.disabled = false;
@@ -193,11 +245,64 @@ export default {
193245
</script>
194246
195247
<style>
196-
.session-timeout-backdrop {
197-
z-index: 20000 !important;
248+
body.session-timeout-open {
249+
overflow: hidden;
250+
}
251+
252+
.session-timeout-overlay {
253+
align-items: center;
254+
background: rgba(0, 0, 0, 0.5);
255+
bottom: 0;
256+
display: flex;
257+
justify-content: center;
258+
left: 0;
259+
overflow-x: hidden;
260+
overflow-y: auto;
261+
padding: 1.75rem;
262+
position: fixed;
263+
right: 0;
264+
top: 0;
265+
z-index: 2147483647;
266+
}
267+
268+
.session-timeout-dialog {
269+
background: #fff;
270+
border-radius: 0.3rem;
271+
border: 0;
272+
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.5);
273+
display: flex;
274+
flex-direction: column;
275+
margin: 0;
276+
max-width: 500px;
277+
outline: 0;
278+
padding: 0;
279+
position: relative;
280+
width: 100%;
281+
}
282+
283+
.session-timeout-header {
284+
align-items: flex-start;
285+
border-bottom: 1px solid #dee2e6;
286+
display: flex;
287+
padding: 1rem;
288+
}
289+
290+
.session-timeout-header h5 {
291+
line-height: 1.5;
292+
margin: 0;
293+
}
294+
295+
.session-timeout-body {
296+
flex: 1 1 auto;
297+
padding: 1rem;
298+
position: relative;
198299
}
199300
200-
.session-timeout-modal {
201-
z-index: 20010 !important;
301+
.session-timeout-footer {
302+
align-items: center;
303+
border-top: 1px solid #dee2e6;
304+
display: flex;
305+
justify-content: flex-end;
306+
padding: 0.75rem;
202307
}
203308
</style>

0 commit comments

Comments
 (0)