Skip to content

Commit 4bcc98f

Browse files
committed
added a star icon, 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 de7a40b commit 4bcc98f

3 files changed

Lines changed: 241 additions & 27 deletions

File tree

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: 52 additions & 25 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"
@@ -74,35 +82,54 @@ html data-theme="light"
7482
span.tagline PostgreSQL Hackers Archive
7583
- if user_signed_in?
7684
- unread = activity_unread_count
85+
- starred_href = starred_active ? topics_path : topics_path(filter: "starred_by_me")
86+
- starred_title = starred_active ? "All topics" : "Starred by me"
87+
= link_to starred_href, class: "nav-link nav-mobile-star#{' is-active' if starred_active}", title: starred_title, aria: { label: starred_title } do
88+
i class=(starred_active ? "fa-solid fa-star" : "fa-regular fa-star") aria-hidden="true"
89+
span.sr-only Starred
7790
= link_to activities_path, class: "nav-link nav-link-activity nav-mobile-bell", title: "Activity" do
7891
i.fa-regular.fa-bell
7992
- if unread.positive?
8093
span.nav-badge = unread
81-
.nav-links
82-
= link_to "Topics", topics_path, class: "nav-link"
83-
- search_link = content_for?(:search_sidebar) ? "#search" : topics_path(anchor: "search")
84-
= link_to "Search", search_link, class: "nav-link"
85-
= link_to "Statistics", stats_path, class: "nav-link"
86-
= link_to "Reports", reports_path, class: "nav-link"
87-
= link_to "Help", help_index_path, class: "nav-link"
88-
.nav-right
89-
button.nav-link.theme-toggle type="button" aria-label="Toggle theme" data-controller="theme" data-action="click->theme#toggle"
90-
i.fas.fa-moon data-theme-target="icon"
91-
span data-theme-target="label" Theme
92-
.nav-auth
94+
.nav-menu data-nav-overflow-target="menu"
95+
.nav-links
9396
- if user_signed_in?
94-
- if current_user&.person&.default_alias
95-
= link_to current_user.person.default_alias.name, person_path(current_user.person.default_alias.email), class: "nav-link nav-user"
96-
- unread = activity_unread_count
97-
= link_to activities_path, class: "nav-link nav-link-activity", title: "Activity" do
98-
i.fa-regular.fa-bell
99-
- if unread.positive?
100-
span.nav-badge = unread
101-
= link_to "Settings", settings_root_path, class: "nav-link"
102-
= button_to "Sign out", session_path, method: :delete, class: "nav-link", form: { style: 'display:inline' }, data: { turbo: false }
103-
- else
104-
= link_to "Sign in", new_session_path, class: "nav-link"
105-
= link_to "Register", new_registration_path, class: "nav-link"
97+
- starred_href = starred_active ? topics_path : topics_path(filter: "starred_by_me")
98+
- starred_title = starred_active ? "All topics" : "Starred by me"
99+
- icon_class = starred_active ? "fa-solid fa-star" : "fa-regular fa-star"
100+
- link_classes = ["nav-link"]
101+
- link_classes << "is-active" if starred_active
102+
= link_to starred_href, class: link_classes.join(" "), title: starred_title, aria: { label: starred_title }, data: { "nav-overflow-target": "item" } do
103+
i class=icon_class aria-hidden="true"
104+
span.sr-only Starred
105+
= link_to "Topics", topics_path, class: "nav-link", data: { "nav-overflow-target": "item" }
106+
- search_link = content_for?(:search_sidebar) ? "#search" : topics_path(anchor: "search")
107+
= link_to "Search", search_link, class: "nav-link", data: { "nav-overflow-target": "item" }
108+
= link_to "Statistics", stats_path, class: "nav-link", data: { "nav-overflow-target": "item" }
109+
= link_to "Reports", reports_path, class: "nav-link", data: { "nav-overflow-target": "item" }
110+
= link_to "Help", help_index_path, class: "nav-link", data: { "nav-overflow-target": "item" }
111+
.nav-right
112+
button.nav-link.theme-toggle type="button" aria-label="Toggle theme" data-controller="theme" data-action="click->theme#toggle" data-nav-overflow-target="item"
113+
i.fas.fa-moon data-theme-target="icon"
114+
span data-theme-target="label" Theme
115+
.nav-auth
116+
- if user_signed_in?
117+
- if current_user&.person&.default_alias
118+
= 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" }
119+
- unread = activity_unread_count
120+
= link_to activities_path, class: "nav-link nav-link-activity", title: "Activity", data: { "nav-overflow-target": "item" } do
121+
i.fa-regular.fa-bell
122+
- if unread.positive?
123+
span.nav-badge = unread
124+
= link_to "Settings", settings_root_path, class: "nav-link", data: { "nav-overflow-target": "item" }
125+
= button_to "Sign out", session_path, method: :delete, class: "nav-link", form: { style: 'display:inline', data: { "nav-overflow-target": "item" } }, data: { turbo: false }
126+
- else
127+
= link_to "Sign in", new_session_path, class: "nav-link", data: { "nav-overflow-target": "item" }
128+
= link_to "Register", new_registration_path, class: "nav-link", data: { "nav-overflow-target": "item" }
129+
details.nav-overflow-dropdown data-nav-overflow-target="overflow"
130+
summary.nav-link.nav-overflow-toggle aria-label="More" data-action="click->sidebar#closeMenuOnNavigate"
131+
i.fa-solid.fa-bars
132+
.nav-overflow-menu data-nav-overflow-target="overflowMenu"
106133

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

0 commit comments

Comments
 (0)