feat: skeletons

This commit is contained in:
TrèsAbhi committed 2024-06-24 14:10:23 -05:00
1 parent fe945cced1
commit 798ef10469
8 files changed
+78 -44

No files matched your search

@@ -0,0 +1,30 @@
import { Flex, Link, Text } from '@radix-ui/themes';
import { useTankopediaFilters } from '../../../../stores/tankopediaFilters';
interface NoResultsProps {
type?: 'filters' | 'search';
}
export function NoResults({ type = 'filters' }: NoResultsProps) {
return (
<Flex flexGrow="1" align="center" justify="center">
<Text color="gray">
No tanks found.{' '}
<Link
href="#"
underline="always"
color="red"
onClick={() =>
useTankopediaFilters.setState(
useTankopediaFilters.getInitialState(),
true,
)
}
>
{type === 'filters' ? 'Try clearing filters' : 'Try searching again'}
</Link>
.
</Text>
</Flex>
);
}
@@ -1,5 +1,6 @@
import { Callout, Flex } from '@radix-ui/themes'; import { Callout, Flex } from '@radix-ui/themes';
import { go } from 'fuzzysort'; import { go } from 'fuzzysort';
import { times } from 'lodash';
import { memo, use, useMemo } from 'react'; import { memo, use, useMemo } from 'react';
import { ExperimentIcon } from '../../../../components/ExperimentIcon'; import { ExperimentIcon } from '../../../../components/ExperimentIcon';
import { resolveNearPenetration } from '../../../../core/blitz/resolveNearPenetration'; import { resolveNearPenetration } from '../../../../core/blitz/resolveNearPenetration';
@@ -16,7 +17,9 @@ import {
import { unionBoundingBox } from '../../../../core/blitzkit/unionBoundingBox'; import { unionBoundingBox } from '../../../../core/blitzkit/unionBoundingBox';
import { useTankopediaFilters } from '../../../../stores/tankopediaFilters'; import { useTankopediaFilters } from '../../../../stores/tankopediaFilters';
import { FilterControl } from './FilterControl'; import { FilterControl } from './FilterControl';
import { NoResults } from './NoResults';
import { SearchBar } from './SearchBar'; import { SearchBar } from './SearchBar';
import { SkeletonTankCard } from './SkeletonTankCard';
import { TankCard } from './TankCard'; import { TankCard } from './TankCard';
import { TankCardWrapper } from './TankCardWrapper'; import { TankCardWrapper } from './TankCardWrapper';
import { TierCard } from './TierCard'; import { TierCard } from './TierCard';
@@ -30,6 +33,7 @@ export const Results = memo(() => {
const search = useTankopediaFilters((state) => state.search); const search = useTankopediaFilters((state) => state.search);
const tier = useTankopediaFilters((state) => state.tier); const tier = useTankopediaFilters((state) => state.tier);
const sort = useTankopediaFilters((state) => state.sort); const sort = useTankopediaFilters((state) => state.sort);
const searching = useTankopediaFilters((state) => state.searching);
const searchedTanks = useMemo(() => { const searchedTanks = useMemo(() => {
if (search !== undefined) { if (search !== undefined) {
const searchedRaw = go(search, awaitedTankNames, { const searchedRaw = go(search, awaitedTankNames, {
@@ -330,7 +334,7 @@ export const Results = memo(() => {
<Flex direction="column" gap="4" flexGrow="1"> <Flex direction="column" gap="4" flexGrow="1">
<SearchBar topResult={searchedTanks?.[0]} /> <SearchBar topResult={searchedTanks?.[0]} />
{!search && <FilterControl />} {!search && !searching && <FilterControl />}
{testing === 'only' && ( {testing === 'only' && (
<Flex justify="center" mt="4"> <Flex justify="center" mt="4">
@@ -346,11 +350,29 @@ export const Results = memo(() => {
</Flex> </Flex>
)} )}
{!search && sort.by === 'meta.tier' && <TierCard tier={tier} />} {!search && sort.by === 'meta.tier' && !searching && (
<TierCard tier={tier} />
)}
{(search || sort.by !== 'meta.tier') && ( {(search || sort.by !== 'meta.tier') && !searching && searchedTanks && (
<TankCardWrapper py="4"> <>
{searchedTanks?.map((tank) => <TankCard key={tank.id} tank={tank} />)} {searchedTanks.length > 0 && (
<TankCardWrapper>
{searchedTanks.map((tank) => (
<TankCard key={tank.id} tank={tank} />
))}
</TankCardWrapper>
)}
{searchedTanks.length === 0 && <NoResults type="search" />}
</>
)}
{searching && (
<TankCardWrapper>
{times(Math.round(10 + 10 * Math.random()), (index) => (
<SkeletonTankCard key={index} />
))}
</TankCardWrapper> </TankCardWrapper>
)} )}
</Flex> </Flex>
@@ -2,7 +2,7 @@ import { CaretRightIcon, MagnifyingGlassIcon } from '@radix-ui/react-icons';
import { Button, Flex, Spinner, TextField } from '@radix-ui/themes'; import { Button, Flex, Spinner, TextField } from '@radix-ui/themes';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { KeyboardEventHandler, useCallback, useRef, useState } from 'react'; import { KeyboardEventHandler, useCallback, useRef } from 'react';
import { Link } from '../../../../components/Link'; import { Link } from '../../../../components/Link';
import { TankDefinition } from '../../../../core/blitzkit/tankDefinitions'; import { TankDefinition } from '../../../../core/blitzkit/tankDefinitions';
import { useTankopediaFilters } from '../../../../stores/tankopediaFilters'; import { useTankopediaFilters } from '../../../../stores/tankopediaFilters';
@@ -14,11 +14,11 @@ interface SearchBarProps {
export function SearchBar({ topResult }: SearchBarProps) { export function SearchBar({ topResult }: SearchBarProps) {
const router = useRouter(); const router = useRouter();
const [searching, setSearching] = useState(false);
const input = useRef<HTMLInputElement>(null); const input = useRef<HTMLInputElement>(null);
const searching = useTankopediaFilters((state) => state.searching);
const search = useCallback( const search = useCallback(
debounce(() => { debounce(() => {
setSearching(false); useTankopediaFilters.setState({ searching: false });
if (!input.current) return; if (!input.current) return;
@@ -31,7 +31,7 @@ export function SearchBar({ topResult }: SearchBarProps) {
[], [],
); );
const handleChange = useCallback(() => { const handleChange = useCallback(() => {
setSearching(true); useTankopediaFilters.setState({ searching: true });
search(); search();
}, []); }, []);
const handleKeyDown = useCallback<KeyboardEventHandler>( const handleKeyDown = useCallback<KeyboardEventHandler>(
@@ -0,0 +1,10 @@
import { Flex, Skeleton } from '@radix-ui/themes';
export function SkeletonTankCard() {
return (
<Flex direction="column" gap="2">
<Skeleton height="64px" />
<Skeleton height="1em" />
</Flex>
);
}
@@ -3,6 +3,7 @@ import { Grid, GridProps } from '@radix-ui/themes';
export function TankCardWrapper(props: GridProps) { export function TankCardWrapper(props: GridProps) {
return ( return (
<Grid <Grid
py="4"
columns="repeat(auto-fill, minmax(100px, 1fr))" columns="repeat(auto-fill, minmax(100px, 1fr))"
gap="2" gap="2"
gapY="6" gapY="6"
@@ -1,6 +1,4 @@
import { Flex, Text } from '@radix-ui/themes';
import { memo, use, useMemo } from 'react'; import { memo, use, useMemo } from 'react';
import { Link } from '../../../../components/Link';
import { TANK_CLASSES } from '../../../../components/Tanks/components/Item/constants'; import { TANK_CLASSES } from '../../../../components/Tanks/components/Item/constants';
import { gameDefinitions } from '../../../../core/blitzkit/gameDefinitions'; import { gameDefinitions } from '../../../../core/blitzkit/gameDefinitions';
import { import {
@@ -9,6 +7,7 @@ import {
} from '../../../../core/blitzkit/tankDefinitions'; } from '../../../../core/blitzkit/tankDefinitions';
import { tankopediaFilterTank } from '../../../../core/blitzkit/tankopediaFilterTank'; import { tankopediaFilterTank } from '../../../../core/blitzkit/tankopediaFilterTank';
import { useTankopediaFilters } from '../../../../stores/tankopediaFilters'; import { useTankopediaFilters } from '../../../../stores/tankopediaFilters';
import { NoResults } from './NoResults';
import { TankCard } from './TankCard'; import { TankCard } from './TankCard';
import { TankCardWrapper } from './TankCardWrapper'; import { TankCardWrapper } from './TankCardWrapper';
import { treeTypeOrder } from './TankSearch/constants'; import { treeTypeOrder } from './TankSearch/constants';
@@ -43,31 +42,10 @@ export const TierCard = memo(
[tier, filters], [tier, filters],
); );
if (tanks.length === 0) if (tanks.length === 0) return <NoResults />;
return (
<Flex flexGrow="1" align="center" justify="center">
<Text color="gray">
No tanks found.{' '}
<Link
href="#"
underline="always"
color="red"
onClick={() =>
useTankopediaFilters.setState(
useTankopediaFilters.getInitialState(),
true,
)
}
>
Try clearing filters
</Link>
.
</Text>
</Flex>
);
return ( return (
<TankCardWrapper mt="6"> <TankCardWrapper>
{tanks.map((tank) => ( {tanks.map((tank) => (
<TankCard key={tank.id} tank={tank} /> <TankCard key={tank.id} tank={tank} />
))} ))}
+1 -10
View File
@@ -1,21 +1,12 @@
'use client'; 'use client';
import { Flex } from '@radix-ui/themes';
import PageWrapper from '../../../components/PageWrapper'; import PageWrapper from '../../../components/PageWrapper';
import { Results } from './components/Results'; import { Results } from './components/Results';
export default function Page() { export default function Page() {
return ( return (
<PageWrapper color="purple" size={1028}> <PageWrapper color="purple" size={1028}>
<Flex <Results />
gap="8"
direction={{ initial: 'column', sm: 'row' }}
py="4"
flexGrow="1"
>
{/* <FilterControl /> */}
<Results />
</Flex>
</PageWrapper> </PageWrapper>
); );
} }
+2
View File
@@ -15,6 +15,7 @@ export interface TankopediaFilters {
by: TankopediaSortBy; by: TankopediaSortBy;
direction: TankopediaSortDirection; direction: TankopediaSortDirection;
}; };
searching: boolean;
} }
export const useTankopediaFilters = create<TankopediaFilters>()(() => ({ export const useTankopediaFilters = create<TankopediaFilters>()(() => ({
@@ -24,6 +25,7 @@ export const useTankopediaFilters = create<TankopediaFilters>()(() => ({
direction: 'descending', direction: 'descending',
}, },
tier: 10, tier: 10,
searching: false,
})); }));
export function mutateTankopediaFilters( export function mutateTankopediaFilters(