diff --git a/src/App.jsx b/src/App.jsx index 4ebc21c..dfa64fc 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,6 +1,7 @@ import { useState } from "react"; import LoanForm from "./LoanForm.jsx"; import { loanMaths, isNumber } from "./loanMaths.js"; +import ModeToggle from "./ModeToggle.jsx"; import LoanPlot from "./LoanPlot.jsx"; import LoanStats from "./LoanStats.jsx"; import EventsForm from "./EventsForm.jsx"; @@ -203,7 +204,7 @@ if (searchParams.has("downPayCash")) initialUserSetDownPercent = false; function App() { const [loanEvent, setLoanEvent] = useState(initialEvents); - const [chosenInput, setChosenInput] = useState("homeVal"); + const [chosenInput, setChosenInput] = useState("monthlyPayment" in initialOverride ? "monthlyPayment" : "homeVal"); const [userSetDownPercent, setUserSetDownPercent] = useState(initialUserSetDownPercent); const [showURLToast, setShowURLToast] = useState(gotStuffFromURL); const [rentSim, setRentSim] = useState({ ...initialRentSimulation, ...overrideRentSimulation }); @@ -568,28 +569,18 @@ function App() {
-
-
-
-
-

- An easy to use mortgage calculator to find out exactly how much it will cost to buy a house. Or, enter a monthly budget. How much can you - afford? This tool supports unlimited overpayment, re-finance and recast events. Also, try adding inflation. -

-
-
-
-
-
-
- updateUserInput(f, v)} /> + + +
+
+ updateUserInput(f, v)} chosenInput={chosenInput} />
-
-
-
+
+
+
-
+
-
-
- -
-
-
-
- setRentSim(r)} - /> -
+ +
+ setRentSim(r)} + />
diff --git a/src/LoanForm.jsx b/src/LoanForm.jsx index d59e718..194f6b6 100644 --- a/src/LoanForm.jsx +++ b/src/LoanForm.jsx @@ -29,13 +29,6 @@ function FbComp({ x }) { function LoanForm({ displayState, flash, updateUserInput, valid }) { const [show, setShow] = useState(false); const feeOptions = ["$ / year", "$ / month", "% / year", "% / month"]; - const additionalPayments = - Number(displayState["propertyTax"]) > 0 || - Number(displayState["hoa"]) > 0 || - Number(displayState["insurance"]) > 0 || - Number(displayState["pmi"]) || - Number(displayState["maintenance"]) > 0 || - Number(displayState["utilities"]) > 0; //builds class for each input based on flash (whether it changed and should flash) and valid (if user input is valid) const startDateOptions = { month: "short", year: "numeric" }; @@ -97,43 +90,6 @@ function LoanForm({ displayState, flash, updateUserInput, valid }) { return (
-
-
- - updateIfChanged(displayState["homeVal"], e.target.value, "homeVal")} - /> - - {additionalPayments ? ( - - ) : null} -
-
or
-
- - updateIfChanged(displayState["monthlyPayment"], e.target.value, "monthlyPayment")} - value={cashFormat(displayState["monthlyPayment"])} - /> - - - {additionalPayments ? ( - - ) : null} -
-
diff --git a/src/LoanPlot.jsx b/src/LoanPlot.jsx index 282b03d..f27faa3 100644 --- a/src/LoanPlot.jsx +++ b/src/LoanPlot.jsx @@ -249,23 +249,16 @@ function LoanPlot({ maxMonthly, loanRes, loanMonths, propertyTax, hoa, pmi, util }; return ( -
+
-
-
-
- -
-
+
+
-
- Show: -
- {["Monthly Breakdown", "Yearly Breakdown"].map((x) => ( -
-
+
+ {["Monthly Breakdown", "Yearly Breakdown"].map((x) => ( +
-
- ))} + ))} +
diff --git a/src/ModeToggle.jsx b/src/ModeToggle.jsx new file mode 100644 index 0000000..5cd2aa2 --- /dev/null +++ b/src/ModeToggle.jsx @@ -0,0 +1,209 @@ +import { cashFormat, isNumber } from "./loanMaths.js"; + +function ModeToggle({ chosenInput, displayState, valid, flash, userInput, updateUserInput }) { + const handleHomeValClick = () => { + if (chosenInput !== "homeVal") { + updateUserInput("homeVal", userInput.homeVal); + } + }; + + const handleMonthlyPaymentClick = () => { + if (chosenInput !== "monthlyPayment") { + const monthlyPaymentValue = userInput.monthlyPayment !== "0" ? userInput.monthlyPayment : displayState.monthlyPayment; + updateUserInput("monthlyPayment", monthlyPaymentValue); + } + }; + + const updateIfChanged = (oldVal, newVal, name) => { + var parsedNewVal = newVal.replace(/[^0-9.]+/g, ""); + if (oldVal !== parsedNewVal) { + var noLeading0 = oldVal === "0" && isNumber(parsedNewVal) ? parseFloat(parsedNewVal).toString() : parsedNewVal; + if (newVal.slice(-1) === "." && noLeading0.slice(-1) !== ".") { + noLeading0 = `${noLeading0}.`; + } + updateUserInput(name, noLeading0); + } + }; + + const additionalPayments = + Number(displayState["propertyTax"]) > 0 || + Number(displayState["hoa"]) > 0 || + Number(displayState["insurance"]) > 0 || + Number(displayState["pmi"]) || + Number(displayState["maintenance"]) > 0 || + Number(displayState["utilities"]) > 0; + + return ( + <> +
+
+
+
I want to start with:
+ +
+ + + +
+
+
+
+
+
+
+
+ {chosenInput === "homeVal" ? ( + <> +
+ + updateIfChanged(userInput["homeVal"], e.target.value, "homeVal")} + /> + {valid["homeVal"] !== null && ( +
+ {valid["homeVal"]} +
+ )} +
+
+ + → + + + → + +
+
+ + + {additionalPayments && ( + + )} +
+ + ) : ( + <> +
+ + + updateIfChanged( + userInput["monthlyPayment"] !== "0" ? userInput["monthlyPayment"] : displayState["monthlyPayment"], + e.target.value, + "monthlyPayment" + ) + } + /> + {valid["monthlyPayment"] !== null && ( +
+ {valid["monthlyPayment"]} +
+ )} + {additionalPayments && ( + + )} +
+
+ + → + + + → + +
+
+ + +
+ + )} +
+
+
+
+ + ); +} + +export default ModeToggle; diff --git a/src/Rent.jsx b/src/Rent.jsx index 3351a49..8db46ce 100644 --- a/src/Rent.jsx +++ b/src/Rent.jsx @@ -89,7 +89,7 @@ function Rent({ loanMonths, inflation, downPayment, equity, homeVal, monthlyPaym // Render chart with explanatory paragraph return ( -
+ <>
Instead of buying a home, what if that money was invested in the stock market?
    @@ -173,7 +173,7 @@ function Rent({ loanMonths, inflation, downPayment, equity, homeVal, monthlyPaym
-
+ ); } diff --git a/src/index.css b/src/index.css index 66b4b10..0bbb742 100644 --- a/src/index.css +++ b/src/index.css @@ -22,11 +22,11 @@ } .anim1 { - animation-name: "example2"; + animation-name: example2; animation-duration: 1.5s; } .anim2 { - animation-name: "example3"; + animation-name: example3; animation-duration: 1.5s; } @@ -108,6 +108,12 @@ .outputLabelWidth { width: auto; } + /* Prevent horizontal overflow */ + .container-xxl, + .row { + max-width: 100%; + overflow-x: hidden; + } /* … */ } @@ -122,3 +128,78 @@ body { --bs-tooltip-bg: var(--bd-violet-bg); --bs-tooltip-color: var(--bs-white); } + +.mode-toggle-btn { + position: relative; + border: 1px solid #0dcaf0 !important; +} + +.mode-toggle-btn:first-child { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +.mode-toggle-btn:last-child { + border-top-left-radius: 0 !important; + border-bottom-left-radius: 0 !important; +} + +.mode-toggle-active { + background-color: #0cbbde !important; + border-color: #0cbbde !important; + color: #fff !important; + z-index: 1; + box-shadow: 0 2px 4px rgba(13, 202, 240, 0.3); +} + +.mode-toggle-active:hover { + background-color: #0aa2c0 !important; + border-color: #0aa2c0 !important; + color: #fff !important; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(13, 202, 240, 0.4); +} + +.mode-toggle-inactive { + background-color: #fff !important; + border-color: #0dcaf0 !important; + color: #000 !important; +} + +.mode-toggle-inactive:hover { + background-color: #cff4fc !important; + border-color: #0dcaf0 !important; + color: #000 !important; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(13, 202, 240, 0.2); +} + +.mode-toggle-btn:active { + transform: translateY(0); +} + +@media (width <= 768px) { + .mode-toggle-btn { + padding: 0.75rem 0.5rem !important; + font-size: 0.875rem !important; + } + + .mode-toggle-btn small { + font-size: 0.65rem !important; + } + + .mode-toggle-btn .fw-semibold { + font-size: 0.875rem !important; + } +} + +@media (width <= 500px) { + .mode-toggle-btn { + padding: 0.5rem 0.25rem !important; + } + + .mode-toggle-btn small { + font-size: 0.6rem !important; + line-height: 1.2 !important; + } +}