- LyricSearch: misc/field focus, validation

- Nav: further improvements
This commit is contained in:
2025-11-26 09:17:30 -05:00
parent ee25ad243c
commit eb38f8865f
5 changed files with 80 additions and 12 deletions

BIN
public/images/zim.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -195,6 +195,11 @@ blockquote p:first-of-type::after {
Custom Custom
*/ */
.logo-auth {
height: 64px;
width: 64px;
}
.footer { .footer {
display: grid; display: grid;
align-items: end; align-items: end;

View File

@@ -101,8 +101,8 @@ export default function LoginPage() {
<div className="flex items-start justify-center bg-gray-50 dark:bg-[#121212] px-4 pt-20 py-10"> <div className="flex items-start justify-center bg-gray-50 dark:bg-[#121212] px-4 pt-20 py-10">
<div className="max-w-md w-full bg-white dark:bg-[#1E1E1E] rounded-2xl shadow-xl px-10 pb-6"> <div className="max-w-md w-full bg-white dark:bg-[#1E1E1E] rounded-2xl shadow-xl px-10 pb-6">
<h2 className="flex flex-col items-center text-3xl font-semibold text-gray-900 dark:text-white mb-8 font-sans"> <h2 className="flex flex-col items-center text-3xl font-semibold text-gray-900 dark:text-white mb-8 font-sans">
<img className="logo-auth mb-4" src="/images/kode.png" alt="Logo" /> <img className="logo-auth mb-4" src="/images/zim.png" alt="Logo" />
Authentication Required Log In
</h2> </h2>
<form className="space-y-6 relative" onSubmit={handleSubmit} noValidate> <form className="space-y-6 relative" onSubmit={handleSubmit} noValidate>

View File

@@ -324,6 +324,16 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
const statusTitle = statusLabels[inputStatus]; const statusTitle = statusLabels[inputStatus];
const StatusIcon = statusIcons[inputStatus] || RemoveRoundedIcon; const StatusIcon = statusIcons[inputStatus] || RemoveRoundedIcon;
useEffect(() => {
const inputEl = autoCompleteInputRef.current;
if (!inputEl) return;
if (statusTitle) {
inputEl.setAttribute("title", statusTitle);
} else {
inputEl.removeAttribute("title");
}
}, [statusTitle]);
return ( return (
<div> <div>
<div className="lyric-search-input-wrapper"> <div className="lyric-search-input-wrapper">

View File

@@ -2,7 +2,7 @@
import { metaData, API_URL } from "../config"; import { metaData, API_URL } from "../config";
import { Icon } from "astro-icon/components"; import { Icon } from "astro-icon/components";
const isLoggedIn = Astro.cookies.get('access_token') || Astro.cookies.get('refresh_token'); const isLoggedIn = Boolean(Astro.cookies.get('access_token') || Astro.cookies.get('refresh_token'));
const padlockIconSvg = ` const padlockIconSvg = `
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.2" viewBox="0 0 24 24"> <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.2" viewBox="0 0 24 24">
@@ -21,14 +21,27 @@ const externalLinkIconSvg = `
const navItems = [ const navItems = [
{ label: "Home", href: "/" }, { label: "Home", href: "/" },
{ label: "Radio", href: "/radio" }, { label: "Radio", href: "/radio" },
{ label: "Memes", href: "/memes" }, { label: "Memes", href: "/memes" },
{ label: "Lighting", href: "/lighting", auth: true, icon: "padlock" }, { label: "Lighting", href: "/lighting", auth: true },
{ label: "TRip", href: "/TRip", auth: true, icon: "padlock" }, { label: "TRip", href: "/TRip", auth: true, icon: "pirate" },
{ label: "Status", href: "https://status.boatson.boats", icon: "external" }, { label: "Status", href: "https://status.boatson.boats", icon: "external" },
{ label: "Git", href: "https://kode.boatson.boats", icon: "external" }, { label: "Git", href: "https://kode.boatson.boats", icon: "external" },
{ label: "Login", href: "/login", guestOnly: true },
...(isLoggedIn ? [{ label: "Logout", href: "#logout", onclick: "handleLogout()" }] : []), ...(isLoggedIn ? [{ label: "Logout", href: "#logout", onclick: "handleLogout()" }] : []),
]; ];
const visibleNavItems = navItems.filter((item) => {
if (item.auth && !isLoggedIn) {
return false;
}
if (item.guestOnly && isLoggedIn) {
return false;
}
return true;
});
const currentPath = Astro.url.pathname; const currentPath = Astro.url.pathname;
--- ---
@@ -37,7 +50,7 @@ const currentPath = Astro.url.pathname;
<nav class="w-full px-4 sm:px-6 py-4 bg-transparent sticky top-0 z-50 backdrop-blur-sm bg-white/80 dark:bg-[#121212]/80 border-b border-neutral-200/50 dark:border-neutral-800/50"> <nav class="w-full px-4 sm:px-6 py-4 bg-transparent sticky top-0 z-50 backdrop-blur-sm bg-white/80 dark:bg-[#121212]/80 border-b border-neutral-200/50 dark:border-neutral-800/50">
<div class="max-w-7xl mx-auto"> <div class="max-w-7xl mx-auto">
<div class="flex items-center justify-between"> <div class="nav-bar-row flex items-center gap-4 justify-between">
<!-- Logo/Brand --> <!-- Logo/Brand -->
<a <a
href="/" href="/"
@@ -47,9 +60,9 @@ const currentPath = Astro.url.pathname;
</a> </a>
<!-- Desktop Navigation --> <!-- Desktop Navigation -->
<div class="desktop-nav flex items-center gap-0.5"> <div class="desktop-nav flex items-center">
<ul class="flex items-center gap-0.5"> <ul class="desktop-nav-list">
{navItems.map((item) => { {visibleNavItems.map((item) => {
const isExternal = item.href?.startsWith("http"); const isExternal = item.href?.startsWith("http");
const isAuthedPath = item.auth ?? false; const isAuthedPath = item.auth ?? false;
const normalize = (url) => (url || '/').replace(/\/+$/, '') || '/'; const normalize = (url) => (url || '/').replace(/\/+$/, '') || '/';
@@ -80,6 +93,9 @@ const currentPath = Astro.url.pathname;
{item.icon === "padlock" && ( {item.icon === "padlock" && (
<span class="inline-flex" aria-hidden="true" set:html={padlockIconSvg}></span> <span class="inline-flex" aria-hidden="true" set:html={padlockIconSvg}></span>
)} )}
{item.icon === "pirate" && (
<span class="inline-flex ml-1" role="img" aria-label="Pirate flag">🏴‍☠️</span>
)}
</a> </a>
</li> </li>
); );
@@ -90,7 +106,7 @@ const currentPath = Astro.url.pathname;
<button <button
aria-label="Toggle theme" aria-label="Toggle theme"
type="button" type="button"
class="flex items-center justify-center w-8 h-8 ml-1 rounded-md hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors" class="flex items-center justify-center w-8 h-8 rounded-md hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors"
onclick="toggleTheme()" onclick="toggleTheme()"
> >
<Icon <Icon
@@ -137,7 +153,7 @@ const currentPath = Astro.url.pathname;
class="mobile-menu-dropdown md:hidden" class="mobile-menu-dropdown md:hidden"
> >
<ul class="flex flex-col gap-1 py-4"> <ul class="flex flex-col gap-1 py-4">
{navItems.map((item) => { {visibleNavItems.map((item) => {
const isExternal = item.href?.startsWith("http"); const isExternal = item.href?.startsWith("http");
const isAuthedPath = item.auth ?? false; const isAuthedPath = item.auth ?? false;
const normalize = (url) => (url || '/').replace(/\/+$/, '') || '/'; const normalize = (url) => (url || '/').replace(/\/+$/, '') || '/';
@@ -168,6 +184,9 @@ const currentPath = Astro.url.pathname;
{item.icon === "padlock" && ( {item.icon === "padlock" && (
<span class="inline-flex" aria-hidden="true" set:html={padlockIconSvg}></span> <span class="inline-flex" aria-hidden="true" set:html={padlockIconSvg}></span>
)} )}
{item.icon === "pirate" && (
<span class="inline-flex ml-1" role="img" aria-label="Pirate flag">🏴‍☠️</span>
)}
</a> </a>
</li> </li>
); );
@@ -222,4 +241,38 @@ const currentPath = Astro.url.pathname;
nav { nav {
transition: background-color 0.2s ease, border-color 0.2s ease; transition: background-color 0.2s ease, border-color 0.2s ease;
} }
.nav-bar-row {
width: 100%;
}
@media (min-width: 768px) {
.desktop-nav {
flex: 1;
display: flex;
align-items: center;
min-width: 0;
margin-left: 2rem;
gap: clamp(0.75rem, 1.5vw, 1.25rem);
}
}
.desktop-nav-list {
display: flex;
flex: 1;
justify-content: flex-end;
align-items: center;
gap: clamp(0.75rem, 1.5vw, 1.25rem);
margin: 0;
padding: 0;
}
.desktop-nav-list li {
flex-shrink: 1;
}
.desktop-nav-list a {
white-space: nowrap;
}
</style> </style>