@@ -7,36 +7,49 @@ import { useEffect, useRef, useState } from 'react';
77import FocusLock from 'react-focus-lock' ;
88import { Button } from '@shared/components/Button/Button.component' ;
99
10- interface IModal {
10+ interface IEvolutionModal {
1111 isVisible : boolean ;
1212 onClose : ( ) => void ;
13+ onConfirm : ( ) => void ;
1314 id : string ;
14- title : string ;
15- children : React . ReactNode | React . ReactNode [ ] ;
15+ pokemon : {
16+ name : string ;
17+ level : number ;
18+ currentSprite : string ;
19+ } ;
20+ evolution : {
21+ name : string ;
22+ sprite : string ;
23+ requirement : string ;
24+ } ;
1625}
1726
1827// For the full guide to making an accessible modal you can follow below to get every instance
1928// โฟ๏ธ WCAG Modal Resource: https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/examples/alertdialog/
2029// ๐ฃ You can get rid of this eslint error comment when finished.
2130// @ts -ignore
2231// eslint-disable-next-line @typescript-eslint/no-unused-vars
23- const Modal = ( {
32+ const EvolutionModal = ( {
2433 isVisible,
2534 // ๐ฃ You can get rid of this eslint error comment when finished.
2635 // @ts -ignore
2736 // eslint-disable-next-line @typescript-eslint/no-unused-vars
2837 onClose,
38+ // ๐ฃ You can get rid of this eslint error comment when finished.
39+ // @ts -ignore
40+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
41+ onConfirm,
2942 id,
30- title ,
31- children
32- } : IModal ) => {
33- // 2a ๐จ๐ปโ ๐ป Create a useRef<HTMLDivElement> and bind the ref to the div on line 58
43+ pokemon ,
44+ evolution
45+ } : IEvolutionModal ) => {
46+ // 2a ๐จ๐ป๐ป Create a useRef<HTMLDivElement> and bind the ref to the div on line 70
3447
3548 useEffect ( ( ) => {
3649 // โ๐ป When a modal is visible you want to navigate the focus from
3750 // the actioner (what caused the modal to open) to the content
3851 // โฟ๏ธ It helps the screenreader not get lost on the page
39- // 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
4053 } , [ isVisible ] ) ;
4154
4255 // ๐ฃ You can get rid of this eslint error comment when finished.
@@ -61,56 +74,139 @@ const Modal = ({
6174 // 2c - ๐ Add an object as the second param with flex: isVisible and hidden !isVisible
6275 ) }
6376 role = "button"
64- // 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.
6578 tabIndex = { 0 }
6679 >
6780 < div
6881 id = { id }
6982 // 2e - Add the following HTML attributes to the div:
7083 // * โฟ๏ธ role="dialog" - this changes how the screenreader understands the html element
71- // * โฟ๏ธ aria-labelledby={`modal_title_ ${id}`} - so when focus lands the title is read out
72- // * โฟ๏ธ aria-describedby={`modal_body_ ${id}`} - so when focus lands the content is read out
84+ // * โฟ๏ธ aria-labelledby={`evolution_title_ ${id}`} - so when focus lands the title is read out
85+ // * โฟ๏ธ aria-describedby={`evolution_body_ ${id}`} - so when focus lands the content is read out
7386 // ๐ค โฌ๏ธ It's always good to make these unique with an id prop so you have unique ids on the page
7487 // * โฟ๏ธ aria-modal="true" - indicates whether an element is modal when displayed.
7588 // * โฟ๏ธ hidden={!isVisible} - we do not want the screenreader to pick this up when it's hidden
7689 // * โฟ๏ธ tabIndex={0} - allows the browser to focus on it via a keyboard
7790 // * onClick={onModalPress} - description in onModalPress.
78- className = "bg-white rounded-2xl p-5 relative z-20"
91+ className = "bg-white rounded-2xl p-6 relative z-20 max-w-md mx-4 "
7992 >
8093 { /* โ๐ป SUPER important for meeting the WCAG quidelines is that you need focus, but locked within this div */ }
8194 { /* When focus is landed in this box with a keyboard you can no longer get out so make sure you have a close button */ }
82- { /* โฟ๏ธ Another requirement is to return focus to the actioner, but FocusLock does that for us when this component unmounts! ๐ฆธ๐ปโ โ๏ธ */ }
83- < FocusLock >
95+ { /* โฟ๏ธ Another requirement is to return focus to the actioner, but FocusLock does that for us when this component unmounts! ๐ฆธ๐ปโ๏ธ */ }
96+ < FocusLock returnFocus = { true } >
8497 < div >
85- { /* 2f - ๐จ๐ปโ๐ปโฟ๏ธ Add id={`modal_title_${id}`} - this creates the relationship between the title and modal */ }
86- < h2 > { title } </ h2 >
87- { /* 2g - ๐จ๐ปโ๐ป Add onClick={onClose} going back to the pattern, we want outside to control the visibility of the modal */ }
88- < Button > Close Dialog</ Button >
98+ { /* 2f - ๐จ๐ป๐ปโฟ๏ธ Add id={`evolution_title_${id}`} - this creates the relationship between the title and modal */ }
99+ < h2 className = "text-2xl font-bold text-center mb-4 text-blue-800" >
100+ โจ Evolution Time! โจ
101+ </ h2 >
102+
103+ < div className = "text-center mb-6" >
104+ < div className = "flex items-center justify-center gap-4 mb-4" >
105+ < div className = "text-center" >
106+ < img
107+ src = { pokemon . currentSprite }
108+ alt = { pokemon . name }
109+ className = "w-20 h-20 mx-auto"
110+ />
111+ < p className = "font-bold" > { pokemon . name } </ p >
112+ < p className = "text-sm text-gray-600" >
113+ Level { pokemon . level }
114+ </ p >
115+ </ div >
116+
117+ < div className = "text-4xl" > โ</ div >
118+
119+ < div className = "text-center" >
120+ < img
121+ src = { evolution . sprite }
122+ alt = { evolution . name }
123+ className = "w-20 h-20 mx-auto"
124+ />
125+ < p className = "font-bold" > { evolution . name } </ p >
126+ < p className = "text-xs text-blue-600" >
127+ { evolution . requirement }
128+ </ p >
129+ </ div >
130+ </ div >
131+ </ div >
132+
133+ < div className = "flex gap-3 justify-center" >
134+ { /* 2g - ๐จ๐ป๐ป Add onClick={onConfirm} for evolution confirmation */ }
135+ < Button className = "bg-green-600 hover:bg-green-700" >
136+ โจ Evolve!
137+ </ Button >
138+ { /* 2h - ๐จ๐ป๐ป Add onClick={onClose} going back to the pattern, we want outside to control the visibility of the modal */ }
139+ < Button className = "bg-gray-600 hover:bg-gray-700" >
140+ Cancel
141+ </ Button >
142+ </ div >
143+ </ div >
144+ { /* 2i - ๐จ๐ป๐ปโฟ๏ธ Add id={`evolution_body_${id}`} - this creates the relationship between the content and modal */ }
145+ < div className = "mt-4 text-center text-sm text-gray-600" >
146+ Your { pokemon . name } is ready to evolve into{ ' ' }
147+ { evolution . name } !
89148 </ div >
90- { /* 2h - ๐จ๐ปโ๐ปโฟ๏ธ Add id={`modal_body_${id}`} - this creates the relationship between the title and modal */ }
91- < div > { children } </ div >
92149 </ FocusLock >
93150 </ div >
94151 </ div >
95152 ) ;
96153} ;
97154
98155export const Exercise = ( ) => {
99- // 1a ๐จ๐ปโ๐ป Create a state hook variable with isVisible and setIsVisible
156+ // 1a ๐จ๐ป๐ป Create a state hook variable with isEvolutionVisible and setIsEvolutionVisible
157+
158+ // 1b ๐จ๐ป๐ป Create an onClose event that sets isEvolutionVisible to false
100159
101- // 1b ๐จ๐ปโ ๐ป Create an onClose event that sets isVisible to false
160+ // 1c ๐จ๐ป๐ป Create an onConfirm event that handles evolution and closes modal
102161
103- // 1c ๐จ๐ปโ๐ป Create an onOpen event that sets isVisible to true
162+ // 1d ๐จ๐ป๐ป Create an onCheckEvolution event that sets isEvolutionVisible to true
163+
164+ const pokemon = {
165+ name : 'Charmander' ,
166+ level : 16 ,
167+ currentSprite :
168+ 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/4.png'
169+ } ;
170+
171+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
172+ const evolution = {
173+ name : 'Charmeleon' ,
174+ sprite :
175+ 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/5.png' ,
176+ requirement : 'Level 16 reached!'
177+ } ;
104178
105179 return (
106- < >
107- { /* 1d ๐จ๐ปโ๐ป Add the onClick={onOpen} event to the button
108- โ๐ป This is an example of a Controlled component but in a HTML context.
180+ < div className = "p-6 bg-blue-50 rounded-lg border-2 border-blue-200" >
181+ < h2 className = "text-2xl font-bold mb-4 text-blue-800" >
182+ ๐ฎ Pokemon Evolution System
183+ </ h2 >
184+
185+ < div className = "text-center mb-6" >
186+ < img
187+ src = { pokemon . currentSprite }
188+ alt = { pokemon . name }
189+ className = "w-32 h-32 mx-auto mb-4"
190+ />
191+ < h3 className = "text-xl font-bold" > { pokemon . name } </ h3 >
192+ < p className = "text-gray-600" > Level { pokemon . level } </ p >
193+ < p className = "text-green-600 font-semibold mt-2" >
194+ Ready to evolve! ๐
195+ </ p >
196+ </ div >
197+
198+ { /* 1e ๐จ๐ป๐ป Add the onClick={onCheckEvolution} event to the button
199+ โ๐ป This is an example of a Controlled component but in a Pokemon context.
109200 As a developer, we are providing the button with those props for the button
110201 to behave how we want it to behave, otherwise, it does nothing. */ }
111- < button type = "button" > Open Modal</ button >
112- { /* 1e ๐จ๐ปโ๐ป Check if isVisible (๐
Conditional Render Pattern) to render the Modal */ }
113- { /* Map the isVisible and onClose props to the Modal. The other props can be whatever you want */ }
114- </ >
202+ < div className = "text-center" >
203+ < Button className = "bg-yellow-500 hover:bg-yellow-600" >
204+ โก Check Evolution
205+ </ Button >
206+ </ div >
207+
208+ { /* 1f ๐จ๐ป๐ป Check if isEvolutionVisible (๐
Conditional Render Pattern) to render the EvolutionModal */ }
209+ { /* Map the isVisible, onClose, onConfirm props to the EvolutionModal. The other props can be whatever you want */ }
210+ </ div >
115211 ) ;
116212} ;
0 commit comments