From c0298cf65d1017bb5b2258174f3a75f66698899a Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Fri, 7 Nov 2025 22:08:49 -0600 Subject: [PATCH] feat(worksheets): replace digit selector with Radix double-thumbed slider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced button groups with @radix-ui/react-slider for min/max digit range selection. Features: - Double-thumbed range slider (1-5 digits) - Tick marks above slider showing digit values - Visual feedback (grab cursor, scale on hover, focus rings) - More intuitive UX for selecting digit ranges 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../addition/components/ConfigPanel.tsx | 177 +++++++++++++++++- 1 file changed, 173 insertions(+), 4 deletions(-) diff --git a/apps/web/src/app/create/worksheets/addition/components/ConfigPanel.tsx b/apps/web/src/app/create/worksheets/addition/components/ConfigPanel.tsx index 1bba4575..01c22c2f 100644 --- a/apps/web/src/app/create/worksheets/addition/components/ConfigPanel.tsx +++ b/apps/web/src/app/create/worksheets/addition/components/ConfigPanel.tsx @@ -30,6 +30,7 @@ import { type DifficultyMode, } from '../difficultyProfiles' import { defaultAdditionConfig } from '../../config-schemas' +import type { DisplayRules } from '../displayRules' interface ConfigPanelProps { formState: WorksheetFormState @@ -381,6 +382,174 @@ export function ConfigPanel({ formState, onChange }: ConfigPanelProps) { })} /> + {/* Digit Range Selector */} +
+
+
+ + + {(() => { + const min = formState.digitRange?.min ?? 2 + const max = formState.digitRange?.max ?? 2 + return min === max ? `${min}` : `${min}-${max}` + })()} + +
+

+ {(() => { + const min = formState.digitRange?.min ?? 2 + const max = formState.digitRange?.max ?? 2 + return min === max + ? `All problems: exactly ${min} digit${min > 1 ? 's' : ''}` + : `Mixed problem sizes from ${min} to ${max} digits` + })()} +

+
+ + {/* Range Slider with Tick Marks */} +
+ {/* Tick marks */} +
+ {[1, 2, 3, 4, 5].map((digit) => ( +
+
+ {digit} +
+
+
+ ))} +
+ + {/* Double-thumbed range slider */} + { + onChange({ + digitRange: { + min: values[0], + max: values[1], + }, + }) + }} + min={1} + max={5} + step={1} + minStepsBetweenThumbs={0} + > + + + + + + +
+
+ {/* Mode Selector */} @@ -1544,8 +1713,8 @@ export function ConfigPanel({ formState, onChange }: ConfigPanelProps) { const y = clientY - rect.top // Convert to difficulty space (0-10) - let regroupingIntensity = fromX(x) - let scaffoldingLevel = fromY(y) + const regroupingIntensity = fromX(x) + const scaffoldingLevel = fromY(y) // Check if we're near a preset (within snap threshold) const snapThreshold = 1.0 // 1.0 units in 0-10 scale @@ -1567,8 +1736,8 @@ export function ConfigPanel({ formState, onChange }: ConfigPanelProps) { // Calculate Euclidean distance const distance = Math.sqrt( - Math.pow(regroupingIntensity - presetReg, 2) + - Math.pow(scaffoldingLevel - presetScaf, 2) + (regroupingIntensity - presetReg) ** 2 + + (scaffoldingLevel - presetScaf) ** 2 ) if (distance <= snapThreshold) {