mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: start of popup
This commit is contained in:
1 parent
b50c47b744
commit
79b4f05809
8 files changed
+225
-19
No files matched your search
+37
-4
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user