Migrate /vods to CSS modules

This commit is contained in:
Kalle
2025-10-26 20:24:04 +02:00
parent 2df015d209
commit d4a1e0a184
8 changed files with 108 additions and 117 deletions

View File

@@ -1,15 +1,13 @@
import { useLoaderData, useMatches } from "@remix-run/react";
import { AddNewButton } from "~/components/AddNewButton";
import { VodListing } from "~/features/vods/components/VodListing";
import styles from "~/features/vods/routes/vods.module.css";
import invariant from "~/utils/invariant";
import type { SendouRouteHandle } from "~/utils/remix.server";
import { newVodPage } from "~/utils/urls";
import { loader } from "../loaders/u.$identifier.vods.server";
export { loader };
import "~/features/vods/vods.css";
export const handle: SendouRouteHandle = {
i18n: ["vods"],
};
@@ -24,7 +22,7 @@ export default function UserVodsPage() {
<div className="stack items-end">
<AddNewButton navIcon="vods" to={newVodPage()} />
</div>
<div className="vods__listing__list">
<div className={styles.listingList}>
{data.vods.map((vod) => (
<VodListing key={vod.id} vod={vod} showUser={false} />
))}

View File

@@ -0,0 +1,34 @@
.listing {
max-width: 355px;
}
.thumbnail {
aspect-ratio: 16 / 9;
width: 100%;
height: 100%;
object-fit: cover;
min-width: 355px;
}
.info {
display: flex;
justify-content: space-between;
align-items: center;
}
.castText {
font-size: var(--fonts-sm);
font-weight: var(--semi-bold);
color: var(--text-lighter);
}
.weaponsNotShown {
background-color: var(--bg-lighter);
border-radius: 100%;
display: grid;
place-items: center;
font-weight: var(--semi-bold);
width: 38px;
height: 38px;
font-size: var(--fonts-sm);
}

View File

@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
import { WeaponImage } from "~/components/Image";
import { vodVideoPage } from "~/utils/urls";
import type { ListVod } from "../vods-types";
import styles from "./VodListing.module.css";
import { PovUser } from "./VodPov";
export function VodListing({
@@ -15,20 +16,18 @@ export function VodListing({
const { t } = useTranslation(["vods"]);
return (
<div className="vods__listing">
<div className={styles.listing}>
<Link to={vodVideoPage(vod.id)} className="stack sm">
<img
className="vods__listing__thumbnail"
className={styles.thumbnail}
alt=""
src={youtubeIdToThumbnailUrl(vod.youtubeId)}
/>
<h2 className="text-sm text-main-forced">{vod.title}</h2>
</Link>
<div className="vods__listing__info">
<div className={styles.info}>
{vod.type === "CAST" || !showUser ? (
<div className="vods__listing__cast-text">
{t(`vods:type.${vod.type}`)}
</div>
<div className={styles.castText}>{t(`vods:type.${vod.type}`)}</div>
) : (
<PovUser pov={vod.pov} />
)}
@@ -58,9 +57,7 @@ function WeaponsPeek({ weapons }: { weapons: ListVod["weapons"] }) {
/>
))}
{amountOfWeaponsNotShown > 0 ? (
<div className="vods__listing__weapons-not-shown">
+{amountOfWeaponsNotShown}
</div>
<div className={styles.weaponsNotShown}>+{amountOfWeaponsNotShown}</div>
) : null}
</div>
);

View File

@@ -0,0 +1,46 @@
.matches {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
flex-wrap: wrap;
column-gap: var(--s-4);
row-gap: var(--s-8);
justify-content: space-between;
margin-block-start: var(--s-2);
place-items: center;
}
.match {
position: relative;
display: flex;
align-items: center;
text-align: center;
flex-direction: column;
font-size: var(--fonts-xs);
line-height: 1.3;
font-weight: var(--semi-bold);
flex-grow: 1;
width: max-content;
gap: var(--s-1);
}
.matchWeapon {
position: absolute;
top: -15px;
left: -20px;
}
.matchWeapons {
display: grid;
grid-template-columns: repeat(2, 1fr);
margin-block-end: var(--s-2);
}
.matchMode {
position: absolute;
top: -15px;
right: -15px;
}
.matchMode.cast {
right: -8px;
}

View File

@@ -31,10 +31,9 @@ import { PovUser } from "../components/VodPov";
import { loader } from "../loaders/vods.$id.server";
import type { Vod } from "../vods-types";
import { canEditVideo, secondsToHoursMinutesSecondString } from "../vods-utils";
import styles from "./vods.$id.module.css";
export { loader, action };
import "../vods.css";
export const handle: SendouRouteHandle = {
breadcrumb: ({ match }) => {
const data = match.data as SerializeFrom<typeof loader> | undefined;
@@ -143,7 +142,7 @@ export default function VodPage() {
) : null}
</div>
</div>
<div className="vods__matches">
<div className={styles.matches}>
{data.vod.matches.map((match) => (
<Match
key={match.id}
@@ -173,7 +172,7 @@ function Match({
const weapons = match.weapons.length === 8 ? match.weapons : null;
return (
<div className="vods__match">
<div className={styles.match}>
<Image
alt=""
path={stageImageUrl(match.stageId)}
@@ -185,20 +184,20 @@ function Match({
weaponSplId={weapon}
variant="badge"
width={42}
className="vods__match__weapon"
className={styles.matchWeapon}
testId={`weapon-img-${weapon}`}
/>
) : null}
<Image
path={modeImageUrl(match.mode)}
width={32}
className={clsx("vods__match__mode", { cast: Boolean(weapons) })}
className={clsx(styles.matchMode, { [styles.cast]: Boolean(weapons) })}
alt={t(`game-misc:MODE_LONG_${match.mode}`)}
title={t(`game-misc:MODE_LONG_${match.mode}`)}
/>
{weapons ? (
<div className="stack horizontal md">
<div className="vods__match__weapons">
<div className={styles.matchWeapons}>
{weapons.slice(0, 4).map((weapon, i) => {
return (
<WeaponImage
@@ -211,7 +210,7 @@ function Match({
);
})}
</div>
<div className="vods__match__weapons">
<div className={styles.matchWeapons}>
{weapons.slice(4).map((weapon, i) => {
const adjustedI = i + 4;
return (

View File

@@ -0,0 +1,10 @@
.listingList {
display: flex;
flex-wrap: wrap;
gap: var(--s-6);
justify-content: center;
}
.typeSelect {
width: 220px;
}

View File

@@ -15,10 +15,9 @@ import { navIconUrl, newVodPage, VODS_PAGE } from "~/utils/urls";
import { VodListing } from "../components/VodListing";
import { loader } from "../loaders/vods.server";
import { VODS_PAGE_BATCH_SIZE, videoMatchTypes } from "../vods-constants";
import styles from "./vods.module.css";
export { loader };
import "../vods.css";
export const handle: SendouRouteHandle = {
i18n: ["vods"],
breadcrumb: () => ({
@@ -58,7 +57,7 @@ export default function VodsSearchPage() {
</div>
{data.vods.length > 0 ? (
<>
<div className="vods__listing__list">
<div className={styles.listingList}>
{data.vods.map((vod) => (
<VodListing key={vod.id} vod={vod} />
))}
@@ -156,7 +155,7 @@ function Filters({
<Label>{t("vods:forms.title.type")}</Label>
<select
name="type"
className="vods__type-select"
className={styles.typeSelect}
value={type ?? ""}
onChange={(e) => addToSearchParams("type", e.target.value)}
>

View File

@@ -1,92 +0,0 @@
.vods__matches {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
flex-wrap: wrap;
column-gap: var(--s-4);
row-gap: var(--s-8);
justify-content: space-between;
margin-block-start: var(--s-2);
place-items: center;
}
.vods__match {
position: relative;
display: flex;
align-items: center;
text-align: center;
flex-direction: column;
font-size: var(--fonts-xs);
line-height: 1.3;
font-weight: var(--semi-bold);
flex-grow: 1;
width: max-content;
gap: var(--s-1);
}
.vods__match__weapon {
position: absolute;
top: -15px;
left: -20px;
}
.vods__match__weapons {
display: grid;
grid-template-columns: repeat(2, 1fr);
margin-block-end: var(--s-2);
}
.vods__match__mode {
position: absolute;
top: -15px;
right: -15px;
}
.vods__match__mode.cast {
right: -8px;
}
.vods__listing__list {
display: flex;
flex-wrap: wrap;
gap: var(--s-6);
justify-content: center;
}
.vods__listing {
max-width: 355px;
}
.vods__listing__thumbnail {
aspect-ratio: 16 / 9;
width: 100%;
height: 100%;
object-fit: cover;
min-width: 355px;
}
.vods__listing__info {
display: flex;
justify-content: space-between;
align-items: center;
}
.vods__listing__cast-text {
font-size: var(--fonts-sm);
font-weight: var(--semi-bold);
color: var(--text-lighter);
}
.vods__listing__weapons-not-shown {
background-color: var(--bg-lighter);
border-radius: 100%;
display: grid;
place-items: center;
font-weight: var(--semi-bold);
width: 38px;
height: 38px;
font-size: var(--fonts-sm);
}
.vods__type-select {
width: 220px;
}