feat: search bar

This commit is contained in:
TrèsAbhi committed 2024-06-21 19:04:34 -05:00
1 parent c8e207b6a2
commit 8e438757fd
7 files changed
+450 -421

No files matched your search

@@ -0,0 +1,318 @@
import { TrashIcon } from '@radix-ui/react-icons';
import { Box, Button, Flex, IconButton, Text } from '@radix-ui/themes';
import { times } from 'lodash';
import { use } from 'react';
import { classIcons } from '../../../../components/ClassIcon';
import { ExperimentIcon } from '../../../../components/ExperimentIcon';
import { NAVBAR_HEIGHT } from '../../../../components/Navbar';
import { ResearchedIcon } from '../../../../components/ResearchedIcon';
import { ScienceIcon } from '../../../../components/ScienceIcon';
import { ScienceOffIcon } from '../../../../components/ScienceOffIcon';
import { TANK_CLASSES } from '../../../../components/Tanks/components/Item/constants';
import { asset } from '../../../../core/blitzkit/asset';
import { gameDefinitions } from '../../../../core/blitzkit/gameDefinitions';
import { Tier } from '../../../../core/blitzkit/tankDefinitions';
import { TIER_ROMAN_NUMERALS } from '../../../../core/blitzkit/tankDefinitions/constants';
import {
mutateTankopediaFilters,
useTankopediaFilters,
} from '../../../../stores/tankopediaFilters';
export function FilterControl() {
const awaitedGameDefinitions = use(gameDefinitions);
const filters = useTankopediaFilters();
return (
<Flex
direction={{ initial: 'row', sm: 'column' }}
height="fit-content"
gap="2"
align={{ initial: 'start', sm: 'start' }}
justify={{ initial: 'center', sm: 'start' }}
position={{ initial: 'static', sm: 'sticky' }}
style={{ top: `calc(${NAVBAR_HEIGHT}px + var(--space-3))` }}
>
<Flex
direction={{ initial: 'row', sm: 'column' }}
overflow="hidden"
style={{ borderRadius: 'var(--radius-4)' }}
>
<Flex direction={{ sm: 'row', initial: 'column' }}>
{times(5, (index) => {
const tier = (index + 1) as Tier;
const selected = filters.tier === tier;
return (
<IconButton
key={tier}
variant={selected ? 'solid' : 'soft'}
radius="none"
color={selected ? undefined : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.tier = draft.tier === tier ? undefined : tier;
})
}
>
<Text size="2">{TIER_ROMAN_NUMERALS[tier]}</Text>
</IconButton>
);
})}
</Flex>
<Flex direction={{ sm: 'row', initial: 'column' }}>
{times(5, (index) => {
const tier = (index + 6) as Tier;
const selected = filters.tier === tier;
return (
<IconButton
key={tier}
variant={selected ? 'solid' : 'soft'}
radius="none"
color={selected ? undefined : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.tier = draft.tier === tier ? undefined : tier;
})
}
>
<Text size="2">{TIER_ROMAN_NUMERALS[tier]}</Text>
</IconButton>
);
})}
</Flex>
</Flex>
<Flex
direction={{ initial: 'row', sm: 'column' }}
overflow="hidden"
style={{ borderRadius: 'var(--radius-4)' }}
>
<Flex direction={{ sm: 'row', initial: 'column' }}>
{awaitedGameDefinitions.nations
.slice(0, Math.ceil(awaitedGameDefinitions.nations.length / 2))
.map((nation) => {
const selected = filters.nation === nation;
return (
<IconButton
key={nation}
variant={selected ? 'solid' : 'soft'}
color={selected ? undefined : 'gray'}
highContrast
radius="none"
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.nation =
draft.nation === nation ? undefined : nation;
})
}
>
<img
style={{ width: '1em', height: '1em' }}
src={asset(`flags/circle/${nation}.webp`)}
/>
</IconButton>
);
})}
</Flex>
<Flex direction={{ sm: 'row', initial: 'column' }}>
{awaitedGameDefinitions.nations
.slice(Math.ceil(awaitedGameDefinitions.nations.length / 2))
.map((nation) => {
const selected = filters.nation === nation;
return (
<IconButton
key={nation}
style={{ flex: 1 }}
variant={selected ? 'solid' : 'soft'}
color={selected ? undefined : 'gray'}
highContrast
radius="none"
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.nation =
draft.nation === nation ? undefined : nation;
})
}
>
<img
style={{ width: '1em', height: '1em' }}
src={asset(`flags/circle/${nation}.webp`)}
/>
</IconButton>
);
})}
</Flex>
</Flex>
<Flex
overflow="hidden"
style={{ borderRadius: 'var(--radius-full)' }}
direction={{ sm: 'row', initial: 'column' }}
>
{TANK_CLASSES.map((tankClass) => {
const Icon = classIcons[tankClass];
const selected = filters.class === tankClass;
return (
<IconButton
key={tankClass}
variant={selected ? 'solid' : 'soft'}
radius="none"
color={selected ? undefined : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.class =
draft.class === tankClass ? undefined : tankClass;
})
}
>
<Icon style={{ width: '1em', height: '1em' }} />
</IconButton>
);
})}
</Flex>
<Flex
overflow="hidden"
style={{ borderRadius: 'var(--radius-full)' }}
direction={{ sm: 'row', initial: 'column' }}
>
<IconButton
variant={filters.type === 'researchable' ? 'solid' : 'soft'}
radius="none"
color={filters.type === 'researchable' ? undefined : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.type =
draft.type === 'researchable' ? undefined : 'researchable';
})
}
>
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
</IconButton>
<IconButton
variant={filters.type === 'premium' ? 'solid' : 'soft'}
radius="none"
color={filters.type === 'premium' ? 'amber' : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.type = draft.type === 'premium' ? undefined : 'premium';
})
}
>
<Text
color={filters.type === 'premium' ? undefined : 'amber'}
style={{ display: 'flex', justifyContent: 'center' }}
>
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
</Text>
</IconButton>
<IconButton
variant={filters.type === 'collector' ? 'solid' : 'soft'}
radius="none"
color={filters.type === 'collector' ? 'blue' : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.type = draft.type === 'collector' ? undefined : 'collector';
})
}
>
<Text
color={filters.type === 'premium' ? undefined : 'blue'}
style={{ display: 'flex', justifyContent: 'center' }}
>
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
</Text>
</IconButton>
</Flex>
<Flex
overflow="hidden"
style={{ borderRadius: 'var(--radius-full)' }}
direction={{ sm: 'row', initial: 'column' }}
>
<IconButton
variant={filters.testing === 'include' ? 'solid' : 'soft'}
radius="none"
color={filters.testing === 'include' ? undefined : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.testing = 'include';
})
}
>
<ExperimentIcon
style={{ width: '1em', height: '1em', color: 'currentColor' }}
/>
</IconButton>
<IconButton
variant={filters.testing === 'exclude' ? 'solid' : 'soft'}
radius="none"
color={filters.testing === 'exclude' ? undefined : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.testing = 'exclude';
})
}
>
<ScienceOffIcon
style={{ width: '1em', height: '1em', color: 'currentColor' }}
/>
</IconButton>
<IconButton
variant={filters.testing === 'only' ? 'solid' : 'soft'}
radius="none"
color={filters.testing === 'only' ? undefined : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.testing = 'only';
})
}
>
<ScienceIcon
style={{ width: '1em', height: '1em', color: 'currentColor' }}
/>
</IconButton>
</Flex>
<Box display={{ initial: 'block', sm: 'none' }}>
<IconButton
color="red"
ml="2"
onClick={() =>
useTankopediaFilters.setState(
useTankopediaFilters.getInitialState(),
)
}
>
<TrashIcon />
</IconButton>
</Box>
<Box display={{ initial: 'none', sm: 'block' }}>
<Button
color="red"
mt="2"
onClick={() =>
useTankopediaFilters.setState(
useTankopediaFilters.getInitialState(),
)
}
>
<TrashIcon /> Clear
</Button>
</Box>
</Flex>
);
}
+31 -29
View File
@@ -1,26 +1,41 @@
import { Callout, Flex } from '@radix-ui/themes';
import { go } from 'fuzzysort';
import { range } from 'lodash';
import { memo, use } from 'react';
import { memo, use, useMemo } from 'react';
import { ExperimentIcon } from '../../../../components/ExperimentIcon';
import { TANK_CLASSES } from '../../../../components/Tanks/components/Item/constants';
import { gameDefinitions } from '../../../../core/blitzkit/gameDefinitions';
import {
Tier,
tanksDefinitionsArray,
tankDefinitions,
tankNames,
} from '../../../../core/blitzkit/tankDefinitions';
import { useTankopediaFilters } from '../../../../stores/tankopediaFilters';
import { treeTypeOrder } from './TankSearch/constants';
import { SearchBar } from './SearchBar';
import { TankCard } from './TankCard';
import { TankCardWrapper } from './TankCardWrapper';
import { TierCard } from './TierCard';
export const Results = memo(() => {
const tanks = use(tanksDefinitionsArray);
const awaitedGameDefinitions = use(gameDefinitions);
const awaitedTankDefinitions = use(tankDefinitions);
const awaitedTankNames = use(tankNames);
const testing = useTankopediaFilters((state) => state.testing);
const search = useTankopediaFilters((state) => state.search);
const searchedTanks = useMemo(() => {
if (search === undefined) return undefined;
const searchedRaw = go(search, awaitedTankNames, { key: 'combined' });
const searchedTanks = searchedRaw.map(
(result) => awaitedTankDefinitions[result.obj.id],
);
return searchedTanks;
}, [search]);
return (
<Flex direction="column" gap="8" flexGrow="1">
<Flex direction="column" gap="4" flexGrow="1">
<SearchBar topResult={searchedTanks?.[0]} />
{testing === 'only' && (
<Callout.Root color="amber" style={{ width: 'fit-content' }}>
<Callout.Root color="amber">
<Callout.Icon>
<ExperimentIcon style={{ width: '1em', height: '1em' }} />
</Callout.Icon>
@@ -31,27 +46,14 @@ export const Results = memo(() => {
</Callout.Root>
)}
{range(10, 0).map((tierUntyped) => {
const tier = tierUntyped as Tier;
const tierTanks = tanks
.filter((tank) => tank.tier === tier)
.sort(
(a, b) =>
treeTypeOrder.indexOf(a.treeType) -
treeTypeOrder.indexOf(b.treeType),
)
.sort(
(a, b) =>
TANK_CLASSES.indexOf(a.class) - TANK_CLASSES.indexOf(b.class),
)
.sort(
(a, b) =>
awaitedGameDefinitions.nations.indexOf(a.nation) -
awaitedGameDefinitions.nations.indexOf(b.nation),
);
{!search &&
range(10, 0).map((tier) => <TierCard key={tier} tier={tier as Tier} />)}
return <TierCard tanks={tierTanks} key={tier} tier={tier} />;
})}
{search && (
<TankCardWrapper py="4">
{searchedTanks?.map((tank) => <TankCard key={tank.id} tank={tank} />)}
</TankCardWrapper>
)}
</Flex>
);
});
@@ -0,0 +1,69 @@
import { CaretRightIcon, MagnifyingGlassIcon } from '@radix-ui/react-icons';
import { Button, Spinner, TextField } from '@radix-ui/themes';
import { debounce } from 'lodash';
import { useRouter } from 'next/navigation';
import { KeyboardEventHandler, useCallback, useRef, useState } from 'react';
import { Link } from '../../../../components/Link';
import { TankDefinition } from '../../../../core/blitzkit/tankDefinitions';
import { useTankopediaFilters } from '../../../../stores/tankopediaFilters';
interface SearchBarProps {
topResult?: TankDefinition;
}
export function SearchBar({ topResult }: SearchBarProps) {
const router = useRouter();
const [searching, setSearching] = useState(false);
const input = useRef<HTMLInputElement>(null);
const search = useCallback(
debounce(() => {
setSearching(false);
if (!input.current) return;
const sanitized = input.current.value.trim();
useTankopediaFilters.setState({
search: sanitized.length === 0 ? undefined : sanitized,
});
}, 500),
[],
);
const handleChange = useCallback(() => {
setSearching(true);
search();
}, []);
const handleKeyDown = useCallback<KeyboardEventHandler>(
(event) => {
if (event.key !== 'Enter' || !topResult) return;
event.preventDefault();
router.push(`/tools/tankopedia/${topResult.id}`);
},
[topResult],
);
return (
<TextField.Root
ref={input}
placeholder="Search tanks..."
size="3"
onChange={handleChange}
onKeyDown={handleKeyDown}
>
<TextField.Slot>
{searching ? <Spinner /> : <MagnifyingGlassIcon />}
</TextField.Slot>
{topResult && (
<TextField.Slot>
<Link href={`/tools/tankopedia/${topResult.id}`}>
<Button variant="ghost" size="3">
{topResult.name} <CaretRightIcon />
</Button>
</Link>
</TextField.Slot>
)}
</TextField.Root>
);
}
@@ -0,0 +1,13 @@
import { Grid, GridProps } from '@radix-ui/themes';
export function TankCardWrapper(props: GridProps) {
return (
<Grid
flexGrow="1"
columns="repeat(auto-fill, minmax(100px, 1fr))"
gap="2"
gapY="6"
{...props}
/>
);
}
@@ -1,22 +1,27 @@
import { Flex, Grid, Heading, Separator } from '@radix-ui/themes';
import { memo, useEffect, useRef } from 'react';
import { Flex, Heading, Separator } from '@radix-ui/themes';
import { memo, use, useEffect, useMemo, useRef } from 'react';
import {
TankDefinition,
Tier,
tanksDefinitionsArray,
} from '../../../../core/blitzkit/tankDefinitions';
import { TIER_ROMAN_NUMERALS } from '../../../../core/blitzkit/tankDefinitions/constants';
import { tankopediaFilterTank } from '../../../../core/blitzkit/tankopediaFilterTank';
import { useTankopediaFilters } from '../../../../stores/tankopediaFilters';
import { TankCard } from './TankCard';
import { TankCardWrapper } from './TankCardWrapper';
interface TierCardProps {
tier: Tier;
tanks: TankDefinition[];
}
export const TierCard = memo(
({ tier, tanks }: TierCardProps) => {
({ tier }: TierCardProps) => {
const awaitedTanksDefinitionsArray = use(tanksDefinitionsArray);
const container = useRef<HTMLDivElement>(null);
const tanks = useMemo(
() => awaitedTanksDefinitionsArray.filter((tank) => tank.tier === tier),
[tier],
);
useEffect(() => {
const unsubscribe = useTankopediaFilters.subscribe((filters) => {
@@ -37,22 +42,18 @@ export const TierCard = memo(
direction="column"
gap="4"
display={tanks.length === 0 ? 'none' : 'flex'}
py="4"
>
<Flex align="center" mb="4" gap="4">
<Heading weight="regular">Tier {TIER_ROMAN_NUMERALS[tier]}</Heading>
<Separator style={{ flex: 1 }} />
</Flex>
<Grid
flexGrow="1"
columns="repeat(auto-fill, minmax(100px, 1fr))"
gap="2"
gapY="6"
>
<TankCardWrapper>
{tanks.map((tank) => (
<TankCard key={tank.id} tank={tank} />
))}
</Grid>
</TankCardWrapper>
</Flex>
);
},
+5 -380
View File
@@ -1,395 +1,20 @@
'use client';
import { TrashIcon } from '@radix-ui/react-icons';
import { Box, Button, Flex, IconButton, Text } from '@radix-ui/themes';
import { times } from 'lodash';
import { use } from 'react';
import { classIcons } from '../../../components/ClassIcon';
import { ExperimentIcon } from '../../../components/ExperimentIcon';
import { NAVBAR_HEIGHT } from '../../../components/Navbar';
import { Flex } from '@radix-ui/themes';
import PageWrapper from '../../../components/PageWrapper';
import { ResearchedIcon } from '../../../components/ResearchedIcon';
import { ScienceIcon } from '../../../components/ScienceIcon';
import { ScienceOffIcon } from '../../../components/ScienceOffIcon';
import { TANK_CLASSES } from '../../../components/Tanks/components/Item/constants';
import { asset } from '../../../core/blitzkit/asset';
import { gameDefinitions } from '../../../core/blitzkit/gameDefinitions';
import { Tier } from '../../../core/blitzkit/tankDefinitions';
import { TIER_ROMAN_NUMERALS } from '../../../core/blitzkit/tankDefinitions/constants';
import {
mutateTankopediaFilters,
useTankopediaFilters,
} from '../../../stores/tankopediaFilters';
import { FilterControl } from './components/FilterControl';
import { Results } from './components/Results';
export default function Page() {
const awaitedGameDefinitions = use(gameDefinitions);
const filters = useTankopediaFilters();
return (
<PageWrapper color="purple" size={1028 + 256}>
<Flex
gap="8"
direction={{
initial: 'column',
sm: 'row',
}}
pt={{
initial: '0',
sm: '6',
}}
direction={{ initial: 'column', sm: 'row' }}
pt={{ initial: '0', sm: '6' }}
pb="6"
>
<Flex
direction={{
initial: 'row',
sm: 'column',
}}
height="fit-content"
gap="2"
align={{
initial: 'start',
sm: 'start',
}}
justify={{
initial: 'center',
sm: 'start',
}}
position={{
initial: 'static',
sm: 'sticky',
}}
style={{
top: `calc(${NAVBAR_HEIGHT}px + var(--space-3))`,
}}
>
<Flex
direction={{
initial: 'row',
sm: 'column',
}}
overflow="hidden"
style={{
borderRadius: 'var(--radius-4)',
}}
>
<Flex
direction={{
sm: 'row',
initial: 'column',
}}
>
{times(5, (index) => {
const tier = (index + 1) as Tier;
const selected = filters.tier === tier;
return (
<IconButton
key={tier}
variant={selected ? 'solid' : 'soft'}
radius="none"
color={selected ? undefined : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.tier = draft.tier === tier ? undefined : tier;
})
}
>
<Text size="2">{TIER_ROMAN_NUMERALS[tier]}</Text>
</IconButton>
);
})}
</Flex>
<Flex
direction={{
sm: 'row',
initial: 'column',
}}
>
{times(5, (index) => {
const tier = (index + 6) as Tier;
const selected = filters.tier === tier;
return (
<IconButton
key={tier}
variant={selected ? 'solid' : 'soft'}
radius="none"
color={selected ? undefined : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.tier = draft.tier === tier ? undefined : tier;
})
}
>
<Text size="2">{TIER_ROMAN_NUMERALS[tier]}</Text>
</IconButton>
);
})}
</Flex>
</Flex>
<Flex
direction={{
initial: 'row',
sm: 'column',
}}
overflow="hidden"
style={{
borderRadius: 'var(--radius-4)',
}}
>
<Flex
direction={{
sm: 'row',
initial: 'column',
}}
>
{awaitedGameDefinitions.nations
.slice(0, Math.ceil(awaitedGameDefinitions.nations.length / 2))
.map((nation) => {
const selected = filters.nation === nation;
return (
<IconButton
variant={selected ? 'solid' : 'soft'}
color={selected ? undefined : 'gray'}
highContrast
radius="none"
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.nation =
draft.nation === nation ? undefined : nation;
})
}
>
<img
style={{ width: '1em', height: '1em' }}
src={asset(`flags/circle/${nation}.webp`)}
/>
</IconButton>
);
})}
</Flex>
<Flex
direction={{
sm: 'row',
initial: 'column',
}}
>
{awaitedGameDefinitions.nations
.slice(Math.ceil(awaitedGameDefinitions.nations.length / 2))
.map((nation) => {
const selected = filters.nation === nation;
return (
<IconButton
style={{ flex: 1 }}
variant={selected ? 'solid' : 'soft'}
color={selected ? undefined : 'gray'}
highContrast
radius="none"
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.nation =
draft.nation === nation ? undefined : nation;
})
}
>
<img
style={{ width: '1em', height: '1em' }}
src={asset(`flags/circle/${nation}.webp`)}
/>
</IconButton>
);
})}
</Flex>
</Flex>
<Flex
overflow="hidden"
style={{
borderRadius: 'var(--radius-full)',
}}
direction={{
sm: 'row',
initial: 'column',
}}
>
{TANK_CLASSES.map((tankClass) => {
const Icon = classIcons[tankClass];
const selected = filters.class === tankClass;
return (
<IconButton
key={tankClass}
variant={selected ? 'solid' : 'soft'}
radius="none"
color={selected ? undefined : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.class =
draft.class === tankClass ? undefined : tankClass;
})
}
>
<Icon style={{ width: '1em', height: '1em' }} />
</IconButton>
);
})}
</Flex>
<Flex
overflow="hidden"
style={{
borderRadius: 'var(--radius-full)',
}}
direction={{
sm: 'row',
initial: 'column',
}}
>
<IconButton
variant={filters.type === 'researchable' ? 'solid' : 'soft'}
radius="none"
color={filters.type === 'researchable' ? undefined : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.type =
draft.type === 'researchable' ? undefined : 'researchable';
})
}
>
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
</IconButton>
<IconButton
variant={filters.type === 'premium' ? 'solid' : 'soft'}
radius="none"
color={filters.type === 'premium' ? 'amber' : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.type = draft.type === 'premium' ? undefined : 'premium';
})
}
>
<Text
color={filters.type === 'premium' ? undefined : 'amber'}
style={{ display: 'flex', justifyContent: 'center' }}
>
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
</Text>
</IconButton>
<IconButton
variant={filters.type === 'collector' ? 'solid' : 'soft'}
radius="none"
color={filters.type === 'collector' ? 'blue' : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.type =
draft.type === 'collector' ? undefined : 'collector';
})
}
>
<Text
color={filters.type === 'premium' ? undefined : 'blue'}
style={{ display: 'flex', justifyContent: 'center' }}
>
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
</Text>
</IconButton>
</Flex>
<Flex
overflow="hidden"
style={{
borderRadius: 'var(--radius-full)',
}}
direction={{
sm: 'row',
initial: 'column',
}}
>
<IconButton
variant={filters.testing === 'include' ? 'solid' : 'soft'}
radius="none"
color={filters.testing === 'include' ? undefined : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.testing = 'include';
})
}
>
<ExperimentIcon
style={{ width: '1em', height: '1em', color: 'currentColor' }}
/>
</IconButton>
<IconButton
variant={filters.testing === 'exclude' ? 'solid' : 'soft'}
radius="none"
color={filters.testing === 'exclude' ? undefined : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.testing = 'exclude';
})
}
>
<ScienceOffIcon
style={{ width: '1em', height: '1em', color: 'currentColor' }}
/>
</IconButton>
<IconButton
variant={filters.testing === 'only' ? 'solid' : 'soft'}
radius="none"
color={filters.testing === 'only' ? undefined : 'gray'}
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.testing = 'only';
})
}
>
<ScienceIcon
style={{ width: '1em', height: '1em', color: 'currentColor' }}
/>
</IconButton>
</Flex>
<Box display={{ initial: 'block', sm: 'none' }}>
<IconButton
color="red"
ml="2"
onClick={() =>
useTankopediaFilters.setState(
useTankopediaFilters.getInitialState(),
)
}
>
<TrashIcon />
</IconButton>
</Box>
<Box display={{ initial: 'none', sm: 'block' }}>
<Button
color="red"
mt="2"
onClick={() =>
useTankopediaFilters.setState(
useTankopediaFilters.getInitialState(),
)
}
>
<TrashIcon /> Clear
</Button>
</Box>
</Flex>
<FilterControl />
<Results />
</Flex>
</PageWrapper>
+1
View File
@@ -9,6 +9,7 @@ export interface TankopediaFilters {
class?: TankClass;
type?: TreeType;
testing: 'include' | 'exclude' | 'only';
search?: string;
}
export const useTankopediaFilters = create<TankopediaFilters>()(() => ({