feat: start of popup

This commit is contained in:
TrèsAbhi committed 2026-01-17 13:36:17 -06:00
1 parent b50c47b744
commit 79b4f05809
8 files changed
+225 -19

No files matched your search

@@ -1,15 +1,15 @@
.avatar-card {
.card {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-4);
.avatar-series {
.series {
position: relative;
width: 100%;
aspect-ratio: 7 / 8;
.avatar-entry {
.entry {
position: absolute;
border-radius: var(--space-3);
@@ -30,10 +30,12 @@
position: absolute;
bottom: var(--space-3);
right: var(--space-3);
box-shadow: var(--shadow-aggressive);
}
}
.avatar-label {
.label {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
@@ -45,3 +47,34 @@
}
}
}
.details {
display: flex;
gap: var(--space-4);
align-items: center;
.preview {
display: flex;
flex-direction: column;
align-items: center;
.image {
aspect-ratio: 7 / 8;
width: 8rem;
background-size: cover;
background-position: center;
}
.grade {
margin-top: calc(var(--space-3) * -1);
}
}
.description {
display: flex;
flex-direction: column;
max-width: 20rem;
}
}
@@ -1,12 +1,15 @@
import { literals } from "@blitzkit/i18n";
import { Grade } from "@protos/blitz_static_standard_grades_enum";
import { useGameStrings } from "../../hooks/useGameStrings";
import { useStrings } from "../../hooks/useStrings";
import type { Avatar } from "../../protos/avatar";
import type { PropsWithSkeleton } from "../../types/propsWithSkeleton";
import { Badge } from "../Badge";
import { Dialog } from "../Dialog";
import { InlineSkeleton } from "../InlineSkeleton";
import { Skeleton } from "../Skeleton";
import { Text } from "../Text";
import "./index.css";
import styles from "./index.module.css";
interface Props {
name: string;
@@ -17,15 +20,68 @@ const MAX_CARDS = 3;
const CARD_TRIM = "var(--space-3)";
export function AvatarCard(props: PropsWithSkeleton<Props>) {
return (
<div className="avatar-card">
const gameStrings = useGameStrings("ProfileAvatarEntity");
const strings = useStrings();
const card = (
<div className={styles.card}>
<Series skeleton={props.skeleton} {...props} />
<Text align="center" className="avatar-label">
<Text align="center" className={styles.label}>
{props.skeleton ? <InlineSkeleton /> : props.name}
</Text>
</div>
);
if (props.skeleton) return card;
return (
<Dialog.Root>
<Dialog.Trigger>{card}</Dialog.Trigger>
<Dialog.Content>
<Text size="major" weight="bold">
{props.name}
</Text>
{[...props.avatars].reverse().map((avatar) => {
const description = gameStrings[avatar.stuff_ui!.description];
const obtaining = gameStrings[avatar.stuff_ui!.obtaining_methods];
const hasDescription = description || obtaining;
return (
<div className={styles.details}>
<div
className={styles.preview}
data-grade={avatar.stuff_ui!.grade}
>
<div
className={styles.image}
style={{
backgroundImage: `url(/api/avatars/${avatar.name}.webp)`,
}}
/>
<Badge className={styles.grade}>
{Grade[avatar.stuff_ui!.grade]}
</Badge>
</div>
<div className={styles.description}>
<Text>
{hasDescription ? (
<>
{description} {obtaining}
</>
) : (
strings.avatars.no_description
)}
</Text>
</div>
</div>
);
})}
</Dialog.Content>
</Dialog.Root>
);
}
interface SeriesProps {
@@ -37,12 +93,12 @@ function Series(props: PropsWithSkeleton<SeriesProps>) {
const strings = useStrings();
return (
<div className="avatar-series">
<div className={styles.series}>
{!props.skeleton &&
props.avatars.slice(-MAX_CARDS).map((avatar, index) => (
<div
key={avatar.name}
className="avatar-entry"
className={styles.entry}
style={{
filter: `brightness(${(index + 1) / count})`,
top: `calc(${index} * ${CARD_TRIM})`,
@@ -55,12 +111,12 @@ function Series(props: PropsWithSkeleton<SeriesProps>) {
))}
{!props.skeleton && props.avatars.length > 1 && (
<Badge color="gray" highContrast className="count">
<Badge color="gray" highContrast className={styles.count}>
{literals(strings.units.x, { value: props.avatars.length })}
</Badge>
)}
{props.skeleton && <Skeleton className="avatar-entry" />}
{props.skeleton && <Skeleton className={styles.entry} />}
</div>
);
}