Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 104 additions & 2 deletions app/assets/stylesheets/components/navigation.css
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
display: flex;
align-items: center;
gap: var(--spacing-3);
flex: 1 1 auto;
flex: 0 0 auto;
}

.brand-link {
Expand All @@ -59,19 +59,37 @@
display: inline-flex;
align-items: center;
gap: var(--spacing-2);
min-width: 0;
flex-shrink: 0;
}

.brand-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.brand-icon {
height: var(--nav-height);
width: auto;
display: block;
object-fit: contain;
flex-shrink: 0;
}

.tagline {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-normal);
color: var(--color-text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

@media (max-width: 1550px) {
.tagline {
display: none;
}
}

@media (max-width: 1500px) {
Expand All @@ -83,12 +101,22 @@
.nav-links {
display: flex;
gap: var(--spacing-6);
flex: 0 0 auto;
}

.nav-right {
display: flex;
align-items: center;
gap: var(--spacing-3);
flex: 0 0 auto;
}

.nav-menu {
display: flex;
align-items: center;
margin-left: auto;
gap: var(--spacing-4);
flex: 0 0 auto;
}

.nav-auth {
Expand Down Expand Up @@ -134,6 +162,10 @@
display: none;
}

.nav-mobile-star {
display: none;
}

.nav-link-activity i {
font-size: 1.05em;
}
Expand Down Expand Up @@ -164,6 +196,67 @@
}
}

.nav-overflow-dropdown {
display: none;
position: relative;
}

.nav-overflow-dropdown.is-visible {
display: inline-flex;
}

.nav-overflow-toggle {
list-style: none;
}

.nav-overflow-toggle::marker,
.nav-overflow-toggle::-webkit-details-marker {
display: none;
}

.nav-overflow-toggle {
padding: var(--spacing-2);
width: auto;
height: auto;
}

.nav-overflow-menu {
position: absolute;
right: 0;
top: calc(100% + var(--spacing-2));
background: var(--color-bg-card);
border: var(--border-width) solid var(--color-border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-lg);
padding: var(--spacing-2);
min-width: 220px;
display: flex;
flex-direction: column;
gap: var(--spacing-2);
z-index: 200;
}

.nav-overflow-menu .nav-link {
width: 100%;
justify-content: space-between;
}

.nav-overflow-menu form {
width: 100%;
}

.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

.mobile-nav-dropdown {
display: none;
position: relative;
Expand Down Expand Up @@ -233,7 +326,8 @@
}

.nav-links,
.nav-right {
.nav-right,
.nav-menu {
display: none;
}

Expand All @@ -242,6 +336,14 @@
margin-left: auto;
}

.nav-mobile-star {
display: inline-flex;
}

.nav-mobile-star + .nav-mobile-bell {
margin-left: 0;
}

body.has-sidebar .nav-burger {
display: inline-flex;
}
Expand Down
85 changes: 85 additions & 0 deletions app/javascript/controllers/nav_overflow_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Controller } from "@hotwired/stimulus"

const MOBILE_BREAKPOINT = "(max-width: 900px)"

export default class extends Controller {
static targets = ["container", "menu", "overflow", "overflowMenu", "item"]

connect() {
this.mediaQuery = window.matchMedia(MOBILE_BREAKPOINT)
this._resizeHandler = this.layout.bind(this)
window.addEventListener("resize", this._resizeHandler)
this.storePositions()
this.layout()
}

disconnect() {
window.removeEventListener("resize", this._resizeHandler)
}

storePositions() {
this.positions = new Map()
this.orderedItems = [...this.itemTargets]
this.orderedItems.forEach((item) => {
const parent = item.parentElement
const index = Array.from(parent.children).indexOf(item)
this.positions.set(item, { parent, index })
})
}

restoreItems() {
const byParent = new Map()
this.positions.forEach((position, item) => {
if (!byParent.has(position.parent)) {
byParent.set(position.parent, [])
}
byParent.get(position.parent).push({ item, index: position.index })
})

byParent.forEach((items, parent) => {
items
.sort((a, b) => a.index - b.index)
.forEach(({ item, index }) => {
const ref = parent.children[index] || null
parent.insertBefore(item, ref)
})
})
}

hideOverflow() {
this.overflowTarget.classList.remove("is-visible")
this.overflowTarget.open = false
}

showOverflow() {
this.overflowTarget.classList.add("is-visible")
}

layout() {
if (!this.hasContainerTarget || !this.hasOverflowMenuTarget) return

this.restoreItems()
this.overflowMenuTarget.innerHTML = ""
this.hideOverflow()

if (this.mediaQuery.matches) {
return
}

const fits = this.containerTarget.scrollWidth <= this.containerTarget.clientWidth
if (fits) return

this.showOverflow()

for (let i = this.orderedItems.length - 1; i >= 0; i -= 1) {
if (this.containerTarget.scrollWidth <= this.containerTarget.clientWidth) break
const item = this.orderedItems[i]
if (this.overflowMenuTarget.contains(item)) continue
this.overflowMenuTarget.insertBefore(item, this.overflowMenuTarget.firstChild)
}

if (!this.overflowMenuTarget.children.length) {
this.hideOverflow()
}
}
}
74 changes: 46 additions & 28 deletions app/views/layouts/application.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ html data-theme="light"
- if user_signed_in? && current_user.username.blank?
.global-warning
span Please set a username in Settings.
- starred_active = controller_name == "topics" && action_name == "index" && params[:filter].to_s == "starred_by_me"
nav.main-navigation
.nav-container
.nav-container data-controller="nav-overflow" data-nav-overflow-target="container"
.nav-brand
- if content_for?(:sidebar)
button.nav-burger type="button" aria-label="Toggle sidebar" data-action="click->sidebar#toggleMobile"
Expand All @@ -56,6 +57,13 @@ html data-theme="light"
i.fa-solid.fa-caret-down
.mobile-nav-menu data-action="click->sidebar#closeMenuOnNavigate"
= link_to "Topics", topics_path, class: "nav-link"
- if user_signed_in?
- icon_class = starred_active ? "fa-solid fa-star" : "fa-regular fa-star"
- link_classes = ["nav-link"]
- link_classes << "is-active" if starred_active
= link_to topics_path(filter: "starred_by_me"), class: link_classes.join(" "), title: "Starred by me", aria: { label: "Starred by me" } do
i class=icon_class aria-hidden="true"
span.sr-only Starred
= link_to "Search", topics_path(anchor: "search"), class: "nav-link"
= link_to "Statistics", stats_path, class: "nav-link"
= link_to "Reports", reports_path, class: "nav-link"
Expand All @@ -76,37 +84,47 @@ html data-theme="light"
span.tagline PostgreSQL Hackers Archive
- if user_signed_in?
- unread = activity_unread_count
- starred_href = starred_active ? topics_path : topics_path(filter: "starred_by_me")
- starred_title = starred_active ? "All topics" : "Starred by me"
= link_to starred_href, class: "nav-link nav-mobile-star#{' is-active' if starred_active}", title: starred_title, aria: { label: starred_title } do
i class=(starred_active ? "fa-solid fa-star" : "fa-regular fa-star") aria-hidden="true"
span.sr-only Starred
= link_to activities_path, class: "nav-link nav-link-activity nav-mobile-bell", title: "Activity" do
i.fa-regular.fa-bell
- if unread.positive?
span.nav-badge = unread
.nav-links
= link_to "Topics", topics_path, class: "nav-link"
- search_link = content_for?(:search_sidebar) ? "#search" : topics_path(anchor: "search")
= link_to "Search", search_link, class: "nav-link"
= link_to "Statistics", stats_path, class: "nav-link"
= link_to "Reports", reports_path, class: "nav-link"
= link_to "Help", help_index_path, class: "nav-link"
.nav-right
button.nav-link.theme-toggle type="button" aria-label="Toggle theme" data-controller="theme" data-action="click->theme#toggle"
i.fas.fa-moon data-theme-target="icon"
span data-theme-target="label" Theme
.nav-auth
- if user_signed_in?
- if current_user&.person&.default_alias
= link_to current_user.person.default_alias.name, person_path(current_user.person.default_alias.email), class: "nav-link nav-user"
- unread = activity_unread_count
= link_to activities_path, class: "nav-link nav-link-activity", title: "Activity" do
i.fa-regular.fa-bell
- if unread.positive?
span.nav-badge = unread
= link_to "Settings", settings_root_path, class: "nav-link"
- if current_admin?
= link_to "Admin", admin_root_path, class: "nav-link"
= button_to "Sign out", session_path, method: :delete, class: "nav-link", form: { style: 'display:inline' }, data: { turbo: false }
- else
= link_to "Sign in", new_session_path, class: "nav-link"
= link_to "Register", new_registration_path, class: "nav-link"
.nav-menu data-nav-overflow-target="menu"
.nav-links
= link_to "Topics", topics_path, class: "nav-link", data: { "nav-overflow-target": "item" }
- search_link = content_for?(:search_sidebar) ? "#search" : topics_path(anchor: "search")
= link_to "Search", search_link, class: "nav-link", data: { "nav-overflow-target": "item" }
= link_to "Statistics", stats_path, class: "nav-link", data: { "nav-overflow-target": "item" }
= link_to "Reports", reports_path, class: "nav-link", data: { "nav-overflow-target": "item" }
= link_to "Help", help_index_path, class: "nav-link", data: { "nav-overflow-target": "item" }
.nav-right
button.nav-link.theme-toggle type="button" aria-label="Toggle theme" data-controller="theme" data-action="click->theme#toggle" data-nav-overflow-target="item"
i.fas.fa-moon data-theme-target="icon"
span data-theme-target="label" Theme
.nav-auth
- if user_signed_in?
- if current_user&.person&.default_alias
= link_to current_user.person.default_alias.name, person_path(current_user.person.default_alias.email), class: "nav-link nav-user", data: { "nav-overflow-target": "item" }
- unread = activity_unread_count
= link_to activities_path, class: "nav-link nav-link-activity", title: "Activity", data: { "nav-overflow-target": "item" } do
i.fa-regular.fa-bell
- if unread.positive?
span.nav-badge = unread
= link_to "Settings", settings_root_path, class: "nav-link", data: { "nav-overflow-target": "item" }
- if current_admin?
= link_to "Admin", admin_root_path, class: "nav-link", data: { "nav-overflow-target": "item" }
= button_to "Sign out", session_path, method: :delete, class: "nav-link", form: { style: 'display:inline', data: { "nav-overflow-target": "item" } }, data: { turbo: false }
- else
= link_to "Sign in", new_session_path, class: "nav-link", data: { "nav-overflow-target": "item" }
= link_to "Register", new_registration_path, class: "nav-link", data: { "nav-overflow-target": "item" }
details.nav-overflow-dropdown data-nav-overflow-target="overflow"
summary.nav-link.nav-overflow-toggle aria-label="More" data-action="click->sidebar#closeMenuOnNavigate"
i.fa-solid.fa-bars
.nav-overflow-menu data-nav-overflow-target="overflowMenu"

- if content_for?(:sidebar)
.page-layout.with-sidebar data-sidebar-target="layout"
Expand Down