feat: skeleton

This commit is contained in:
TrèsAbhi committed 2026-01-16 20:37:11 -06:00
1 parent f7c6fdd861
commit b50c47b744
11 files changed
+88 -28

No files matched your search

@@ -17,6 +17,13 @@
background-size: cover;
background-position: center;
&.skeleton {
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
.count {
@@ -31,5 +38,10 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
.skeleton {
width: 100%;
}
}
}
@@ -1,8 +1,10 @@
import { literals } from "@blitzkit/i18n";
import { useStrings } from "../../hooks/useStrings";
import type { Avatar } from "../../protos/avatar";
import type { SkeletonProps } from "../../types/SkeletonProps";
import type { PropsWithSkeleton } from "../../types/propsWithSkeleton";
import { Badge } from "../Badge";
import { InlineSkeleton } from "../InlineSkeleton";
import { Skeleton } from "../Skeleton";
import { Text } from "../Text";
import "./index.css";
@@ -14,13 +16,13 @@ interface Props {
const MAX_CARDS = 3;
const CARD_TRIM = "var(--space-3)";
export function AvatarCard(props: SkeletonProps<Props>) {
export function AvatarCard(props: PropsWithSkeleton<Props>) {
return (
<div className="avatar-card">
{!props.skeleton && <Series avatars={props.avatars} />}
<Series skeleton={props.skeleton} {...props} />
<Text align="center" className="avatar-label">
{props.skeleton ? "LOADING" : props.name}
{props.skeleton ? <InlineSkeleton /> : props.name}
</Text>
</div>
);
@@ -30,32 +32,35 @@ interface SeriesProps {
avatars: Avatar[];
}
function Series({ avatars }: SeriesProps) {
const count = Math.min(avatars.length, MAX_CARDS);
function Series(props: PropsWithSkeleton<SeriesProps>) {
const count = Math.min(props.skeleton ? 0 : props.avatars.length, MAX_CARDS);
const strings = useStrings();
return (
<div className="avatar-series">
{avatars.slice(-MAX_CARDS).map((avatar, index) => (
<div
key={avatar.name}
className="avatar-entry"
style={{
filter: `brightness(${(index + 1) / count})`,
top: `calc(${index} * ${CARD_TRIM})`,
left: `calc(${index} * ${CARD_TRIM})`,
width: `calc(100% - ${CARD_TRIM} * ${count - 1})`,
height: `calc(100% - ${CARD_TRIM} * ${count - 1})`,
backgroundImage: `url(/api/avatars/${avatar.name}.webp)`,
}}
/>
))}
{!props.skeleton &&
props.avatars.slice(-MAX_CARDS).map((avatar, index) => (
<div
key={avatar.name}
className="avatar-entry"
style={{
filter: `brightness(${(index + 1) / count})`,
top: `calc(${index} * ${CARD_TRIM})`,
left: `calc(${index} * ${CARD_TRIM})`,
width: `calc(100% - ${CARD_TRIM} * ${count - 1})`,
height: `calc(100% - ${CARD_TRIM} * ${count - 1})`,
backgroundImage: `url(/api/avatars/${avatar.name}.webp)`,
}}
/>
))}
{avatars.length > 1 && (
{!props.skeleton && props.avatars.length > 1 && (
<Badge color="gray" highContrast className="count">
{literals(strings.units.x, { value: avatars.length })}
{literals(strings.units.x, { value: props.avatars.length })}
</Badge>
)}
{props.skeleton && <Skeleton className="avatar-entry" />}
</div>
);
}