From 526e09bbf0f2e16b6134ff2fdd50f13120b929b0 Mon Sep 17 00:00:00 2001 From: Kolade Chris <65571316+Ksound22@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:44:12 +0100 Subject: [PATCH] feat(curriculum): adding shape manager TS workshop (#63083) Co-authored-by: Dario <105294544+Dario-DC@users.noreply.github.com> Co-authored-by: Oliver Eyton-Williams --- client/i18n/locales/english/intro.json | 6 + .../68ff37f55fb2122df640cd78.md | 326 +++++++ .../68ff41972b5bb84f802ee6ca.md | 329 +++++++ .../68ff420e11f90153864d39e2.md | 339 +++++++ .../68ff42692df48e5ab1429b79.md | 376 ++++++++ .../68ff42cfa7004f6353fbb5f0.md | 339 +++++++ .../68ff495abf98da70d59e6f44.md | 326 +++++++ .../68ff4d778884847a60af06a5.md | 342 +++++++ .../6914547efa31c01853d24fb0.md | 344 +++++++ .../6914579719bb4621b7e3f0bd.md | 356 +++++++ .../6914582d975d2d277026a8c4.md | 352 +++++++ .../691458e6fdf1a6300a9f6105.md | 337 +++++++ .../69145951b67f9b34b66215d3.md | 342 +++++++ .../69145a9503ed163b83adbbde.md | 380 ++++++++ .../69145aefce21f93ed77b626c.md | 367 ++++++++ .../69145b634bdd4245697bf344.md | 365 ++++++++ .../69145bb447c0c14bf43bff1f.md | 415 ++++++++ .../69145da9693cb9620fffa595.md | 390 ++++++++ .../69145fa5538ad66a9cc848a8.md | 385 ++++++++ .../691475d76745ed889b1a39cb.md | 417 +++++++++ .../691476d751bc8592c46da5be.md | 410 ++++++++ .../691598c8e3b1671f01f70c7a.md | 406 ++++++++ .../69159948e52168265730f74f.md | 422 +++++++++ .../69159a1480af042cdc6bb7ff.md | 431 +++++++++ .../69159aef42f9f83752e8e80a.md | 417 +++++++++ .../6915a0e270dd52416cd4ae5a.md | 441 +++++++++ .../6915a2eaf470eb4734728e9e.md | 437 +++++++++ .../6915a372c993bf4c92aee240.md | 426 +++++++++ .../6915a641acd82d52d8040de0.md | 445 +++++++++ .../6915a808bf21675d5640a4e3.md | 485 ++++++++++ .../6915a89c51615a64942515aa.md | 528 +++++++++++ .../6915a8fed59e336bc937f5f2.md | 476 ++++++++++ .../6915aa2ea2f30176eccd9307.md | 451 +++++++++ .../6915ab1afa7f7d7ecaef5050.md | 456 +++++++++ .../6915abb14ee980884ed93cc7.md | 884 ++++++++++++++++++ .../691c252d4dde25176ec1234f.md | 357 +++++++ .../69207366266db1326ddf3b13.md | 435 +++++++++ .../692804fad5603e1a78d3b589.md | 341 +++++++ .../6964bcefeca39b7ac54c8779.md | 334 +++++++ .../6964cf876e580cc2f22f7588.md | 388 ++++++++ .../6964e083368e3e6e90e8cefc.md | 410 ++++++++ .../69934d84e861d41ee06a84a3.md | 325 +++++++ .../69934d8f1d9687e903e7aad2.md | 300 ++++++ .../69934d945aecf10f2b9192a4.md | 291 ++++++ .../69934d9a6c9d9df8cb7d9984.md | 314 +++++++ .../69934d9e75af58a7c2d42afd.md | 306 ++++++ .../69934da2bdf08dc065664ae3.md | 295 ++++++ .../69934da7fc6f3a9937b5197f.md | 304 ++++++ .../69934db03655ea8a2b70f46d.md | 312 +++++++ .../69934db46439c3e19ae8073e.md | 306 ++++++ .../69934dbd9f8b2a16ff8a571f.md | 300 ++++++ .../6994d9f10efe45942d548629.md | 351 +++++++ .../blocks/workshop-shape-manager.json | 62 ++ .../front-end-development-libraries-v9.json | 3 +- 54 files changed, 19681 insertions(+), 1 deletion(-) create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/68ff37f55fb2122df640cd78.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/68ff41972b5bb84f802ee6ca.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/68ff420e11f90153864d39e2.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/68ff42692df48e5ab1429b79.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/68ff42cfa7004f6353fbb5f0.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/68ff495abf98da70d59e6f44.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/68ff4d778884847a60af06a5.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6914547efa31c01853d24fb0.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6914579719bb4621b7e3f0bd.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6914582d975d2d277026a8c4.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/691458e6fdf1a6300a9f6105.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69145951b67f9b34b66215d3.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69145a9503ed163b83adbbde.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69145aefce21f93ed77b626c.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69145b634bdd4245697bf344.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69145bb447c0c14bf43bff1f.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69145da9693cb9620fffa595.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69145fa5538ad66a9cc848a8.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/691475d76745ed889b1a39cb.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/691476d751bc8592c46da5be.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/691598c8e3b1671f01f70c7a.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69159948e52168265730f74f.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69159a1480af042cdc6bb7ff.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69159aef42f9f83752e8e80a.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6915a0e270dd52416cd4ae5a.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6915a2eaf470eb4734728e9e.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6915a372c993bf4c92aee240.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6915a641acd82d52d8040de0.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6915a808bf21675d5640a4e3.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6915a89c51615a64942515aa.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6915a8fed59e336bc937f5f2.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6915aa2ea2f30176eccd9307.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6915ab1afa7f7d7ecaef5050.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6915abb14ee980884ed93cc7.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/691c252d4dde25176ec1234f.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69207366266db1326ddf3b13.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/692804fad5603e1a78d3b589.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6964bcefeca39b7ac54c8779.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6964cf876e580cc2f22f7588.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6964e083368e3e6e90e8cefc.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69934d84e861d41ee06a84a3.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69934d8f1d9687e903e7aad2.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69934d945aecf10f2b9192a4.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69934d9a6c9d9df8cb7d9984.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69934d9e75af58a7c2d42afd.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69934da2bdf08dc065664ae3.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69934da7fc6f3a9937b5197f.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69934db03655ea8a2b70f46d.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69934db46439c3e19ae8073e.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/69934dbd9f8b2a16ff8a571f.md create mode 100644 curriculum/challenges/english/blocks/workshop-shape-manager/6994d9f10efe45942d548629.md create mode 100644 curriculum/structure/blocks/workshop-shape-manager.json diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json index 200e223eb65..7ead750b2b8 100644 --- a/client/i18n/locales/english/intro.json +++ b/client/i18n/locales/english/intro.json @@ -6206,6 +6206,12 @@ "In these lessons, you will learn how to work with union types, interfaces, void types and more." ] }, + "workshop-shape-manager": { + "title": "Build a Shape Manager", + "intro": [ + "In this workshop, you will practice basic TypeScript features like types and interfaces by building a shape manager program." + ] + }, "lecture-working-with-generics-and-type-narrowing": { "title": "Working with Generics and Type Narrowing", "intro": [ diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/68ff37f55fb2122df640cd78.md b/curriculum/challenges/english/blocks/workshop-shape-manager/68ff37f55fb2122df640cd78.md new file mode 100644 index 00000000000..39af5447be8 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/68ff37f55fb2122df640cd78.md @@ -0,0 +1,326 @@ +--- +id: 68ff37f55fb2122df640cd78 +title: Step 11 +challengeType: 0 +dashedName: step-11 +--- + +# --description-- + +You have a working circle area calculation logic now. You can type in a radius and see the area update instantly. + +However, if you continue writing this way for rectangles and triangles, things will become a clutter of loose variables and repeated logic. To prevent this, you can structure the data using **interfaces**, and create a function that handles all area calculation cases. + + +An interface defines the structure of an object by specifying its property types. Here's what an interface looks like in Typescript. + +```ts +interface Footballer { + name: string; + age: number; + isCaptain: boolean; +} +``` + +Create a base interface named `Shape`. Inside it, set `type` to `string`. + +# --hints-- + +You should have an interface named `Shape`. + +```js +const explorer = await __helpers.Explorer(code); +assert.exists(explorer.interfaces.Shape); +``` + +Your `Shape` interface should have a `type` set to `string`. + +```js +const explorer = await __helpers.Explorer(code); +const { Shape } = explorer.interfaces; +const prop = { name: "type", type: "string" }; +assert.isTrue(Shape.hasTypeProps(prop)); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { + const radius = Number(radiusInput.value) || 0; + const area = Math.PI * radius ** 2; + resultText.textContent = `Area of Circle: ${area.toFixed(2)}`; +}); + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/68ff41972b5bb84f802ee6ca.md b/curriculum/challenges/english/blocks/workshop-shape-manager/68ff41972b5bb84f802ee6ca.md new file mode 100644 index 00000000000..e55dd80279c --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/68ff41972b5bb84f802ee6ca.md @@ -0,0 +1,329 @@ +--- +id: 68ff41972b5bb84f802ee6ca +title: Step 12 +challengeType: 0 +dashedName: step-12 +--- + +# --description-- + +Extending an interface allows an interface to inherit the properties of another, creating a "child" interface that includes everything from the "parent" and allowing you to add new, more specific requirements: + +```ts +interface Child extends Parent { + // your types +} +``` + +`Circle` and other shapes need to extend the `Shape` interface so all the shapes in the app share a common structure. This will allow the app to handle the shapes in a consistent and type-safe way. + +Create an interface named `Circle` that extends the `Shape` interface. Inside it, set `type` to the string `circle` and `radius` to `number`. + +# --hints-- + +You should create a `Circle` interface that extends the `Shape` interface. + +```js +const explorer = await __helpers.Explorer(code); +const { Circle } = explorer.interfaces; +assert.isTrue(Circle.doesExtend("Shape")); +``` + +Your `Circle` interface should have a `type` set to the string `circle` and a `radius` set to `number`. + +```js +const explorer = await __helpers.Explorer(code); +const { Circle } = explorer.interfaces; +const props = [ + { name: "type", type: "'circle'" }, + { name: "radius", type: "number" } +]; +assert.isTrue(Circle.hasTypeProps(props)); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { + const radius = Number(radiusInput.value) || 0; + const area = Math.PI * radius ** 2; + resultText.textContent = `Area of Circle: ${area.toFixed(2)}`; +}); + +interface Shape { + type: string; +} + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/68ff420e11f90153864d39e2.md b/curriculum/challenges/english/blocks/workshop-shape-manager/68ff420e11f90153864d39e2.md new file mode 100644 index 00000000000..d203dba1f8e --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/68ff420e11f90153864d39e2.md @@ -0,0 +1,339 @@ +--- +id: 68ff420e11f90153864d39e2 +title: Step 13 +challengeType: 0 +dashedName: step-13 +--- + +# --description-- + +Create an interface named `Rectangle` that extends the `Shape` interface. Inside it, set `type` to the string `rectangle`, `width` to `number`, and `height` to `number`. + +# --hints-- + +You should create an interface `Rectangle` that extends `Shape`. + +```js +const explorer = await __helpers.Explorer(code); +const { Rectangle } = explorer.interfaces; +assert.isTrue(Rectangle.doesExtend("Shape")); +``` + +Your `Rectangle` interface should have a `type` set to the string `rectangle`. + +```js +const explorer = await __helpers.Explorer(code); +const { Rectangle } = explorer.interfaces; +const prop = { name: "type", type: "'rectangle'" }; +assert.isTrue(Rectangle.hasTypeProps(prop)); +``` + +Your `Rectangle` interface should have a `width` of `number`. + +```js +const explorer = await __helpers.Explorer(code); +const { Rectangle } = explorer.interfaces; +const prop = { name: "width", type: "number" }; +assert.isTrue(Rectangle.hasTypeProps(prop)); +``` + +Your `Rectangle` interface should have a `height` of `number`. + +```js +const explorer = await __helpers.Explorer(code); +const { Rectangle } = explorer.interfaces; +const prop = { name: "height", type: "number" }; +assert.isTrue(Rectangle.hasTypeProps(prop)); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { + const radius = Number(radiusInput.value) || 0; + const area = Math.PI * radius ** 2; + resultText.textContent = `Area of Circle: ${area.toFixed(2)}`; +}); + +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/68ff42692df48e5ab1429b79.md b/curriculum/challenges/english/blocks/workshop-shape-manager/68ff42692df48e5ab1429b79.md new file mode 100644 index 00000000000..595a4087f40 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/68ff42692df48e5ab1429b79.md @@ -0,0 +1,376 @@ +--- +id: 68ff42692df48e5ab1429b79 +title: Step 14 +challengeType: 0 +dashedName: step-14 +--- + +# --description-- + +Create an interface named `Triangle` that extends the `Shape` interface. Inside it, set `type` to the string `triangle`, `base` to `number`, and `height` to `number`. + +# --hints-- + +You should create an interface `Triangle` that extends `Shape`. + +```js +const explorer = await __helpers.Explorer(code); +const { Triangle } = explorer.interfaces; +assert.isTrue(Triangle.doesExtend("Shape")); +``` + +Your `Triangle` interface should have a `type` set to the string `triangle`. + +```js +const triType = String.raw`type\s*:\s*["']triangle["'];?`; + +const pattern = __helpers.permutateRegex( + [triType], + { elementsSeparator: String.raw`\s*;?` } +); + +assert.match( + __helpers.removeJSComments(code), + new RegExp( + `interface\\s+Triangle\\s+extends\\s+Shape\\s*{[\\s\\S]*${pattern.source}[\\s\\S]*}` + ) +); +``` + +Your `Triangle` interface should have a `base` of `number`. + +```js +const triType = String.raw`type\s*:\s*["']triangle["'];?`; +const triBase = String.raw`base\s*:\s*number;?`; +const triHeightOptional = String.raw`(?:\s*;?\s*height\s*:\s*number;?)?`; + +const pattern = __helpers.permutateRegex( + [triType, triBase, triHeightOptional], + { elementsSeparator: String.raw`\s*;?` } +); + +assert.match( + __helpers.removeJSComments(code), + new RegExp( + `interface\\s+Triangle\\s+extends\\s+Shape\\s*{[\\s\\S]*${pattern.source}[\\s\\S]*}` + ) +); +``` + +Your `Triangle` interface should have a `height` of `number`. + +```js +const triType = String.raw`type\s*:\s*["']triangle["'];?`; +const triBase = String.raw`base\s*:\s*number;?`; +const triHeight = String.raw`height\s*:\s*number;?`; + +const pattern = __helpers.permutateRegex( + [triHeight, triType, triBase], + { elementsSeparator: String.raw`\s*;?` } +); + +assert.match( + __helpers.removeJSComments(code), + new RegExp( + `interface\\s+Triangle\\s+extends\\s+Shape\\s*{[\\s\\S]*${pattern.source}[\\s\\S]*}` + ) +); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { + const radius = Number(radiusInput.value) || 0; + const area = Math.PI * radius ** 2; + resultText.textContent = `Area of Circle: ${area.toFixed(2)}`; +}); + +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/68ff42cfa7004f6353fbb5f0.md b/curriculum/challenges/english/blocks/workshop-shape-manager/68ff42cfa7004f6353fbb5f0.md new file mode 100644 index 00000000000..992b490204d --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/68ff42cfa7004f6353fbb5f0.md @@ -0,0 +1,339 @@ +--- +id: 68ff42cfa7004f6353fbb5f0 +title: Step 15 +challengeType: 0 +dashedName: step-15 +--- + +# --description-- + +Now, create a `type` named `Shapes` with the union of `Circle`, `Triangle`, and `Rectangle`. + +Here's what a union type looks like: + +```ts +type Role = "Forward" | "Midfielder" | "Defender" | "Goalkeeper"; +``` + +The `Shapes` union type tells TypeScript that a shape can be a circle, rectangle, or triangle, so some functions you will define later know which properties each shape has. + +# --hints-- + +You should create a `type` named `Shapes`. + +```js +const explorer = await __helpers.Explorer(code); +assert.exists(explorer.types.Shapes); +``` + +Your `Shapes` type should be set to a union of the `Circle`, `Triangle`, and `Rectangle` interfaces. + +```js +const explorer = await __helpers.Explorer(code); +const { Shapes } = explorer.types; +assert.isTrue(Shapes.isUnionOf(['Circle', 'Triangle', 'Rectangle'])); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { + const radius = Number(radiusInput.value) || 0; + const area = Math.PI * radius ** 2; + resultText.textContent = `Area of Circle: ${area.toFixed(2)}`; +}); + +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/68ff495abf98da70d59e6f44.md b/curriculum/challenges/english/blocks/workshop-shape-manager/68ff495abf98da70d59e6f44.md new file mode 100644 index 00000000000..88668ebf4cb --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/68ff495abf98da70d59e6f44.md @@ -0,0 +1,326 @@ +--- +id: 68ff495abf98da70d59e6f44 +title: Step 16 +challengeType: 0 +dashedName: step-16 +--- + +# --description-- + +You need to select the HTML elements to work with. For this, you can create a function that gets an element and throws an error when the element is not found. + +Create a `getElement` function and leave it empty. You will supply the types and parameters in the next step. + +# --hints-- + +You should create a `getElement` function. + +```js +assert.isFunction(getElement) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { + const radius = Number(radiusInput.value) || 0; + const area = Math.PI * radius ** 2; + resultText.textContent = `Area of Circle: ${area.toFixed(2)}`; +}); + +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/68ff4d778884847a60af06a5.md b/curriculum/challenges/english/blocks/workshop-shape-manager/68ff4d778884847a60af06a5.md new file mode 100644 index 00000000000..740d845e129 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/68ff4d778884847a60af06a5.md @@ -0,0 +1,342 @@ +--- +id: 68ff4d778884847a60af06a5 +title: Step 18 +challengeType: 0 +dashedName: step-18 +--- + +# --description-- + +Your `getElement` function needs a return type. In Typescript, a return type is the type of value a function gives back when it is done running. + +Here's how to specify a return type: + +```ts +const addNumbers = (a: number, b: number): number => { + return a + b +} +``` + +Here the return type is `number`, specified as `: number`, just after the parameters bracket. + +For the return type of the `getElement` function, make it `HTMLElement`. + +# --hints-- + +Your `getElement` function should have a return type of `HTMLElement`. + +```js +const explorer = await __helpers.Explorer(code); +const { getElement } = explorer.allFunctions; +assert.isTrue( + getElement.hasReturnAnnotation('HTMLElement') +); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { + const radius = Number(radiusInput.value) || 0; + const area = Math.PI * radius ** 2; + resultText.textContent = `Area of Circle: ${area.toFixed(2)}`; +}); + +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +--fcc-editable-region-- +const getElement = (id: string) => { +--fcc-editable-region-- + +}; +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6914547efa31c01853d24fb0.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6914547efa31c01853d24fb0.md new file mode 100644 index 00000000000..c7bd51cd5a3 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6914547efa31c01853d24fb0.md @@ -0,0 +1,344 @@ +--- +id: 6914547efa31c01853d24fb0 +title: Step 20 +challengeType: 0 +dashedName: step-20 +--- + +# --description-- + +The next few steps will focus on using `let` to declare variables with type annotations that will later be assigned DOM element references. This is also called an inline type. + +Here's an example of that with a one-line inline type: + +```js +let variableName: type; +``` + +Use `let` to declare a variable `shapeTypeSelect` with the type `HTMLSelectElement`. Don't assign any value yet. This will hold the dropdown element for selecting shapes. + +# --hints-- + +You should use `let` to create a `shapeTypeSelect` variable. + +```js +assert.match(code, /let\s+shapeTypeSelect/) +``` + +Your `shapeTypeSelect` variable should have the type `HTMLSelectElement`. + +```js +assert.match(code, /let\s+shapeTypeSelect\s*:\s*HTMLSelectElement/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { + const radius = Number(radiusInput.value) || 0; + const area = Math.PI * radius ** 2; + resultText.textContent = `Area of Circle: ${area.toFixed(2)}`; +}); + +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6914579719bb4621b7e3f0bd.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6914579719bb4621b7e3f0bd.md new file mode 100644 index 00000000000..4994983fa78 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6914579719bb4621b7e3f0bd.md @@ -0,0 +1,356 @@ +--- +id: 6914579719bb4621b7e3f0bd +title: Step 21 +challengeType: 0 +dashedName: step-21 +--- + +# --description-- + +You can also define inline object types. For example: + +```js +let variableName: { + property1: type1; + property2: type2; + property3: type3; +}; +``` + +Create another variable named `propertyGroups`. Give it an inline object type with the properties `circle`, `rectangle`, and `triangle`, each with a type of `HTMLElement`. + +# --hints-- + +You should use `let` to create a `propertyGroups` variable with an object inline type. + +```js +assert.match(code, /let\s+propertyGroups:\s*{\s*/) +``` + +Your `propertyGroups` should have `circle`, `rectangle`, and `triangle` properties, each of type `HTMLElement`. + +```js +const explorer = await __helpers.Explorer(code); +const { propertyGroups } = explorer.variables; +assert.isTrue(propertyGroups.annotation.hasTypeProps( + [ + { name: 'circle', type: 'HTMLElement' }, + { name: 'rectangle', type: 'HTMLElement' }, + { name: 'triangle', type: 'HTMLElement' } + ] +)); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { + const radius = Number(radiusInput.value) || 0; + const area = Math.PI * radius ** 2; + resultText.textContent = `Area of Circle: ${area.toFixed(2)}`; +}); + +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6914582d975d2d277026a8c4.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6914582d975d2d277026a8c4.md new file mode 100644 index 00000000000..bbc089a42f7 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6914582d975d2d277026a8c4.md @@ -0,0 +1,352 @@ +--- +id: 6914582d975d2d277026a8c4 +title: Step 22 +challengeType: 0 +dashedName: step-22 +--- + +# --description-- + +Create a `propertyInputs` variable. Give it an object inline type with the properties `radius`, `width`, and `height`, each with the type `HTMLInputElement`. This will hold the input fields to type in numbers that will calculate the area of each shape. + +# --hints-- + +You should use `let` to create a `propertyInputs` variable with an object inline type. + +```js +assert.match(code, /let\s+propertyInputs:\s*{\s*/) +``` + +Your `propertyInputs` variable should have `radius`, `width`, and `height` properties, each set to `HTMLInputElement`. + +```js +const explorer = await __helpers.Explorer(code); +const { propertyInputs } = explorer.variables; +assert.isTrue(propertyInputs.annotation.hasTypeProps( + [ + { name: 'radius', type: 'HTMLInputElement' }, + { name: 'width', type: 'HTMLInputElement' }, + { name: 'height', type: 'HTMLInputElement' } + ] +)); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { + const radius = Number(radiusInput.value) || 0; + const area = Math.PI * radius ** 2; + resultText.textContent = `Area of Circle: ${area.toFixed(2)}`; +}); + +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/691458e6fdf1a6300a9f6105.md b/curriculum/challenges/english/blocks/workshop-shape-manager/691458e6fdf1a6300a9f6105.md new file mode 100644 index 00000000000..b62d37e1b3d --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/691458e6fdf1a6300a9f6105.md @@ -0,0 +1,337 @@ +--- +id: 691458e6fdf1a6300a9f6105 +title: Step 25 +challengeType: 0 +dashedName: step-25 +--- + +# --description-- + +Now, use `let` to create two variables `resultText` and `resultCard`, both of type `HTMLElement`. These will hold the elements that display the calculated area result, not just for the circle but for the other shapes as well. + +# --hints-- + +You should use let to create a `resultText` variable of type `HTMLElement`. + +```js +assert.match(code, /let\s+resultText\s*:\s*HTMLElement;?/) +``` + +You should use let to create a `resultCard` variable of type `HTMLElement`. + +```js +assert.match(code, /let\s+resultCard\s*:\s*HTMLElement;?/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69145951b67f9b34b66215d3.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69145951b67f9b34b66215d3.md new file mode 100644 index 00000000000..069277da216 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69145951b67f9b34b66215d3.md @@ -0,0 +1,342 @@ +--- +id: 69145951b67f9b34b66215d3 +title: Step 26 +challengeType: 0 +dashedName: step-26 +--- + +# --description-- + +You need a way to dynamically show only the input fields relevant to the shape selected. For this, create a `chooseShape` function with a `shapeType` parameter set to a type of `string`. + +# --hints-- + +You should create a `chooseShape` function. + +```js +assert.isFunction(chooseShape) +``` + +Your `chooseShape` function should have a `shapeType` parameter of type `string`. + +```js +const explorer = await __helpers.Explorer(code); +const { chooseShape } = explorer.allFunctions; +assert.isTrue(chooseShape.parameters[0].matches("shapeType: string")); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69145a9503ed163b83adbbde.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69145a9503ed163b83adbbde.md new file mode 100644 index 00000000000..308b002b70c --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69145a9503ed163b83adbbde.md @@ -0,0 +1,380 @@ +--- +id: 69145a9503ed163b83adbbde +title: Step 27 +challengeType: 0 +dashedName: step-27 +--- + +# --description-- + +To make the `chooseShape` function work as intended, you can use `Object.entries()` with `forEach()` to loop through the `propertyGroups` object. The callback for the `forEach()` has to receive `name` and `group` as destructured parameters. + +Then inside the `forEach()` loop, check if `name` is equal to `shapeType`. If it is, use `classList` to remove the `hidden` class from `group`. Otherwise, add the `hidden` class to the `group`. + +# --hints-- + +When the selected shape is a circle, only the input field for circle should be shown. + +```js +propertyGroups = { + circle: document.getElementById("circle-props"), + rectangle: document.getElementById("rectangle-props"), + triangle: document.getElementById("triangle-props"), +}; + +chooseShape("circle") + +assert.isFalse(propertyGroups.circle.classList.contains("hidden")) +assert.isTrue(propertyGroups.rectangle.classList.contains("hidden")) +assert.isTrue(propertyGroups.triangle.classList.contains("hidden")) +``` + +When the selected shape is a rectangle, only the input fields for rectangle should be shown. + +```js +propertyGroups = { + circle: document.getElementById("circle-props"), + rectangle: document.getElementById("rectangle-props"), + triangle: document.getElementById("triangle-props"), +}; + +chooseShape("rectangle") + +assert.isFalse(propertyGroups.rectangle.classList.contains("hidden")) +assert.isTrue(propertyGroups.circle.classList.contains("hidden")) +assert.isTrue(propertyGroups.triangle.classList.contains("hidden")) +``` + +When the selected shape is a triangle, only the input fields for triangle should be shown. + +```js +propertyGroups = { + circle: document.getElementById("circle-props"), + rectangle: document.getElementById("rectangle-props"), + triangle: document.getElementById("triangle-props"), +}; + +chooseShape("triangle") + +assert.isFalse(propertyGroups.triangle.classList.contains("hidden")) +assert.isTrue(propertyGroups.rectangle.classList.contains("hidden")) +assert.isTrue(propertyGroups.circle.classList.contains("hidden")) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { +--fcc-editable-region-- + +--fcc-editable-region-- +}; +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69145aefce21f93ed77b626c.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69145aefce21f93ed77b626c.md new file mode 100644 index 00000000000..ae9c93973ab --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69145aefce21f93ed77b626c.md @@ -0,0 +1,367 @@ +--- +id: 69145aefce21f93ed77b626c +title: Step 28 +challengeType: 0 +dashedName: step-28 +--- + +# --description-- + +You also need to toggle the result card on and off based on what is calculated and the result to show. + +For this, create a `toggleResultCard` function that takes a boolean type parameter called `show`. If `show` is true, add the `visible` class to `resultCard`. Otherwise, remove it. + +# --hints-- + +You should create a `toggleResultCard` function. + +```js +assert.isFunction(toggleResultCard) +``` + +Your `toggleResultCard` function should have a `show` parameter set to a type of `boolean`. + +```js +const explorer = await __helpers.Explorer(code); +const { toggleResultCard } = explorer.allFunctions; +assert.isTrue(toggleResultCard.parameters[0].matches("show: boolean")); +``` + +Your `toggleResultCard` function should add the `visible` class to `resultCard` when `show` is `true`, and remove it otherwise. + +```js +resultCard = document.getElementById("result-card"); + +resultCard.classList.remove("visible"); + +toggleResultCard(true); +assert.isOk(resultCard.classList.contains("visible")); + +toggleResultCard(false); +assert.isNotOk(resultCard.classList.contains("visible")); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +--fcc-editable-region-- +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69145b634bdd4245697bf344.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69145b634bdd4245697bf344.md new file mode 100644 index 00000000000..73e95a4f0aa --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69145b634bdd4245697bf344.md @@ -0,0 +1,365 @@ +--- +id: 69145b634bdd4245697bf344 +title: Step 29 +challengeType: 0 +dashedName: step-29 +--- + +# --description-- + +Now, you should work on the function that calculates the areas of the shapes. It will do this based on the shape selected and the figures entered in the input fields. + +Create a `calculateArea` function with a `shape` parameter of type `Shapes`. + +You will work on the proper calculations in the coming steps. + +# --hints-- + +You should create a `calculateArea` function. + +```js +assert.isFunction(calculateArea) +``` + +Your `calculateArea` function should have a `shape` parameter of type `Shapes`. + +```js +const explorer = await __helpers.Explorer(code); +const { calculateArea } = explorer.allFunctions; +assert.isTrue(calculateArea.parameters[0].matches("shape: Shapes")); +``` + + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69145bb447c0c14bf43bff1f.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69145bb447c0c14bf43bff1f.md new file mode 100644 index 00000000000..d784bfcb2bd --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69145bb447c0c14bf43bff1f.md @@ -0,0 +1,415 @@ +--- +id: 69145bb447c0c14bf43bff1f +title: Step 30 +challengeType: 0 +dashedName: step-30 +--- + +# --description-- + +To get the function to calculate the results, you can use an `if...else if` or `switch` statement. + +First, make the return type of the function `string`, then calculate the result of the area for a circle, rectangle, triangle, and a case of shape that doesn't exist. + +For now, handle the case for circle by using the formula `Math.PI * shape.radius ** 2`, then add a default case that uses the `never` type. + +The `never` type in the default case acts as an exhaustiveness check. If a new shape is ever added to the `Shapes` union, TypeScript will catch it here because `shape` would no longer narrow to `never`, giving you a compile error that reminds you to handle it. + +Here's an example: + +```ts +type Status = "pending" | "approved" | "rejected"; + +function getColor(status: Status): string { + switch (status) { + case "pending": return "yellow"; + case "approved": return "green"; + case "rejected": return "red"; + default: + const _doesNotExist: never = status; + throw new Error(`Unhandled: ${_doesNotExist}`); + } +} +``` + +For now, you can return an empty string in the cases for both rectangle and triangle so the test can pass. + +# --hints-- + +Your `calculateArea` function should have a `string` as its return type. + +```js +const explorer = await __helpers.Explorer(code); +const { calculateArea } = explorer.allFunctions; +assert.isTrue(calculateArea.hasReturnAnnotation("string")); +``` + +Your `calculateArea` function should return the correct result of calculating the area of a circle. + +```js +const circle = { type: "circle", radius: 5 } +const result = calculateArea(circle) + +const match = String(result).match(/-?\d+(\.\d+)?/); +assert.isOk(match) + +const value = Number(match[0]) +const expected = Math.PI * (5 ** 2) + +assert.approximately(value, expected, 0.01) +``` + +You should use the `never` type. + +```js +assert.match(code, /:\s*never/) +``` + +Your `calculateArea` function should handle non-existent shapes using the `never` type. + +```js +const unrecognizedShape = { type: "hexagon" }; + +try { + const res = calculateArea(unrecognizedShape); + assert.isTrue(res === undefined || String(res).match(/-?\d+(\.\d+)?/) === null); +} catch (err) { + assert.instanceOf(err, Error); +} +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +--fcc-editable-region-- +const calculateArea = (shape: Shapes) => { + +}; +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69145da9693cb9620fffa595.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69145da9693cb9620fffa595.md new file mode 100644 index 00000000000..78de0ee8f4f --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69145da9693cb9620fffa595.md @@ -0,0 +1,390 @@ +--- +id: 69145da9693cb9620fffa595 +title: Step 32 +challengeType: 0 +dashedName: step-32 +--- + +# --description-- + +Now, create an `updateResult` function. Inside it, create a `shape` variable set to `shapeTypeSelect.value`, then use `let` to create a `result` variable of type `string` set to an empty string. Under that, use the `textContent` property to set the content of `resultText` to `result`. + +# --hints-- + +You should create an `updateResult` function. + +```js +assert.isFunction(updateResult) +``` + +You should create a `shape` variable set to `shapeTypeSelect.value` inside your `updateResult` function. + +```js +assert.match(updateResult.toString(), /(let|const|var)\s+shape\s*=\s*shapeTypeSelect\.value;?/) +``` + +You should use `let` to create a `result` variable set to an empty string inside your `updateResult` function. + +```js +assert.match(updateResult.toString(), /(let|var)\s+result\s*=\s*("|')("|');?/) +``` + +Your `result` variable should have `string` as its type. + +```js +assert.match(code, /(let|var)\s+result\s*:\s*string\s*=\s*("|')("|');?/) +``` + +You should set the `textContent` of `resultText` to `result`. + +```js +assert.match(updateResult.toString(), /resultText\.textContent\s*=\s*result;?/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69145fa5538ad66a9cc848a8.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69145fa5538ad66a9cc848a8.md new file mode 100644 index 00000000000..19eb94623de --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69145fa5538ad66a9cc848a8.md @@ -0,0 +1,385 @@ +--- +id: 69145fa5538ad66a9cc848a8 +title: Step 33 +challengeType: 0 +dashedName: step-33 +--- + +# --description-- + +The `updateResult` function will read the selected shape type from the dropdown, gather the corresponding input values, create a `shape` object, pass it to the `calculateArea()` function to compute the area, and display the calculated result in the `resultText` element. + +Inside the function, check the selected shape and build the correct object to pass into the `calculateArea` function. + +If the shape is `"circle"`, call the `calculateArea` function with an object containing the type and the numeric value of the radius input. + +# --hints-- + +Your `updateResult` function should correctly update the result text when the shape is circle. + +```js +propertyInputs = {} + +shapeTypeSelect = document.getElementById("shape-type") +propertyInputs.radius = document.getElementById("radius") +resultText = document.getElementById("result-text") + +shapeTypeSelect.value = "circle" +propertyInputs.radius.value = "5" +updateResult() + +assert.include(resultText.textContent, (Math.PI * 5 ** 2).toFixed(2)) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; +--fcc-editable-region-- + +--fcc-editable-region-- + resultText.textContent = result; +} +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/691475d76745ed889b1a39cb.md b/curriculum/challenges/english/blocks/workshop-shape-manager/691475d76745ed889b1a39cb.md new file mode 100644 index 00000000000..dd69402d3e2 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/691475d76745ed889b1a39cb.md @@ -0,0 +1,417 @@ +--- +id: 691475d76745ed889b1a39cb +title: Step 35 +challengeType: 0 +dashedName: step-35 +--- + +# --description-- + +You should also work on a function that will clear the input fields. + +Create a `clearInputFields` function. Inside it, use `querySelectorAll` to get all the input fields and loop through them using `forEach()`. Inside the `forEach()`, set the value of each `input` to an empty string. + +# --hints-- + +You should create a `clearInputFields` function. + +```js +assert.isFunction(clearInputFields) +``` + +Your `clearInputFields` function should set each input field to an empty string. + +```js +const input1 = document.createElement("input") +const input2 = document.createElement("input") +input1.value = "fCC" +input2.value = "fCCAgain" + +document.body.appendChild(input1) +document.body.appendChild(input2) + +assert.notStrictEqual(input1.value, "") +assert.notStrictEqual(input2.value, "") + +clearInputFields() + +assert.strictEqual(input1.value, "") +assert.strictEqual(input2.value, "") + +input1.remove() +input2.remove() +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +--fcc-editable-region-- + +--fcc-editable-region-- + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/691476d751bc8592c46da5be.md b/curriculum/challenges/english/blocks/workshop-shape-manager/691476d751bc8592c46da5be.md new file mode 100644 index 00000000000..13f76e37128 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/691476d751bc8592c46da5be.md @@ -0,0 +1,410 @@ +--- +id: 691476d751bc8592c46da5be +title: Step 36 +challengeType: 0 +dashedName: step-36 +--- + +# --description-- + +You need a way to respond when users change the shape selection in the dropdown. For this, create a `handleShapeSelect` function with an `e` parameter typed `Event`. + +This function will handle the dropdown change by clearing all inputs, preventing the default behavior, validating the selection, showing and hiding the result card based on whether a shape is selected, displaying the appropriate input fields for the chosen shape, and recalculating the area. + +Leave the function empty for now. + +# --hints-- + +You should create a `handleShapeSelect` function. + +```js +assert.isFunction(handleShapeSelect) +``` + +Your `handleShapeSelect` function should have an `e` parameter with the type `Event`. + +```js +const explorer = await __helpers.Explorer(code); +const { handleShapeSelect } = explorer.allFunctions; +assert.isTrue(handleShapeSelect.parameters[0].matches("e: Event")); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/691598c8e3b1671f01f70c7a.md b/curriculum/challenges/english/blocks/workshop-shape-manager/691598c8e3b1671f01f70c7a.md new file mode 100644 index 00000000000..4f87f9d3844 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/691598c8e3b1671f01f70c7a.md @@ -0,0 +1,406 @@ +--- +id: 691598c8e3b1671f01f70c7a +title: Step 37 +challengeType: 0 +dashedName: step-37 +--- + +# --description-- + +Inside the `handleShapeSelect` function, call the `.preventDefault()` method on `e`, and call the `clearInputFields()` function to clear the input fields. + +# --hints-- + +You should call `.preventDefault()` on `e`. + +```js +assert.match(handleShapeSelect.toString(), /e\.preventDefault\(\);?/) +``` + +You should call the `clearInputFields` function. + +```js +assert.match(handleShapeSelect.toString(), /clearInputFields\(\);?/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +const handleShapeSelect = (e: Event) => { +--fcc-editable-region-- + +--fcc-editable-region-- +}; +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69159948e52168265730f74f.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69159948e52168265730f74f.md new file mode 100644 index 00000000000..8f9ee3847f4 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69159948e52168265730f74f.md @@ -0,0 +1,422 @@ +--- +id: 69159948e52168265730f74f +title: Step 38 +challengeType: 0 +dashedName: step-38 +--- + +# --description-- + +You need a way to safely access the select element and guard against `null` values. For that, create a `val` constant with the value `e.currentTarget` and type it `HTMLSelectElement` using the `as` keyword. + +Under the constant, use an `if` statement to check if `val` is falsy by negating it. In the `if` block, return `target value not found`. + +# --hints-- + +You should have a constant called `val`. + +```js +assert.match(code, /const\s+val/) +``` + +Your `val` constant should have a `e.currentTarget` value typed `HTMLSelectElement` using the `as` keyword. + +```js +assert.match(code, /const\s+val\s*=\s*e\.currentTarget\s+as\s+HTMLSelectElement;?/) +``` + +You should have an `if` statement that checks if `val` is null. + +```js +assert.match(handleShapeSelect.toString(), /if\s*\((!val|val===null)\)\s*\{?\s*/) +``` + +Your `if` statement should return `target value not found`. + +```js +assert.match(handleShapeSelect.toString(), /if\s*\((\s*!val\s*)\)\s*\{?\s*return\s*("|')target\s+value\s+not\s+found("|');?\s*\}?/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +const handleShapeSelect = (e: Event) => { + e.preventDefault(); + clearInputFields(); +--fcc-editable-region-- + +--fcc-editable-region-- +}; +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69159a1480af042cdc6bb7ff.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69159a1480af042cdc6bb7ff.md new file mode 100644 index 00000000000..86da0de2683 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69159a1480af042cdc6bb7ff.md @@ -0,0 +1,431 @@ +--- +id: 69159a1480af042cdc6bb7ff +title: Step 39 +challengeType: 0 +dashedName: step-39 +--- + +# --description-- + +Still inside the `handleShapeSelect` function, create a `hasSelection` variable that checks if `val.value` exists. Make sure it is wrapped in `Boolean()`. + +Under that, call the `toggleResultCard()` function with your `hasSelection` variable. + +This will show the result card only when a shape is actually selected. + +# --hints-- + +You should create a `hasSelection` variable. + +```js +assert.match(handleShapeSelect.toString(), /(const|let|var)\s+hasSelection\s*/) +``` + + +You should wrap `Boolean()` around your `hasSelection` value. + +```js +assert.match(handleShapeSelect.toString(), /(const|let|var)\s+hasSelection\s*=\s*Boolean\(/) +``` + +Your `hasSelection` should be set to a value that checks if `val.value` exists and is not an empty string. + +```js +assert.match(handleShapeSelect.toString(), /(const|let|var)\s+hasSelection\s*=\s*Boolean\(\s*val\.value\s*\);?/) +``` + +You should call the `toggleResultCard` function with your `hasSelection` variable. + +```js +assert.match(handleShapeSelect.toString(), /toggleResultCard\(hasSelection\)/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +const handleShapeSelect = (e: Event) => { + e.preventDefault(); + clearInputFields(); + + const val = e.currentTarget as HTMLSelectElement; + if (!val) { + return "target value not found"; + } + +--fcc-editable-region-- + +--fcc-editable-region-- +}; +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69159aef42f9f83752e8e80a.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69159aef42f9f83752e8e80a.md new file mode 100644 index 00000000000..ecd4fd82c71 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69159aef42f9f83752e8e80a.md @@ -0,0 +1,417 @@ +--- +id: 69159aef42f9f83752e8e80a +title: Step 40 +challengeType: 0 +dashedName: step-40 +--- + +# --description-- + +Now, call the `chooseShape()` function with `val.value` to show the correct input fields, then call `updateResult()` to recalculate and display the area. + +# --hints-- + +You should call the `chooseShape` function with `val.value`. + +```js +assert.match(handleShapeSelect.toString(), /chooseShape\(val\.value\);?/) +``` + +You should call the `updateResult()` function. + +```js +assert.match(handleShapeSelect.toString(), /updateResult\(\);?/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +const handleShapeSelect = (e: Event) => { + e.preventDefault(); + clearInputFields(); + + const val = e.currentTarget as HTMLSelectElement; + if (!val) { + return "target value not found"; + } + + const hasSelection = Boolean(val.value); + toggleResultCard(hasSelection); + +--fcc-editable-region-- + +--fcc-editable-region-- +}; +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6915a0e270dd52416cd4ae5a.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6915a0e270dd52416cd4ae5a.md new file mode 100644 index 00000000000..20b0241ae06 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6915a0e270dd52416cd4ae5a.md @@ -0,0 +1,441 @@ +--- +id: 6915a0e270dd52416cd4ae5a +title: Step 41 +challengeType: 0 +dashedName: step-41 +--- + +# --description-- + +You need a function to validate the numbers entered and make sure they are not negative. + +For that, create a `handleInput` function with an `e` parameter of type `Event`. Inside it, create a `value` variable set to the value of the target value. Make sure it is converted to a number and cast to the `HTMLInputElement` type using the `as` keyword. + +# --hints-- + +You should create a `handleInput` function. + +```js +assert.isFunction(handleInput) +``` + +Your `handleInput` function should have an `e` parameter with the type `Event`. + +```js +const explorer = await __helpers.Explorer(code); +const { handleInput } = explorer.allFunctions; +assert.isTrue(handleInput.parameters[0].matches("e: Event")); +``` + +You should create a `value` variable inside your `handleInput` function. + +```js +assert.match(handleInput.toString(), /(const|let|var)\s+value\s*/) +``` + +Your `value` variable value should be wrapped with the `Number()` function. + +```js +assert.match(handleInput.toString(), /(const|let|var)\s+value\s*=\s*Number\(/) +``` + +Your `value` variable value should be set to the value of the target. + +```js +assert.match(code, /(const|let|var)\s+value\s*=\s*Number\s*\(\s*\(\s*e\.target\s+as\s+HTMLInputElement\s*\)\s*\.?\s*value\s*\)\s*;?/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +const handleShapeSelect = (e: Event) => { + e.preventDefault(); + clearInputFields(); + + const val = e.currentTarget as HTMLSelectElement; + if (!val) { + return "target value not found"; + } + + const hasSelection = Boolean(val.value); + toggleResultCard(hasSelection); + + chooseShape(val.value); + updateResult(); +}; + +--fcc-editable-region-- +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6915a2eaf470eb4734728e9e.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6915a2eaf470eb4734728e9e.md new file mode 100644 index 00000000000..1afc0273728 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6915a2eaf470eb4734728e9e.md @@ -0,0 +1,437 @@ +--- +id: 6915a2eaf470eb4734728e9e +title: Step 42 +challengeType: 0 +dashedName: step-42 +--- + +# --description-- + +Now, create an `if` statement that checks if `value` is less than `0`. If that is true, it means the value entered is negative. So, using an alert, show the message `Negative values are not allowed.` to the user, then clear the input fields by calling the `clearInputFields()` function. + +Under the `if` statement, call the `updateResult` function to recalculate the area. + +# --hints-- + +You should create an `if` statement with the condition `value < 0` inside your `handleInput` function. + +```js +assert.match(handleInput.toString(), /if\s*\(\s*value\s*<\s*0\s*\)\s*\{\s*/) +``` + +You should alert `Negative values are not allowed.` inside your `if` statement. + +```js +assert.match(handleInput.toString(), /if\s*\(\s*value\s*<\s*0\s*\)\s*\{\s*(window.)?alert\(["']Negative\s+values\s+are\s+not\s+allowed\.["']\);?\s*/) +``` + +You should call the `clearInputFields` function inside your `if` statement. + +```js +assert.match(handleInput.toString(), /if\s*\(\s*value\s*<\s*0\s*\)\s*\{\s*(window.)?alert\(["']Negative\s+values\s+are\s+not\s+allowed\.?["']\);?\s*clearInputFields\(\);?\s*\}/) +``` + +You should call the `updateResult` function under your `if` statement. + +```js +assert.match(handleInput.toString(), /if\s*\(\s*value\s*<\s*0\s*\)\s*\{\s*(window.)?alert\(["']Negative\s+values\s+are\s+not\s+allowed\.["']\);?\s*clearInputFields\(\);?\s*\}\s*updateResult\(\);?/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +const handleShapeSelect = (e: Event) => { + e.preventDefault(); + clearInputFields(); + + const val = e.currentTarget as HTMLSelectElement; + if (!val) { + return "target value not found"; + } + + const hasSelection = Boolean(val.value); + toggleResultCard(hasSelection); + + chooseShape(val.value); + updateResult(); +}; + +const handleInput = (e: Event) => { + const value = Number((e.target as HTMLInputElement).value); +--fcc-editable-region-- + +--fcc-editable-region-- +}; +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6915a372c993bf4c92aee240.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6915a372c993bf4c92aee240.md new file mode 100644 index 00000000000..d0a224738bd --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6915a372c993bf4c92aee240.md @@ -0,0 +1,426 @@ +--- +id: 6915a372c993bf4c92aee240 +title: Step 43 +challengeType: 0 +dashedName: step-43 +--- + +# --description-- + +Now, create an `initializeApp` function and leave it empty for now. + +This will initialize the entire app once the DOM is loaded. So, it's the function you will soon pass into a `DOMContentLoaded` event that will get the app working. + +# --hints-- + +You should create an `initializeApp` function. + +```js +assert.isFunction(initializeApp) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +const handleShapeSelect = (e: Event) => { + e.preventDefault(); + clearInputFields(); + + const val = e.currentTarget as HTMLSelectElement; + if (!val) { + return "target value not found"; + } + + const hasSelection = Boolean(val.value); + toggleResultCard(hasSelection); + + chooseShape(val.value); + updateResult(); +}; + +const handleInput = (e: Event) => { + const value = Number((e.target as HTMLInputElement).value); + + if (value < 0) { + alert("Negative values are not allowed."); + clearInputFields(); + } + + updateResult(); +}; + +--fcc-editable-region-- +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6915a641acd82d52d8040de0.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6915a641acd82d52d8040de0.md new file mode 100644 index 00000000000..f387ceff16a --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6915a641acd82d52d8040de0.md @@ -0,0 +1,445 @@ +--- +id: 6915a641acd82d52d8040de0 +title: Step 45 +challengeType: 0 +dashedName: step-45 +--- + +# --description-- + +Back to the `initializeApp` function, this is where you will put the inline types and `getElement()` function you created earlier into use by getting the DOM elements needed for the app. + +Inside the `initializeApp` function, reassign `shapeTypeSelect` by calling `getElement()` with `"shape-type"`, then use the `as` keyword to cast the result to `HTMLSelectElement`. + +# --hints-- + +You should reassign `shapeTypeSelect` with the appropriate value. + +```js +assert.match(initializeApp.toString(), /shapeTypeSelect\s*/) +``` + +You should call the `getElement` function with `shape-type` and assign it to `shapeTypeSelect`. + +```js +assert.match(initializeApp.toString(), /shapeTypeSelect\s*=\s*getElement\(["']shape-type["']\);?/) +``` + +You should call `getElement` with `HTMLSelectElement` as the type of `shapeTypeSelect` using the `as` keyword. + +```js +assert.match(code, /shapeTypeSelect\s*=\s*getElement\(["']shape-type["']\)\s+as\s+HTMLSelectElement;?/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +const handleShapeSelect = (e: Event) => { + e.preventDefault(); + clearInputFields(); + + const val = e.currentTarget as HTMLSelectElement; + if (!val) { + return "target value not found"; + } + + const hasSelection = Boolean(val.value); + toggleResultCard(hasSelection); + + chooseShape(val.value); + updateResult(); +}; + +const handleInput = (e: Event) => { + const value = Number((e.target as HTMLInputElement).value); + + if (value < 0) { + alert("Negative values are not allowed."); + clearInputFields(); + } + + updateResult(); +}; + +const initializeApp = () => { +--fcc-editable-region-- + +--fcc-editable-region-- +}; + +document.addEventListener("DOMContentLoaded", initializeApp); +``` + + diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6915a808bf21675d5640a4e3.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6915a808bf21675d5640a4e3.md new file mode 100644 index 00000000000..5b286fcbfb2 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6915a808bf21675d5640a4e3.md @@ -0,0 +1,485 @@ +--- +id: 6915a808bf21675d5640a4e3 +title: Step 46 +challengeType: 0 +dashedName: step-46 +--- + +# --description-- + +Now, you should select the shapes. Each of them is in a `div` with the id `[shape]-props`. Instead of selecting them one after the other, assign an object with `circle`, `rectangle`, and `triangle` properties to the `propertyGroups` variable you declared before. + +Each of the properties should use the `getElement` function with `HTMLElement` type to get elements with ids `circle-props`, `rectangle-props`, and `triangle-props` respectively. + +# --hints-- + +You should assign an object to the `propertyGroups` variable declared before. + +```js +assert.match(initializeApp.toString(), /propertyGroups\s*=\s*{\s*/) +``` + +Your `propertyGroups` should have a `circle` property with its value calling `getElement()` with `circle-props` and casting it `HTMLElement`. + +```js +const circle = String.raw`circle\s*:\s*getElement\(["']circle-props["']\)\s+as\s+HTMLElement`; + +const pattern = __helpers.permutateRegex( + [circle], + { elementsSeparator: String.raw`\s*;?` } +); + +assert.match( + __helpers.removeJSComments(code), + new RegExp(`propertyGroups\\s*=\\s*{[\\s\\S]*${pattern.source}[\\s\\S]*}`) +); +``` + +Your `propertyGroups` should have a `rectangle` property with its value calling `getElement()` with `rectangle-props` and casting it `HTMLElement`. + +```js +const circleOptional = String.raw`(?:circle\s*:\s*getElement\(["']circle-props["']\)\s+as\s+HTMLElement)?`; +const rectangle = String.raw`rectangle\s*:\s*getElement\(["']rectangle-props["']\)\s+as\s+HTMLElement`; + +const pattern = __helpers.permutateRegex( + [rectangle, circleOptional], + { elementsSeparator: String.raw`\s*;?` } +); + +assert.match( + __helpers.removeJSComments(code), + new RegExp(`propertyGroups\\s*=\\s*{[\\s\\S]*${pattern.source}[\\s\\S]*}`) +); +``` + +Your `propertyGroups` should have a `triangle` property with its value calling `getElement()` with `triangle-props` and casting it `HTMLElement`. + +```js +const circleOptional = String.raw`(?:circle\s*:\s*getElement\(["']circle-props["']\)\s+as\s+HTMLElement)?`; +const rectangleOptional = String.raw`(?:rectangle\s*:\s*getElement\(["']rectangle-props["']\)\s+as\s+HTMLElement)?`; +const triangle = String.raw`triangle\s*:\s*getElement\(["']triangle-props["']\)\s+as\s+HTMLElement`; + +const pattern = __helpers.permutateRegex( + [triangle, circleOptional, rectangleOptional], + { elementsSeparator: String.raw`\s*;?` } +); + +assert.match( + __helpers.removeJSComments(code), + new RegExp(`propertyGroups\\s*=\\s*{[\\s\\S]*${pattern.source}[\\s\\S]*}`) +); + +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +const handleShapeSelect = (e: Event) => { + e.preventDefault(); + clearInputFields(); + + const val = e.currentTarget as HTMLSelectElement; + if (!val) { + return "target value not found"; + } + + const hasSelection = Boolean(val.value); + toggleResultCard(hasSelection); + + chooseShape(val.value); + updateResult(); +}; + +const handleInput = (e: Event) => { + const value = Number((e.target as HTMLInputElement).value); + + if (value < 0) { + alert("Negative values are not allowed."); + clearInputFields(); + } + + updateResult(); +}; + +const initializeApp = () => { + shapeTypeSelect = getElement("shape-type") as HTMLSelectElement; + +--fcc-editable-region-- + +--fcc-editable-region-- +}; + +document.addEventListener("DOMContentLoaded", initializeApp); +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6915a89c51615a64942515aa.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6915a89c51615a64942515aa.md new file mode 100644 index 00000000000..6f78a90a552 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6915a89c51615a64942515aa.md @@ -0,0 +1,528 @@ +--- +id: 6915a89c51615a64942515aa +title: Step 47 +challengeType: 0 +dashedName: step-47 +--- + +# --description-- + +Now, create an object with `radius`, `width`, `height`, `base`, and `triangleHeight` properties. + +For each property, use `getElement`, this time with the `HTMLInputElement` type, to grab the input element with the corresponding id. Assign the object to the `propertyInputs` variable previously declared. + +This represents all the input fields where you will enter the number for different shape properties so the area will be calculated. + +# --hints-- + +You should assign an object to the `propertyInputs` variable previously declared. + +```js +assert.match(initializeApp.toString(), /propertyInputs\s*=\s*{\s*/) +``` + +Your `propertyInputs` object should have a `radius` property with its value calling `getElement()` with `radius` and casting it `HTMLInputElement`. + +```js +const radius = String.raw`radius\s*:\s*getElement\(["']radius["']\)`; + +const pattern = __helpers.permutateRegex( + [radius], + { elementsSeparator: String.raw`\s*;?` } +); + +assert.match( + __helpers.removeJSComments(code), + new RegExp( + `propertyInputs\\s*=\\s*{[\\s\\S]*${pattern.source}[\\s\\S]*}` + ) +); +``` + +Your `propertyInputs` object should have a `width` property with its value calling `getElement()` with `width` and casting it `HTMLInputElement`. + +```js +const radiusOptional = String.raw`(?:radius\s*:\s*getElement\(["']radius["']\)\s+as\s+HTMLInputElement)?`; +const width = String.raw`width\s*:\s*getElement\(["']width["']\)\s+as\s+HTMLInputElement`; + +const pattern = __helpers.permutateRegex( + [width, radiusOptional], + { elementsSeparator: String.raw`\s*;?` } +); + +assert.match( + __helpers.removeJSComments(code), + new RegExp( + `propertyInputs\\s*=\\s*{[\\s\\S]*${pattern.source}[\\s\\S]*}` + ) +); +``` + +Your `propertyInputs` object should have a `height` property with its value calling `getElement()` with `height` and casting it `HTMLInputElement`. + +```js +const radiusOptional = String.raw`(?:radius\s*:\s*getElement\(["']radius["']\)\s+as\s+HTMLInputElement)?`; +const widthOptional = String.raw`(?:width\s*:\s*getElement\(["']width["']\)\s+as\s+HTMLInputElement)?`; +const height = String.raw`height\s*:\s*getElement\(["']height["']\)\s+as\s+HTMLInputElement`; + +const pattern = __helpers.permutateRegex( + [height, radiusOptional, widthOptional], + { elementsSeparator: String.raw`\s*;?` } +); + +assert.match( + __helpers.removeJSComments(code), + new RegExp( + `propertyInputs\\s*=\\s*{[\\s\\S]*${pattern.source}[\\s\\S]*}` + ) +); +``` + +Your `propertyInputs` object should have a `base` property with its value calling `getElement()` with `base` and casting it `HTMLInputElement`. + +```js +const radiusOptional = String.raw`(?:radius\s*:\s*getElement\(["']radius["']\)\s+as\s+HTMLInputElement)?`; +const widthOptional = String.raw`(?:width\s*:\s*getElement\(["']width["']\)\s+as\s+HTMLInputElement)?`; +const heightOptional = String.raw`(?:height\s*:\s*getElement\(["']height["']\)\s+as\s+HTMLInputElement)?`; +const base = String.raw`base\s*:\s*getElement\(["']base["']\)\s+as\s+HTMLInputElement`; + +const pattern = __helpers.permutateRegex( + [base, radiusOptional, widthOptional, heightOptional], + { elementsSeparator: String.raw`\s*;?` } +); + +assert.match( + __helpers.removeJSComments(code), + new RegExp( + `propertyInputs\\s*=\\s*{[\\s\\S]*${pattern.source}[\\s\\S]*}` + ) +); +``` + +Your `propertyInputs` object should have a `triangleHeight` property with its value calling `getElement()` with `triangle-height` and casting it `HTMLInputElement`. + +```js +assert.match( + __helpers.removeJSComments(code), + /triangleHeight\s*:\s*getElement\(["']triangle-height["']\)\s+as\s+HTMLInputElement/ +); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +const handleShapeSelect = (e: Event) => { + e.preventDefault(); + clearInputFields(); + + const val = e.currentTarget as HTMLSelectElement; + if (!val) { + return "target value not found"; + } + + const hasSelection = Boolean(val.value); + toggleResultCard(hasSelection); + + chooseShape(val.value); + updateResult(); +}; + +const handleInput = (e: Event) => { + const value = Number((e.target as HTMLInputElement).value); + + if (value < 0) { + alert("Negative values are not allowed."); + clearInputFields(); + } + + updateResult(); +}; + +const initializeApp = () => { + shapeTypeSelect = getElement("shape-type") as HTMLSelectElement; + + propertyGroups = { + circle: getElement("circle-props") as HTMLElement, + rectangle: getElement("rectangle-props") as HTMLElement, + triangle: getElement("triangle-props") as HTMLElement, + }; + +--fcc-editable-region-- + +--fcc-editable-region-- +}; + +document.addEventListener("DOMContentLoaded", initializeApp); +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6915a8fed59e336bc937f5f2.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6915a8fed59e336bc937f5f2.md new file mode 100644 index 00000000000..ebf48a087d1 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6915a8fed59e336bc937f5f2.md @@ -0,0 +1,476 @@ +--- +id: 6915a8fed59e336bc937f5f2 +title: Step 48 +challengeType: 0 +dashedName: step-48 +--- + +# --description-- + +The last set of elements to select are the result elements. Use your `getElement` function to get the element with the ids `result-text` and `result-card`. Both should use `HTMLElement` as the type. + +Remember you have already declared the `resultText` and `resultCard` variables for them, so you only need to assign them. + +# --hints-- + +You should assign a value to the `resultText` variable. + +```js +assert.match(initializeApp.toString(), /resultText\s*=\s*/) +``` + +`resultText` should select the `result-text` element. + +```js +assert.match(initializeApp.toString(), /resultText\s*=\s*getElement\(["']result-text["']\);?/) +``` + +You should use `HTMLElement` as the type of the `getElement` you're using to select the `result-text` element. + +```js +assert.match(code, /resultText\s*=\s*getElement\(["']result-text["']\)\s+as\s+HTMLElement;?/) +``` + +You should assign a value to the `resultCard` variable. + +```js +assert.match(initializeApp.toString(), /resultCard\s*=\s*/) +``` + +`resultCard` should select the `result-card` element. + +```js +assert.match(initializeApp.toString(), /resultCard\s*=\s*getElement\(["']result-card["']\);?/) +``` + +You should use `HTMLElement` as the type of the `getElement` you're using to select the `result-card` element. + +```js +assert.match(code, /resultCard\s*=\s*getElement\(["']result-card["']\)\s+as\s+HTMLElement;?/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +const handleShapeSelect = (e: Event) => { + e.preventDefault(); + clearInputFields(); + + const val = e.currentTarget as HTMLSelectElement; + if (!val) { + return "target value not found"; + } + + const hasSelection = Boolean(val.value); + toggleResultCard(hasSelection); + + chooseShape(val.value); + updateResult(); +}; + +const handleInput = (e: Event) => { + const value = Number((e.target as HTMLInputElement).value); + + if (value < 0) { + alert("Negative values are not allowed."); + clearInputFields(); + } + + updateResult(); +}; + +const initializeApp = () => { + shapeTypeSelect = getElement("shape-type") as HTMLSelectElement; + + propertyGroups = { + circle: getElement("circle-props") as HTMLElement, + rectangle: getElement("rectangle-props") as HTMLElement, + triangle: getElement("triangle-props") as HTMLElement, + }; + + propertyInputs = { + radius: getElement("radius") as HTMLInputElement, + width: getElement("width") as HTMLInputElement, + height: getElement("height") as HTMLInputElement, + base: getElement("base") as HTMLInputElement, + triangleHeight: getElement("triangle-height") as HTMLInputElement, + }; +--fcc-editable-region-- + +--fcc-editable-region-- +}; + +document.addEventListener("DOMContentLoaded", initializeApp); +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6915aa2ea2f30176eccd9307.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6915aa2ea2f30176eccd9307.md new file mode 100644 index 00000000000..385aa12385c --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6915aa2ea2f30176eccd9307.md @@ -0,0 +1,451 @@ +--- +id: 6915aa2ea2f30176eccd9307 +title: Step 49 +challengeType: 0 +dashedName: step-49 +--- + +# --description-- + +Use the `oninput` event on `shapeTypeSelect` and set it to `handleShapeSelect`. This will make the function run each time the shape selection changes. + +After doing that, you should be able to see the input fields of each shape. But the calculations won't work yet, and that's what you will handle next. + +# --hints-- + +You should set `shapeTypeSelect.oninput` to `handleShapeSelect`. + +```js +console.log(initializeApp.toString()) +assert.match(initializeApp.toString(), /shapeTypeSelect\.oninput\s*=\s*handleShapeSelect;?/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +const handleShapeSelect = (e: Event) => { + e.preventDefault(); + clearInputFields(); + + const val = e.currentTarget as HTMLSelectElement; + if (!val) { + return "target value not found"; + } + + const hasSelection = Boolean(val.value); + toggleResultCard(hasSelection); + + chooseShape(val.value); + updateResult(); +}; + +const handleInput = (e: Event) => { + const value = Number((e.target as HTMLInputElement).value); + + if (value < 0) { + alert("Negative values are not allowed."); + clearInputFields(); + } + + updateResult(); +}; + +const initializeApp = () => { + shapeTypeSelect = getElement("shape-type") as HTMLSelectElement; + + propertyGroups = { + circle: getElement("circle-props") as HTMLElement, + rectangle: getElement("rectangle-props") as HTMLElement, + triangle: getElement("triangle-props") as HTMLElement, + }; + + propertyInputs = { + radius: getElement("radius") as HTMLInputElement, + width: getElement("width") as HTMLInputElement, + height: getElement("height") as HTMLInputElement, + base: getElement("base") as HTMLInputElement, + triangleHeight: getElement("triangle-height") as HTMLInputElement, + }; + + resultText = getElement("result-text") as HTMLElement; + resultCard = getElement("result-card") as HTMLElement; + + --fcc-editable-region-- + + --fcc-editable-region-- +}; + +document.addEventListener("DOMContentLoaded", initializeApp); +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6915ab1afa7f7d7ecaef5050.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6915ab1afa7f7d7ecaef5050.md new file mode 100644 index 00000000000..16881f8dfc9 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6915ab1afa7f7d7ecaef5050.md @@ -0,0 +1,456 @@ +--- +id: 6915ab1afa7f7d7ecaef5050 +title: Step 50 +challengeType: 0 +dashedName: step-50 +--- + +# --description-- + +To finally show the calculations, loop through the `propertyInputs` object by using a `for...of` loop with `Object.entries()`. You can ignore the key by using a comma before `input` in the loop declaration. + +# --hints-- + +You should create a `for...of` loop that destructures each entry from `Object.entries(propertyInputs)` by skipping the key and keeping only `input`. + +```js +assert.match(code, /for\s*\(const\s*\[\s*,\s*input\s*\]\s+of/) +``` + +Your `for...of` loop should iterate over `Object.entries(propertyInputs)`. + +```js +assert.match(code, /for\s*\(const\s*\[\s*,\s*input\s*\]\s+of\s+Object\.entries\(propertyInputs\)\)\s*\{\s*\}/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +const handleShapeSelect = (e: Event) => { + e.preventDefault(); + clearInputFields(); + + const val = e.currentTarget as HTMLSelectElement; + if (!val) { + return "target value not found"; + } + + const hasSelection = Boolean(val.value); + toggleResultCard(hasSelection); + + chooseShape(val.value); + updateResult(); +}; + +const handleInput = (e: Event) => { + const value = Number((e.target as HTMLInputElement).value); + + if (value < 0) { + alert("Negative values are not allowed."); + clearInputFields(); + } + + updateResult(); +}; + +const initializeApp = () => { + shapeTypeSelect = getElement("shape-type") as HTMLSelectElement; + + propertyGroups = { + circle: getElement("circle-props") as HTMLElement, + rectangle: getElement("rectangle-props") as HTMLElement, + triangle: getElement("triangle-props") as HTMLElement, + }; + + propertyInputs = { + radius: getElement("radius") as HTMLInputElement, + width: getElement("width") as HTMLInputElement, + height: getElement("height") as HTMLInputElement, + base: getElement("base") as HTMLInputElement, + triangleHeight: getElement("triangle-height") as HTMLInputElement, + }; + + resultText = getElement("result-text") as HTMLElement; + resultCard = getElement("result-card") as HTMLElement; + + shapeTypeSelect.oninput = handleShapeSelect; + + --fcc-editable-region-- + + --fcc-editable-region-- +}; + +document.addEventListener("DOMContentLoaded", initializeApp); +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6915abb14ee980884ed93cc7.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6915abb14ee980884ed93cc7.md new file mode 100644 index 00000000000..f81004dc5c9 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6915abb14ee980884ed93cc7.md @@ -0,0 +1,884 @@ +--- +id: 6915abb14ee980884ed93cc7 +title: Step 51 +challengeType: 0 +dashedName: step-51 +--- + +# --description-- + +Inside the loop, set the `oninput` event of each `input` element to the `handleInput` function. + +With that, the shape manager workshop is complete! + +# --hints-- + +You should set `input.oninput` to the `handleInput` function. + +```js +assert.match(code, /input\.oninput\s*=\s*handleInput;?/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +const handleShapeSelect = (e: Event) => { + e.preventDefault(); + clearInputFields(); + + const val = e.currentTarget as HTMLSelectElement; + if (!val) { + return "target value not found"; + } + + const hasSelection = Boolean(val.value); + toggleResultCard(hasSelection); + + chooseShape(val.value); + updateResult(); +}; + +const handleInput = (e: Event) => { + const value = Number((e.target as HTMLInputElement).value); + + if (value < 0) { + alert("Negative values are not allowed."); + clearInputFields(); + } + + updateResult(); +}; + +const initializeApp = () => { + shapeTypeSelect = getElement("shape-type") as HTMLSelectElement; + + propertyGroups = { + circle: getElement ("circle-props") as HTMLElement, + rectangle: getElement("rectangle-props") as HTMLElement, + triangle: getElement("triangle-props") as HTMLElement, + }; + + propertyInputs = { + radius: getElement("radius") as HTMLInputElement, + width: getElement("width") as HTMLInputElement, + height: getElement("height") as HTMLInputElement, + base: getElement("base") as HTMLInputElement, + triangleHeight: getElement("triangle-height") as HTMLInputElement, + }; + + resultText = getElement("result-text") as HTMLElement; + resultCard = getElement("result-card") as HTMLElement; + + shapeTypeSelect.oninput = handleShapeSelect; + + for (const [, input] of Object.entries(propertyInputs)) { +--fcc-editable-region-- + +--fcc-editable-region-- + } +}; + +document.addEventListener("DOMContentLoaded", initializeApp); +``` + +# --solutions-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +const handleShapeSelect = (e: Event) => { + e.preventDefault(); + clearInputFields(); + + const val = e.currentTarget as HTMLSelectElement; + if (!val) { + return "target value not found"; + } + + const hasSelection = Boolean(val.value); + toggleResultCard(hasSelection); + + chooseShape(val.value); + updateResult(); +}; + +const handleInput = (e: Event) => { + const value = Number((e.target as HTMLInputElement).value); + + if (value < 0) { + alert("Negative values are not allowed."); + clearInputFields(); + } + + updateResult(); +}; + +const initializeApp = () => { + shapeTypeSelect = getElement("shape-type") as HTMLSelectElement; + + propertyGroups = { + circle: getElement("circle-props") as HTMLElement, + rectangle: getElement("rectangle-props") as HTMLElement, + triangle: getElement("triangle-props") as HTMLElement, + }; + + propertyInputs = { + radius: getElement("radius") as HTMLInputElement, + width: getElement("width") as HTMLInputElement, + height: getElement("height") as HTMLInputElement, + base: getElement("base") as HTMLInputElement, + triangleHeight: getElement("triangle-height") as HTMLInputElement, + }; + + resultText = getElement ("result-text") as HTMLElement; + resultCard = getElement ("result-card") as HTMLElement; + + shapeTypeSelect.oninput = handleShapeSelect; + + for (const [, input] of Object.entries(propertyInputs)) { + input.oninput = handleInput; + } +}; + +document.addEventListener("DOMContentLoaded", initializeApp); +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/691c252d4dde25176ec1234f.md b/curriculum/challenges/english/blocks/workshop-shape-manager/691c252d4dde25176ec1234f.md new file mode 100644 index 00000000000..7e541df1a07 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/691c252d4dde25176ec1234f.md @@ -0,0 +1,357 @@ +--- +id: 691c252d4dde25176ec1234f +title: Step 23 +challengeType: 0 +dashedName: step-23 +--- + +# --description-- + +Now, add the remaining `base` and `triangleHeight` properties, with each set to `HTMLInputElement`. + +# --hints-- + +Your `propertyInputs` inline type should have a `base` property of type `HTMLInputElement`. + +```js +const explorer = await __helpers.Explorer(code); +const { propertyInputs } = explorer.variables; +assert.isTrue(propertyInputs.annotation.hasTypeProps( + { name: 'base', type: 'HTMLInputElement' } +)); +``` + +Your `propertyInputs` inline type should have a `triangleHeight` property of type `HTMLInputElement`. + +```js +const explorer = await __helpers.Explorer(code); +const { propertyInputs } = explorer.variables; +assert.isTrue(propertyInputs.annotation.hasTypeProps( + { name: 'triangleHeight', type: 'HTMLInputElement' } +)); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { + const radius = Number(radiusInput.value) || 0; + const area = Math.PI * radius ** 2; + resultText.textContent = `Area of Circle: ${area.toFixed(2)}`; +}); + +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +--fcc-editable-region-- +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + +}; +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69207366266db1326ddf3b13.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69207366266db1326ddf3b13.md new file mode 100644 index 00000000000..274058342ac --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69207366266db1326ddf3b13.md @@ -0,0 +1,435 @@ +--- +id: 69207366266db1326ddf3b13 +title: Step 44 +challengeType: 0 +dashedName: step-44 +--- + +# --description-- + +Add the `DOMContentLoaded` event to the root document and pass in `initializeApp` as the callback function to run. This ensures all initialization happens only after the DOM is fully loaded. + +# --hints-- + +You should add the `DOMContentLoaded` event to the root `document`. + +```js +assert.match(code, /document\.addEventListener\(["']DOMContentLoaded["']/) +``` + +You should reference `initializeApp` as the function to run in your event listener. + +```js +assert.match(code, /document\.addEventListener\(["']DOMContentLoaded["'],\s*initializeApp\);?/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +const clearInputFields = () => { + document.querySelectorAll("input").forEach((input) => { + input.value = ""; + }); +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); + } else if (shape === "rectangle") { + result = calculateArea({ + type: "rectangle", + width: Number(propertyInputs.width.value), + height: Number(propertyInputs.height.value), + }); + } else if (shape === "triangle") { + result = calculateArea({ + type: "triangle", + base: Number(propertyInputs.base.value), + height: Number(propertyInputs.triangleHeight.value), + }); + } + + resultText.textContent = result; +} + +const handleShapeSelect = (e: Event) => { + e.preventDefault(); + clearInputFields(); + + const val = e.currentTarget as HTMLSelectElement; + if (!val) { + return "target value not found"; + } + + const hasSelection = Boolean(val.value); + toggleResultCard(hasSelection); + + chooseShape(val.value); + updateResult(); +}; + +const handleInput = (e: Event) => { + const value = Number((e.target as HTMLInputElement).value); + + if (value < 0) { + alert("Negative values are not allowed."); + clearInputFields(); + } + + updateResult(); +}; + +const initializeApp = () => { + +}; + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/692804fad5603e1a78d3b589.md b/curriculum/challenges/english/blocks/workshop-shape-manager/692804fad5603e1a78d3b589.md new file mode 100644 index 00000000000..222756daf73 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/692804fad5603e1a78d3b589.md @@ -0,0 +1,341 @@ +--- +id: 692804fad5603e1a78d3b589 +title: Step 19 +challengeType: 0 +dashedName: step-19 +--- + +# --description-- + +Now, inside the `getElement` function, use the `getElementById()` method to find the element with the given `id`. Assign that to a variable of your choice and return the variable. + +You also need to account for a situation the element might be missing. For that, create an `if` statement that checks if choice variable is falsy. If it is, throw an error with the message `Element not found: ${id}`. + +# --hints-- + +Your `getElement` function should return the element with the given ID. + +```js +const el = getElement("circle-props") +const anotherEl = getElement("result-card") + +const sampleEl = document.getElementById("circle-props") +const sampleElTwo = document.getElementById("result-card") + +assert.strictEqual(el, sampleEl) +assert.strictEqual(anotherEl, sampleElTwo) +``` + +Your `getElement` function should throw an error when the element is not found. Use the provided format for the error message. + +```js +assert.throws(() => getElement("doesNotExist"), /Element not found: doesNotExist/); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { + const radius = Number(radiusInput.value) || 0; + const area = Math.PI * radius ** 2; + resultText.textContent = `Area of Circle: ${area.toFixed(2)}`; +}); + +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { +--fcc-editable-region-- + +--fcc-editable-region-- +}; +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6964bcefeca39b7ac54c8779.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6964bcefeca39b7ac54c8779.md new file mode 100644 index 00000000000..dd109664098 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6964bcefeca39b7ac54c8779.md @@ -0,0 +1,334 @@ +--- +id: 6964bcefeca39b7ac54c8779 +title: Step 17 +challengeType: 0 +dashedName: step-17 +--- + +# --description-- + +Now you need to supply the parameter and its type. Here's an example that shows how to create a function with parameter type in TypeScript: + +```ts +const addNumbers = (a: number, b: number) => { + return a + b; +} +``` + +Give your `getElement` function an `id` of type `string`. For now, leave the return type and the function block empty. + +# --hints-- + +Your `getElement` function should have an `id` parameter of type `string`. + +```js +assert.match(code, /\(id\s*:\s*string\)/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { + const radius = Number(radiusInput.value) || 0; + const area = Math.PI * radius ** 2; + resultText.textContent = `Area of Circle: ${area.toFixed(2)}`; +}); + +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +--fcc-editable-region-- +const getElement = () => { + +}; +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6964cf876e580cc2f22f7588.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6964cf876e580cc2f22f7588.md new file mode 100644 index 00000000000..88a7143cd84 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6964cf876e580cc2f22f7588.md @@ -0,0 +1,388 @@ +--- +id: 6964cf876e580cc2f22f7588 +title: Step 31 +challengeType: 0 +dashedName: step-31 +--- + +# --description-- + +Now handle the case for rectangle and triangle. The formula to use for rectangle is `shape.width * shape.height` and that of triangle is `0.5 * shape.base * shape.height`. + +# --hints-- + +Your `calculateArea` function should return the correct result of calculating the area of a rectangle. + +```js +const rect = { type: "rectangle", width: 9, height: 13 } +const result = calculateArea(rect) + +const match = String(result).match(/-?\d+(\.\d+)?/) +assert.isOk(match) + +const value = Number(match[0]) +const expected = 9 * 13 + +assert.approximately(value, expected, 0.01) +``` + +Your `calculateArea` function should return the correct result of calculating the area of a triangle. + +```js +const triangle = { type: "triangle", base: 11, height: 16 } +const result = calculateArea(triangle) + +const match = String(result).match(/-?\d+(\.\d+)?/) +assert.isOk(match) + +const value = Number(match[0]) +const expected = 0.5 * 11 * 16 + +assert.approximately(value, expected, 0.01) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; +--fcc-editable-region-- + case "rectangle": + return ""; + case "triangle": + return ""; +--fcc-editable-region-- + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6964e083368e3e6e90e8cefc.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6964e083368e3e6e90e8cefc.md new file mode 100644 index 00000000000..4c8ac1215d6 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6964e083368e3e6e90e8cefc.md @@ -0,0 +1,410 @@ +--- +id: 6964e083368e3e6e90e8cefc +title: Step 34 +challengeType: 0 +dashedName: step-34 +--- + +# --description-- + +Using `else if` blocks, check if the shape is `"rectangle"` and `"triangle"`. + +For `"rectangle"`, call the `calculateArea` function with the rectangle type, `width`, and `height` from the inputs. And for `"triangle"`, call the `calculateArea` function with the triangle type, `base`, and `height` from the inputs. + +# --hints-- + +Your `updateResult` function should correctly update the result text when the shape is rectangle. + +```js +propertyInputs = {} + +shapeTypeSelect = document.getElementById("shape-type") +propertyInputs.width = document.getElementById("width") +propertyInputs.height = document.getElementById("height") +resultText = document.getElementById("result-text") + +shapeTypeSelect.value = "rectangle" +propertyInputs.width.value = "4" +propertyInputs.height.value = "6" +updateResult() + +assert.include(resultText.textContent, (4 * 6).toString()) +``` + +Your `updateResult` function should correctly update the result text when the shape is triangle. + +```js +propertyInputs = {} + +shapeTypeSelect = document.getElementById("shape-type") +propertyInputs.base = document.getElementById("base") +propertyInputs.triangleHeight = document.getElementById("triangle-height") +resultText = document.getElementById("result-text") + +shapeTypeSelect.value = "triangle" +propertyInputs.base.value = "10" +propertyInputs.triangleHeight.value = "8" +updateResult() + +assert.include(resultText.textContent, (0.5 * 10 * 8).toString()) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; + +let resultText: HTMLElement; +let resultCard: HTMLElement; + +const chooseShape = (shapeType: string) => { + Object.entries(propertyGroups).forEach(([name, group]) => { + if (name === shapeType) { + group.classList.remove("hidden"); + } else { + group.classList.add("hidden"); + } + }); +}; + +const toggleResultCard = (show: boolean) => { + if (show) { + resultCard.classList.add("visible"); + } else { + resultCard.classList.remove("visible"); + } +}; + +const calculateArea = (shape: Shapes): string => { + switch (shape.type) { + case "circle": + return `Area of Circle: ${(Math.PI * shape.radius ** 2).toFixed(2)}`; + case "rectangle": + return `Area of Rectangle: ${shape.width * shape.height}`; + case "triangle": + return `Area of Triangle: ${0.5 * shape.base * shape.height}`; + default: + const _nonExistent: never = shape; + return _nonExistent; + } +}; + +function updateResult() { + const shape = shapeTypeSelect.value; + let result: string = ""; + + if (shape === "circle") { + result = calculateArea({ + type: "circle", + radius: Number(propertyInputs.radius.value), + }); +--fcc-editable-region-- + } + +--fcc-editable-region-- + resultText.textContent = result; +} +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69934d84e861d41ee06a84a3.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69934d84e861d41ee06a84a3.md new file mode 100644 index 00000000000..2cb409244cf --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69934d84e861d41ee06a84a3.md @@ -0,0 +1,325 @@ +--- +id: 69934d84e861d41ee06a84a3 +title: Step 1 +challengeType: 0 +dashedName: step-1 +demoType: onLoad +--- + +# --description-- + +In this workshop, you'll practice the fundamentals of TypeScript by building a shape manager that calculates the areas of a circle, triangle, and rectangle. + +The HTML and CSS for the project have been provided for you so you can focus on the TypeScript. + +To get started, you need to reference the elements from the HTML to start adding interactivity to them. + +Use `document.getElementById()` method to select the elements with the ids `result-card` and `result-text`. Assign them to the constants `resultCard` and `resultText` respectively. + +Since the elements exist in the HTML, use the non-null assertion operator `!` at the end of each `getElementById()` method. + +# --hints-- + +You should select the element with id `result-card`. + +```js +assert.match(__helpers.removeJSComments(code), /document\.getElementById\(\s*['"`]result-card['"`]\s*\)/); +``` + +You should assign the selection to a `resultCard` constant. + +```js +assert.match(__helpers.removeJSComments(code), /const\s+resultCard\s*=/); +``` + +You should use the non-null assertion operator `!` on the `result-card` selection. + +```js +assert.match(__helpers.removeJSComments(code), /document\.getElementById\(\s*['"`]result-card['"`]\s*\)!/); +``` + +You should select the element with id `result-text`. + +```js +assert.match(__helpers.removeJSComments(code), /document\.getElementById\(\s*['"`]result-text['"`]\s*\)/); +``` + +You should assign the selection to a `resultText` constant. + +```js +assert.match(__helpers.removeJSComments(code), /const\s+resultText\s*=/); +``` + +You should use the non-null assertion operator `!` on the `result-text` selection. + +```js +assert.match(__helpers.removeJSComments(code), /document\.getElementById\(\s*['"`]result-text['"`]\s*\)!/); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69934d8f1d9687e903e7aad2.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69934d8f1d9687e903e7aad2.md new file mode 100644 index 00000000000..2b7d75ccf24 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69934d8f1d9687e903e7aad2.md @@ -0,0 +1,300 @@ +--- +id: 69934d8f1d9687e903e7aad2 +title: Step 2 +challengeType: 0 +dashedName: step-2 +--- + +# --description-- + +To make sure the setup is working, you should make the result card visible immediately. + +If you take a look at the CSS, there's a class `visible` that controls opacity. + +Access the `classList` property of `resultCard` and call the `add` method with the string `"visible"`. + +After doing that, you should see a card appear under the shape selector. + +# --hints-- + +You should access the `classList` of `resultCard`. + +```js +assert.match(__helpers.removeJSComments(code), /resultCard\.classList/); +``` + +You should call the `add` method with `visible`. + +```js +assert.match(__helpers.removeJSComments(code), /resultCard\.classList\.add\(\s*['"`]visible['"`]\s*\)/); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69934d945aecf10f2b9192a4.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69934d945aecf10f2b9192a4.md new file mode 100644 index 00000000000..ff0ea978c5f --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69934d945aecf10f2b9192a4.md @@ -0,0 +1,291 @@ +--- +id: 69934d945aecf10f2b9192a4 +title: Step 3 +challengeType: 0 +dashedName: step-3 +--- + +# --description-- + +Now you should set some initial text so you know something is going on in the UI. + +Set the `textContent` property of `resultText` to the string `"Enter a radius to see the area"`. + +# --hints-- + +You should set the `textContent` property of the `resultText` to `Enter a radius to see the area`. + +```js +assert.match(__helpers.removeJSComments(code), /resultText\.textContent\s*=\s*['"`]Enter a radius to see the area['"`]/); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +--fcc-editable-region-- + +--fcc-editable-region-- +resultCard.classList.add("visible"); +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69934d9a6c9d9df8cb7d9984.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69934d9a6c9d9df8cb7d9984.md new file mode 100644 index 00000000000..9a01c116aec --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69934d9a6c9d9df8cb7d9984.md @@ -0,0 +1,314 @@ +--- +id: 69934d9a6c9d9df8cb7d9984 +title: Step 4 +challengeType: 0 +dashedName: step-4 +--- + +# --description-- + +Now you can start by implementing the area of circle calculation. You would have to select the elements for it and cast the selection using the `as` keyword. + +The `as` keyword is used for type assertions. It tells Typescript to treat a value as a specific type without changing the value itself. This is useful when Typescript cannot automatically know the exact type of a value. Here's a typical usage of it: + +```ts +const container = document.getElementById("main-section") as HTMLElement; +``` + +Get the element with the id `circle-props` and assign it to a constant named `circleGroup`. This is the `div` element that contains the circle inputs. Cast this element as an `HTMLElement` using the `as` keyword. + +# --hints-- + +You should get the element with id `circle-props`. + +```js +assert.match(__helpers.removeJSComments(code), /document\.getElementById\(\s*['"`]circle-props['"`]\)/); +``` + +You should assign the selection to a constant `circleGroup`. + +```js +assert.match(__helpers.removeJSComments(code), /const\s+circleGroup\s*=/); +``` + +You should cast the `circleGroup` selection as `HTMLElement`. + +```js +const explorer = await __helpers.Explorer(code); +const { circleGroup } = explorer.variables; +assert.isTrue(circleGroup.value.hasCast("HTMLElement")); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +--fcc-editable-region-- + +--fcc-editable-region-- + +resultCard.classList.add("visible"); +resultText.textContent = "Enter a radius to see the area"; +``` + diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69934d9e75af58a7c2d42afd.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69934d9e75af58a7c2d42afd.md new file mode 100644 index 00000000000..1ca1da9906d --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69934d9e75af58a7c2d42afd.md @@ -0,0 +1,306 @@ +--- +id: 69934d9e75af58a7c2d42afd +title: Step 5 +challengeType: 0 +dashedName: step-5 +--- + +# --description-- + +You also need to access the input field for the radius. So, select the element with the id `radius` and assign it to a constant named `radiusInput`. + +Cast this element as `HTMLInputElement` using the `as` keyword, so TypeScript knows it has a `value` property. + +# --hints-- + +You should get the element with id `radius`. + +```js +assert.match(__helpers.removeJSComments(code), /document\.getElementById\(\s*['"`]radius['"`]\)/); +``` + +You should assign the selection to a constant `radiusInput`. + +```js +assert.match(__helpers.removeJSComments(code), /const\s+radiusInput\s*=/); +``` + +You should cast the `radius` element selection as `HTMLInputElement`. + +```js +assert.match(__helpers.removeJSComments(code), /document\.getElementById\(\s*['"`]radius['"`]\)\s+as\s+HTMLInputElement/); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +--fcc-editable-region-- + +--fcc-editable-region-- + +resultCard.classList.add("visible"); +resultText.textContent = "Enter a radius to see the area"; +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69934da2bdf08dc065664ae3.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69934da2bdf08dc065664ae3.md new file mode 100644 index 00000000000..6f25253a5d3 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69934da2bdf08dc065664ae3.md @@ -0,0 +1,295 @@ +--- +id: 69934da2bdf08dc065664ae3 +title: Step 6 +challengeType: 0 +dashedName: step-6 +--- + +# --description-- + +The circle inputs are hidden by default through the CSS. To show them, access the `classList` of `circleGroup` and call the `remove` method with the string `hidden`. + +For now, selecting the other shapes won't reveal their input fields. You will handle that later. + +# --hints-- + +You should call `remove` on the `classList` of `circleGroup` with `hidden` as the argument. + +```js +assert.match(__helpers.removeJSComments(code), /circleGroup\.classList\.remove\(\s*['"`]hidden['"`]\)/); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +--fcc-editable-region-- + +--fcc-editable-region-- +resultText.textContent = "Enter a radius to see the area"; +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69934da7fc6f3a9937b5197f.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69934da7fc6f3a9937b5197f.md new file mode 100644 index 00000000000..a6f74777a34 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69934da7fc6f3a9937b5197f.md @@ -0,0 +1,304 @@ +--- +id: 69934da7fc6f3a9937b5197f +title: Step 7 +challengeType: 0 +dashedName: step-7 +--- + +# --description-- + +Now you should work on calculating the area whenever the user enters a value into the radius input. + +Add the `input` event to `radiusInput` using the `addEventListener()` method. For now, pass in an empty arrow function as the second argument. + +# --hints-- + +You should use the `addEventListener()` method to add an `input` event to the `radiusInput` element. + +```js +assert.match(__helpers.removeJSComments(code), /radiusInput\.addEventListener\(\s*['"`]input['"`]/); +``` + +You should pass in an empty arrow function as the second parameter of your `input` event listener. + +```js +assert.match(__helpers.removeJSComments(code), /radiusInput\.addEventListener\(\s*['"`]input['"`]\s*,\s*\(\)\s*=>\s*\{\s*\}\)/) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +--fcc-editable-region-- + +--fcc-editable-region-- +``` + diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69934db03655ea8a2b70f46d.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69934db03655ea8a2b70f46d.md new file mode 100644 index 00000000000..cb2ae997529 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69934db03655ea8a2b70f46d.md @@ -0,0 +1,312 @@ +--- +id: 69934db03655ea8a2b70f46d +title: Step 8 +challengeType: 0 +dashedName: step-8 +--- + +# --description-- + +Inside the event listener, you need to get the value of the radius. Values from HTML inputs are always strings, so you must convert it. + +Create a constant named `radius`. Assign it the result of calling `Number()` on `radiusInput.value`. Also, handle invalid input like empty strings by using the logical OR operator `||` to default to `0`. + +# --hints-- + +You should create a `radius` constant. + +```js +assert.match(__helpers.removeJSComments(code), /const\s+radius\s*=/); +``` + +You should set the `radius` constant to `radiusInput.value` and make sure it is converted to a number. + +```js +assert.match(__helpers.removeJSComments(code), /const\s+radius\s*=\s*Number\(radiusInput\.value\)/) +``` + +Your `radius` constant should default to `0`. + +```js +assert.match(__helpers.removeJSComments(code), /const\s+radius\s*=\s*Number\(radiusInput\.value\)\s*\|\|\s*0/); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { +--fcc-editable-region-- + +--fcc-editable-region-- +}); +``` + diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69934db46439c3e19ae8073e.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69934db46439c3e19ae8073e.md new file mode 100644 index 00000000000..483a2c92e7f --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69934db46439c3e19ae8073e.md @@ -0,0 +1,306 @@ +--- +id: 69934db46439c3e19ae8073e +title: Step 9 +challengeType: 0 +dashedName: step-9 +--- + +# --description-- + +It's time to calculate the area of the circle. Remember the formula for this is πr², which can be programmatically represented with `Math.PI * radius ** 2;`. + +Inside the event listener, create a constant named `area`. For the value, use `Math.PI` multiplied by `radius` to the power of `2`. + +# --hints-- + +You should create a constant named `area`. + +```js +assert.match(__helpers.removeJSComments(code), /const\s+area\s*=/); +``` + +You should set the value of the `area` constant to the programmatic representation of the formula for calculating the area of a circle. + +```js +assert.match(__helpers.removeJSComments(code), /Math\.PI\s*\*\s*radius\s*\*\*\s*2/); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { + const radius = Number(radiusInput.value) || 0; +--fcc-editable-region-- + +--fcc-editable-region-- +}); +``` diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/69934dbd9f8b2a16ff8a571f.md b/curriculum/challenges/english/blocks/workshop-shape-manager/69934dbd9f8b2a16ff8a571f.md new file mode 100644 index 00000000000..880e17d8ecf --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/69934dbd9f8b2a16ff8a571f.md @@ -0,0 +1,300 @@ +--- +id: 69934dbd9f8b2a16ff8a571f +title: Step 10 +challengeType: 0 +dashedName: step-10 +--- + +# --description-- + +To display the result, set the `textContent` of `resultText` to a template literal showing `Area of Circle: `, followed by the `area` formatted to 2 decimal places using the `toFixed()` method. + +# --hints-- + +You should set the `textContent` of `resultText` to show the calculated circle area. + +```js +assert.match(__helpers.removeJSComments(code), /resultText\.textContent\s*=\s*\`Area\s+of\s+Circle:\s+\$\{area\.toFixed\(2\)\}`/); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { + const radius = Number(radiusInput.value) || 0; + const area = Math.PI * radius ** 2; +--fcc-editable-region-- + +--fcc-editable-region-- +}); +``` + diff --git a/curriculum/challenges/english/blocks/workshop-shape-manager/6994d9f10efe45942d548629.md b/curriculum/challenges/english/blocks/workshop-shape-manager/6994d9f10efe45942d548629.md new file mode 100644 index 00000000000..f8f198e7a27 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-shape-manager/6994d9f10efe45942d548629.md @@ -0,0 +1,351 @@ +--- +id: 6994d9f10efe45942d548629 +title: Step 24 +challengeType: 0 +dashedName: step-24 +--- + +# --description-- + +You've been using some early circle-only code to display results and test the UI quickly. + +Now it's time to introduce the proper shape types and structure for the project, so you don't need the area of circle calculation logic anymore. + +Remove the earlier circle-specific setup from the `resultCard` variable to the input event listener. + +# --hints-- + +You should remove the earlier logic calculating the area of a circle with the variables. + +```js +assert.notMatch( + __helpers.removeJSComments(code), + /const\s+resultCard\s*=|const\s+resultText\s*=|const\s+\w+\s*=\s*document\.getElementById\("circle-props"\)|const\s+radiusInput\s*=|radiusInput\.addEventListener|Enter a radius to see the area|Math\.PI\s*\*\s*radius/ +) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + Shape Manager + + +
+
+

Shape Manager

+

Calculate areas of geometric shapes

+
+ +
+
+

Shape Calculator

+
+
+
+ + +
+ +
+ + + + + + +
+
+
+ +
+
+
+
+
+
+
+ + + +``` + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Helvetica Neue", Arial, sans-serif; + background: linear-gradient(to bottom right, #f8fafc, #f1f5f9); + color: #334155; + min-height: 100vh; + padding: 1rem; +} + +.container { + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.text-center { + text-align: center; +} + +.mb-8 { + margin-bottom: 2rem; +} + +h1 { + font-size: 2.25rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #475569; +} + +.card { + background-color: white; + border-radius: 0.75rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; +} + +.card-content { + padding: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +.form-group { + margin-bottom: 1.5rem; +} + +.label { + display: block; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 1rem; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.input:focus, +.select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +.result-card-content { + display: flex; + align-items: center; + gap: 1rem; +} +.result-icon-container { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} +.result-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 9999px; +} + +#result-text { + flex-grow: 1; +} +#result-text h3 { + font-size: 1.25rem; + font-weight: 700; +} +#result-text p { + color: #475569; + text-transform: capitalize; +} + +.hidden { + display: none; +} + +/* Hide the result card until a shape is selected */ +#result-card { + display: none; + opacity: 0; + transition: opacity 0.15s ease-in-out; +} + +/* Add this class to show the result card */ +#result-card.visible { + display: block; + opacity: 1; +} +``` + +```ts +--fcc-editable-region-- +const resultCard = document.getElementById("result-card")!; +const resultText = document.getElementById("result-text")!; +const circleGroup = document.getElementById("circle-props") as HTMLElement; +const radiusInput = document.getElementById("radius") as HTMLInputElement; + +resultCard.classList.add("visible"); +circleGroup.classList.remove("hidden"); +resultText.textContent = "Enter a radius to see the area"; + +radiusInput.addEventListener("input", () => { + const radius = Number(radiusInput.value) || 0; + const area = Math.PI * radius ** 2; + resultText.textContent = `Area of Circle: ${area.toFixed(2)}`; +}); +--fcc-editable-region-- + +interface Shape { + type: string; +} + +interface Circle extends Shape { + type: "circle"; + radius: number; +} + +interface Rectangle extends Shape { + type: "rectangle"; + width: number; + height: number; +} + +interface Triangle extends Shape { + type: "triangle"; + base: number; + height: number; +} + +type Shapes = Circle | Triangle | Rectangle; + +const getElement = (id: string): HTMLElement => { + const el = document.getElementById(id); + if (!el) throw new Error(`Element not found: ${id}`); + return el; +}; + +let shapeTypeSelect: HTMLSelectElement; + +let propertyGroups: { + circle: HTMLElement; + rectangle: HTMLElement; + triangle: HTMLElement; +}; + +let propertyInputs: { + radius: HTMLInputElement; + width: HTMLInputElement; + height: HTMLInputElement; + base: HTMLInputElement; + triangleHeight: HTMLInputElement; +}; +``` diff --git a/curriculum/structure/blocks/workshop-shape-manager.json b/curriculum/structure/blocks/workshop-shape-manager.json new file mode 100644 index 00000000000..75f0d04decd --- /dev/null +++ b/curriculum/structure/blocks/workshop-shape-manager.json @@ -0,0 +1,62 @@ +{ + "isUpcomingChange": true, + "dashedName": "workshop-shape-manager", + "helpCategory": "JavaScript", + "blockLayout": "challenge-grid", + "challengeOrder": [ + { "id": "69934d84e861d41ee06a84a3", "title": "Step 1" }, + { "id": "69934d8f1d9687e903e7aad2", "title": "Step 2" }, + { "id": "69934d945aecf10f2b9192a4", "title": "Step 3" }, + { "id": "69934d9a6c9d9df8cb7d9984", "title": "Step 4" }, + { "id": "69934d9e75af58a7c2d42afd", "title": "Step 5" }, + { "id": "69934da2bdf08dc065664ae3", "title": "Step 6" }, + { "id": "69934da7fc6f3a9937b5197f", "title": "Step 7" }, + { "id": "69934db03655ea8a2b70f46d", "title": "Step 8" }, + { "id": "69934db46439c3e19ae8073e", "title": "Step 9" }, + { "id": "69934dbd9f8b2a16ff8a571f", "title": "Step 10" }, + { "id": "68ff37f55fb2122df640cd78", "title": "Step 11" }, + { "id": "68ff41972b5bb84f802ee6ca", "title": "Step 12" }, + { "id": "68ff420e11f90153864d39e2", "title": "Step 13" }, + { "id": "68ff42692df48e5ab1429b79", "title": "Step 14" }, + { "id": "68ff42cfa7004f6353fbb5f0", "title": "Step 15" }, + { "id": "68ff495abf98da70d59e6f44", "title": "Step 16" }, + { "id": "6964bcefeca39b7ac54c8779", "title": "Step 17" }, + { "id": "68ff4d778884847a60af06a5", "title": "Step 18" }, + { "id": "692804fad5603e1a78d3b589", "title": "Step 19" }, + { "id": "6914547efa31c01853d24fb0", "title": "Step 20" }, + { "id": "6914579719bb4621b7e3f0bd", "title": "Step 21" }, + { "id": "6914582d975d2d277026a8c4", "title": "Step 22" }, + { "id": "691c252d4dde25176ec1234f", "title": "Step 23" }, + { "id": "6994d9f10efe45942d548629", "title": "Step 24" }, + { "id": "691458e6fdf1a6300a9f6105", "title": "Step 25" }, + { "id": "69145951b67f9b34b66215d3", "title": "Step 26" }, + { "id": "69145a9503ed163b83adbbde", "title": "Step 27" }, + { "id": "69145aefce21f93ed77b626c", "title": "Step 28" }, + { "id": "69145b634bdd4245697bf344", "title": "Step 29" }, + { "id": "69145bb447c0c14bf43bff1f", "title": "Step 30" }, + { "id": "6964cf876e580cc2f22f7588", "title": "Step 31" }, + { "id": "69145da9693cb9620fffa595", "title": "Step 32" }, + { "id": "69145fa5538ad66a9cc848a8", "title": "Step 33" }, + { "id": "6964e083368e3e6e90e8cefc", "title": "Step 34" }, + { "id": "691475d76745ed889b1a39cb", "title": "Step 35" }, + { "id": "691476d751bc8592c46da5be", "title": "Step 36" }, + { "id": "691598c8e3b1671f01f70c7a", "title": "Step 37" }, + { "id": "69159948e52168265730f74f", "title": "Step 38" }, + { "id": "69159a1480af042cdc6bb7ff", "title": "Step 39" }, + { "id": "69159aef42f9f83752e8e80a", "title": "Step 40" }, + { "id": "6915a0e270dd52416cd4ae5a", "title": "Step 41" }, + { "id": "6915a2eaf470eb4734728e9e", "title": "Step 42" }, + { "id": "6915a372c993bf4c92aee240", "title": "Step 43" }, + { "id": "69207366266db1326ddf3b13", "title": "Step 44" }, + { "id": "6915a641acd82d52d8040de0", "title": "Step 45" }, + { "id": "6915a808bf21675d5640a4e3", "title": "Step 46" }, + { "id": "6915a89c51615a64942515aa", "title": "Step 47" }, + { "id": "6915a8fed59e336bc937f5f2", "title": "Step 48" }, + { "id": "6915aa2ea2f30176eccd9307", "title": "Step 49" }, + { "id": "6915ab1afa7f7d7ecaef5050", "title": "Step 50" }, + { "id": "6915abb14ee980884ed93cc7", "title": "Step 51" } + ], + "blockLabel": "workshop", + "usesMultifileEditor": true, + "hasEditableBoundaries": true +} diff --git a/curriculum/structure/superblocks/front-end-development-libraries-v9.json b/curriculum/structure/superblocks/front-end-development-libraries-v9.json index a0e3be4c5ce..fe4386b4994 100644 --- a/curriculum/structure/superblocks/front-end-development-libraries-v9.json +++ b/curriculum/structure/superblocks/front-end-development-libraries-v9.json @@ -94,9 +94,10 @@ "workshop-type-safe-user-profile", "workshop-type-safe-math-toolkit", "lecture-understanding-type-composition", + "workshop-shape-manager", "lecture-working-with-generics-and-type-narrowing", - "lab-product-showcase", "workshop-bug-emoji-picker", + "lab-product-showcase", "lecture-working-with-typescript-configuration-files", "review-typescript", "quiz-typescript"