Refactor TrackCard and TrackModal components for improved functionality and styling

- Replaced hash-based modal opening in TrackCard with state management for better control.
- Integrated TrackModal into TrackCard, allowing for direct interaction and improved user experience.
- Updated TrackModal to accept props for track data and theme, enhancing its reusability.
- Refined styling in both components to maintain visual consistency with dynamic theming.
This commit is contained in:
m4x809 2025-10-25 03:26:25 +02:00
parent b93b0c2160
commit 9fad47fc65
Signed by: m4x809
SSH key fingerprint: SHA256:YCoFF78p2DUP94EnCScqLwldjkKDwdKSZq3r8p/6EiU
4 changed files with 155 additions and 161 deletions

View file

@ -4,7 +4,7 @@ import { Card, Group, Text, Badge, Box } from "@mantine/core";
import { useState } from "react"; import { useState } from "react";
import type { AlbumTheme } from "@/lib/themes"; import type { AlbumTheme } from "@/lib/themes";
import type { Track } from "@/lib/ListTypes"; import type { Track } from "@/lib/ListTypes";
import { useHash } from "@mantine/hooks"; import TrackModal from "./TrackModal";
interface TrackCardProps { interface TrackCardProps {
track: Track; track: Track;
@ -14,100 +14,103 @@ interface TrackCardProps {
export default function TrackCard({ track, index, theme }: TrackCardProps) { export default function TrackCard({ track, index, theme }: TrackCardProps) {
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const [_hash, setHash] = useHash(); const [modalOpened, setModalOpened] = useState(false);
return ( return (
<Card <>
onClick={() => setHash(`trackModal_${track.id}`)} <Card
className="backdrop-blur-sm transition-all duration-300" onClick={() => setModalOpened(true)}
style={{ className="backdrop-blur-sm transition-all duration-300"
background: isHovered ? theme.card.backgroundHover : theme.card.background, style={{
border: `1px solid ${isHovered ? theme.border.light : theme.card.border}`, background: isHovered ? theme.card.backgroundHover : theme.card.background,
cursor: "pointer", border: `1px solid ${isHovered ? theme.border.light : theme.card.border}`,
// transform: isHovered ? "translateY(-2px)" : "translateY(0)", cursor: "pointer",
boxShadow: isHovered ? `0 8px 24px ${theme.primary.DEFAULT}30` : "none", // transform: isHovered ? "translateY(-2px)" : "translateY(0)",
}} boxShadow: isHovered ? `0 8px 24px ${theme.primary.DEFAULT}30` : "none",
onMouseEnter={() => setIsHovered(true)} }}
onMouseLeave={() => setIsHovered(false)} onMouseEnter={() => setIsHovered(true)}
> onMouseLeave={() => setIsHovered(false)}
<Group justify="space-between" align="center"> >
<Group gap="md"> <Group justify="space-between" align="center">
<Box <Group gap="md">
style={{ <Box
minWidth: "40px", style={{
height: "40px", minWidth: "40px",
display: "flex", height: "40px",
alignItems: "center", display: "flex",
justifyContent: "center", alignItems: "center",
background: theme.primary.DEFAULT, justifyContent: "center",
borderRadius: "8px", background: theme.primary.DEFAULT,
fontWeight: 700, borderRadius: "8px",
fontSize: "18px", fontWeight: 700,
color: theme.text.contrast, fontSize: "18px",
boxShadow: `0 4px 12px ${theme.primary.DEFAULT}60`, color: theme.text.contrast,
}} boxShadow: `0 4px 12px ${theme.primary.DEFAULT}60`,
> }}
{index + 1} >
</Box> {index + 1}
<Text size="lg" fw={500} style={{ color: theme.text.primary }}> </Box>
{track.label} <Text size="lg" fw={500} style={{ color: theme.text.primary }}>
</Text> {track.label}
</Group> </Text>
</Group>
<Group gap="md">
<Group gap="xs"> <Group gap="md">
{track.studioUrl && ( <Group gap="xs">
<Badge {track.studioUrl && (
size="sm" <Badge
variant="filled" size="sm"
style={{ variant="filled"
background: theme.badges.studio, style={{
color: theme.text.contrast, background: theme.badges.studio,
}} color: theme.text.contrast,
> }}
Studio >
</Badge> Studio
)} </Badge>
{track.emilyLive && ( )}
<Badge {track.emilyLive && (
size="sm" <Badge
variant="filled" size="sm"
style={{ variant="filled"
background: theme.badges.liveEmily, style={{
color: theme.text.contrast, background: theme.badges.liveEmily,
}} color: theme.text.contrast,
> }}
Emily Live >
</Badge> Emily Live
)} </Badge>
{track.lpLive && ( )}
<Badge {track.lpLive && (
size="sm" <Badge
variant="filled" size="sm"
style={{ variant="filled"
background: theme.badges.liveLP, style={{
color: theme.text.contrast, background: theme.badges.liveLP,
}} color: theme.text.contrast,
> }}
LP Live >
</Badge> LP Live
)} </Badge>
)}
</Group>
<Text
size="sm"
fw={500}
style={{
color: theme.text.muted,
fontFamily: "monospace",
background: theme.background.tertiary,
padding: "4px 12px",
borderRadius: "6px",
}}
>
{track.duration}
</Text>
</Group> </Group>
<Text
size="sm"
fw={500}
style={{
color: theme.text.muted,
fontFamily: "monospace",
background: theme.background.tertiary,
padding: "4px 12px",
borderRadius: "6px",
}}
>
{track.duration}
</Text>
</Group> </Group>
</Group> </Card>
</Card> <TrackModal opened={modalOpened} onClose={() => setModalOpened(false)} track={track} theme={theme} />
</>
); );
} }

View file

@ -1,72 +1,53 @@
"use client"; "use client";
import { albums } from "@/lib/list";
import { getSongLink } from "@/lib/songLinks"; import { getSongLink } from "@/lib/songLinks";
import { getThemeColors } from "@/lib/themes"; import type { AlbumTheme } from "@/lib/themes";
import { faApple, faSpotify, faYoutube, faYoutubeSquare, IconDefinition } from "@fortawesome/free-brands-svg-icons"; import type { Track } from "@/lib/ListTypes";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { import {
ActionIcon, faApple,
Anchor, faSpotify,
Box, faYoutube,
Divider, faYoutubeSquare,
Grid, type IconDefinition,
GridCol, } from "@fortawesome/free-brands-svg-icons";
Group, import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
Modal, import { ActionIcon, Box, Divider, Grid, GridCol, Group, Modal, Stack, Text, Title, Tooltip } from "@mantine/core";
Stack,
Text,
Title,
Tooltip,
} from "@mantine/core";
import { useDisclosure, useHash } from "@mantine/hooks";
import Link from "next/link"; import Link from "next/link";
export default function TrackModal() { interface TrackModalProps {
const [hash, setHash] = useHash(); opened: boolean;
console.log("🚀 ~ TrackModal.tsx:8 ~ TrackModal ~ hash:", hash); onClose: () => void;
const close = () => { track: Track;
setHash(""); theme: AlbumTheme;
}; }
const album = albums.find((album) => { export default function TrackModal({ opened, onClose, track, theme }: TrackModalProps) {
return album.id === hash.split("_")[1]; const songLink = getSongLink(track.id);
});
const track = album?.tracks.find((track) => {
return track.id === `${hash.split("_")[1]}_${hash.split("_")[2]}`;
});
const songLink = getSongLink(track?.id || "");
console.log("🚀 ~ TrackModal.tsx:25 ~ TrackModal ~ songLink:", songLink);
const theme = getThemeColors(album?.id || "");
return ( return (
<Modal <Modal
opened={hash.startsWith("#trackModal_")} centered
onClose={close} opened={opened}
onClose={onClose}
withCloseButton={false} withCloseButton={false}
transitionProps={{ duration: 150, transition: "fade" }}
styles={{ styles={{
// content: {
// background: theme.background.gradient,
// border: `2px solid ${theme.border.light}`,
// boxShadow: "none",
// },
// header: {
// background: theme.background.gradient,
// border: `2px solid ${theme.border.light}`,
// boxShadow: "none",
// },
body: { body: {
background: theme.background.gradient, background: theme.background.gradient,
// border: `2px solid ${theme.border.light}`,
boxShadow: "none", boxShadow: "none",
}, },
}} }}
> >
<Stack> <Stack>
<Title order={1}>{track?.label}</Title> <Title order={1}>{track.label}</Title>
<Divider size={"lg"} label={<Title order={3}>Studio Version</Title>} /> <Divider
color={theme.accent.DEFAULT}
size={"lg"}
label={
<Title c={theme.accent.DEFAULT} order={3}>
Studio Version
</Title>
}
/>
<Grid columns={songLink && Object.keys(songLink).length > 0 ? Object.keys(songLink).length : 4}> <Grid columns={songLink && Object.keys(songLink).length > 0 ? Object.keys(songLink).length : 4}>
{songLink && {songLink &&
Object.entries(songLink).map(([key, value]) => { Object.entries(songLink).map(([key, value]) => {
@ -113,11 +94,12 @@ export default function TrackModal() {
return ( return (
<GridCol <GridCol
span={1} span={1}
key={`${track?.id}_${key}`} key={`${track.id}_${key}`}
style={{ display: "flex", alignItems: "center", justifyContent: "center" }} style={{ display: "flex", alignItems: "center", justifyContent: "center" }}
> >
<Tooltip label={themeColor.tooltip}> <Tooltip label={themeColor.tooltip}>
<ActionIcon <ActionIcon
tabIndex={-1}
variant="filled" variant="filled"
color={themeColor.color} color={themeColor.color}
size="lg" size="lg"
@ -133,11 +115,19 @@ export default function TrackModal() {
); );
})} })}
</Grid> </Grid>
{Array.isArray(track?.emilyLive) && ( {Array.isArray(track.emilyLive) && (
<> <>
<Divider size={"lg"} label={<Title order={3}>Fan Live Versions</Title>} /> <Divider
color={theme.accent.DEFAULT}
size={"lg"}
label={
<Title c={theme.accent.DEFAULT} order={3}>
Fan Live Versions
</Title>
}
/>
<Stack> <Stack>
{track.emilyLive.map((live, idx) => ( {track.emilyLive.map((live) => (
<Box style={{ width: "100%" }} key={`emilyLive_${live.url}`}> <Box style={{ width: "100%" }} key={`emilyLive_${live.url}`}>
<Group justify="space-between" align="center"> <Group justify="space-between" align="center">
<Box style={{ display: "flex", flexDirection: "column", gap: "4px" }}> <Box style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
@ -148,6 +138,7 @@ export default function TrackModal() {
</Text> </Text>
</Box> </Box>
<ActionIcon <ActionIcon
tabIndex={-1}
variant="filled" variant="filled"
color={"#FF0000"} color={"#FF0000"}
size="lg" size="lg"
@ -165,26 +156,35 @@ export default function TrackModal() {
</> </>
)} )}
{track?.lpLive && ( {track.lpLive && (
<> <>
<Divider size={"lg"} label={<Title order={3}>Linkin Park Live Versions</Title>} /> <Divider
color={theme.accent.DEFAULT}
size={"lg"}
label={
<Title c={theme.accent.DEFAULT} order={3}>
Linkin Park Live Versions
</Title>
}
/>
<Group justify="space-between" align="center"> <Group justify="space-between" align="center">
<Box style={{ display: "flex", flexDirection: "column", gap: "4px" }}> <Box style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
<Text> <Text>
{new Date(track?.lpLive.date).toLocaleDateString("en-US", { {new Date(track.lpLive.date).toLocaleDateString("en-US", {
year: "numeric", year: "numeric",
month: "long", month: "long",
day: "numeric", day: "numeric",
})} })}
</Text> </Text>
<Text>{track?.lpLive.location}</Text> <Text>{track.lpLive.location}</Text>
</Box> </Box>
<ActionIcon <ActionIcon
tabIndex={-1}
variant="filled" variant="filled"
color={"#FF0000"} color={"#FF0000"}
size="lg" size="lg"
component={Link} component={Link}
href={track?.lpLive.url} href={track.lpLive.url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >

View file

@ -4,7 +4,6 @@ import { albums } from "../../../lib/list";
import BackButton from "../../../Components/BackButton"; import BackButton from "../../../Components/BackButton";
import { getThemeColors } from "@/lib/themes"; import { getThemeColors } from "@/lib/themes";
import TrackCard from "@/Components/TrackCard"; import TrackCard from "@/Components/TrackCard";
import TrackModal from "@/Components/TrackModal";
export function generateStaticParams() { export function generateStaticParams() {
return albums.map((album) => ({ return albums.map((album) => ({
@ -46,7 +45,6 @@ export default async function AlbumDetail({ params }: { params: Promise<{ albumI
viewTransitionName: `album-card-background-${album.id}`, viewTransitionName: `album-card-background-${album.id}`,
}} }}
> >
<TrackModal />
<Container size="xl" className="py-12"> <Container size="xl" className="py-12">
{/* Back Button */} {/* Back Button */}
<BackButton theme={theme} /> <BackButton theme={theme} />
@ -57,7 +55,6 @@ export default async function AlbumDetail({ params }: { params: Promise<{ albumI
style={{ style={{
border: `3px solid ${theme.border.light}`, border: `3px solid ${theme.border.light}`,
borderRadius: "8px", borderRadius: "8px",
padding: "4px",
background: theme.background.secondary, background: theme.background.secondary,
boxShadow: `0 8px 32px ${theme.primary.DEFAULT}40`, boxShadow: `0 8px 32px ${theme.primary.DEFAULT}40`,
viewTransitionName: `album-card-image-${album.id}`, viewTransitionName: `album-card-image-${album.id}`,

View file

@ -22,12 +22,6 @@ export const albums: Album[] = [
location: "Austin, Texas, USA", location: "Austin, Texas, USA",
author: "Erynn Halvorson", author: "Erynn Halvorson",
}, },
{
url: "https://youtu.be/miQ9Y5UW08dasdasdg",
date: "2025-04-26",
location: "Austin, Texas, USdasdsaA",
author: "Erynn Halvorson",
},
], ],
lpLive: { lpLive: {
url: "https://www.youtube.com/watch?v=DOKcCl6iKaA", url: "https://www.youtube.com/watch?v=DOKcCl6iKaA",