Skip to content

Commit 191b2a8

Browse files
feat: implement portals pokemon style
1 parent e75e3b0 commit 191b2a8

File tree

6 files changed

+331
-163
lines changed

6 files changed

+331
-163
lines changed

src/course/02-lessons/02-Silver/Controlled/exercise/exercise.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ export const Exercise = () => {
168168
'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/4.png'
169169
};
170170

171+
// 💣 You can get rid of this eslint error comment when finished.
172+
// @ts-ignore
171173
// eslint-disable-next-line @typescript-eslint/no-unused-vars
172174
const evolution = {
173175
name: 'Charmeleon',
Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
11
import classNames from 'classnames';
22
import { useEffect, useRef } from 'react';
3-
// 👨🏻💻 1B - import { createPortal } from 'react-dom';
3+
// 👨🏻💻 1B - import { createPortal } from 'react-dom';
44
import FocusLock from 'react-focus-lock';
55
import { Button } from '@shared/components/Button/Button.component';
66

7-
interface IModal {
7+
interface IBattleOverlay {
88
isVisible: boolean;
99
onClose: () => void;
1010
id: string;
11-
title: string;
12-
children: React.ReactNode | React.ReactNode[];
11+
wildPokemon: {
12+
name: string;
13+
level: number;
14+
sprite: string;
15+
};
16+
onBattleAction: (action: 'attack' | 'run') => void;
17+
battleResult: 'won' | 'fled' | null;
1318
}
1419

15-
export const Modal = ({
20+
export const BattleOverlay = ({
1621
isVisible,
1722
onClose,
1823
id,
19-
title,
20-
children
21-
}: IModal) => {
24+
wildPokemon,
25+
onBattleAction,
26+
battleResult
27+
}: IBattleOverlay) => {
2228
const modal = useRef<HTMLDivElement>(null);
2329

2430
useEffect(() => {
@@ -32,14 +38,14 @@ export const Modal = ({
3238
event.preventDefault();
3339
};
3440

35-
// 👨🏻💻 1C - call createPortal(modalCode, document.body);
36-
// 🧪 Test the storybook and look at how you can all of a sudden click the pay now button
41+
// 👨🏻💻 1C - call createPortal(battleOverlayCode, document.body);
42+
// 🧪 Test the storybook and look at how you can all of a sudden click the battle buttons
3743
// This isn't saying the solution to override z-index is to use portal but more of the sense that if you need something
3844
// put at the root of the DOM but do not wish to implement something extremely complex or app level then portal is handy for this.
3945
return (
4046
<div
4147
className={classNames(
42-
'bg-modal-bg fixed top-0 left-0 right-0 bottom-0 justify-center items-center z-20',
48+
'bg-black/80 fixed top-0 left-0 right-0 bottom-0 justify-center items-center z-20',
4349
{ flex: isVisible, hidden: !isVisible }
4450
)}
4551
role="button"
@@ -49,23 +55,75 @@ export const Modal = ({
4955
<div
5056
role="dialog"
5157
id={id}
52-
aria-labelledby={`modal_title_${id}`}
53-
aria-describedby={`modal_body_${id}`}
58+
aria-labelledby={`battle_title_${id}`}
59+
aria-describedby={`battle_body_${id}`}
5460
aria-modal="true"
5561
hidden={!isVisible}
5662
tabIndex={0}
5763
ref={modal}
58-
className="bg-white rounded-2xl p-5 relative z-20"
64+
className="bg-gradient-to-b from-blue-400 to-green-400 rounded-2xl p-6 relative z-20 max-w-lg mx-4 border-4 border-yellow-400"
5965
onClick={onModalPress}
6066
>
6167
<FocusLock>
6268
<div>
63-
<h2 id={`modal_title_${id}`}>{title}</h2>
64-
<Button onClick={onClose}>Close Dialog</Button>
69+
<h2 id={`battle_title_${id}`} className="text-2xl font-bold text-center mb-4 text-white">
70+
⚔️ Wild Pokemon Battle!
71+
</h2>
72+
73+
{!battleResult ? (
74+
<div className="text-center">
75+
<div className="bg-white/90 rounded-lg p-4 mb-6">
76+
<img
77+
src={wildPokemon.sprite}
78+
alt={wildPokemon.name}
79+
className="w-24 h-24 mx-auto mb-2"
80+
/>
81+
<h3 className="text-xl font-bold text-gray-800">
82+
A wild {wildPokemon.name} appeared!
83+
</h3>
84+
<p className="text-gray-600">Level {wildPokemon.level}</p>
85+
</div>
86+
87+
<div className="flex gap-4 justify-center">
88+
<Button
89+
onClick={() => onBattleAction('attack')}
90+
className="bg-red-600 hover:bg-red-700"
91+
>
92+
⚔️ Attack
93+
</Button>
94+
<Button
95+
onClick={() => onBattleAction('run')}
96+
className="bg-gray-600 hover:bg-gray-700"
97+
>
98+
🏃 Run Away
99+
</Button>
100+
</div>
101+
</div>
102+
) : (
103+
<div className="text-center">
104+
<div className="bg-white/90 rounded-lg p-6">
105+
{battleResult === 'won' ? (
106+
<>
107+
<div className="text-6xl mb-4">🎉</div>
108+
<h3 className="text-xl font-bold text-green-600 mb-2">Victory!</h3>
109+
<p className="text-gray-600">You defeated the wild {wildPokemon.name}!</p>
110+
</>
111+
) : (
112+
<>
113+
<div className="text-6xl mb-4">💨</div>
114+
<h3 className="text-xl font-bold text-blue-600 mb-2">Escaped!</h3>
115+
<p className="text-gray-600">The wild {wildPokemon.name} got away!</p>
116+
</>
117+
)}
118+
</div>
119+
</div>
120+
)}
121+
</div>
122+
<div id={`battle_body_${id}`} className="mt-4 text-center text-sm text-white/80">
123+
{!battleResult && `What will you do against the wild ${wildPokemon.name}?`}
65124
</div>
66-
<div id={`modal_body_${id}`}>{children}</div>
67125
</FocusLock>
68126
</div>
69127
</div>
70128
);
71-
};
129+
};
Lines changed: 82 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,104 @@
11
import { useState } from 'react';
2-
import { Modal } from './components/modal';
2+
import { BattleOverlay } from './components/modal';
33
import { Button } from '@shared/components/Button/Button.component';
44

5-
// 👨🏻💻 1A - have a look at the current implementation of the modal and then go to components/modal.tsx
5+
// 👨🏻💻 1A - have a look at the current implementation of the battle overlay and then go to components/modal.tsx
66

77
export const Exercise = () => {
8-
const [isVisible, setIsVisible] = useState(false);
9-
const [isComplete, setIsComplete] = useState(false);
8+
const [isBattleActive, setIsBattleActive] = useState(false);
9+
const [battleResult, setBattleResult] = useState<'won' | 'fled' | null>(null);
1010

11-
const onClose = () => {
12-
setIsVisible(false);
11+
const onCloseBattle = () => {
12+
setIsBattleActive(false);
1313
};
1414

15-
const onOpen = () => {
16-
setIsVisible(true);
15+
const onStartBattle = () => {
16+
setIsBattleActive(true);
17+
setBattleResult(null);
1718
};
1819

19-
const onCheckout = () => {
20-
setIsComplete(true);
20+
const onBattleAction = (action: 'attack' | 'run') => {
21+
if (action === 'attack') {
22+
setBattleResult('won');
23+
} else {
24+
setBattleResult('fled');
25+
}
26+
setTimeout(() => {
27+
setIsBattleActive(false);
28+
setBattleResult(null);
29+
}, 2000);
2130
};
2231

2332
return (
24-
// 🧪 We have z-index 10 on the section and then z-9998 on a div that's purposely there. Our Modal has a z-20 which means:
33+
// 🧪 We have z-index 10 on the section and then z-9998 on a div that's purposely there. Our BattleOverlay has a z-20 which means:
2534
// section z-10
26-
// modal z-20 (but this means z-20 within the z-10) think of it as a sub layer.
27-
// the bug is 9998 and a css hack for the pay now is 9999
28-
<section className="z-10 relative h-screen">
29-
<div className="z-[9998] absolute top-0 left-0 right-0 bottom-0" />
30-
{isComplete && (
31-
<>
32-
<h1 className="text-xl font-semibold">
33-
Payment Successful
34-
</h1>
35-
<p className="text-md mb-2">Well done you did it!</p>
36-
</>
37-
)}
35+
// battle overlay z-20 (but this means z-20 within the z-10) think of it as a sub layer.
36+
// the bug is 9998 and a css hack for the battle buttons is 9999
37+
<section className="z-10 relative h-screen bg-green-100 p-6">
38+
<div className="z-[9998] absolute top-0 left-0 right-0 bottom-0 bg-black/10" />
39+
40+
<div className="relative z-[9999]">
41+
<h1 className="text-2xl font-bold mb-4 text-green-800">🌿 Pokemon World</h1>
3842

39-
{!isComplete && (
40-
<>
41-
<h1 className="text-xl font-semibold">Payment Page</h1>
43+
<div className="grid grid-cols-3 gap-4 mb-6">
44+
<div className="bg-green-200 p-4 rounded-lg text-center">
45+
<div className="text-4xl mb-2">🌲</div>
46+
<p className="text-sm">Tall Grass</p>
47+
</div>
48+
<div className="bg-blue-200 p-4 rounded-lg text-center">
49+
<div className="text-4xl mb-2">🏠</div>
50+
<p className="text-sm">Pokemon Center</p>
51+
</div>
52+
<div className="bg-yellow-200 p-4 rounded-lg text-center">
53+
<div className="text-4xl mb-2">🏪</div>
54+
<p className="text-sm">Poke Mart</p>
55+
</div>
56+
</div>
4257

43-
<p className="text-md mb-2">
44-
Please see your selected options from the previous steps
45-
before continuing.
58+
<div className="bg-white p-6 rounded-lg border-2 border-green-300 mb-6">
59+
<h2 className="text-lg font-semibold mb-4">🎒 Trainer Actions</h2>
60+
<p className="text-md mb-4">
61+
You're walking through the tall grass. Wild Pokemon might appear!
4662
</p>
63+
64+
<div className="text-center">
65+
<Button
66+
onClick={onStartBattle}
67+
className="bg-red-500 hover:bg-red-600"
68+
disabled={isBattleActive}
69+
>
70+
🔍 Search for Pokemon
71+
</Button>
72+
</div>
73+
</div>
4774

48-
<section className="my-6">
49-
<h2 className="text-lg font-semibold mb-2">
50-
Delivery Details
51-
</h2>
52-
<address className="border border-grey-300 rounded-md p-3 mb-2 block">
53-
<p>12 john doe street, Manchester, M12 3RT</p>
54-
</address>
55-
</section>
75+
<div className="bg-gray-100 p-4 rounded-lg">
76+
<h3 className="font-semibold mb-2">🎮 Game Status</h3>
77+
<p className="text-sm text-gray-600">
78+
{isBattleActive ? 'Battle in progress...' : 'Exploring the world'}
79+
</p>
80+
{battleResult && (
81+
<p className="text-sm font-semibold mt-2">
82+
{battleResult === 'won' ? '🎉 Victory!' : '💨 Pokemon fled!'}
83+
</p>
84+
)}
85+
</div>
86+
</div>
5687

57-
<section className="z-[9999] relative">
58-
<h2 className="text-lg font-semibold mb-2">
59-
Make Payment
60-
</h2>
61-
<Button onClick={onOpen}>Pay now</Button>
62-
</section>
63-
</>
64-
)}
65-
{isVisible && !isComplete && (
66-
<Modal
67-
id="modal"
68-
onClose={onClose}
69-
isVisible={isVisible}
70-
title="Some fancy payment form..."
71-
>
72-
<Button onClick={onCheckout}>Pay now</Button>
73-
</Modal>
88+
{isBattleActive && (
89+
<BattleOverlay
90+
id="battle-overlay"
91+
onClose={onCloseBattle}
92+
isVisible={isBattleActive}
93+
wildPokemon={{
94+
name: 'Pidgey',
95+
level: 5,
96+
sprite: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/16.png'
97+
}}
98+
onBattleAction={onBattleAction}
99+
battleResult={battleResult}
100+
/>
74101
)}
75102
</section>
76103
);
77-
};
104+
};

0 commit comments

Comments
 (0)