|
1 | 1 | import { HTMLAttributes } from 'react'; |
2 | 2 |
|
3 | 3 | /** |
4 | | - * Exercise: Refactor the Heading component to correctly use the polymorphic pattern. |
| 4 | + * Exercise: Refactor the StatusEffect component to correctly use the polymorphic pattern. |
5 | 5 | * |
6 | 6 | * 🤔 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. |
9 | 9 | * |
10 | 10 | * We need to tackle this in stages... |
11 | 11 | * |
12 | 12 | * 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. |
15 | 15 | * |
16 | 16 | */ |
17 | 17 |
|
18 | | -// 🧑🏻💻 1.a - Create a type called allowedHTMLElements |
| 18 | +// 🧑🏻💻 1.a - Create a type called AllowedElements for 'span' | 'div' | 'button' | 'li' |
19 | 19 |
|
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' |
21 | 21 |
|
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 |
24 | 24 | 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'; |
27 | 27 | children?: React.ReactNode | React.ReactNode[]; |
28 | 28 | } |
29 | 29 |
|
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 |
35 | 35 | children, |
36 | 36 | ...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. |
40 | 40 |
|
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. |
42 | 42 |
|
43 | 43 | // 🧪 3.d Head down to the storybook Exercise Component and add a few more variants in. |
44 | 44 |
|
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 |
46 | 46 | // 💣 1.f remove the old code below. Move onto step 2.a. |
47 | 47 | if (as) |
48 | 48 | switch (as) { |
49 | | - case 'h1': |
| 49 | + case 'span': |
50 | 50 | 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 | + > |
52 | 55 | {children} |
53 | | - </h1> |
| 56 | + </span> |
54 | 57 | ); |
55 | | - case 'h3': |
| 58 | + case 'div': |
56 | 59 | 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 | + > |
58 | 64 | {children} |
59 | | - </h3> |
| 65 | + </div> |
60 | 66 | ); |
61 | | - case 'h4': |
| 67 | + case 'button': |
62 | 68 | 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 | + > |
64 | 73 | {children} |
65 | | - </h4> |
| 74 | + </button> |
66 | 75 | ); |
67 | | - case 'h5': |
| 76 | + case 'li': |
68 | 77 | 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 | + > |
70 | 82 | {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> |
78 | 84 | ); |
79 | 85 | default: |
80 | 86 | 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 | + > |
82 | 91 | {children} |
83 | | - </h2> |
| 92 | + </span> |
84 | 93 | ); |
85 | 94 | } |
86 | 95 | }; |
87 | 96 |
|
88 | 97 | 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> |
102 | 146 | ); |
0 commit comments