contact messages: centered panel on paskia's shared blur backdrop

The panel was a right-docked sidebar with its own translucent backdrop. It is main content: now centered at the paskia dialog's dimensions (min(36rem,100%) x min(42rem,92vh)), full-screen below 36rem, holding the refcounted global blur backdrop (z 1099) while open with the panel's click-catch overlay at 1100 (below the auth iframe at 9999). Escape closes too.
This commit is contained in:
LeoVasanko committed 2026-09-28 01:04:33 +00:00
1 parent f3c288c76c
commit 9a2435f03a
2 files changed
+45 -13

No files matched your search

+35 -12
View File
@@ -3,7 +3,7 @@
// socket (via the bridge pagerite.js hands to contact-main), with the
// composer shared with the admin inbox. state is contact-main's reactive
// store ({open, thread, unread}).
import { computed, nextTick, reactive, ref, watch } from 'vue'
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
import { formatWhen } from './analytics/format.js'
import Composer from './Composer.vue'
@@ -26,6 +26,13 @@ watch(() => props.state.open, (open) => { if (open) scrollDown() })
// Relative timestamps like in analytics.
const fmtTime = (t) => formatWhen(t)
// Escape closes, like the paskia dialogs whose backdrop we share.
function onKey(ev) {
if (ev.key === 'Escape' && props.state.open) emit('close')
}
onMounted(() => addEventListener('keydown', onKey))
onUnmounted(() => removeEventListener('keydown', onKey))
const NAME_KEY = 'pagerite-contact-name'
const EMAIL_KEY = 'pagerite-contact-email'
@@ -53,7 +60,7 @@ function onSend(md) {
</script>
<template>
<div v-if="state.open" class="contact-backdrop" @click.self="emit('close')">
<div v-if="state.open" class="contact-overlay" @click.self="emit('close')">
<section class="contact-panel" role="dialog" aria-label="Contact us">
<header>
<h2>Contact us</h2>
@@ -89,27 +96,43 @@ function onSend(md) {
</template>
<style scoped>
.contact-backdrop {
.contact-overlay {
position: fixed;
inset: 0;
z-index: 30;
background: rgb(0 0 0 / 0.25);
/* Above paskia's shared blur backdrop (1099, held by contact-main while
open), below the authentication iframe (9999). Transparent: the blur
is the backdrop's job, this layer only centers the panel and catches
outside clicks (the backdrop itself is pointer-events: none). */
z-index: 1100;
display: flex;
align-items: center;
justify-content: center;
}
.contact-panel {
position: absolute;
top: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
width: min(26rem, 94vw);
/* Same dimensions as paskia's own dialog. */
width: min(36rem, 100%);
height: min(42rem, 92vh);
padding: 0.8rem;
gap: 0.6rem;
background: var(--bg, Canvas);
color: var(--text, CanvasText);
border-left: 1px solid var(--line);
box-shadow: -0.5rem 0 2rem rgb(0 0 0 / 0.2);
border: 1px solid var(--line);
border-radius: 0.5rem;
box-shadow: 0 0.5rem 2rem rgb(0 0 0 / 0.3);
}
/* Mobile: full screen. */
@media (max-width: 36rem) {
.contact-panel {
width: 100%;
height: 100%;
border: none;
border-radius: 0;
box-shadow: none;
}
}
.contact-panel header {
+10 -1
View File
@@ -3,7 +3,8 @@
// socket through the bridge pagerite.js hands over ({send, register}) —
// no extra connection is opened. The session token is a localStorage
// random; the server stores only its hash.
import { createApp, reactive } from 'vue'
import { createApp, reactive, watch } from 'vue'
import { holdGlobalBackdrop, releaseGlobalBackdrop } from 'paskia'
import ContactPanel from './ContactPanel.vue'
const TOKEN_KEY = 'pagerite-contact-token'
@@ -12,6 +13,14 @@ const SEEN_KEY = 'pagerite-contact-seen'
let app = null
const state = reactive({ open: false, thread: null, unread: false })
// The panel is main content, not a sidebar: while open it holds paskia's
// shared blur backdrop (refcounted, z-index 1099; the panel itself sits at
// 1100, below the auth iframe's 9999).
watch(() => state.open, (open) => {
if (open) holdGlobalBackdrop()
else releaseGlobalBackdrop()
})
function ensureToken() {
let token = localStorage.getItem(TOKEN_KEY)
if (!token) {