|
1 | | -import { useAsyncEffect } from '@siberiacancode/reactuse'; |
2 | | -import { useState } from 'react'; |
| 1 | +import { useAsyncEffect, useBoolean, useToggle } from '@siberiacancode/reactuse'; |
| 2 | +import { Loader2Icon } from 'lucide-react'; |
| 3 | +import { Fragment, useState } from 'react'; |
| 4 | + |
| 5 | +import { cn } from '@/utils/lib'; |
3 | 6 |
|
4 | 7 | interface Pokemon { |
5 | | - abilities: string[]; |
6 | | - height: number; |
7 | 8 | id: number; |
8 | 9 | name: string; |
9 | | - types: string[]; |
10 | | - weight: number; |
11 | 10 | } |
12 | 11 |
|
13 | | -const getRandomPokemon = async () => { |
14 | | - const randomId = Math.floor(Math.random() * 150) + 1; |
15 | | - const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${randomId}`); |
16 | | - const data = await response.json(); |
| 12 | +const CHAINS = { |
| 13 | + bulbasaur: [1, 2, 3], |
| 14 | + charmander: [4, 5, 6], |
| 15 | + squirtle: [7, 8, 9] |
| 16 | +}; |
17 | 17 |
|
18 | | - return { |
19 | | - name: data.name, |
20 | | - id: data.id, |
21 | | - height: data.height, |
22 | | - weight: data.weight, |
23 | | - types: data.types.map((type: any) => type.type.name), |
24 | | - abilities: data.abilities.map((ability: any) => ability.ability.name) |
25 | | - } as Pokemon; |
| 18 | +const getPokemons = async (ids: number[]) => { |
| 19 | + await new Promise((resolve) => setTimeout(resolve, 1000)); |
| 20 | + return Promise.all( |
| 21 | + ids.map((id) => fetch(`https://pokeapi.co/api/v2/pokemon/${id}`).then((res) => res.json())) |
| 22 | + ); |
26 | 23 | }; |
27 | 24 |
|
28 | 25 | const Demo = () => { |
29 | | - const [pokemon, setPokemon] = useState<Pokemon>(); |
| 26 | + const [chain, toggleChain] = useToggle<keyof typeof CHAINS>([ |
| 27 | + 'bulbasaur', |
| 28 | + 'charmander', |
| 29 | + 'squirtle' |
| 30 | + ]); |
| 31 | + |
| 32 | + const [pokemons, setPokemons] = useState<Pokemon[]>([]); |
| 33 | + const [isLoading, setIsLoading] = useBoolean(true); |
30 | 34 |
|
31 | 35 | useAsyncEffect(async () => { |
32 | | - try { |
33 | | - const newPokemon = await getRandomPokemon(); |
34 | | - setPokemon(newPokemon); |
35 | | - } catch (err) { |
36 | | - console.error(err); |
37 | | - } |
38 | | - }, []); |
| 36 | + setIsLoading(true); |
| 37 | + const data = await getPokemons(CHAINS[chain]); |
| 38 | + setPokemons(data); |
| 39 | + setIsLoading(false); |
| 40 | + }, [chain]); |
39 | 41 |
|
40 | 42 | return ( |
41 | | - <> |
42 | | - <div className='flex flex-col gap-4'> |
43 | | - {pokemon && ( |
44 | | - <div> |
45 | | - <h3 className='mb-1 text-2xl font-bold capitalize'> |
46 | | - {pokemon.name} #{pokemon.id} |
47 | | - </h3> |
| 43 | + <section className='flex min-w-md flex-col gap-4'> |
| 44 | + <div data-slot='tabs'> |
| 45 | + <div className='mb-6' data-slot='tabs-list'> |
| 46 | + {Object.keys(CHAINS).map((name) => ( |
| 47 | + <button |
| 48 | + key={name} |
| 49 | + data-state={cn(chain === name && 'active')} |
| 50 | + data-variant='tabs-trigger' |
| 51 | + type='button' |
| 52 | + onClick={() => toggleChain(name as keyof typeof CHAINS)} |
| 53 | + > |
| 54 | + {name} |
| 55 | + </button> |
| 56 | + ))} |
| 57 | + </div> |
48 | 58 |
|
49 | | - <div className='flex gap-2 text-sm'> |
50 | | - {pokemon.types.map((type) => ( |
51 | | - <code key={type}>{type}</code> |
52 | | - ))} |
| 59 | + <div data-slot='tabs-content'> |
| 60 | + {isLoading && ( |
| 61 | + <div className='flex h-36 flex-col items-center justify-center gap-2'> |
| 62 | + <Loader2Icon className='size-6 animate-spin' /> |
| 63 | + <p> |
| 64 | + Loading <code>evolution</code> chain |
| 65 | + </p> |
53 | 66 | </div> |
| 67 | + )} |
| 68 | + |
| 69 | + {!isLoading && ( |
| 70 | + <div className='flex h-36 items-center justify-between gap-2'> |
| 71 | + {pokemons.map((pokemon, index) => ( |
| 72 | + <Fragment key={pokemon.id}> |
| 73 | + <div className='flex flex-col items-center gap-2'> |
| 74 | + <div className='flex size-28 items-center justify-center'> |
| 75 | + <img |
| 76 | + alt={pokemon.name} |
| 77 | + className='h-28' |
| 78 | + src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/${pokemon.id}.png`} |
| 79 | + /> |
| 80 | + </div> |
54 | 81 |
|
55 | | - <div className='mt-4 flex flex-col gap-1'> |
56 | | - <div> |
57 | | - height: <code>{pokemon.height / 10} m</code> |
58 | | - </div> |
59 | | - <div> |
60 | | - weight: <code>{pokemon.weight / 10} kg</code> |
61 | | - </div> |
| 82 | + <p className='text-sm capitalize'>{pokemon.name}</p> |
| 83 | + </div> |
| 84 | + |
| 85 | + {index < pokemons.length - 1 && <div className='text-xl'>{'>'}</div>} |
| 86 | + </Fragment> |
| 87 | + ))} |
62 | 88 | </div> |
63 | | - </div> |
64 | | - )} |
| 89 | + )} |
| 90 | + </div> |
65 | 91 | </div> |
66 | | - </> |
| 92 | + </section> |
67 | 93 | ); |
68 | 94 | }; |
69 | 95 |
|
|
0 commit comments