This commit is contained in:
2025-12-17 13:33:31 -05:00
parent e18aa3f42c
commit c49bfe5a3d
38 changed files with 2436 additions and 436 deletions

View File

@@ -34,10 +34,26 @@ export default function LyricSearch() {
const [showLyrics, setShowLyrics] = useState(false);
return (
<div className="lyric-search">
<h1 className="text-3xl font-bold mb-8 text-neutral-900 dark:text-white tracking-tight">
Lyric Search
</h1>
<div className="lyric-search w-full">
{/* Hero section */}
<div className="mt-8 mb-12 text-center flex flex-col items-center">
<div className="relative w-32 h-32 flex items-center justify-center mb-4">
<div
className="absolute inset-0 rounded-full"
style={{
background: 'radial-gradient(circle at 50% 50%, rgba(168,85,247,0.25) 0%, rgba(168,85,247,0.15) 30%, rgba(236,72,153,0.08) 60%, transparent 80%)',
}}
></div>
<span className="relative text-6xl" style={{ marginTop: '-4px' }}>🎤</span>
</div>
<h1 className="text-4xl font-bold mb-3 text-neutral-900 dark:text-white tracking-tight">
Lyric Search
</h1>
<p className="text-neutral-600 dark:text-neutral-400 text-base max-w-sm leading-relaxed">
Search millions of songs instantly.<br />
<span className="text-neutral-400 dark:text-neutral-500 text-sm">Powered by Genius, LRCLib & more</span>
</p>
</div>
<LyricSearchInputField
id="lyric-search-input"
placeholder="Artist - Song"
@@ -268,7 +284,9 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
const evaluation = evaluateSearchValue(searchValue);
if (!evaluation?.valid) {
const message = statusLabels[evaluation?.status || inputStatus] || "Please use Artist - Song";
toast.error(message);
if (!toast.isActive("lyrics-validation-error-toast")) {
toast.error(message, { toastId: "lyrics-validation-error-toast" });
}
return;
}
@@ -335,13 +353,14 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
dismissSearchToast();
toast.success(`Found! (Took ${duration}s)`, {
autoClose: 2500,
toastId: `lyrics-success-${Date.now()}`,
toastId: "lyrics-success-toast",
});
} catch (error) {
dismissSearchToast();
toast.error(error.message, {
icon: () => "😕",
autoClose: 5000,
toastId: "lyrics-error-toast",
});
} finally {
setIsLoading(false);
@@ -412,7 +431,7 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
}, [statusTitle]);
return (
<div>
<div className="w-full">
<div className="lyric-search-input-wrapper">
<AutoComplete
id={id}
@@ -446,7 +465,7 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
{statusTitle}
</span>
</div>
<div className="flex items-center gap-4 mt-5">
<div className="flex flex-wrap items-center justify-center gap-4 mt-5 mb-8">
<Button
onClick={() => handleSearch()}
className="search-btn"
@@ -454,12 +473,12 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
>
Search
</Button>
<div className="h-6 w-px bg-neutral-300 dark:bg-neutral-700" aria-hidden="true"></div>
<div className="h-6 w-px bg-neutral-300 dark:bg-neutral-700 hidden sm:block" aria-hidden="true"></div>
<div className="exclude-sources">
<span className="exclude-label">Exclude:</span>
<UICheckbox id="excl-Genius" label="Genius" onToggle={toggleExclusion} />
<UICheckbox id="excl-LRCLib-Cache" label="LRCLib" onToggle={toggleExclusion} />
<UICheckbox id="excl-Cache" label="Cache" onToggle={toggleExclusion} />
<span className="exclude-label hidden sm:inline">Exclude:</span>
<UICheckbox id="excl-Genius" label="Genius" value="Genius" onToggle={toggleExclusion} />
<UICheckbox id="excl-LRCLib-Cache" label="LRCLib" value="LRCLib-Cache" onToggle={toggleExclusion} />
<UICheckbox id="excl-Cache" label="Cache" value="Cache" onToggle={toggleExclusion} />
</div>
</div>
@@ -585,7 +604,7 @@ export const UICheckbox = forwardRef(function UICheckbox(props = {}, ref) {
const newChecked = !checked;
setChecked(newChecked);
if (props.onToggle) {
const source = props.label;
const source = props.value || props.label;
props.onToggle(source);
}
setTimeout(verifyExclusions, 0);