Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/* Keep desktop layout as established */
* {
text-align: center;
-webkit-user-select: none; /* Safari */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Standard syntax */
}

body {
overflow: hidden;
}

img {
pointer-events: none;
-webkit-user-drag: none;
user-drag: none;
}

#root {
display: flex;
flex-direction: row;
align-items: stretch;
justify-self: center;
justify-content: center;
height: 100vh;
width: 90%;
box-sizing: border-box;
}

#root > div {
border: 1px solid #ccc;
padding: 10px;
box-sizing: border-box;
display: flex;
min-height: 0;
}

#root > #pokemonList {
order: 1;
flex: 1;
}

#root > #pokemonCard {
order: 2;
flex: 2;
flex-direction: column;
}

#root > #favoritePokemon {
order: 3;
flex: 1;
}

.toggle-yellow-btn {
margin-bottom: 8px;
align-self: flex-end;
cursor: pointer;
padding: 4px 8px;
}

input {
width: 25%;
}

.row {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin-bottom: 10px;
}

.nav {
display: flex;
gap: 6px;
justify-content: center;
}

#sorting {
}

#favorite {
}

@media (max-width: 1000px) {
body {
-ms-overflow-style: none; /* Internet Explorer 10+ */
scrollbar-width: none; /* Firefox, Safari 18.2+, Chromium 121+ */
overflow: scroll;
}

#root {
flex-direction: column;
width: 100%;
height: auto;
align-items: stretch;
}

#root > div {
flex: 0 0 auto !important;
width: 100% !important;
height: auto !important;
min-height: 0;
}

/* Explicit stack order: Card -> List -> Favorite */
#pokemonCard {
order: 1;
}

#pokemonList {
order: 2;
height: auto !important;
}

#favoritePokemon {
order: 3;
}

/* Disable inner scroll constraints and let blocks expand full vertical height */
.pokemon-list,
.favorite-list {
max-height: none !important;
overflow-y: visible !important;
flex: 0 0 auto !important;
}
}
97 changes: 59 additions & 38 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,69 +1,90 @@
import { useState } from "react";
import "./App.css";
import { useQuery } from "@tanstack/react-query";
import { getPokemonByID } from "./api/pokemon";
import { PokemonCard } from "./components/PokemonCard/PokemonCard";
import { Favorite } from "./components/Favorite/Favorite";
import { useFavorites } from "./controllers/useFavorites";
import { PokemonList } from "./components/PokemonList/PokemonList";
import { FavoritePokemon } from "./components/FavoritePokemon/FavoritePokemon";

export default function App() {
// Start at a default ID
const [pokemonId, setPokemonId] = useState(1);
const { isFavorite, toggleFavorite } = useFavorites();
const [currentId, setCurrentId] = useState(1);
const { favoritePokemons, isFavorite, toggleFavorite, isLoadingFavorites } =
useFavorites();

const query = useQuery({
queryKey: ["pokemon", pokemonId],
queryFn: () => getPokemonByID(pokemonId),
queryKey: ["pokemon", currentId],
queryFn: () => getPokemonByID(currentId),
});

const handlePrevious = () => {
setPokemonId((prev) => Math.max(1, prev - 1));
setCurrentId((prev) => Math.max(1, prev - 1));
};

const handleNext = () => {
setPokemonId((prev) => prev + 1);
setCurrentId((prev) => prev + 1);
};

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = parseInt(e.target.value, 10);
// Ensure the user cannot enter negative numbers or invalid text
if (!isNaN(value) && value > 0) {
setPokemonId(value);
setCurrentId(value);
}
};

return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "16px",
}}
>
<div style={{ display: "flex", gap: "10px", alignItems: "center" }}>
<button onClick={handlePrevious} disabled={pokemonId <= 1}>
&larr; Prev
</button>
<div id="root">
<div id="pokemonCard">
<div>
<div className="row">
<div id="sort">sorting</div>
<div className="nav">
<button onClick={handlePrevious} disabled={currentId <= 1}>
&larr; Prev
</button>

<input
type="number"
value={pokemonId}
onChange={handleInputChange}
min="1"
style={{ width: "70px", textAlign: "center", padding: "4px" }}
aria-label="Pokemon ID"
/>
<input
type="number"
value={currentId}
onChange={handleInputChange}
min="1"
aria-label="Pokemon ID"
/>

<button onClick={handleNext}>Next &rarr;</button>
</div>
<button onClick={handleNext}>Next &rarr;</button>
</div>
<div id="favorite">
<Favorite
isFavorite={isFavorite(currentId)}
onToggle={() => toggleFavorite(currentId)}
/>
</div>
</div>
</div>

{query.isPending && <h1>Loading...</h1>}
{query.isError && <h1>Failed to load Pokemon</h1>}
{query.data && <PokemonCard pokemon={query.data} />}
<Favorite
isFavorite={isFavorite(pokemonId)}
onToggle={() => toggleFavorite(pokemonId)}
/>
{query.isPending && <h1>Loading...</h1>}
{query.isError && <h1>Failed to load Pokemon</h1>}
{query.data && <PokemonCard current={query.data} />}
</div>
<div id="pokemonList">
{query.data && (
<PokemonList
current={query.data}
sizeOfList={11}
onSelectPokemon={setCurrentId}
/>
)}
</div>
<div id="favoritePokemon">
{!isLoadingFavorites && (
<FavoritePokemon
pokemonList={favoritePokemons}
currentId={currentId}
onSelectPokemon={setCurrentId}
/>
)}
</div>
</div>
);
}
66 changes: 48 additions & 18 deletions src/components/FavoritePokemon/FavoritePokemon.css
Original file line number Diff line number Diff line change
@@ -1,46 +1,76 @@
.favorite-container {
width: 100%;
max-width: 400px;
margin: 0 auto;
margin: 0;
overflow: hidden;
}

.favorite-list {
list-style-type: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 12px;
gap: 8px;
max-height: 95%;
box-sizing: border-box;
background: #f8f9fa;
padding: 12px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
overflow: scroll !important;
-ms-overflow-style: none; /* Internet Explorer 10+ */
scrollbar-width: none; /* Firefox, Safari 18.2+, Chromium 121+ */
}

.favorite-list .selected {
border-color: #007bff;
background: #e6f2ff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}

.favorite-item {
display: flex;
align-items: center;
background-color: #f8f9fa;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 8px 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
gap: 12px;
padding: 8px 12px;
background: #fff;
border: 1px solid #ddd;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
text-align: left;
font-family: sans-serif;
}

.favorite-item:hover {
background: #f0f0f0;
transform: translateY(-1px);
}

.favorite-sprite {
width: 64px;
height: 64px;
margin-right: 16px;
width: 40px;
height: 40px;
object-fit: contain;
margin-right: 0;
}

.favorite-info {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
flex: 1;
}

.dex-number {
font-size: 0.85rem;
color: #6c757d;
font-size: 12px;
color: #888;
font-weight: bold;
min-width: 35px;
}

.pokemon-name {
font-size: 1.1rem;
font-weight: bold;
.favorite-item .pokemon-name {
font-size: 14px;
font-weight: 500;
color: #333;
text-transform: capitalize;
font-family: sans-serif;
}
Loading