feat: popup

This commit is contained in:
TrèsAbhi committed 2025-05-31 18:21:57 -05:00
1 parent 06de86c5a6
commit 4a79f98f3f
7 files changed
+136 -46

No files matched your search

+19
View File
@@ -6,6 +6,14 @@
}, },
"common": { "common": {
"grades": {
"0": "Unspecified grade",
"1": "Common",
"2": "Rare",
"3": "Epic",
"4": "Legendary"
},
"units": { "units": {
"m": "%s1m", "m": "%s1m",
"mm": "%s1mm", "mm": "%s1mm",
@@ -1260,10 +1268,21 @@
"seo_description": "All World of Tanks Blitz player profile avatars", "seo_description": "All World of Tanks Blitz player profile avatars",
"description": "Player profile avatars", "description": "Player profile avatars",
"no_description": "This avatar has no description.",
"download": "Download",
"hidden": "Hidden",
"unnamed_avatars": "Unnamed avatars", "unnamed_avatars": "Unnamed avatars",
"search": { "search": {
"hint": "Search avatars...", "hint": "Search avatars...",
"results": "%s1 avatars" "results": "%s1 avatars"
},
"categories": {
"0": "Unspecified category",
"1": "Premium",
"2": "Special",
"3": "Accumulative",
"4": "Other",
"5": "Temporary"
} }
}, },
@@ -1,12 +1,19 @@
import type { ProfileAvatarComponent } from '@protos/blitz_static_profile_avatar_component'; import {
ProfileAvatarComponent_Category,
type ProfileAvatarComponent,
} from '@protos/blitz_static_profile_avatar_component';
import type { SellableComponent } from '@protos/blitz_static_sellable_component'; import type { SellableComponent } from '@protos/blitz_static_sellable_component';
import { Grade } from '@protos/blitz_static_standard_grades_enum';
import type { StuffUIComponent } from '@protos/blitz_static_stuff_ui_component'; import type { StuffUIComponent } from '@protos/blitz_static_stuff_ui_component';
import { DownloadIcon, StarFilledIcon } from '@radix-ui/react-icons';
import { import {
Badge, Badge,
Box, Box,
Button,
Dialog, Dialog,
Flex, Flex,
Text, Text,
type BadgeProps,
type FlexProps, type FlexProps,
} from '@radix-ui/themes'; } from '@radix-ui/themes';
import { literals } from 'packages/i18n/src'; import { literals } from 'packages/i18n/src';
@@ -22,30 +29,116 @@ interface AvatarGroupProps {
}[]; }[];
} }
const dx = '4px'; export function AvatarGroup(props: AvatarGroupProps) {
const MAX_AVATARS_PREVIEW = 5;
export function AvatarGroup({ name, avatars }: AvatarGroupProps) {
return ( return (
<Dialog.Root> <Dialog.Root>
<Dialog.Trigger> <Dialog.Trigger>
<Content avatars={avatars} name={name} /> <Content {...props} />
</Dialog.Trigger> </Dialog.Trigger>
<Dialog.Content>asd</Dialog.Content> <Dialog.Content>
<Popup {...props} />
</Dialog.Content>
</Dialog.Root> </Dialog.Root>
); );
} }
const CATEGORY_COLORS: Record<
ProfileAvatarComponent_Category,
BadgeProps['color']
> = {
[ProfileAvatarComponent_Category.CATEGORY_UNSPECIFIED]: 'gray',
[ProfileAvatarComponent_Category.CATEGORY_PREMUM]: 'gray',
[ProfileAvatarComponent_Category.CATEGORY_SPECIAL]: 'gray',
[ProfileAvatarComponent_Category.CATEGORY_ACCUMULATIVE]: 'gray',
[ProfileAvatarComponent_Category.CATEGORY_OTHER]: 'gray',
[ProfileAvatarComponent_Category.CATEGORY_TEMPORARY]: 'gray',
};
const GRADE_COLORS = {
[Grade.GRADE_UNSPECIFIED]: 'gray',
[Grade.GRADE_COMMON]: 'gray',
[Grade.GRADE_RARE]: 'blue',
[Grade.GRADE_EPIC]: 'purple',
[Grade.GRADE_LEGENDARY]: 'amber',
} satisfies Record<Grade, BadgeProps['color']>;
function Popup({ name, avatars }: AvatarGroupProps) {
const { strings, gameStrings } = useLocale();
return (
<>
<Dialog.Title>{name}</Dialog.Title>
<Flex direction="column" gap="3" mt="5">
{avatars.map(({ avatar, stuff }, index) => (
<Flex key={index} gap="5">
<Box
m="1"
mb="4"
height="10rem"
style={{
aspectRatio: '7 / 8',
borderRadius: Var('radius-4'),
outline: `${Var('space-1')} solid ${Var(`${GRADE_COLORS[stuff.grade]}-9`)}`,
boxShadow: `0 0 ${
1.5 * (stuff.grade / Grade.GRADE_LEGENDARY)
}rem ${Var(`${GRADE_COLORS[stuff.grade]}-9`)}`,
backgroundImage: `url(${avatar.avatar})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
position="relative"
>
<Badge
mt="1"
style={{
position: 'absolute',
top: '100%',
left: '50%',
transform: 'translate(-50%, -50%)',
}}
variant="solid"
color={GRADE_COLORS[stuff.grade]}
>
<StarFilledIcon /> {strings.common.grades[stuff.grade]}
</Badge>
</Box>
<Flex direction="column" gap="3" justify="center">
<Text>
{gameStrings[stuff.description] ??
(gameStrings[stuff.obtaining_methods]
? undefined
: strings.website.tools.avatars.no_description)}{' '}
{gameStrings[stuff.obtaining_methods]}
</Text>
<Flex>
<Button variant="outline">
<DownloadIcon /> {strings.website.tools.avatars.download}
</Button>
</Flex>
</Flex>
</Flex>
))}
</Flex>
</>
);
}
const dx = '4px';
const MAX_AVATARS_PREVIEW = 5;
function Content({ avatars, name, ...props }: FlexProps & AvatarGroupProps) { function Content({ avatars, name, ...props }: FlexProps & AvatarGroupProps) {
const { strings } = useLocale(); const { strings } = useLocale();
const avatarsReduced = [...avatars].reverse().slice(0, MAX_AVATARS_PREVIEW); const avatarsReduced = [...avatars].slice(-MAX_AVATARS_PREVIEW);
return ( return (
<Flex direction="column" gap="3" style={{ cursor: 'pointer' }} {...props}> <Flex direction="column" gap="3" style={{ cursor: 'pointer' }} {...props}>
<Box width="100%" style={{ aspectRatio: '7 / 8' }} position="relative"> <Box width="100%" style={{ aspectRatio: '7 / 8' }} position="relative">
{avatarsReduced.map((avatar, index) => ( {avatarsReduced.map((avatar, index) => (
<Box <Box
key={index}
position="absolute" position="absolute"
top={`calc(${dx} * ${index})`} top={`calc(${dx} * ${index})`}
left={`calc(${dx} * ${index})`} left={`calc(${dx} * ${index})`}
@@ -53,7 +146,7 @@ function Content({ avatars, name, ...props }: FlexProps & AvatarGroupProps) {
height={`calc(100% - ${dx} * ${avatarsReduced.length - 1})`} height={`calc(100% - ${dx} * ${avatarsReduced.length - 1})`}
style={{ style={{
filter: `brightness(${(index + 1) / avatarsReduced.length})`, filter: `brightness(${(index + 1) / avatarsReduced.length})`,
borderRadius: Var('radius-2'), borderRadius: Var('radius-4'),
boxShadow: Var('shadow-2'), boxShadow: Var('shadow-2'),
backgroundImage: `url(${avatar.avatar.avatar})`, backgroundImage: `url(${avatar.avatar.avatar})`,
backgroundSize: 'cover', backgroundSize: 'cover',
@@ -19,7 +19,7 @@ export function BlitzKitTheme({
appearance="dark" appearance="dark"
panelBackground="solid" panelBackground="solid"
radius="full" radius="full"
accentColor="amber" accentColor="iris"
suppressHydrationWarning suppressHydrationWarning
suppressContentEditableWarning suppressContentEditableWarning
{...props} {...props}
@@ -38,7 +38,11 @@ const strings = await getStrings(locale);
sm: 'start', sm: 'start',
}} }}
> >
<Heading>BlitzKit</Heading> <Flex align="center" gap="2">
<Heading> BlitzKit </Heading>
<Code size="1" color="gray" variant="ghost">{packageJson.version}</Code>
</Flex>
<Text color="gray">{strings.website.footer.motto}</Text> <Text color="gray">{strings.website.footer.motto}</Text>
<Text size="1" color="gray"> <Text size="1" color="gray">
{strings.website.footer.copyright} {strings.website.footer.copyright}
@@ -127,23 +131,4 @@ const strings = await getStrings(locale);
</Flex> </Flex>
</Flex> </Flex>
</Flex> </Flex>
<Flex gap="5" display={{ initial: 'flex', sm: 'none' }}>
<Flex direction="column" justify="between" align="end">
<Text size="2" color="gray">BlitzKit</Text>
</Flex>
<Flex direction="column" justify="between" align="start">
<Code size="2" color="gray">
{packageJson.version}
</Code>
</Flex>
</Flex>
<Flex gap="2" align="center" display={{ initial: 'none', sm: 'flex' }}>
<Text size="2" color="gray">BlitzKit</Text>
<Code size="2" color="gray">
{packageJson.version}
</Code>
</Flex>
</Flex> </Flex>
@@ -13,11 +13,10 @@ import { Var } from '../core/radix/var';
import { LinkI18n } from './LinkI18n'; import { LinkI18n } from './LinkI18n';
export interface Props { export interface Props {
integrated?: boolean;
locale: string; locale: string;
} }
const { integrated, locale } = Astro.props; const { locale } = Astro.props;
const toolsFiltered = tools.filter((tool) => tool.enabled); const toolsFiltered = tools.filter((tool) => tool.enabled);
const branchName = resolveBranchName(); const branchName = resolveBranchName();
const isBranchNamed = branchName !== undefined; const isBranchNamed = branchName !== undefined;
@@ -76,12 +75,6 @@ const strings = await getStrings(locale);
}); });
</script> </script>
{
!integrated && (
<Box className="padder" height={{ initial: '3.5rem', sm: '3rem' }} />
)
}
<Flex <Flex
direction="column" direction="column"
align="center" align="center"
@@ -89,7 +82,7 @@ const strings = await getStrings(locale);
className="navbar" className="navbar"
width="100%" width="100%"
top="0" top="0"
position="fixed" position="sticky"
style={{ style={{
zIndex: 1, zIndex: 1,
backdropFilter: 'blur(4rem)', backdropFilter: 'blur(4rem)',
@@ -101,7 +94,6 @@ const strings = await getStrings(locale);
p={{ initial: '4', sm: '3' }} p={{ initial: '4', sm: '3' }}
direction="column" direction="column"
align="center" align="center"
style={{ backgroundColor: integrated ? undefined : Var('gray-1') }}
> >
<Flex gap="4" direction="column" maxWidth="80rem" width="100%"> <Flex gap="4" direction="column" maxWidth="80rem" width="100%">
<Flex align="center"> <Flex align="center">
@@ -38,6 +38,7 @@ export const chartsTool: Tool = {
export const avatarsTool: Tool = { export const avatarsTool: Tool = {
id: 'avatars', id: 'avatars',
enabled: true, enabled: true,
significant: true,
button: { color: 'gold' }, button: { color: 'gold' },
}; };
@@ -16,10 +16,10 @@ import { useMemo } from 'react';
const allAvatars = await metadata const allAvatars = await metadata
.get('BlitzkitAllAvatarsEntity.blitzkit_all_avatars') .get('BlitzkitAllAvatarsEntity.blitzkit_all_avatars')
.then( .then((entity) =>
(entity) => entity
entity.get(BlitzkitAllAvatarsComponent, 'blitzkitAllAvatarsComponent') .get(BlitzkitAllAvatarsComponent, 'blitzkitAllAvatarsComponent')
.avatars, .avatars.reverse(),
); );
export function Page({ localeData }: LocaleAcceptorProps) { export function Page({ localeData }: LocaleAcceptorProps) {
@@ -71,7 +71,7 @@ function Content() {
}, []); }, []);
return ( return (
<PageWrapper color="gold"> <PageWrapper color="amber">
<Grid <Grid
columns={{ columns={{
initial: 'repeat(auto-fill, minmax(6rem, 1fr))', initial: 'repeat(auto-fill, minmax(6rem, 1fr))',
@@ -83,7 +83,7 @@ function Content() {
flexGrow="1" flexGrow="1"
> >
{groups.map(([name, avatars]) => ( {groups.map(([name, avatars]) => (
<AvatarGroup key={name} name={name} avatars={avatars} /> <AvatarGroup key={`${name}`} name={name} avatars={avatars} />
))} ))}
</Grid> </Grid>
</PageWrapper> </PageWrapper>