UI-UX/src/components/ArtistFilters.tsx
iye ed222d1c5f
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m17s
feat(ui): scroll-aware nav glass + floating back button + hero polish
- Navigation: fixed + transparent over Hero (home) / at page top (other routes);
  fades to glass-on-scroll. Glass uses surface tone matching site cards.
- Filter bar sticky glass synced to nav recipe (no seam between layers).
- HeroBanner: full-viewport video, center title removed, bottom dim overlay
  removed, eyebrow/countdown repositioned below the nav.
- ArtistDetail: removed portrait shadow; added FloatingBackButton that uses
  router.back() with internal-history fallback to /.
- Floating buttons (back + vote) translateY upward to avoid footer rather
  than disappearing, via useFooterPush.
- Home: useScrollRestore preserves scroll position on return from detail
  pages; temporarily disables scroll-snap during restore.
- PerformanceVideo: max-w capped by 85svh*16/9 so small viewports never crop.
- ArtistFilters: hide horizontal scrollbar thumb in tag container.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 12:25:54 +08:00

106 lines
2.9 KiB
TypeScript

"use client";
import { cn } from "@/lib/cn";
import type { ArtistTag } from "@/types/artist";
import type { SortKey } from "@/lib/mock-data";
export type TagFilter = ArtistTag | "all";
interface ArtistFiltersProps {
tagFilter: TagFilter;
onTagChange: (tag: TagFilter) => void;
sort: SortKey;
onSortChange: (sort: SortKey) => void;
}
const TAG_OPTIONS: { key: TagFilter; label: string }[] = [
{ key: "all", label: "全部" },
{ key: "rock", label: "摇滚" },
{ key: "pop", label: "流行" },
{ key: "chinese", label: "国风" },
{ key: "hiphop", label: "嘻哈说唱" },
{ key: "folk", label: "民谣治愈" },
{ key: "jazz", label: "爵士" },
];
const SORT_OPTIONS: { key: SortKey; label: string }[] = [
{ key: "votes", label: "实时排名" },
{ key: "no", label: "编号顺序" },
];
export default function ArtistFilters({
tagFilter,
onTagChange,
sort,
onSortChange,
}: ArtistFiltersProps) {
return (
<div className="flex items-center gap-3 py-3 border-b border-white/[0.06]">
{/* 左:标签筛选 · 隐藏横向滚动条 thumb,避免在毛玻璃栏里出现深色小矩形 */}
<div className="flex items-center gap-1 overflow-x-auto flex-1 min-w-0 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
{TAG_OPTIONS.map((opt) => (
<TagPill
key={opt.key}
active={tagFilter === opt.key}
onClick={() => onTagChange(opt.key)}
>
{opt.label}
</TagPill>
))}
</div>
{/* 右:排序切换(segmented control 胶囊) */}
<div className="flex-shrink-0 inline-flex rounded-full bg-white/[0.04] border border-white/10 p-0.5 text-xs">
{SORT_OPTIONS.map((opt) => {
const active = sort === opt.key;
return (
<button
key={opt.key}
type="button"
onClick={() => onSortChange(opt.key)}
aria-pressed={active}
className={cn(
"px-3 sm:px-3.5 h-7 rounded-full transition-colors whitespace-nowrap",
active
? "bg-purple-500/25 text-purple-100 shadow-[0_0_10px_rgba(139,92,246,0.35)]"
: "text-white/55 hover:text-white/85",
)}
>
{opt.label}
</button>
);
})}
</div>
</div>
);
}
function TagPill({
active,
onClick,
children,
}: {
active: boolean;
onClick: () => void;
children: React.ReactNode;
}) {
return (
<button
type="button"
onClick={onClick}
className={cn(
"relative px-3.5 py-1.5 text-xs whitespace-nowrap transition-colors",
active ? "text-purple-300" : "text-white/55 hover:text-white/85",
)}
>
{children}
{active && (
<span
className="absolute -bottom-0.5 left-3 right-3 h-0.5 bg-purple-400 rounded-full"
style={{ boxShadow: "0 0 6px #a78bfa" }}
/>
)}
</button>
);
}