mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: popup
This commit is contained in:
1 parent
06de86c5a6
commit
4a79f98f3f
7 files changed
+136
-46
No files matched your search
@@ -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>
|
||||||
|
|||||||
Reference in new issue
Block a user