Skip to content

Commit 32c9683

Browse files
committed
added a star icon on mobile view, which is a quick link to the starred by me, which gets filled once clicked and unfilled and links back to the actual topic overview once clicked again. I also adjusted the menu, to show a burger menu in case the window will be made smaller and the icons overflow, to ensure, these are simply grouped underneath the burger menu. This fixes current odd behavior on smaller screens
Signed-off-by: Kai Wagner <kai.wagner@percona.com>
1 parent 5bc61ba commit 32c9683

File tree

3 files changed

+235
-30
lines changed

3 files changed

+235
-30
lines changed

app/assets/stylesheets/components/navigation.css

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
display: flex;
4848
align-items: center;
4949
gap: var(--spacing-3);
50-
flex: 1 1 auto;
50+
flex: 0 0 auto;
5151
}
5252

5353
.brand-link {
@@ -59,19 +59,37 @@
5959
display: inline-flex;
6060
align-items: center;
6161
gap: var(--spacing-2);
62+
min-width: 0;
63+
flex-shrink: 0;
64+
}
65+
66+
.brand-text {
67+
white-space: nowrap;
68+
overflow: hidden;
69+
text-overflow: ellipsis;
6270
}
6371

6472
.brand-icon {
6573
height: var(--nav-height);
6674
width: auto;
6775
display: block;
6876
object-fit: contain;
77+
flex-shrink: 0;
6978
}
7079

7180
.tagline {
7281
font-size: var(--font-size-xs);
7382
font-weight: var(--font-weight-normal);
7483
color: var(--color-text-muted);
84+
white-space: nowrap;
85+
overflow: hidden;
86+
text-overflow: ellipsis;
87+
}
88+
89+
@media (max-width: 1550px) {
90+
.tagline {
91+
display: none;
92+
}
7593
}
7694

7795
@media (max-width: 1500px) {
@@ -83,12 +101,22 @@
83101
.nav-links {
84102
display: flex;
85103
gap: var(--spacing-6);
104+
flex: 0 0 auto;
86105
}
87106

88107
.nav-right {
89108
display: flex;
90109
align-items: center;
91110
gap: var(--spacing-3);
111+
flex: 0 0 auto;
112+
}
113+
114+
.nav-menu {
115+
display: flex;
116+
align-items: center;
117+
margin-left: auto;
118+
gap: var(--spacing-4);
119+
flex: 0 0 auto;
92120
}
93121

94122
.nav-auth {
@@ -134,6 +162,10 @@
134162
display: none;
135163
}
136164

165+
.nav-mobile-star {
166+
display: none;
167+
}
168+
137169
.nav-link-activity i {
138170
font-size: 1.05em;
139171
}
@@ -164,6 +196,67 @@
164196
}
165197
}
166198

199+
.nav-overflow-dropdown {
200+
display: none;
201+
position: relative;
202+
}
203+
204+
.nav-overflow-dropdown.is-visible {
205+
display: inline-flex;
206+
}
207+
208+
.nav-overflow-toggle {
209+
list-style: none;
210+
}
211+
212+
.nav-overflow-toggle::marker,
213+
.nav-overflow-toggle::-webkit-details-marker {
214+
display: none;
215+
}
216+
217+
.nav-overflow-toggle {
218+
padding: var(--spacing-2);
219+
width: auto;
220+
height: auto;
221+
}
222+
223+
.nav-overflow-menu {
224+
position: absolute;
225+
right: 0;
226+
top: calc(100% + var(--spacing-2));
227+
background: var(--color-bg-card);
228+
border: var(--border-width) solid var(--color-border);
229+
border-radius: var(--radius-xl);
230+
box-shadow: var(--shadow-lg);
231+
padding: var(--spacing-2);
232+
min-width: 220px;
233+
display: flex;
234+
flex-direction: column;
235+
gap: var(--spacing-2);
236+
z-index: 200;
237+
}
238+
239+
.nav-overflow-menu .nav-link {
240+
width: 100%;
241+
justify-content: space-between;
242+
}
243+
244+
.nav-overflow-menu form {
245+
width: 100%;
246+
}
247+
248+
.sr-only {
249+
position: absolute;
250+
width: 1px;
251+
height: 1px;
252+
padding: 0;
253+
margin: -1px;
254+
overflow: hidden;
255+
clip: rect(0, 0, 0, 0);
256+
white-space: nowrap;
257+
border: 0;
258+
}
259+
167260
.mobile-nav-dropdown {
168261
display: none;
169262
position: relative;
@@ -233,7 +326,8 @@
233326
}
234327

235328
.nav-links,
236-
.nav-right {
329+
.nav-right,
330+
.nav-menu {
237331
display: none;
238332
}
239333

@@ -242,6 +336,14 @@
242336
margin-left: auto;
243337
}
244338

339+
.nav-mobile-star {
340+
display: inline-flex;
341+
}
342+
343+
.nav-mobile-star + .nav-mobile-bell {
344+
margin-left: 0;
345+
}
346+
245347
body.has-sidebar .nav-burger {
246348
display: inline-flex;
247349
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { Controller } from "@hotwired/stimulus"
2+
3+
const MOBILE_BREAKPOINT = "(max-width: 900px)"
4+
5+
export default class extends Controller {
6+
static targets = ["container", "menu", "overflow", "overflowMenu", "item"]
7+
8+
connect() {
9+
this.mediaQuery = window.matchMedia(MOBILE_BREAKPOINT)
10+
this._resizeHandler = this.layout.bind(this)
11+
window.addEventListener("resize", this._resizeHandler)
12+
this.storePositions()
13+
this.layout()
14+
}
15+
16+
disconnect() {
17+
window.removeEventListener("resize", this._resizeHandler)
18+
}
19+
20+
storePositions() {
21+
this.positions = new Map()
22+
this.orderedItems = [...this.itemTargets]
23+
this.orderedItems.forEach((item) => {
24+
const parent = item.parentElement
25+
const index = Array.from(parent.children).indexOf(item)
26+
this.positions.set(item, { parent, index })
27+
})
28+
}
29+
30+
restoreItems() {
31+
const byParent = new Map()
32+
this.positions.forEach((position, item) => {
33+
if (!byParent.has(position.parent)) {
34+
byParent.set(position.parent, [])
35+
}
36+
byParent.get(position.parent).push({ item, index: position.index })
37+
})
38+
39+
byParent.forEach((items, parent) => {
40+
items
41+
.sort((a, b) => a.index - b.index)
42+
.forEach(({ item, index }) => {
43+
const ref = parent.children[index] || null
44+
parent.insertBefore(item, ref)
45+
})
46+
})
47+
}
48+
49+
hideOverflow() {
50+
this.overflowTarget.classList.remove("is-visible")
51+
this.overflowTarget.open = false
52+
}
53+
54+
showOverflow() {
55+
this.overflowTarget.classList.add("is-visible")
56+
}
57+
58+
layout() {
59+
if (!this.hasContainerTarget || !this.hasOverflowMenuTarget) return
60+
61+
this.restoreItems()
62+
this.overflowMenuTarget.innerHTML = ""
63+
this.hideOverflow()
64+
65+
if (this.mediaQuery.matches) {
66+
return
67+
}
68+
69+
const fits = this.containerTarget.scrollWidth <= this.containerTarget.clientWidth
70+
if (fits) return
71+
72+
this.showOverflow()
73+
74+
for (let i = this.orderedItems.length - 1; i >= 0; i -= 1) {
75+
if (this.containerTarget.scrollWidth <= this.containerTarget.clientWidth) break
76+
const item = this.orderedItems[i]
77+
if (this.overflowMenuTarget.contains(item)) continue
78+
this.overflowMenuTarget.insertBefore(item, this.overflowMenuTarget.firstChild)
79+
}
80+
81+
if (!this.overflowMenuTarget.children.length) {
82+
this.hideOverflow()
83+
}
84+
}
85+
}

app/views/layouts/application.html.slim

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ html data-theme="light"
4141
- if user_signed_in? && current_user.username.blank?
4242
.global-warning
4343
span Please set a username in Settings.
44+
- starred_active = controller_name == "topics" && action_name == "index" && params[:filter].to_s == "starred_by_me"
4445
nav.main-navigation
45-
.nav-container
46+
.nav-container data-controller="nav-overflow" data-nav-overflow-target="container"
4647
.nav-brand
4748
- if content_for?(:sidebar)
4849
button.nav-burger type="button" aria-label="Toggle sidebar" data-action="click->sidebar#toggleMobile"
@@ -56,6 +57,13 @@ html data-theme="light"
5657
i.fa-solid.fa-caret-down
5758
.mobile-nav-menu data-action="click->sidebar#closeMenuOnNavigate"
5859
= link_to "Topics", topics_path, class: "nav-link"
60+
- if user_signed_in?
61+
- icon_class = starred_active ? "fa-solid fa-star" : "fa-regular fa-star"
62+
- link_classes = ["nav-link"]
63+
- link_classes << "is-active" if starred_active
64+
= link_to topics_path(filter: "starred_by_me"), class: link_classes.join(" "), title: "Starred by me", aria: { label: "Starred by me" } do
65+
i class=icon_class aria-hidden="true"
66+
span.sr-only Starred
5967
= link_to "Search", topics_path(anchor: "search"), class: "nav-link"
6068
= link_to "Statistics", stats_path, class: "nav-link"
6169
= link_to "Reports", reports_path, class: "nav-link"
@@ -76,37 +84,47 @@ html data-theme="light"
7684
span.tagline PostgreSQL Hackers Archive
7785
- if user_signed_in?
7886
- unread = activity_unread_count
87+
- starred_href = starred_active ? topics_path : topics_path(filter: "starred_by_me")
88+
- starred_title = starred_active ? "All topics" : "Starred by me"
89+
= link_to starred_href, class: "nav-link nav-mobile-star#{' is-active' if starred_active}", title: starred_title, aria: { label: starred_title } do
90+
i class=(starred_active ? "fa-solid fa-star" : "fa-regular fa-star") aria-hidden="true"
91+
span.sr-only Starred
7992
= link_to activities_path, class: "nav-link nav-link-activity nav-mobile-bell", title: "Activity" do
8093
i.fa-regular.fa-bell
8194
- if unread.positive?
8295
span.nav-badge = unread
83-
.nav-links
84-
= link_to "Topics", topics_path, class: "nav-link"
85-
- search_link = content_for?(:search_sidebar) ? "#search" : topics_path(anchor: "search")
86-
= link_to "Search", search_link, class: "nav-link"
87-
= link_to "Statistics", stats_path, class: "nav-link"
88-
= link_to "Reports", reports_path, class: "nav-link"
89-
= link_to "Help", help_index_path, class: "nav-link"
90-
.nav-right
91-
button.nav-link.theme-toggle type="button" aria-label="Toggle theme" data-controller="theme" data-action="click->theme#toggle"
92-
i.fas.fa-moon data-theme-target="icon"
93-
span data-theme-target="label" Theme
94-
.nav-auth
95-
- if user_signed_in?
96-
- if current_user&.person&.default_alias
97-
= link_to current_user.person.default_alias.name, person_path(current_user.person.default_alias.email), class: "nav-link nav-user"
98-
- unread = activity_unread_count
99-
= link_to activities_path, class: "nav-link nav-link-activity", title: "Activity" do
100-
i.fa-regular.fa-bell
101-
- if unread.positive?
102-
span.nav-badge = unread
103-
= link_to "Settings", settings_root_path, class: "nav-link"
104-
- if current_admin?
105-
= link_to "Admin", admin_root_path, class: "nav-link"
106-
= button_to "Sign out", session_path, method: :delete, class: "nav-link", form: { style: 'display:inline' }, data: { turbo: false }
107-
- else
108-
= link_to "Sign in", new_session_path, class: "nav-link"
109-
= link_to "Register", new_registration_path, class: "nav-link"
96+
.nav-menu data-nav-overflow-target="menu"
97+
.nav-links
98+
= link_to "Topics", topics_path, class: "nav-link", data: { "nav-overflow-target": "item" }
99+
- search_link = content_for?(:search_sidebar) ? "#search" : topics_path(anchor: "search")
100+
= link_to "Search", search_link, class: "nav-link", data: { "nav-overflow-target": "item" }
101+
= link_to "Statistics", stats_path, class: "nav-link", data: { "nav-overflow-target": "item" }
102+
= link_to "Reports", reports_path, class: "nav-link", data: { "nav-overflow-target": "item" }
103+
= link_to "Help", help_index_path, class: "nav-link", data: { "nav-overflow-target": "item" }
104+
.nav-right
105+
button.nav-link.theme-toggle type="button" aria-label="Toggle theme" data-controller="theme" data-action="click->theme#toggle" data-nav-overflow-target="item"
106+
i.fas.fa-moon data-theme-target="icon"
107+
span data-theme-target="label" Theme
108+
.nav-auth
109+
- if user_signed_in?
110+
- if current_user&.person&.default_alias
111+
= 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" }
112+
- unread = activity_unread_count
113+
= link_to activities_path, class: "nav-link nav-link-activity", title: "Activity", data: { "nav-overflow-target": "item" } do
114+
i.fa-regular.fa-bell
115+
- if unread.positive?
116+
span.nav-badge = unread
117+
= link_to "Settings", settings_root_path, class: "nav-link", data: { "nav-overflow-target": "item" }
118+
- if current_admin?
119+
= link_to "Admin", admin_root_path, class: "nav-link", data: { "nav-overflow-target": "item" }
120+
= button_to "Sign out", session_path, method: :delete, class: "nav-link", form: { style: 'display:inline', data: { "nav-overflow-target": "item" } }, data: { turbo: false }
121+
- else
122+
= link_to "Sign in", new_session_path, class: "nav-link", data: { "nav-overflow-target": "item" }
123+
= link_to "Register", new_registration_path, class: "nav-link", data: { "nav-overflow-target": "item" }
124+
details.nav-overflow-dropdown data-nav-overflow-target="overflow"
125+
summary.nav-link.nav-overflow-toggle aria-label="More" data-action="click->sidebar#closeMenuOnNavigate"
126+
i.fa-solid.fa-bars
127+
.nav-overflow-menu data-nav-overflow-target="overflowMenu"
110128

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

0 commit comments

Comments
 (0)