fix: Viewport issue in PWA (#995)

* fix: move safe-area padding from body/HTML to navbars. Add script to compute app height dynamically.

* fix: Initialize sankey tooltip with top-0 to avoid overflow

* fix: add fallback to HTML height

* fix: properly set bottom spacing and use position fixed for bottom navbar

* fix: move viewport controller initialization
This commit is contained in:
Alessio Cappa
2026-02-15 13:23:19 +01:00
committed by GitHub
parent e573896efe
commit e0ae71f33a
6 changed files with 56 additions and 9 deletions

View File

@@ -63,7 +63,7 @@ module SettingsHelper
previous_setting = adjacent_setting(request.path, -1)
next_setting = adjacent_setting(request.path, 1)
content_tag :div, class: "md:hidden flex flex-col gap-4" do
content_tag :div, class: "md:hidden flex flex-col gap-4 pb-[env(safe-area-inset-bottom)]" do
concat(previous_setting)
concat(next_setting)
end

View File

@@ -349,7 +349,7 @@ export default class extends Controller {
const dialog = this.element.closest("dialog");
this.tooltip = d3.select(dialog || document.body)
.append("div")
.attr("class", "bg-gray-700 text-white text-sm p-2 rounded pointer-events-none absolute z-50")
.attr("class", "bg-gray-700 text-white text-sm p-2 rounded pointer-events-none absolute z-50 top-0")
.style("opacity", 0)
.style("pointer-events", "none");
}

View File

@@ -0,0 +1,46 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["content", "bottomNav"]
connect() {
this.updateViewport()
this.updateBottomSpacing()
window.addEventListener("resize", this.handleResize)
window.addEventListener("orientationchange", this.handleResize)
if (this.hasBottomNavTarget) {
this.resizeObserver = new ResizeObserver(() => {
this.updateBottomSpacing()
})
this.resizeObserver.observe(this.bottomNavTarget)
}
}
disconnect() {
window.removeEventListener("resize", this.handleResize)
window.removeEventListener("orientationchange", this.handleResize)
if (this.resizeObserver) {
this.resizeObserver.disconnect()
}
}
handleResize = () => {
this.updateViewport()
this.updateBottomSpacing()
}
updateViewport() {
const height = window.innerHeight
document.documentElement.style.setProperty("--app-height", `${height}px`)
}
updateBottomSpacing() {
if (!this.hasBottomNavTarget || !this.hasContentTarget) return
const navHeight = this.bottomNavTarget.offsetHeight
this.contentTarget.style.paddingBottom = `${navHeight}px`
}
}

View File

@@ -44,7 +44,7 @@ end %>
</div>
<%# MOBILE - Top nav %>
<nav class="lg:hidden flex justify-between items-center p-3">
<nav class="lg:hidden flex justify-between items-center p-3 pt-[calc(env(safe-area-inset-top)+0.75rem)]">
<% if intro_mode %>
<% else %>
<%= icon("panel-left", as_button: true, data: { action: "app-layout#openMobileSidebar"}) %>
@@ -126,7 +126,7 @@ end %>
<% end %>
<%# SHARED - Main content %>
<%= tag.main class: class_names("grow overflow-y-auto px-3 lg:px-10 pt-0 pb-4 w-full mx-auto max-w-5xl"), data: { app_layout_target: "content" } do %>
<%= tag.main class: class_names("grow overflow-y-auto px-3 lg:px-10 w-full mx-auto max-w-5xl"), data: { app_layout_target: "content", viewport_target: "content" } do %>
<% unless intro_mode %>
<div class="hidden lg:flex gap-2 items-center justify-between mb-6 sticky top-0 z-10 -mx-3 lg:-mx-10 px-3 lg:px-10 py-4 bg-surface border-b border-tertiary">
<div class="flex items-center gap-2">
@@ -173,7 +173,8 @@ end %>
<% end %>
<%# MOBILE - Bottom Nav %>
<%= tag.nav class: "lg:hidden sticky bottom-0 left-0 right-0 bg-surface z-10 border-t border-tertiary flex justify-around" do %>
<%= tag.nav class: "lg:hidden fixed bottom-0 left-0 right-0 bg-surface z-10 border-t border-tertiary flex justify-around pb-[env(safe-area-inset-bottom)]",
data: { viewport_target: "bottomNav" } do %>
<% mobile_nav_items.each do |nav_item| %>
<%= render "layouts/shared/nav_item", **nav_item %>
<% end %>

View File

@@ -1,5 +1,5 @@
<%= render "layouts/shared/htmldoc" do %>
<div class="flex flex-col md:flex-row h-full bg-surface">
<div class="flex flex-col md:flex-row h-full bg-surface pt-[env(safe-area-inset-top)]">
<div class="p-4 w-full md:w-96 shrink-0 md:h-full md:overflow-y-auto">
<%= render "settings/settings_nav" %>
</div>

View File

@@ -5,15 +5,15 @@
<html
lang="en"
data-theme="<%= theme %>"
data-controller="theme"
data-controller="theme viewport"
data-theme-user-preference-value="<%= Current.user&.theme || "system" %>"
class="text-primary bg-surface overflow-hidden overscroll-none font-sans <%= @os %>">
class="h-[var(--app-height, 100dvh)] text-primary bg-surface overflow-hidden overscroll-none font-sans <%= @os %>">
<head>
<%= render "layouts/shared/head" %>
<%= yield :head %>
</head>
<body class="h-[100dvh] overflow-hidden antialiased pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)]">
<body class="h-[var(--app-height)] overflow-hidden antialiased">
<% if Rails.env.development? %>
<button hidden data-controller="hotkey" data-hotkey="t t /" data-action="theme#toggle"></button>
<% end %>