Skip to content

Commit 8f1e580

Browse files
feat: implement pokemon style for poly components
1 parent 191b2a8 commit 8f1e580

File tree

6 files changed

+329
-188
lines changed

6 files changed

+329
-188
lines changed

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ const EvolutionModal = ({
4343
pokemon,
4444
evolution
4545
}: IEvolutionModal) => {
46-
// 2a 👨🏻💻 Create a useRef<HTMLDivElement> and bind the ref to the div on line 70
46+
// 2a 💻 Create a useRef<HTMLDivElement> and bind the ref to the div on line 70
4747

4848
useEffect(() => {
4949
// ✍🏻 When a modal is visible you want to navigate the focus from
5050
// the actioner (what caused the modal to open) to the content
5151
// ♿️ It helps the screenreader not get lost on the page
52-
// 2b - 👨🏻💻 Check if isVisible is true and the modal.current is defined before setting focus to the modal
52+
// 2b - 💻 Check if isVisible is true and the modal.current is defined before setting focus to the modal
5353
}, [isVisible]);
5454

5555
// 💣 You can get rid of this eslint error comment when finished.
@@ -74,7 +74,7 @@ const EvolutionModal = ({
7474
// 2c - 💄 Add an object as the second param with flex: isVisible and hidden !isVisible
7575
)}
7676
role="button"
77-
// 2d - 👨🏻💻 Pass the onClose event from the props to the onClick event.
77+
// 2d - 💻 Pass the onClose event from the props to the onClick event.
7878
tabIndex={0}
7979
>
8080
<div
@@ -131,11 +131,11 @@ const EvolutionModal = ({
131131
</div>
132132

133133
<div className="flex gap-3 justify-center">
134-
{/* 2g - 👨🏻💻 Add onClick={onConfirm} for evolution confirmation */}
134+
{/* 2g - 💻 Add onClick={onConfirm} for evolution confirmation */}
135135
<Button className="bg-green-600 hover:bg-green-700">
136136
✨ Evolve!
137137
</Button>
138-
{/* 2h - 👨🏻💻 Add onClick={onClose} going back to the pattern, we want outside to control the visibility of the modal */}
138+
{/* 2h - 💻 Add onClick={onClose} going back to the pattern, we want outside to control the visibility of the modal */}
139139
<Button className="bg-gray-600 hover:bg-gray-700">
140140
Cancel
141141
</Button>
@@ -153,13 +153,13 @@ const EvolutionModal = ({
153153
};
154154

155155
export const Exercise = () => {
156-
// 1a 👨🏻💻 Create a state hook variable with isEvolutionVisible and setIsEvolutionVisible
156+
// 1a 💻 Create a state hook variable with isEvolutionVisible and setIsEvolutionVisible
157157

158-
// 1b 👨🏻💻 Create an onClose event that sets isEvolutionVisible to false
158+
// 1b 💻 Create an onClose event that sets isEvolutionVisible to false
159159

160-
// 1c 👨🏻💻 Create an onConfirm event that handles evolution and closes modal
160+
// 1c 💻 Create an onConfirm event that handles evolution and closes modal
161161

162-
// 1d 👨🏻💻 Create an onCheckEvolution event that sets isEvolutionVisible to true
162+
// 1d 💻 Create an onCheckEvolution event that sets isEvolutionVisible to true
163163

164164
const pokemon = {
165165
name: 'Charmander',
@@ -197,7 +197,7 @@ export const Exercise = () => {
197197
</p>
198198
</div>
199199

200-
{/* 1e 👨🏻💻 Add the onClick={onCheckEvolution} event to the button
200+
{/* 1e 💻 Add the onClick={onCheckEvolution} event to the button
201201
✍🏻 This is an example of a Controlled component but in a Pokemon context.
202202
As a developer, we are providing the button with those props for the button
203203
to behave how we want it to behave, otherwise, it does nothing. */}
@@ -207,7 +207,7 @@ export const Exercise = () => {
207207
</Button>
208208
</div>
209209

210-
{/* 1f 👨🏻💻 Check if isEvolutionVisible (💅 Conditional Render Pattern) to render the EvolutionModal */}
210+
{/* 1f 💻 Check if isEvolutionVisible (💅 Conditional Render Pattern) to render the EvolutionModal */}
211211
{/* Map the isVisible, onClose, onConfirm props to the EvolutionModal. The other props can be whatever you want */}
212212
</div>
213213
);
Lines changed: 98 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,146 @@
11
import { HTMLAttributes } from 'react';
22

33
/**
4-
* Exercise: Refactor the Heading component to correctly use the polymorphic pattern.
4+
* Exercise: Refactor the StatusEffect component to correctly use the polymorphic pattern.
55
*
66
* 🤔 Observations of this file
7-
* In the current component you can see that the as prop is a string so if a developer in a team uses the wrong element they would just get the h2 element.
8-
* Font sizes are clearly defined to the element so there is no flexibility in sizes which can lead to developers pleasing designers but... breaking accessibility or vice versa where designs do not look the same as what was provided.
7+
* In the current component you can see that the as prop is a string so if a developer in a team uses the wrong element they would just get the span element.
8+
* Status styles are clearly defined to the element so there is no flexibility in status effects which can lead to developers pleasing designers but... breaking accessibility or vice versa where designs do not look the same as what was provided.
99
*
1010
* We need to tackle this in stages...
1111
*
1212
* Stage one - Refactoring the component to use Polymorphic style so we remove the switch statement.
13-
* Stage two - decouple the font size to the element
14-
* Stage three - allow for developers to have a size medium breakpoint for special designs.
13+
* Stage two - decouple the status effect to the element
14+
* Stage three - allow for developers to have a severity level for special status effects.
1515
*
1616
*/
1717

18-
// 🧑🏻💻 1.a - Create a type called allowedHTMLElements
18+
// 🧑🏻💻 1.a - Create a type called AllowedElements for 'span' | 'div' | 'button' | 'li'
1919

20-
// 🧑🏻💻 2.a - Create a type called FontSizes and it's a union of 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'
20+
// 🧑🏻💻 2.a - Create a type called StatusTypes and it's a union of 'normal' | 'burned' | 'poisoned' | 'paralyzed' | 'frozen' | 'asleep'
2121

22-
interface IHeading extends HTMLAttributes<HTMLHeadingElement> {
23-
// 🧑🏻💻 1.b - Update the type of string to be the type you defined as part of 1.a
22+
interface IStatusEffect extends HTMLAttributes<HTMLElement> {
23+
// 🧑🏻💻 1.b - Update the type of string to be the type you defined as part of 1.a
2424
as?: string;
25-
// 🧑🏻💻 2.b - Create a new prop called size?: FontSizes;
26-
// 🧑🏻💻 3.a - Create a new prop called sizeMd?: FontSizes;
25+
// 🧑🏻💻 2.b - Create a new prop called status?: StatusTypes;
26+
// 🧑🏻💻 3.a - Create a new prop called severity?: 'mild' | 'severe';
2727
children?: React.ReactNode | React.ReactNode[];
2828
}
2929

30-
const Heading = ({
31-
// 🧑🏻💻 1.c - add : Element = 'h2' what this will do is redefine the prop to be a capital variable which can be used as a React Component.
32-
as = 'h2',
33-
// 🧑🏻💻 2.c - Create a new prop called size
34-
// 🧑🏻💻 3.b - Create a new prop called sizeMd
30+
const StatusEffect = ({
31+
// 🧑🏻💻 1.c - add : Element = 'span' what this will do is redefine the prop to be a capital variable which can be used as a React Component.
32+
as = 'span',
33+
// 🧑🏻💻 2.c - Create a new prop called status
34+
// 🧑🏻💻 3.b - Create a new prop called severity
3535
children,
3636
...rest
37-
}: IHeading) => {
38-
// 🧑🏻💻 1.d - Create a variable called elementFontSize which uses useMemo to return a string from an object key mapping. For example: useMemo(() => ({ h1: 'text-3xl' }[Element]), [Element]);
39-
// 🧑🏻💻 2.d - In the useMemo add the size as a dependency and then check if size exists. If it does, return `text-${size}` if not, return what was there previously. Move onto 3.a.
37+
}: IStatusEffect) => {
38+
// 🧑🏻💻 1.d - Create a variable called statusClass which uses useMemo to return a string from an object key mapping. For example: useMemo(() => ({ span: 'text-gray-600' }[Element]), [Element]);
39+
// 🧑🏻💻 2.d - In the useMemo add the status as a dependency and then check if status exists. If it does, return status-specific classes if not, return what was there previously. Move onto 3.a.
4040

41-
// 🧑🏻💻 3.c - create another useMemo for largeFontSizes where we find an array of md:text-(sm-3xl) and we need to "find" which one in that array "includes" sizeMd props value.
41+
// 🧑🏻💻 3.c - create another useMemo for severityClass where we check if severity exists and return severity-specific classes.
4242

4343
// 🧪 3.d Head down to the storybook Exercise Component and add a few more variants in.
4444

45-
// 🧑🏻💻 1.e return the Element with the className={classNames('mb-3 font-semibold', elementFontSize)} don't forget the ...rest
45+
// 🧑🏻💻 1.e return the Element with the className={classNames('px-2 py-1 rounded text-sm', statusClass)} don't forget the ...rest
4646
// 💣 1.f remove the old code below. Move onto step 2.a.
4747
if (as)
4848
switch (as) {
49-
case 'h1':
49+
case 'span':
5050
return (
51-
<h1 {...rest} className="text-3xl mb-3 font-semibold">
51+
<span
52+
{...rest}
53+
className="px-2 py-1 rounded text-sm bg-gray-200 text-gray-800"
54+
>
5255
{children}
53-
</h1>
56+
</span>
5457
);
55-
case 'h3':
58+
case 'div':
5659
return (
57-
<h3 {...rest} className="text-xl mb-3 font-semibold">
60+
<div
61+
{...rest}
62+
className="p-3 rounded bg-red-200 text-red-800 border border-red-300"
63+
>
5864
{children}
59-
</h3>
65+
</div>
6066
);
61-
case 'h4':
67+
case 'button':
6268
return (
63-
<h4 {...rest} className="text-lg mb-3 font-semibold">
69+
<button
70+
{...rest}
71+
className="px-3 py-2 rounded bg-yellow-200 text-yellow-800 border border-yellow-300 hover:bg-yellow-300"
72+
>
6473
{children}
65-
</h4>
74+
</button>
6675
);
67-
case 'h5':
76+
case 'li':
6877
return (
69-
<h5 {...rest} className="text-md mb-3 font-semibold">
78+
<li
79+
{...rest}
80+
className="p-2 rounded bg-purple-200 text-purple-800 border border-purple-300 list-none"
81+
>
7082
{children}
71-
</h5>
72-
);
73-
case 'h6':
74-
return (
75-
<h6 {...rest} className="text-sm mb-3 font-semibold">
76-
{children}
77-
</h6>
83+
</li>
7884
);
7985
default:
8086
return (
81-
<h2 {...rest} className="text-2xl mb-3 font-semibold">
87+
<span
88+
{...rest}
89+
className="px-2 py-1 rounded text-sm bg-gray-200 text-gray-800"
90+
>
8291
{children}
83-
</h2>
92+
</span>
8493
);
8594
}
8695
};
8796

8897
export const Exercise = () => (
89-
<article>
90-
<Heading as="h1">Heading One</Heading>
91-
<Heading as="h2">Heading Two</Heading>
92-
<Heading as="h3">Heading Three</Heading>
93-
<Heading as="h4">Heading Four</Heading>
94-
<Heading as="h5">Heading Five</Heading>
95-
<Heading as="h6">Heading Six</Heading>
96-
{/* 3.e 👨🏻‍💻 Implement a heading as h2 and size sm */}
97-
98-
{/* 3.e 👨🏻‍💻 Implement a heading as h2 and size sm and sizeMd is 3xl */}
99-
100-
{/* 3.e 👨🏻‍💻 Implement a heading as h2 and sizeMd is 3xl */}
101-
</article>
98+
<div className="p-6 bg-blue-50 rounded-lg border-2 border-blue-200">
99+
<h2 className="text-2xl font-bold mb-4 text-blue-800">
100+
🎮 Pokemon Status Effects
101+
</h2>
102+
103+
<div className="space-y-4">
104+
<div>
105+
<h3 className="font-semibold mb-2">Battle Status (Spans)</h3>
106+
<div className="flex gap-2 flex-wrap">
107+
<StatusEffect as="span">Normal</StatusEffect>
108+
<StatusEffect as="span">Burned</StatusEffect>
109+
<StatusEffect as="span">Poisoned</StatusEffect>
110+
<StatusEffect as="span">Paralyzed</StatusEffect>
111+
</div>
112+
</div>
113+
114+
<div>
115+
<h3 className="font-semibold mb-2">Status Alerts (Divs)</h3>
116+
<StatusEffect as="div">
117+
Your Pokemon is badly poisoned!
118+
</StatusEffect>
119+
</div>
120+
121+
<div>
122+
<h3 className="font-semibold mb-2">Heal Actions (Buttons)</h3>
123+
<div className="flex gap-2">
124+
<StatusEffect as="button">Heal Burn</StatusEffect>
125+
<StatusEffect as="button">Cure Paralysis</StatusEffect>
126+
</div>
127+
</div>
128+
129+
<div>
130+
<h3 className="font-semibold mb-2">
131+
Status List (List Items)
132+
</h3>
133+
<ul className="space-y-1">
134+
<StatusEffect as="li">Frozen - Cannot move</StatusEffect>
135+
<StatusEffect as="li">Asleep - Skips turn</StatusEffect>
136+
</ul>
137+
</div>
138+
</div>
139+
140+
{/* 3.e Implement a status effect as span with status burned */}
141+
142+
{/* 3.e Implement a status effect as div with status poisoned and severity severe */}
143+
144+
{/* 3.e Implement a status effect as button with severity mild */}
145+
</div>
102146
);

0 commit comments

Comments
 (0)