Give the tab rail its own glass bar and reserve space for it
The three tab buttons now share one frosted glass pill instead of each floating separately, and BrowseView/RoomView reserve TAB_RAIL_CLEARANCE of right-side padding so a full-width row or grid never renders under it - verified against the real library, where dense rows (e.g. Conni's 82 audiobooks) previously butted right up against the rail. Also gitignore the real (non-.example) tippen-curriculum.yml and tippen-progress.json, matching config.yml's own privacy treatment, since the curriculum now contains real reward paths into a personal library. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2
python-backend/.gitignore
vendored
2
python-backend/.gitignore
vendored
@@ -1,2 +1,4 @@
|
|||||||
config.yml
|
config.yml
|
||||||
|
tippen-curriculum.yml
|
||||||
|
tippen-progress.json
|
||||||
/.musicmouse-cache
|
/.musicmouse-cache
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
rowTint,
|
rowTint,
|
||||||
SHOW_ROW_ICONS,
|
SHOW_ROW_ICONS,
|
||||||
SHOW_ROW_TITLES,
|
SHOW_ROW_TITLES,
|
||||||
|
TAB_RAIL_CLEARANCE,
|
||||||
} from "../lib/theme";
|
} from "../lib/theme";
|
||||||
import { Cover } from "./Cover";
|
import { Cover } from "./Cover";
|
||||||
|
|
||||||
@@ -142,7 +143,9 @@ export function BrowseView({
|
|||||||
<div
|
<div
|
||||||
ref={scroller}
|
ref={scroller}
|
||||||
className={ANIMATE_VIEW_TRANSITIONS ? "view-enter" : undefined}
|
className={ANIMATE_VIEW_TRANSITIONS ? "view-enter" : undefined}
|
||||||
style={{ flex: 1, overflow: "auto", minHeight: 0, padding: "14px 32px 250px" }}
|
// Right padding reserves room for the tab rail's own glass bar (see
|
||||||
|
// TAB_RAIL_CLEARANCE) so a full-width shelf row never renders under it.
|
||||||
|
style={{ flex: 1, overflow: "auto", minHeight: 0, padding: `14px ${TAB_RAIL_CLEARANCE}px 250px 32px` }}
|
||||||
>
|
>
|
||||||
{shelves.map(({ group: shelfGroup, categories: shelfCategories }, index) => (
|
{shelves.map(({ group: shelfGroup, categories: shelfCategories }, index) => (
|
||||||
<div
|
<div
|
||||||
@@ -299,7 +302,9 @@ export function BrowseView({
|
|||||||
ref={scroller}
|
ref={scroller}
|
||||||
key={`${group}:${category}:${mode}`}
|
key={`${group}:${category}:${mode}`}
|
||||||
className={ANIMATE_VIEW_TRANSITIONS ? "view-enter" : undefined}
|
className={ANIMATE_VIEW_TRANSITIONS ? "view-enter" : undefined}
|
||||||
style={{ flex: 1, overflow: "auto", minHeight: 0, padding: "14px 32px 250px" }}
|
// Right padding reserves room for the tab rail's own glass bar (see
|
||||||
|
// TAB_RAIL_CLEARANCE) so a full-width row/grid never renders under it.
|
||||||
|
style={{ flex: 1, overflow: "auto", minHeight: 0, padding: `14px ${TAB_RAIL_CLEARANCE}px 250px 32px` }}
|
||||||
>
|
>
|
||||||
{songs.length > 0 && (
|
{songs.length > 0 && (
|
||||||
<div style={{ marginBottom: 30 }}>
|
<div style={{ marginBottom: 30 }}>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useState } from "react";
|
|||||||
|
|
||||||
import type { HaConfig } from "../api/types";
|
import type { HaConfig } from "../api/types";
|
||||||
import { useHomeAssistant } from "../hooks/useHomeAssistant";
|
import { useHomeAssistant } from "../hooks/useHomeAssistant";
|
||||||
import { SHOW_DECORATIVE_ANIMATIONS } from "../lib/theme";
|
import { SHOW_DECORATIVE_ANIMATIONS, TAB_RAIL_CLEARANCE } from "../lib/theme";
|
||||||
import { AppHeader } from "./AppHeader";
|
import { AppHeader } from "./AppHeader";
|
||||||
import { Bubbles } from "./Bubbles";
|
import { Bubbles } from "./Bubbles";
|
||||||
import { LightCard } from "./LightCard";
|
import { LightCard } from "./LightCard";
|
||||||
@@ -40,7 +40,11 @@ export function RoomView({ config }: { config: HaConfig }) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AppHeader title="Mein Zimmer" mascot="/dolphin-remote.png" />
|
<AppHeader title="Mein Zimmer" mascot="/dolphin-remote.png" />
|
||||||
<div style={{ flex: 1, overflow: "auto", minHeight: 0, padding: "14px 32px 64px" }}>
|
<div
|
||||||
|
// Right padding reserves room for the tab rail's own glass bar (see
|
||||||
|
// TAB_RAIL_CLEARANCE) so the device grid never renders under it.
|
||||||
|
style={{ flex: 1, overflow: "auto", minHeight: 0, padding: `14px ${TAB_RAIL_CLEARANCE}px 64px 32px` }}
|
||||||
|
>
|
||||||
<div style={{ maxWidth: 1180, margin: "0 auto" }}>
|
<div style={{ maxWidth: 1180, margin: "0 auto" }}>
|
||||||
{config.scenes.length > 0 && (
|
{config.scenes.length > 0 && (
|
||||||
<SceneRow
|
<SceneRow
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
/** The tab rail: icon-only, vertically centered on the right edge - audio player and
|
/** The tab rail: icon-only, vertically centered on the right edge, all three sharing
|
||||||
|
* one frosted glass bar rather than each button floating separately - audio player and
|
||||||
* typing always shown, the smarthome tab only when Home Assistant is configured, same
|
* typing always shown, the smarthome tab only when Home Assistant is configured, same
|
||||||
* gate the old single toggle button used. Replaces that button now that there are
|
* gate the old single toggle button used. Replaces that button now that there are
|
||||||
* three destinations instead of two. */
|
* three destinations instead of two.
|
||||||
|
*
|
||||||
|
* Its footprint (this file's own `right`/padding/button-size numbers) is mirrored in
|
||||||
|
* `lib/theme.ts`'s `TAB_RAIL_CLEARANCE`, which the scrollable views reserve as padding
|
||||||
|
* on their own right edge so a full-width row or grid never renders underneath it. */
|
||||||
|
|
||||||
import { PAGE_ICON, PAGE_LABEL } from "../lib/theme";
|
import { PAGE_ICON, PAGE_LABEL, SHOW_GLASS_BLUR } from "../lib/theme";
|
||||||
import type { UiState } from "../lib/keyboard";
|
import type { UiState } from "../lib/keyboard";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -22,12 +27,19 @@ export function TabRail({ page, onSelect, showRoom }: Props) {
|
|||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "50%",
|
top: "50%",
|
||||||
right: 24,
|
right: 16,
|
||||||
transform: "translateY(-50%)",
|
transform: "translateY(-50%)",
|
||||||
zIndex: 3,
|
zIndex: 3,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: 10,
|
gap: 8,
|
||||||
|
padding: 8,
|
||||||
|
borderRadius: 999,
|
||||||
|
background: "oklch(97% 0.01 210 / .22)",
|
||||||
|
backdropFilter: SHOW_GLASS_BLUR ? "blur(14px)" : undefined,
|
||||||
|
WebkitBackdropFilter: SHOW_GLASS_BLUR ? "blur(14px)" : undefined,
|
||||||
|
border: "1px solid oklch(97% 0.01 210 / .3)",
|
||||||
|
boxShadow: "0 8px 24px oklch(15% 0.05 210 / .35)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{pages.map((candidate) => {
|
{pages.map((candidate) => {
|
||||||
@@ -47,13 +59,11 @@ export function TabRail({ page, onSelect, showRoom }: Props) {
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
border: "none",
|
border: "none",
|
||||||
borderRadius: 999,
|
borderRadius: 999,
|
||||||
background: active ? "var(--accent)" : "oklch(97% 0.01 210 / .95)",
|
background: active ? "var(--accent)" : "transparent",
|
||||||
boxShadow: "0 6px 18px oklch(15% 0.05 210 / .4)",
|
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: 900,
|
fontWeight: 900,
|
||||||
color: active ? "#fff" : "var(--ink)",
|
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
transition: "background 0.15s ease, color 0.15s ease",
|
transition: "background 0.15s ease",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{PAGE_ICON[candidate]}
|
{PAGE_ICON[candidate]}
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ export const GROUP_LABEL: Record<Group, string> = {
|
|||||||
podcasts: "Podcasts",
|
podcasts: "Podcasts",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** How much horizontal space the tab rail's own glass bar claims on the right edge
|
||||||
|
* (its `right` inset plus its rendered width) - scrollable content reserves this much
|
||||||
|
* padding on that side so a full row/grid never renders underneath it. Keep in sync
|
||||||
|
* with the geometry in `TabRail.tsx`. */
|
||||||
|
export const TAB_RAIL_CLEARANCE = 88;
|
||||||
|
|
||||||
/** Icon/label for the vertical tab rail - one entry per `UiState["page"]`. The
|
/** Icon/label for the vertical tab rail - one entry per `UiState["page"]`. The
|
||||||
* smarthome tab only renders when Home Assistant is configured (see `App.tsx`); the
|
* smarthome tab only renders when Home Assistant is configured (see `App.tsx`); the
|
||||||
* other two always show. */
|
* other two always show. */
|
||||||
|
|||||||
Reference in New Issue
Block a user