diff --git a/README.md b/README.md index b87cb00..cf19cf3 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,38 @@ -# Getting Started with Create React App +# Frontend Mentor - Body Mass Index Calculator solution -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). +This is a solution to the [Body Mass Index Calculator challenge on Frontend Mentor](https://www.frontendmentor.io/challenges/body-mass-index-calculator-brrBkfSz1T). Frontend Mentor challenges help you improve your coding skills by building realistic projects. -## Available Scripts +## Table of contents +- [The challenge](#the-challenge) +- [Screenshot](#screenshot) +- [Links](#links) +- [Built with](#built-with) -In the project directory, you can run: +### The challenge -### `npm start` +Users should be able to: -Runs the app in the development mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in the browser. +- Select whether they want to use metric or imperial units +- Enter their height and weight +- See their BMI result, with their weight classification and healthy weight range +- View the optimal layout for the interface depending on their device's screen size +- See hover and focus states for all interactive elements on the page -The page will reload if you make edits.\ -You will also see any lint errors in the console. +### Screenshot -### `npm test` +![myScreenshot](./src/screenshot.png) -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. +### Links -### `npm run build` +- [Solution URL](https://github.com/mriyaz/Body-mass-index-calculator-challenge) +- [Live Site URL](https://Body-mass-index-calculator-challenge.vercel.app/) -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. +### Built with -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! +- HTML +- CSS +- Flexbox +- TypeScript +- [React](https://reactjs.org/) - JS library +- Tailwind CSS -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). diff --git a/package-lock.json b/package-lock.json index ef14abd..44e888f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,11 @@ "react-scripts": "5.0.1", "typescript": "^4.9.5", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "autoprefixer": "^10.4.16", + "postcss": "^8.4.32", + "tailwindcss": "^3.4.0" } }, "node_modules/@aashutoshrathi/word-wrap": { diff --git a/package.json b/package.json index 523344a..08166ca 100644 --- a/package.json +++ b/package.json @@ -39,5 +39,10 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "autoprefixer": "^10.4.16", + "postcss": "^8.4.32", + "tailwindcss": "^3.4.0" } } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/src/App.tsx b/src/App.tsx index a53698a..b3a6e01 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,24 +1,23 @@ import React from 'react'; -import logo from './logo.svg'; +import Header from './components/Header'; +import BmiCalculator from './components/BmiCalculator'; +import BmiMeaning from './components/BmiMeaning'; +import BmiTips from './components/BmiTips'; +import BmiLimitations from './components/BmiLimitations'; + import './App.css'; function App() { return ( -
-
- logo -

- Edit src/App.tsx and save to reload. -

- - Learn React - -
+
+
+
+ +
+ + + +
); } diff --git a/assets/fonts/Inter-Regular.ttf b/src/assets/fonts/Inter-Regular.ttf similarity index 100% rename from assets/fonts/Inter-Regular.ttf rename to src/assets/fonts/Inter-Regular.ttf diff --git a/assets/fonts/Inter-SemiBold.ttf b/src/assets/fonts/Inter-SemiBold.ttf similarity index 100% rename from assets/fonts/Inter-SemiBold.ttf rename to src/assets/fonts/Inter-SemiBold.ttf diff --git a/assets/images/favicon-32x32.png b/src/assets/images/favicon-32x32.png similarity index 100% rename from assets/images/favicon-32x32.png rename to src/assets/images/favicon-32x32.png diff --git a/assets/images/icon-age.svg b/src/assets/images/icon-age.svg similarity index 100% rename from assets/images/icon-age.svg rename to src/assets/images/icon-age.svg diff --git a/assets/images/icon-eating.svg b/src/assets/images/icon-eating.svg similarity index 100% rename from assets/images/icon-eating.svg rename to src/assets/images/icon-eating.svg diff --git a/assets/images/icon-exercise.svg b/src/assets/images/icon-exercise.svg similarity index 100% rename from assets/images/icon-exercise.svg rename to src/assets/images/icon-exercise.svg diff --git a/assets/images/icon-gender.svg b/src/assets/images/icon-gender.svg similarity index 100% rename from assets/images/icon-gender.svg rename to src/assets/images/icon-gender.svg diff --git a/assets/images/icon-muscle.svg b/src/assets/images/icon-muscle.svg similarity index 100% rename from assets/images/icon-muscle.svg rename to src/assets/images/icon-muscle.svg diff --git a/assets/images/icon-pregnancy.svg b/src/assets/images/icon-pregnancy.svg similarity index 100% rename from assets/images/icon-pregnancy.svg rename to src/assets/images/icon-pregnancy.svg diff --git a/assets/images/icon-race.svg b/src/assets/images/icon-race.svg similarity index 100% rename from assets/images/icon-race.svg rename to src/assets/images/icon-race.svg diff --git a/assets/images/icon-sleep.svg b/src/assets/images/icon-sleep.svg similarity index 100% rename from assets/images/icon-sleep.svg rename to src/assets/images/icon-sleep.svg diff --git a/assets/images/image-man-eating.webp b/src/assets/images/image-man-eating.webp similarity index 100% rename from assets/images/image-man-eating.webp rename to src/assets/images/image-man-eating.webp diff --git a/assets/images/logo.svg b/src/assets/images/logo.svg similarity index 100% rename from assets/images/logo.svg rename to src/assets/images/logo.svg diff --git a/assets/images/pattern-curved-line-left.svg b/src/assets/images/pattern-curved-line-left.svg similarity index 100% rename from assets/images/pattern-curved-line-left.svg rename to src/assets/images/pattern-curved-line-left.svg diff --git a/assets/images/pattern-curved-line-right.svg b/src/assets/images/pattern-curved-line-right.svg similarity index 100% rename from assets/images/pattern-curved-line-right.svg rename to src/assets/images/pattern-curved-line-right.svg diff --git a/src/components/BmiCalculator.tsx b/src/components/BmiCalculator.tsx new file mode 100644 index 0000000..2631bf0 --- /dev/null +++ b/src/components/BmiCalculator.tsx @@ -0,0 +1,199 @@ +// Import statements +import React, { useState, useEffect } from 'react'; +import '../App.css'; // Make sure to have an App.css for additional styling + + +// BMIFormState interface definition +interface BMIFormState { + system: 'metric' | 'imperial'; + height: { cm: number; feet: number; inches: number }; + weight: { kg: number; stones: number; pounds: number }; + bmi?: number; + category?: string; + idealWeight?: string; +} + +// BmiCalculator functional component +const BmiCalculator: React.FC = () => { + // State initialization for formState + const [formState, setFormState] = useState({ + // Initial state setup + system: 'imperial', + height: { cm: 0, feet: 0, inches: 0 }, + weight: { kg: 0, stones: 0, pounds: 0 } + }); + + // useEffect hook for BMI calculation + useEffect(() => { + // Function to calculate BMI + const calculateBMI = () => { + //Variables + const { system, height, weight } = formState; + let bmi = 0; + + // BMI calculation for metric system + if (system === 'metric') { + if (height.cm > 0 && weight.kg > 0) { + bmi = weight.kg / ((height.cm / 100) ** 2); + } + } else { + // BMI calculation for imperial system + const totalHeightInInches = height.feet * 12 + height.inches; + const totalWeightInPounds = weight.stones * 14 + weight.pounds; + if (totalHeightInInches > 0 && totalWeightInPounds > 0) { + bmi = (totalWeightInPounds / (totalHeightInInches ** 2)) * 703; + } + } + + // Setting the calculated BMI, category, and ideal weight in state + const category = getBMICategory(bmi); + const idealWeight = getIdealWeight(height, system); + setFormState(prev => ({ ...prev, bmi, category, idealWeight })); + } + + // Call the function to calculate BMI + calculateBMI(); + + }, [formState.height, formState.weight, formState.system]); + + // Function to handle measurement system change (metric/imperial) + const handleSystemChange = (system: 'metric' | 'imperial') => { + // Update state based on selected system & reset height & weight + setFormState({ + ...formState, system, height: { cm: 0, feet: 0, inches: 0 }, + weight: { kg: 0, stones: 0, pounds: 0 } + }) + + }; + + // Function to handle input changes for height and weight fields + const handleInputChange = (name: string, value: number) => { + // Update state based on input change + setFormState((prev) => ({ + ...prev, + height: { ...prev.height, [name]: value }, + weight: { ...prev.weight, [name]: value }, + })) + }; + + // Function to render individual input fields + const renderInputField = ( + name: string, + label: string, + value: number, + onChange: (name: string, value: number) => void + ) => ( + // Return JSX for input field +
+ onChange(name, parseFloat(e.target.value))} + /> + {label} +
+ ); + + // Function to get BMI category + const getBMICategory = (bmi: number): string => { + // Determine and return BMI category + if (bmi < 18.5) return 'Underweight'; + if (bmi >= 18.5 && bmi < 24.9) return 'Healthy weight'; + if (bmi >= 25 && bmi < 29.9) return 'Overweight'; + return 'Obesity'; + }; + + // Function to get ideal weight range + const getIdealWeight = (height: { feet: number; inches: number; cm: number }, system: 'metric' | 'imperial'): string => { + // Calculate and return ideal weight range + const idealBMI = 22.5; + if (system === 'metric' && height.cm) { + const idealKg = idealBMI * ((height.cm / 100) ** 2); + return `${(idealKg - 5).toFixed(1)}kg - ${(idealKg + 5).toFixed(1)}kg`; + } else if (height.feet && height.inches) { + const totalHeightInInches = height.feet * 12 + height.inches; + const idealLbs = idealBMI * (totalHeightInInches ** 2) / 703; + const idealSt = idealLbs / 14; + return `${Math.floor(idealSt)}st ${Math.round(idealLbs % 14)}lbs - ${Math.floor(idealSt + 0.5)}st ${Math.round((idealLbs + 10) % 14)}lbs`; + } + return ''; + }; + + // Component JSX return + return ( + // Main container for the BMI Calculator +
+ {/* // Heading for the BMI Calculator */} +

Enter your details below

+ + {/* // Radio buttons for system selection (Metric and Imperial) */} +
+ {/* // Radio button for Metric system */} + + {/* // Radio button for Imperial system */} + +
+ + {/* // Conditional rendering of input fields based on selected measurement system */} + {formState.system === 'metric' ? ( + // Input fields for Metric system +
+
Height + {renderInputField('cm', 'cm', formState.height.cm, handleInputChange)} +
+
+ Weight + {renderInputField('kg', 'kg', formState.weight.kg, handleInputChange)} +
+
+ ) : ( + +
+ Height +
+ {renderInputField('feet', 'ft', formState.height.feet, handleInputChange)} + {renderInputField('inches', 'in', formState.height.inches, handleInputChange)} +
+ Weight +
+ {renderInputField('stones', 'st', formState.weight.stones, handleInputChange)} + {renderInputField('pounds', 'lbs', formState.weight.pounds, handleInputChange)} +
+
+ )} + + {/* // Result display section showing BMI, category, and ideal weight range */} +
+ {/* // Conditional rendering for displaying BMI result or welcome message */} + {formState.bmi !== 0 && formState.bmi !== undefined && formState.bmi != null ? ( +
+ Your BMI is... +

{formState.bmi.toFixed(2)}

+

Your BMI suggests that you are {formState.category}. Your ideal weight is between {formState.idealWeight}.

+ +
+ ) : ( +
+

Welcome!

+

Enter your height and weight, and you'll see your BMI result here.

+
+ )} +
+
+ ); +}; + +// Export the BmiCalculator component +export default BmiCalculator; diff --git a/src/components/BmiLimitations.tsx b/src/components/BmiLimitations.tsx new file mode 100644 index 0000000..b1cebb7 --- /dev/null +++ b/src/components/BmiLimitations.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import genderIcon from '../assets/images/icon-gender.svg'; +import ageIcon from '../assets/images/icon-age.svg'; +import muscleIcon from '../assets/images/icon-muscle.svg'; +import pregnancyIcon from '../assets/images/icon-pregnancy.svg'; +import raceIcon from '../assets/images/icon-race.svg'; + +type LimitationCardProps = { + icon: string; + alt: string; + heading: string, + ptext: string, + classname?: string, + +}; + +const LimitationCard: React.FC = ({ icon, alt, heading, ptext, classname }) => ( +
+
+ {alt} +

{heading} +

+
+

{ptext} +

+
+) + +const BmiLimitations = () => { + + return ( +
+
+

Limitations of BMI

+

+ Although BMI is often a practical indicator of healthy weight, it is not suited for every person. Specific groups should carefully consider their BMI outcomes, and in certain cases, the measurement may not be beneficial to use. +

+
+ +
+ + + + + + + + + + + +
+ +
+ ) +} + +export default BmiLimitations \ No newline at end of file diff --git a/src/components/BmiMeaning.tsx b/src/components/BmiMeaning.tsx new file mode 100644 index 0000000..3ecf534 --- /dev/null +++ b/src/components/BmiMeaning.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import manEatingImg from '../assets/images/image-man-eating.webp' + +const BmiMeaning = () => { + return ( +
+ + Man eating + +
+

What your BMI result means

+

+ A BMI range of 18.5 to 24.9 is considered a 'healthy weight.' Maintaining a healthy weight may lower your chances of experiencing health issues later on, such as obesity and type 2 diabetes. Aim for a nutritious diet with reduced fat and sugar content, incorporating ample fruits and vegetables. Additionally, strive for regular physical activity, ideally about 30 minutes daily for five days a week. +

+
+
+ ) +} + +export default BmiMeaning \ No newline at end of file diff --git a/src/components/BmiTips.tsx b/src/components/BmiTips.tsx new file mode 100644 index 0000000..a6273e4 --- /dev/null +++ b/src/components/BmiTips.tsx @@ -0,0 +1,41 @@ +import React from 'react' +import eatingIcon from '../assets/images/icon-eating.svg'; +import exerciseIcon from '../assets/images/icon-exercise.svg'; +import sleepIcon from '../assets/images/icon-sleep.svg'; + + +type TipRendererProps = { + icon: string; + alt: string; + heading: string, + ptext: string + +}; + +const BmiTips = () => { + + const TipRenderer: React.FC = ({ icon, alt, heading, ptext }) => ( +
+ {alt} +
+

{heading}

+

{ptext} +

+
+
+ ) + + + return ( +
+ + + + + + +
+ ) +} + +export default BmiTips \ No newline at end of file diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..5ce1e80 --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import logo from '../assets/images/logo.svg'; + +const Header = () => { + return ( +
+ logo +

Body Mass Index Calculator

+

Better understand your weight in relation to your height using our body mass index (BM) calculator. While BMI is not the sole determinant of a healthy weight, it offers a valuable starting point to evaluate your overall health and well-being.

+ +
+ ) +} + +export default Header \ No newline at end of file diff --git a/src/index.css b/src/index.css index ec2585e..b5c61c9 100644 --- a/src/index.css +++ b/src/index.css @@ -1,13 +1,3 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..bdde18b --- /dev/null +++ b/src/index.html @@ -0,0 +1,13 @@ + + + + + + + Frontend Mentor | Body Mass Index Calculator + + +
+ + + diff --git a/src/index.tsx b/src/index.tsx index 032464f..db4c36e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; -import reportWebVitals from './reportWebVitals'; + const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement @@ -16,4 +16,4 @@ root.render( // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); + diff --git a/src/reportWebVitals.ts b/src/reportWebVitals.ts deleted file mode 100644 index 49a2a16..0000000 --- a/src/reportWebVitals.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ReportHandler } from 'web-vitals'; - -const reportWebVitals = (onPerfEntry?: ReportHandler) => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/src/screenshot.png b/src/screenshot.png new file mode 100644 index 0000000..1b4d43f Binary files /dev/null and b/src/screenshot.png differ diff --git a/src/setupTests.ts b/src/setupTests.ts deleted file mode 100644 index 8f2609b..0000000 --- a/src/setupTests.ts +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..3595199 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,17 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ["./src/**/*.{js,jsx,ts,tsx}"], + theme: { + extend: { + backgroundImage: { + "blue-gray-gradient": "linear-gradient(290.1deg,#D6E6FE 0%,rgba(214,252,254,0) 100%)", + }, + boxShadow:{ + 'custom': 'blue-gray-gradient 0px 7px 29px 10px', + + } + }, + }, + plugins: [], +} +