mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
feat: per-page theming
This commit is contained in:
1 parent
bd1e17bac9
commit
1bb83da253
6 files changed
+106
-97
No files matched your search
+1
-6
@@ -41,12 +41,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|||||||
: 'url(https://i.imgur.com/PhS06NJ.png)',
|
: 'url(https://i.imgur.com/PhS06NJ.png)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Theme
|
<Theme appearance="dark" panelBackground="translucent" radius="full">
|
||||||
appearance="dark"
|
|
||||||
panelBackground="translucent"
|
|
||||||
radius="full"
|
|
||||||
accentColor="blue"
|
|
||||||
>
|
|
||||||
{!isEmbed && <Navbar />}
|
{!isEmbed && <Navbar />}
|
||||||
|
|
||||||
<Flex direction="column">{children}</Flex>
|
<Flex direction="column">{children}</Flex>
|
||||||
|
|||||||
@@ -5,14 +5,7 @@ import {
|
|||||||
CaretRightIcon,
|
CaretRightIcon,
|
||||||
MagnifyingGlassIcon,
|
MagnifyingGlassIcon,
|
||||||
} from '@radix-ui/react-icons';
|
} from '@radix-ui/react-icons';
|
||||||
import {
|
import { Button, Flex, Popover, Select, TextField } from '@radix-ui/themes';
|
||||||
Button,
|
|
||||||
Flex,
|
|
||||||
Popover,
|
|
||||||
Select,
|
|
||||||
Text,
|
|
||||||
TextField,
|
|
||||||
} from '@radix-ui/themes';
|
|
||||||
import { produce } from 'immer';
|
import { produce } from 'immer';
|
||||||
import { range } from 'lodash';
|
import { range } from 'lodash';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
@@ -73,11 +66,15 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
const pageInput = useRef<HTMLInputElement>(null);
|
|
||||||
const playerSlice = players.data?.slice(
|
const playerSlice = players.data?.slice(
|
||||||
page * rowsPerPage,
|
page * rowsPerPage,
|
||||||
page * rowsPerPage + rowsPerPage,
|
page * rowsPerPage + rowsPerPage,
|
||||||
);
|
);
|
||||||
|
const previousPage = () => setPage((page) => Math.max(page - 1, 0));
|
||||||
|
const nextPage = () =>
|
||||||
|
setPage((page) =>
|
||||||
|
Math.min(page + 1, Math.floor((players.data?.length ?? 0) / rowsPerPage)),
|
||||||
|
);
|
||||||
|
|
||||||
cachePage(page - 1);
|
cachePage(page - 1);
|
||||||
cachePage(page);
|
cachePage(page);
|
||||||
@@ -115,16 +112,51 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (pageInput.current) pageInput.current.value = `${page + 1}`;
|
|
||||||
}, [page]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPage(0);
|
setPage(0);
|
||||||
}, [season, region]);
|
}, [season, region]);
|
||||||
|
|
||||||
|
function PageTurner() {
|
||||||
|
const pageInput = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pageInput.current) pageInput.current.value = `${page + 1}`;
|
||||||
|
}, [page]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TextField.Root className={noArrows}>
|
||||||
|
<TextField.Slot>Page</TextField.Slot>
|
||||||
|
<TextField.Input
|
||||||
|
className={noArrows}
|
||||||
|
type="number"
|
||||||
|
ref={pageInput}
|
||||||
|
style={{ width: 64, textAlign: 'center' }}
|
||||||
|
onBlur={(event) => {
|
||||||
|
setPage(
|
||||||
|
Math.max(
|
||||||
|
0,
|
||||||
|
Math.min(
|
||||||
|
Math.floor((players.data?.length ?? 0) / rowsPerPage),
|
||||||
|
event.target.valueAsNumber - 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
(event.target as HTMLInputElement).blur();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextField.Slot>
|
||||||
|
out of {Math.ceil((players.data?.length ?? 0) / rowsPerPage)}
|
||||||
|
</TextField.Slot>
|
||||||
|
</TextField.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageWrapper>
|
<PageWrapper color="tomato">
|
||||||
<Flex gap="2">
|
<Flex gap="2">
|
||||||
<Select.Root
|
<Select.Root
|
||||||
defaultValue={region}
|
defaultValue={region}
|
||||||
@@ -256,72 +288,44 @@ export default function Page() {
|
|||||||
</Select.Root>
|
</Select.Root>
|
||||||
|
|
||||||
<Flex gap="2" align="center">
|
<Flex gap="2" align="center">
|
||||||
<Button
|
<Button variant="soft" onClick={previousPage}>
|
||||||
variant="soft"
|
|
||||||
onClick={() => setPage((page) => Math.max(page - 1, 0))}
|
|
||||||
>
|
|
||||||
<CaretLeftIcon />
|
<CaretLeftIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<TextField.Root className={noArrows}>
|
<PageTurner />
|
||||||
<TextField.Slot>Page</TextField.Slot>
|
<Button variant="soft" onClick={nextPage}>
|
||||||
<TextField.Input
|
|
||||||
className={noArrows}
|
|
||||||
type="number"
|
|
||||||
ref={pageInput}
|
|
||||||
style={{ width: 64, textAlign: 'center' }}
|
|
||||||
onBlur={(event) => {
|
|
||||||
setPage(
|
|
||||||
Math.max(
|
|
||||||
0,
|
|
||||||
Math.min(
|
|
||||||
Math.floor((players.data?.length ?? 0) / rowsPerPage),
|
|
||||||
event.target.valueAsNumber - 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
(event.target as HTMLInputElement).blur();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextField.Slot>
|
|
||||||
out of {Math.ceil((players.data?.length ?? 0) / rowsPerPage)}
|
|
||||||
</TextField.Slot>
|
|
||||||
</TextField.Root>
|
|
||||||
<Button
|
|
||||||
variant="soft"
|
|
||||||
onClick={() =>
|
|
||||||
setPage((page) =>
|
|
||||||
Math.min(
|
|
||||||
page + 1,
|
|
||||||
Math.floor((players.data?.length ?? 0) / rowsPerPage),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<CaretRightIcon />
|
<CaretRightIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Leaderboard.Root>
|
<Leaderboard.Root>
|
||||||
{playerSlice?.map((player, index) => (
|
{players.isLoading ? (
|
||||||
<Leaderboard.Item
|
<Leaderboard.Gap message="Loading players..." />
|
||||||
nickname={
|
) : (
|
||||||
usernameCache[region][player.id] ??
|
playerSlice?.map((player, index) => (
|
||||||
`Loading player ${player.id}...`
|
<Leaderboard.Item
|
||||||
}
|
nickname={
|
||||||
position={page * rowsPerPage + index + 1}
|
usernameCache[region][player.id] ??
|
||||||
score={player.score}
|
`Loading player ${player.id}...`
|
||||||
clan={clanCache[region][player.id]}
|
}
|
||||||
key={player.id}
|
position={page * rowsPerPage + index + 1}
|
||||||
/>
|
score={player.score}
|
||||||
))}
|
clan={clanCache[region][player.id]}
|
||||||
|
key={player.id}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</Leaderboard.Root>
|
</Leaderboard.Root>
|
||||||
|
|
||||||
{players.isLoading && <Text>Loading...</Text>}
|
<Flex justify="center" gap="2">
|
||||||
|
<Button variant="soft" onClick={previousPage}>
|
||||||
|
<CaretLeftIcon />
|
||||||
|
</Button>
|
||||||
|
<PageTurner />
|
||||||
|
<Button variant="soft" onClick={nextPage}>
|
||||||
|
<CaretRightIcon />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ export default function Page() {
|
|||||||
}, [session]);
|
}, [session]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageWrapper>
|
<PageWrapper color="blue">
|
||||||
<div className={styles.toolBar}>
|
<div className={styles.toolBar}>
|
||||||
<div style={{ flex: 1, boxSizing: 'border-box', position: 'relative' }}>
|
<div style={{ flex: 1, boxSizing: 'border-box', position: 'relative' }}>
|
||||||
<TextField.Root>
|
<TextField.Root>
|
||||||
|
|||||||
@@ -474,10 +474,12 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
|||||||
{items && (
|
{items && (
|
||||||
<Leaderboard.Root>
|
<Leaderboard.Root>
|
||||||
{playersBefore > 0 && (
|
{playersBefore > 0 && (
|
||||||
<Leaderboard.Gap number={playersBefore} />
|
<Leaderboard.Gap message={`+ ${playersBefore} more`} />
|
||||||
)}
|
)}
|
||||||
{items}
|
{items}
|
||||||
{playersAfter > 0 && <Leaderboard.Gap number={playersAfter} />}
|
{playersAfter > 0 && (
|
||||||
|
<Leaderboard.Gap message={`+ ${playersAfter} more`} />
|
||||||
|
)}
|
||||||
</Leaderboard.Root>
|
</Leaderboard.Root>
|
||||||
)}
|
)}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { theme } from '../../../stitches.config';
|
import { theme } from '../../../stitches.config';
|
||||||
|
|
||||||
export interface GapProps {
|
export interface GapProps {
|
||||||
number: number;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Gap({ number }: GapProps) {
|
export function Gap({ message: number }: GapProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -22,7 +22,7 @@ export function Gap({ number }: GapProps) {
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
+ {number.toLocaleString()} more
|
{number.toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,24 +1,32 @@
|
|||||||
|
import { Theme } from '@radix-ui/themes';
|
||||||
import { ComponentProps } from 'react';
|
import { ComponentProps } from 'react';
|
||||||
|
|
||||||
|
interface PageWrapperProps extends ComponentProps<'div'> {
|
||||||
|
color?: ComponentProps<typeof Theme>['accentColor'];
|
||||||
|
}
|
||||||
|
|
||||||
export default function PageWrapper({
|
export default function PageWrapper({
|
||||||
style,
|
style,
|
||||||
|
color,
|
||||||
...props
|
...props
|
||||||
}: ComponentProps<'div'>) {
|
}: PageWrapperProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<Theme accentColor={color}>
|
||||||
style={{
|
<div
|
||||||
width: '100%',
|
style={{
|
||||||
maxWidth: 800,
|
width: '100%',
|
||||||
margin: 'auto',
|
maxWidth: 800,
|
||||||
display: 'flex',
|
margin: 'auto',
|
||||||
padding: 16,
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
padding: 16,
|
||||||
gap: 16,
|
flexDirection: 'column',
|
||||||
boxSizing: 'border-box',
|
gap: 16,
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
|
||||||
...style,
|
...style,
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
</Theme>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user