fix(appshell): wrap PrimeVue responsive elements to bypass specificity conflict
Tailwind's lg:hidden loses to PrimeVue's .p-button { display: inline-flex }
due to equal specificity but later cascade order. Resulted in the mobile
hamburger remaining visible on desktop, allowing the Drawer to open over
the already-visible permanent sidebar.
Fix: wrap mobile-only cluster (hamburger + title) in a plain <div lg:hidden>
so the wrapper owns the visibility toggle. The wrapper is not a PrimeVue
component, so no specificity competition.
The Drawer itself had the same anti-pattern (class="lg:hidden") and is
worse, because PrimeVue Drawer teleports to body — a wrapping div on the
parent does not isolate the teleported overlay, and a class on the Drawer
root loses to .p-drawer { display: flex } when visible. Converted to
v-if="!isLg" driven by useMediaQuery('(min-width: 1024px)'). Vue simply
does not render the component on lg+, so no display rule competes.
Audited all 5 layouts for the same anti-pattern:
- AppShell.vue — fixed (Button + Drawer described above)
- default.vue / OrganizerLayout.vue / PortalLayout.vue — delegate to
AppShell; no PrimeVue elements with responsive classes
- blank.vue — plain <div>, no PrimeVue
- PublicLayout.vue — plain <main>, no PrimeVue
useMediaQuery is auto-imported via unplugin-auto-import's @vueuse/core
entry in vite.config.ts; explicit imports get stripped by the post-edit
ESLint --fix hook as redundant.
F3-introduced bug (commit 43915501); surfaced during F3.5 testing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,13 @@ const authStore = useAuthStore()
|
||||
|
||||
const mobileNavOpen = ref(false)
|
||||
|
||||
// Tailwind's lg breakpoint, mirrored in script so Vue can own the
|
||||
// visibility of PrimeVue elements that would otherwise lose a CSS
|
||||
// specificity duel to .p-button / .p-drawer / etc. See the wrapper
|
||||
// `<div class="lg:hidden">` around the topbar mobile cluster and the
|
||||
// `v-if="!isLg"` on the Drawer.
|
||||
const isLg = useMediaQuery('(min-width: 1024px)')
|
||||
|
||||
function isHeading(item: NavItem): item is NavHeading {
|
||||
return 'heading' in item
|
||||
}
|
||||
@@ -143,11 +150,16 @@ function onHelpClick() {
|
||||
<SidebarUserCard />
|
||||
</aside>
|
||||
|
||||
<!-- Mobile drawer (overlay, <lg) -->
|
||||
<!--
|
||||
Mobile drawer (overlay, <lg). v-if (not lg:hidden class) because
|
||||
PrimeVue Drawer teleports to body, so a wrapping div or class
|
||||
on the Drawer root can't beat .p-drawer's display rule in the
|
||||
cascade — Vue must simply not render the component on lg+.
|
||||
-->
|
||||
<Drawer
|
||||
v-if="!isLg"
|
||||
v-model:visible="mobileNavOpen"
|
||||
position="left"
|
||||
class="lg:hidden"
|
||||
:pt="{
|
||||
root: { style: { width: '18rem' } },
|
||||
header: { class: 'p-0' },
|
||||
@@ -190,20 +202,27 @@ function onHelpClick() {
|
||||
<!-- Top bar -->
|
||||
<header class="flex h-16 items-center justify-between border-b border-surface-200 bg-surface-0 px-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
class="lg:hidden"
|
||||
severity="secondary"
|
||||
text
|
||||
rounded
|
||||
aria-label="Menu openen"
|
||||
@click="mobileNavOpen = true"
|
||||
>
|
||||
<Icon
|
||||
name="tabler-menu-2"
|
||||
size="24"
|
||||
/>
|
||||
</Button>
|
||||
<span class="text-base font-medium text-surface-700 lg:hidden">{{ title }}</span>
|
||||
<!--
|
||||
Mobile cluster: visibility owned by this plain DIV. The
|
||||
PrimeVue Button alone with lg:hidden loses to
|
||||
.p-button { display: inline-flex } in the cascade — same
|
||||
specificity, PrimeVue's stylesheet loads later.
|
||||
-->
|
||||
<div class="flex items-center gap-2 lg:hidden">
|
||||
<Button
|
||||
severity="secondary"
|
||||
text
|
||||
rounded
|
||||
aria-label="Menu openen"
|
||||
@click="mobileNavOpen = true"
|
||||
>
|
||||
<Icon
|
||||
name="tabler-menu-2"
|
||||
size="24"
|
||||
/>
|
||||
</Button>
|
||||
<span class="text-base font-medium text-surface-700">{{ title }}</span>
|
||||
</div>
|
||||
<nav
|
||||
class="hidden items-center gap-2 text-sm lg:flex"
|
||||
aria-label="Kruimelpad"
|
||||
|
||||
Reference in New Issue
Block a user