Compare commits

..

5 Commits

Author SHA1 Message Date
45a11a379a Make max slider bar work for GCF 2026-04-23 14:25:16 -04:00
8a7b952deb Update documentation 2026-04-23 14:24:58 -04:00
540fdb208c Update dependencies 2026-04-23 14:24:43 -04:00
f2e29f2735 Fix TS compile error 2026-04-23 14:24:11 -04:00
97c0fb7f8e Remove arrows from numeric input control 2026-04-23 14:23:13 -04:00
8 changed files with 666 additions and 653 deletions

View File

@@ -1,7 +1,15 @@
GCF (and LCM) Practice
GCF/LCM/Multiplication Practice
===
This is a simple app to practice on greatest common factor (GCF) and least
common multiple (LCM) math problems.
common multiple (LCM) math problems, as well as simple multiplication. It
was created to help a primary school student practice math skills. Solutions
are provided on request.
While the defaults for GCF and LCM (pairs of positive integers up to 200) may
seem large for such a young learner, the app demonstrates how to apply Euclid's
algorithm, which requires only a knowledge of integer division with remainders,
to calculate GCF. Numeric values are color coded so that the student can follow
each step of the calculation.
Compiling
---

View File

@@ -17,7 +17,8 @@
"react-bootstrap": "^2.10.1",
"react-dom": "npm:@preact/compat",
"react-router": "^6.22.2",
"react-router-dom": "^6.22.2"
"react-router-dom": "^6.22.2",
"rollup": "npm:@rollup/wasm-node"
},
"devDependencies": {
"@preact/preset-vite": "^2.8.1",

View File

@@ -27,10 +27,10 @@
.no-arrows::-webkit-outer-spin-button,
.no-arrows::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
-webkit-appearance: none !important;
margin: 0 !important;
}
.no-arrows {
-moz-appearance: textfield;
-moz-appearance: textfield !important;
}

View File

@@ -18,7 +18,7 @@ export function App() {
<Navbar maxNum={maxNum} onMaxNumChanged={onMaxNumSet} />
<Routes>
<Route path="/" element={<Navigate to="/gcf" />} />
<Route path="/gcf" element={<GCF />} />
<Route path="/gcf" element={<GCF maxNum={maxNum} />} />
<Route path="/times" element={<TimesPractice maxNum={12} />} />
<Route path="/lcm" element={<LCM />} />
</Routes>

View File

@@ -1,4 +1,4 @@
import { useState } from 'preact/hooks'
import { useState, useEffect } from 'preact/hooks'
import './gcf.css'
import { gcf } from './lib'
import type { JSX } from 'preact'
@@ -37,15 +37,17 @@ function Solution({rows}:SolutionParams) {
</div>
}
const DEFAULT_MAXNUM = 200
export function GCF() {
export function GCF({ maxNum } : { maxNum? : number }) {
// choose number between 2 and max inclusive
const getRandomInt = (max : number) : number => Math.floor(Math.random() * (max - 1) + 2)
const chooseFactorsRandom = () : [number, number] => [ getRandomInt(200), getRandomInt(200) ]
const chooseFactorsRandom = () : [number, number] => [ getRandomInt(maxNum || DEFAULT_MAXNUM), getRandomInt(maxNum || DEFAULT_MAXNUM) ]
const _chooseMultiplesOf = (f : number) : [number, number] => {
const max = Math.floor(200 / f)
const max = Math.floor((maxNum || DEFAULT_MAXNUM) / f)
return [ f * getRandomInt(max), f * getRandomInt(max) ]
}
@@ -54,7 +56,7 @@ export function GCF() {
}
const chooseMultiples = () : [number, number] => {
const f = getRandomInt(50)
const f = getRandomInt(Math.min(50, Math.floor((maxNum || DEFAULT_MAXNUM) / 2)))
return _chooseMultiplesOf(f)
}
@@ -135,6 +137,16 @@ export function GCF() {
const [feedback, setFeedback] = useState(false)
const [solution, setSolution] = useState<JSX.Element|null>(null)
useEffect(()=>{
if (maxNum) {
if ((factors[0] > maxNum) || (factors[1] > maxNum)) {
// don't change unless new constraint violated
setFactors(chooseFactors())
}
}
}, [ maxNum ])
return (
<div className="px-4 py-5 my-5 text-center">
<h1 className="my-5 d-none d-sm-block">Greatest Common Factor Practice</h1>

View File

@@ -46,7 +46,7 @@ function Navbar({maxNum, onMaxNumChanged} : NavbarSettings) {
<div className="nav-item ms-auto me-6 g-3 row align-items-center">
<label className="col-form-label col-sm-4 col-form-label-sm ge-2 flex-shrink-0" for="maxNum">Max:</label>
<div className="col-sm-4 flex-shrink-0">
<input className="form-range" type="range" id="maxNum" step={10} value={maxNum} max={200} min={50} onInput={handleSliderInput} onChange={onSliderChange} />
<input className="form-range" type="range" id="maxNum" step={10} value={displayedMax} max={200} min={50} onMouseUp={handleSliderInput} onKeyUp={handleSliderInput} onChange={onSliderChange} />
</div>
<label className="col-form-label col-sm-2 ms-auto">{displayedMax}</label>
</div>

View File

@@ -1,5 +1,6 @@
import { useEffect, useState } from 'preact/hooks'
import './times.css'
import type { JSX } from 'preact'
interface TimesPracticeParams {
maxNum : number

1271
yarn.lock

File diff suppressed because it is too large Load Diff