feat(qbz-slint): three-section header with responsive tabs

The header is now three equal sections — navigation tabs (left), the
search field (center, so it lands at the window center), and the user
element (right) — fixing the tabs stretching to full width under the
search field.

- Nav tabs regain Lucide icons (compass, library, hard-drive,
  cassette-tape) and use a 12px label.
- The user element uses a hamburger icon instead of the QBZ logo, which
  aliased badly as a scaled PNG.
- Responsive: below 1140px the tabs drop their icons; below 960px the
  search field narrows to 70%.

cc
This commit is contained in:
vicrodh committed 2026-05-18 15:27:36 -06:00
1 parent 8c27de5651
commit b9be2dc0d9
5 files changed
+155 -84

No files matched your search

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="20" height="16" x="2" y="4" rx="2" />
<circle cx="8" cy="10" r="2" />
<path d="M8 12h8" />
<circle cx="16" cy="10" r="2" />
<path d="m6 20 .7-2.9A1.4 1.4 0 0 1 8.1 16h7.8a1.4 1.4 0 0 1 1.4 1l.7 3" />
</svg>

After

Width:  |  Height:  |  Size: 407 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z" />
</svg>

After

Width:  |  Height:  |  Size: 327 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 16h.01" />
<path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" />
<path d="M21.946 12.013H2.054" />
<path d="M6 16h.01" />
</svg>

After

Width:  |  Height:  |  Size: 435 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="8" height="18" x="3" y="3" rx="1" />
<path d="M7 3v18" />
<path d="M20.4 18.9c.2.5-.1 1.1-.6 1.3l-1.9.7c-.5.2-1.1-.1-1.3-.6L11.1 5.1c-.2-.5.1-1.1.6-1.3l1.9-.7c.5-.2 1.1.1 1.3.6Z" />
</svg>

After

Width:  |  Height:  |  Size: 385 B

+133 -84
View File
@@ -1,5 +1,9 @@
// Top header bar — primary navigation tabs, a window-centered search
// field, and the user element with its dropdown menu.
// Top header bar — three equal sections: navigation tabs (left), the
// search field (center, so it lands at the window center), and the user
// element with its dropdown menu (right).
//
// Responsive: below the first breakpoint the tabs drop their icons;
// below the second the search field narrows to 70%.
import { Theme } from "../foundation/semantic-colors.slint";
import { Spacing } from "../foundation/spacing.slint";
@@ -10,22 +14,34 @@ import { SessionState } from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint";
component NavTab inherits Rectangle {
in property <image> icon;
in property <string> label;
in property <bool> active: false;
in property <bool> show-icon: true;
callback clicked();
height: 32px;
height: 30px;
border-radius: Radius.sm;
background: root.active ? Theme.surface-elevated
: ta.has-hover ? Theme.surface-hover : transparent;
HorizontalLayout {
padding-left: Spacing.md;
padding-right: Spacing.md;
padding-left: root.show-icon ? 9px : 11px;
padding-right: 11px;
spacing: 6px;
if root.show-icon: VerticalLayout {
alignment: center;
QbzIcon {
source: root.icon;
width: 14px;
height: 14px;
tint: root.active ? Theme.text-primary : Theme.text-secondary;
}
}
Text {
text: root.label;
color: root.active ? Theme.text-primary : Theme.text-secondary;
font-size: Typography.body;
font-size: 12px;
font-weight: root.active ? Typography.semibold : Typography.regular;
vertical-alignment: center;
}
@@ -84,92 +100,125 @@ export component HeaderBar inherits Rectangle {
height: Layout.header-height;
background: Theme.surface-card;
// --- Nav tabs (left) -------------------------------------------------
VerticalLayout {
x: Spacing.md;
height: parent.height;
alignment: center;
HorizontalLayout {
spacing: Spacing.xs;
NavTab { label: "Discover"; active: true; }
NavTab { label: "Library"; }
NavTab { label: "Local Library"; }
NavTab { label: "My QBZ"; }
}
}
// Responsive breakpoints.
property <bool> show-tab-icons: root.width >= 1140px;
property <length> search-width: root.width < 960px ? 224px : 320px;
// --- Search field, centered to the header width ----------------------
Rectangle {
width: 320px;
height: 32px;
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
border-radius: 6px;
border-width: 1px;
border-color: Theme.border-subtle;
background: Theme.surface-elevated;
HorizontalLayout {
padding-left: 10px;
padding-right: 10px;
spacing: 7px;
VerticalLayout {
alignment: center;
QbzIcon {
source: @image-url("../assets/icons/search.svg");
width: 14px;
height: 14px;
tint: Theme.text-muted;
HorizontalLayout {
// --- Tabs (left third) -------------------------------------------
VerticalLayout {
horizontal-stretch: 1;
alignment: center;
HorizontalLayout {
padding-left: Spacing.md;
spacing: Spacing.xs;
alignment: start;
NavTab {
icon: @image-url("../assets/icons/compass.svg");
label: "Discover";
active: true;
show-icon: root.show-tab-icons;
}
NavTab {
icon: @image-url("../assets/icons/library-big.svg");
label: "Library";
show-icon: root.show-tab-icons;
}
NavTab {
icon: @image-url("../assets/icons/hard-drive.svg");
label: "Local Library";
show-icon: root.show-tab-icons;
}
NavTab {
icon: @image-url("../assets/icons/cassette-tape.svg");
label: "My QBZ";
show-icon: root.show-tab-icons;
}
}
Text {
text: @tr("Search");
color: Theme.text-muted;
font-size: 13px;
vertical-alignment: center;
horizontal-stretch: 1;
}
}
}
// --- User element + dropdown (right) ---------------------------------
user-area := TouchArea {
width: user-row.preferred-width + 16px;
height: 36px;
x: parent.width - self.width - Spacing.md;
y: (parent.height - self.height) / 2;
mouse-cursor: pointer;
clicked => {
menu.show();
}
Rectangle {
width: 100%;
height: 100%;
border-radius: Radius.sm;
background: user-area.has-hover ? Theme.surface-hover : transparent;
}
user-row := HorizontalLayout {
x: 8px;
height: parent.height;
spacing: 8px;
VerticalLayout {
// --- Search (center third) ---------------------------------------
VerticalLayout {
horizontal-stretch: 1;
alignment: center;
HorizontalLayout {
alignment: center;
Text {
text: SessionState.user-name == ""
? @tr("Guest")
: SessionState.user-name;
color: Theme.text-primary;
font-size: Typography.body;
Rectangle {
width: root.search-width;
height: 32px;
border-radius: 6px;
border-width: 1px;
border-color: Theme.border-subtle;
background: Theme.surface-elevated;
HorizontalLayout {
padding-left: 10px;
padding-right: 10px;
spacing: 7px;
VerticalLayout {
alignment: center;
QbzIcon {
source: @image-url("../assets/icons/search.svg");
width: 14px;
height: 14px;
tint: Theme.text-muted;
}
}
Text {
text: @tr("Search");
color: Theme.text-muted;
font-size: 13px;
vertical-alignment: center;
horizontal-stretch: 1;
}
}
}
}
VerticalLayout {
alignment: center;
Image {
source: @image-url("../assets/qbz-logo.png");
width: 28px;
height: 28px;
image-fit: contain;
}
// --- User element (right third) ----------------------------------
VerticalLayout {
horizontal-stretch: 1;
alignment: center;
HorizontalLayout {
padding-right: Spacing.md;
alignment: end;
user-area := TouchArea {
width: user-row.preferred-width + 16px;
height: 36px;
mouse-cursor: pointer;
clicked => {
menu.show();
}
Rectangle {
width: 100%;
height: 100%;
border-radius: Radius.sm;
background: user-area.has-hover ? Theme.surface-hover : transparent;
}
user-row := HorizontalLayout {
x: 8px;
height: parent.height;
spacing: 9px;
VerticalLayout {
alignment: center;
Text {
text: SessionState.user-name == ""
? @tr("Guest")
: SessionState.user-name;
color: Theme.text-primary;
font-size: Typography.body;
}
}
VerticalLayout {
alignment: center;
QbzIcon {
source: @image-url("../assets/icons/menu.svg");
width: 18px;
height: 18px;
tint: Theme.text-secondary;
}
}
}
}
}
}