mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 11:39:50 +02:00
feat: migration 3
This commit is contained in:
1 parent
6285f33989
commit
4055988478
110 files changed
+28
-76
No files matched your search
@@ -0,0 +1,27 @@
|
||||
import { Button, ButtonProps, Tooltip } from '@radix-ui/themes';
|
||||
import { useState } from 'react';
|
||||
|
||||
interface CopyButtonProps extends ButtonProps {
|
||||
copy: () => string | undefined;
|
||||
}
|
||||
|
||||
export function CopyButton({ copy, ...props }: CopyButtonProps) {
|
||||
const [tooltipOpen, setTooltipOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Tooltip open={tooltipOpen} content="Copied!">
|
||||
<Button
|
||||
{...props}
|
||||
onClick={() => {
|
||||
const content = copy();
|
||||
|
||||
if (content === undefined) return;
|
||||
|
||||
navigator.clipboard.writeText(content);
|
||||
setTooltipOpen(true);
|
||||
setTimeout(() => setTooltipOpen(false), 2000);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
ChevronUpIcon,
|
||||
ReloadIcon,
|
||||
} from '@radix-ui/react-icons';
|
||||
import {
|
||||
Button,
|
||||
ChevronDownIcon,
|
||||
Flex,
|
||||
Grid,
|
||||
IconButton,
|
||||
Link,
|
||||
Popover,
|
||||
Text,
|
||||
} from '@radix-ui/themes';
|
||||
import { times } from 'lodash';
|
||||
import { useState } from 'react';
|
||||
|
||||
const DAY_TITLES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
const YEAR_OPTIONS = 7;
|
||||
|
||||
interface DatePickerProps {
|
||||
onDateChange?: (date: Date) => void;
|
||||
defaultDate?: Date;
|
||||
}
|
||||
|
||||
export function DatePicker({
|
||||
onDateChange,
|
||||
defaultDate = new Date(),
|
||||
}: DatePickerProps) {
|
||||
const [year, setYear] = useState(defaultDate.getFullYear());
|
||||
const [month, setMonth] = useState(defaultDate.getMonth());
|
||||
const [day, setDay] = useState(defaultDate.getDate());
|
||||
const [yearScroll, setYearScroll] = useState(0);
|
||||
const [yearOffset, setYearOffset] = useState(0);
|
||||
const [monthOffset, setMonthOffset] = useState(0);
|
||||
const date = new Date(year + yearOffset, month + monthOffset, day);
|
||||
|
||||
const daysInLastMonth = new Date(
|
||||
year + yearOffset,
|
||||
month + monthOffset,
|
||||
0,
|
||||
).getDate();
|
||||
const daysInMonth = new Date(
|
||||
year + yearOffset,
|
||||
month + monthOffset + 1,
|
||||
0,
|
||||
).getDate();
|
||||
const firstDayOfMonth = new Date(
|
||||
year + yearOffset,
|
||||
month + monthOffset,
|
||||
1,
|
||||
).getDay();
|
||||
const lastDayOfMonth = new Date(
|
||||
year + yearOffset,
|
||||
month + monthOffset,
|
||||
daysInMonth,
|
||||
).getDay();
|
||||
|
||||
return (
|
||||
<Flex direction="column" width="fit-content">
|
||||
<Flex justify="between" mb="2" align="center" py="2">
|
||||
<IconButton
|
||||
size="3"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
if (monthOffset === 0) {
|
||||
setYearOffset((state) => state - 1);
|
||||
setMonthOffset(11);
|
||||
} else {
|
||||
setMonthOffset((state) => state - 1);
|
||||
}
|
||||
}}
|
||||
ml="2"
|
||||
>
|
||||
<ChevronLeftIcon />
|
||||
</IconButton>
|
||||
|
||||
<Flex align="center" gap="2">
|
||||
<IconButton
|
||||
size="3"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setYear(defaultDate.getFullYear());
|
||||
setMonth(defaultDate.getMonth());
|
||||
setDay(defaultDate.getDate());
|
||||
setYearOffset(0);
|
||||
setMonthOffset(0);
|
||||
}}
|
||||
>
|
||||
<ReloadIcon />
|
||||
</IconButton>
|
||||
|
||||
<Flex gap="1">
|
||||
<Text>
|
||||
{date.toLocaleString(undefined, {
|
||||
month: 'long',
|
||||
})}
|
||||
</Text>
|
||||
|
||||
<Popover.Root onOpenChange={() => setYearScroll(0)}>
|
||||
<Popover.Trigger>
|
||||
<Link underline="always" href="#">
|
||||
{date.getFullYear()}
|
||||
</Link>
|
||||
</Popover.Trigger>
|
||||
|
||||
<Popover.Content side="right" align="center">
|
||||
<Flex direction="column" gap="1">
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
onClick={() => setYearScroll((state) => state + 1)}
|
||||
mb="2"
|
||||
>
|
||||
<ChevronUpIcon />
|
||||
</IconButton>
|
||||
|
||||
{times(YEAR_OPTIONS, (index) => {
|
||||
const thisYear =
|
||||
year +
|
||||
yearOffset -
|
||||
index +
|
||||
Math.floor(YEAR_OPTIONS / 2) +
|
||||
yearScroll;
|
||||
const selected = year + yearOffset === thisYear;
|
||||
|
||||
return (
|
||||
<Popover.Close key={thisYear}>
|
||||
<Button
|
||||
variant={selected ? 'solid' : 'soft'}
|
||||
radius="large"
|
||||
onClick={() => {
|
||||
setYearOffset(thisYear - year);
|
||||
}}
|
||||
>
|
||||
{thisYear}
|
||||
</Button>
|
||||
</Popover.Close>
|
||||
);
|
||||
})}
|
||||
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
onClick={() => setYearScroll((state) => state - 1)}
|
||||
mt="2"
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<IconButton
|
||||
size="3"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
if (monthOffset === 11) {
|
||||
setYearOffset((state) => state + 1);
|
||||
setMonthOffset(0);
|
||||
} else {
|
||||
setMonthOffset((state) => state + 1);
|
||||
}
|
||||
}}
|
||||
mr="2"
|
||||
>
|
||||
<ChevronRightIcon />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
|
||||
<Flex mb="1">
|
||||
{times(7, (index) => (
|
||||
<Text
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
size="2"
|
||||
align="center"
|
||||
key={index}
|
||||
color="gray"
|
||||
>
|
||||
{DAY_TITLES[index]}
|
||||
</Text>
|
||||
))}
|
||||
</Flex>
|
||||
|
||||
<Grid columns="7" gap="1" width="fit-content">
|
||||
{times(firstDayOfMonth, (index) => (
|
||||
<IconButton radius="large" key={index} disabled>
|
||||
{daysInLastMonth - firstDayOfMonth + index + 1}
|
||||
</IconButton>
|
||||
))}
|
||||
|
||||
{times(daysInMonth, (index) => {
|
||||
const selected =
|
||||
index === day - 1 && monthOffset === 0 && yearOffset === 0;
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
radius="large"
|
||||
key={index}
|
||||
variant={selected ? 'solid' : 'soft'}
|
||||
onClick={() => {
|
||||
const newYear =
|
||||
year + yearOffset + Math.floor((month + monthOffset) / 12);
|
||||
const newMonth = (month + monthOffset) % 12;
|
||||
const newDay = index + 1;
|
||||
|
||||
setYear(newYear);
|
||||
setMonth(newMonth);
|
||||
setDay(newDay);
|
||||
setYearOffset(0);
|
||||
setMonthOffset(0);
|
||||
|
||||
onDateChange?.(new Date(newYear, newMonth, newDay));
|
||||
}}
|
||||
>
|
||||
{index + 1}
|
||||
</IconButton>
|
||||
);
|
||||
})}
|
||||
|
||||
{times(6 - lastDayOfMonth, (index) => (
|
||||
<IconButton radius="large" key={index} disabled>
|
||||
{index + 1}
|
||||
</IconButton>
|
||||
))}
|
||||
</Grid>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Link, LinkProps } from '@radix-ui/themes';
|
||||
|
||||
export function DummyLink({ href = '#', onClick, ...props }: LinkProps) {
|
||||
return (
|
||||
<Link href={href} onClick={(event) => event.preventDefault()} {...props} />
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { assertSecret } from '@blitzkit/core';
|
||||
import strings from '@blitzkit/core/lang/en-US.json';
|
||||
import { REGIONS } from '@blitzkit/core/src/blitzkit/regions';
|
||||
import { Button, ButtonProps, Dialog, Flex } from '@radix-ui/themes';
|
||||
import { REGIONS } from '../../../../../../src/constants/regions';
|
||||
import { Link } from '../../Link';
|
||||
import { WargamingIcon } from '../../WargamingIcon';
|
||||
|
||||
@@ -29,14 +29,10 @@ export function WargamingLoginButton({ children, ...props }: ButtonProps) {
|
||||
<Link
|
||||
href={
|
||||
typeof window !== 'undefined'
|
||||
? `https://api.worldoftanks.${
|
||||
region
|
||||
}/wot/auth/login/?application_id=${assertSecret(
|
||||
? `https://api.worldoftanks.${region}/wot/auth/login/?application_id=${assertSecret(
|
||||
process.env.WARGAMING_APPLICATION_ID,
|
||||
)}&redirect_uri=${encodeURIComponent(
|
||||
`${location.origin}/auth/wargaming?return=${
|
||||
location.origin
|
||||
}${location.pathname}`,
|
||||
`${location.origin}/auth/wargaming?return=${location.origin}${location.pathname}`,
|
||||
)}`
|
||||
: undefined
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { slateDarkA } from '@radix-ui/colors';
|
||||
import { keyframes, style } from '@vanilla-extract/css';
|
||||
|
||||
const animation = keyframes({
|
||||
'0%': { backgroundColor: slateDarkA.slateA2 },
|
||||
'50%': { backgroundColor: slateDarkA.slateA3 },
|
||||
'100%': { backgroundColor: slateDarkA.slateA2 },
|
||||
});
|
||||
|
||||
export const skeleton = style({
|
||||
borderRadius: 8,
|
||||
animation: `${animation} 2s ease-in-out infinite`,
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ComponentProps } from 'react';
|
||||
import { skeleton } from './index.css';
|
||||
|
||||
export function Skeleton({ className, ...props }: ComponentProps<'div'>) {
|
||||
return <div {...props} className={`${className} ${skeleton}`} />;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { asset } from '../core/blitzkit/asset';
|
||||
|
||||
interface SmallTankIconProps {
|
||||
id: number;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const WIDTH = 70;
|
||||
const HEIGHT = 24;
|
||||
|
||||
export function SmallTankIcon({ id, size = HEIGHT }: SmallTankIconProps) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: size,
|
||||
overflow: 'hidden',
|
||||
width: WIDTH * (size / HEIGHT),
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={`Tank ${id}`}
|
||||
src={asset(`icons/tanks/small/${id}.webp`)}
|
||||
style={{
|
||||
transform: `scale(${(size / HEIGHT) * 100}%)`,
|
||||
transformOrigin: 'top left',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Flex, FlexProps, Text } from '@radix-ui/themes';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
type SpeedometerProps = FlexProps & {
|
||||
value: ReactNode;
|
||||
color: string;
|
||||
fill: number;
|
||||
label: ReactNode;
|
||||
};
|
||||
|
||||
export function Speedometer({
|
||||
value,
|
||||
color,
|
||||
fill,
|
||||
label,
|
||||
...props
|
||||
}: SpeedometerProps) {
|
||||
return (
|
||||
<Flex direction="column" align="center" {...props}>
|
||||
<svg
|
||||
width={180}
|
||||
height={98}
|
||||
style={{
|
||||
transform: 'scaleX(-1)',
|
||||
}}
|
||||
>
|
||||
<circle
|
||||
cx={90}
|
||||
cy={90}
|
||||
r={80}
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
stroke={`var(--${color}-3)`}
|
||||
strokeWidth={16}
|
||||
strokeDasharray={80 * Math.PI}
|
||||
strokeDashoffset={80 * Math.PI}
|
||||
/>
|
||||
<circle
|
||||
cx={90}
|
||||
cy={90}
|
||||
r={80}
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
stroke={`var(--${color}-indicator)`}
|
||||
strokeWidth={16}
|
||||
strokeDasharray={(160 - 80 * fill) * Math.PI}
|
||||
strokeDashoffset={(160 - 80 * fill) * Math.PI}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<Flex direction="column" align="center" mt="-8">
|
||||
<Text weight="bold" size="8" mt="-1">
|
||||
{value}
|
||||
</Text>
|
||||
|
||||
{label}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Table } from '@radix-ui/themes';
|
||||
import { ColumnHeaderCellProps } from '@radix-ui/themes/dist/cjs/components/table';
|
||||
|
||||
interface StickyColumnHeaderCellProps extends ColumnHeaderCellProps {
|
||||
top?: number | string;
|
||||
}
|
||||
|
||||
export function StickyColumnHeaderCell({
|
||||
style,
|
||||
top = 0,
|
||||
...props
|
||||
}: StickyColumnHeaderCellProps) {
|
||||
return (
|
||||
<Table.ColumnHeaderCell
|
||||
style={{
|
||||
position: 'sticky',
|
||||
top,
|
||||
background: 'var(--gray-3)',
|
||||
zIndex: 1,
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Table } from '@radix-ui/themes';
|
||||
import { RowHeaderCellProps } from '@radix-ui/themes/dist/cjs/components/table';
|
||||
|
||||
export function StickyRowHeaderCell({ style, ...props }: RowHeaderCellProps) {
|
||||
return (
|
||||
<Table.RowHeaderCell
|
||||
style={{
|
||||
display: 'flex',
|
||||
position: 'sticky',
|
||||
left: 0,
|
||||
backgroundColor: 'var(--color-panel)',
|
||||
boxShadow: 'var(--shadow-6)',
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
'use client';
|
||||
|
||||
import { Table } from '@radix-ui/themes';
|
||||
import { RootProps } from '@radix-ui/themes/dist/cjs/components/table';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
export function StickyTableRoot({ style, ...props }: RootProps) {
|
||||
const table = useRef<HTMLTableElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
table.current
|
||||
?.querySelector('table')
|
||||
?.style.setProperty('overflow', 'auto');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Table.Root
|
||||
{...props}
|
||||
ref={table}
|
||||
style={{ overflow: 'hidden', ...style }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { tankIcon } from '@blitzkit/core/src/blitzkit/tankIcon';
|
||||
import { classIcons } from '../../packages/website/src/components/ClassIcon';
|
||||
import { TankDefinition } from '../core/blitzkit/tankDefinitions';
|
||||
import { ExperimentIcon } from './ExperimentIcon';
|
||||
import { Link } from './Link';
|
||||
import { StickyRowHeaderCell } from './StickyRowHeaderCell';
|
||||
|
||||
interface TankRowHeaderCellProps {
|
||||
tank: TankDefinition;
|
||||
}
|
||||
|
||||
export function TankRowHeaderCell({ tank }: TankRowHeaderCellProps) {
|
||||
const Icon = classIcons[tank.class];
|
||||
|
||||
return (
|
||||
<StickyRowHeaderCell
|
||||
width={{ initial: '144px', sm: '240px' }}
|
||||
style={{ overflow: 'hidden' }}
|
||||
>
|
||||
<Link href={`/tools/tankopedia/${tank.id}`} tabIndex={-1}>
|
||||
<img
|
||||
alt={tank.name}
|
||||
draggable={false}
|
||||
src={tankIcon(tank.id)}
|
||||
style={{
|
||||
margin: 'calc(-1 * var(--table-cell-padding)) 0',
|
||||
height: 'calc(100% + 2 * var(--table-cell-padding))',
|
||||
aspectRatio: '4 / 3',
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
color={
|
||||
tank.treeType === 'collector'
|
||||
? 'blue'
|
||||
: tank.treeType === 'premium'
|
||||
? 'amber'
|
||||
: 'gray'
|
||||
}
|
||||
highContrast={tank.treeType === 'researchable'}
|
||||
underline="hover"
|
||||
wrap="nowrap"
|
||||
href={`/tools/tankopedia/${tank.id}`}
|
||||
style={{
|
||||
paddingLeft: 'var(--space-2)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 'var(--space-1)',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
size={{ initial: '1', sm: '2' }}
|
||||
>
|
||||
<Icon
|
||||
width="1em"
|
||||
height="1em"
|
||||
style={{ minWidth: '1em', minHeight: '1em' }}
|
||||
/>
|
||||
|
||||
{tank.testing && (
|
||||
<ExperimentIcon style={{ width: '1em', height: '1em' }} />
|
||||
)}
|
||||
|
||||
{tank.name}
|
||||
</Link>
|
||||
</StickyRowHeaderCell>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import { amberDark, blueDark } from '@radix-ui/colors';
|
||||
import { Flex, Text } from '@radix-ui/themes';
|
||||
import { times } from 'lodash';
|
||||
import { ReactNode } from 'react';
|
||||
import { ClassHeavy } from '../../packages/website/src/components/ClassIcon/components/ClassHeavy';
|
||||
import { ClassLight } from '../../packages/website/src/components/ClassIcon/components/ClassLight';
|
||||
import { ClassMedium } from '../../packages/website/src/components/ClassIcon/components/ClassMedium';
|
||||
import { ClassTankDestroyer } from '../../packages/website/src/components/ClassIcon/components/ClassTankDestroyer';
|
||||
import { useEmbedStateCurry } from '../../packages/website/src/stores/embedState/utilities';
|
||||
import { breakdownConfig } from '../app/tools/embed/configurations/breakdown';
|
||||
import { toRadiusVar } from '../app/tools/embed/utilities';
|
||||
import { TankDefinition } from '../core/blitzkit/tankDefinitions';
|
||||
import { previewCompositeStat } from '../core/statistics/compositeStats';
|
||||
import { CompositeStatsKey } from '../core/statistics/compositeStats/constants';
|
||||
import strings from '../lang/en-US.json';
|
||||
|
||||
interface BreakdownEmbedWrapperProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function BreakdownEmbedWrapper({
|
||||
children,
|
||||
}: BreakdownEmbedWrapperProps) {
|
||||
const { useState } = useEmbedStateCurry<typeof breakdownConfig>();
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap={useState('listGap')} height="100%">
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
interface BreakdownEmbedCardProps {
|
||||
tank: TankDefinition | null;
|
||||
}
|
||||
|
||||
export function BreakdownEmbedCard({ tank }: BreakdownEmbedCardProps) {
|
||||
const { useState, useRichText } =
|
||||
useEmbedStateCurry<typeof breakdownConfig>();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
flexShrink="0"
|
||||
direction="column"
|
||||
style={{
|
||||
borderRadius: toRadiusVar(useState('cardRadius')),
|
||||
overflow: 'hidden',
|
||||
backgroundColor: useState('cardBodyBackgroundColor'),
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
justify="center"
|
||||
style={{
|
||||
backgroundColor: useState('cardHeaderBackgroundColor'),
|
||||
}}
|
||||
p={useState('cardHeaderPadding')}
|
||||
>
|
||||
<Text
|
||||
{...{
|
||||
...useRichText('cardTitle'),
|
||||
style:
|
||||
tank === null
|
||||
? undefined
|
||||
: tank.treeType === 'researchable' ||
|
||||
!useState('cardTitleTypeColor')
|
||||
? undefined
|
||||
: {
|
||||
color:
|
||||
tank.treeType === 'premium'
|
||||
? amberDark.amber11
|
||||
: blueDark.blue11,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Flex align="center" gap="1">
|
||||
{useState('cardTitleClassIcon') && tank !== null && (
|
||||
<>
|
||||
{tank.class === 'lightTank' && (
|
||||
<ClassLight width="1em" height="1em" />
|
||||
)}
|
||||
{tank.class === 'mediumTank' && (
|
||||
<ClassMedium width="1em" height="1em" />
|
||||
)}
|
||||
{tank.class === 'heavyTank' && (
|
||||
<ClassHeavy width="1em" height="1em" />
|
||||
)}
|
||||
{tank.class === 'AT-SPG' && (
|
||||
<ClassTankDestroyer width="1em" height="1em" />
|
||||
)}
|
||||
</>
|
||||
)}{' '}
|
||||
{tank === null ? 'Total' : tank.name}
|
||||
</Flex>
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex p={useState('cardBodyPadding')}>
|
||||
{times(4, (index) => {
|
||||
const column = (index + 1) as 1 | 2 | 3 | 4;
|
||||
const columnState = useState(`column${column}`) as CompositeStatsKey;
|
||||
const customLabel = useState(`column${column}CustomLabel`);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction="column"
|
||||
align="center"
|
||||
gap={useState('columnGap')}
|
||||
flexGrow="1"
|
||||
>
|
||||
<Text {...useRichText('columnValue')}>
|
||||
{previewCompositeStat(columnState)}
|
||||
</Text>
|
||||
<Text {...useRichText('columnLabel')}>
|
||||
{customLabel.length === 0
|
||||
? strings.common.composite_stats[columnState]
|
||||
: customLabel}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user