feat: show visual feedback for auto-resolved scaffolding values
Add secondary color highlighting to show which scaffolding option "auto" is currently deferring to in mastery mode.
**Changes:**
1. **RuleThermometer.tsx**
- Add `resolvedValue` prop to show what "auto" resolves to
- When "auto" is selected, highlight the resolved option with green secondary color
- Add border and semibold font to the auto-resolved button
- Update tooltip to show "(currently selected by Auto)"
- Distinct visual styles:
- Primary selection (user's choice): Brand blue with white text
- Auto-resolved: Green background with green border
- Inactive: Gray background
2. **ScaffoldingTab.tsx**
- Import getSkillById to fetch skill recommendations
- Calculate resolvedDisplayRules based on current mode and skill
- Pass resolvedValue to each RuleThermometer component
- Support both single operator and mixed operator modes
**Visual Design:**
When user selects "Auto":
- "Auto" button: Highlighted in brand blue (selected)
- Resolved option (e.g., "Regroup"): Highlighted in green (what auto defers to)
- Other options: Gray (inactive)
**Example:**
```
User selects: "Auto" for Answer Boxes
Skill recommends: "always"
Result:
[Auto] ← Blue (selected)
[Always] ← Green (auto-resolved)
[Regroup] [2+] [3+ dig] [Never] ← Gray (inactive)
```
This provides clear visual feedback about what "auto" is actually doing without requiring users to check skill documentation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a945a620c4
commit
fbe776ac09
|
|
@ -9,6 +9,8 @@ export interface RuleThermometerProps {
|
||||||
value: RuleMode
|
value: RuleMode
|
||||||
onChange: (value: RuleMode) => void
|
onChange: (value: RuleMode) => void
|
||||||
isDark?: boolean
|
isDark?: boolean
|
||||||
|
/** When value is 'auto', this shows which value 'auto' resolves to */
|
||||||
|
resolvedValue?: RuleMode
|
||||||
}
|
}
|
||||||
|
|
||||||
const RULE_OPTIONS: Array<{ value: RuleMode; label: string; short: string }> = [
|
const RULE_OPTIONS: Array<{ value: RuleMode; label: string; short: string }> = [
|
||||||
|
|
@ -26,8 +28,10 @@ export function RuleThermometer({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
isDark = false,
|
isDark = false,
|
||||||
|
resolvedValue,
|
||||||
}: RuleThermometerProps) {
|
}: RuleThermometerProps) {
|
||||||
const selectedIndex = RULE_OPTIONS.findIndex((opt) => opt.value === value)
|
const selectedIndex = RULE_OPTIONS.findIndex((opt) => opt.value === value)
|
||||||
|
const isAutoSelected = value === 'auto'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -74,6 +78,8 @@ export function RuleThermometer({
|
||||||
>
|
>
|
||||||
{RULE_OPTIONS.map((option, index) => {
|
{RULE_OPTIONS.map((option, index) => {
|
||||||
const isSelected = value === option.value
|
const isSelected = value === option.value
|
||||||
|
// When 'auto' is selected, show which value it resolves to with secondary color
|
||||||
|
const isAutoResolved = isAutoSelected && resolvedValue === option.value
|
||||||
const isLeftmost = index === 0
|
const isLeftmost = index === 0
|
||||||
const isRightmost = index === RULE_OPTIONS.length - 1
|
const isRightmost = index === RULE_OPTIONS.length - 1
|
||||||
|
|
||||||
|
|
@ -82,16 +88,31 @@ export function RuleThermometer({
|
||||||
key={option.value}
|
key={option.value}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onChange(option.value)}
|
onClick={() => onChange(option.value)}
|
||||||
title={option.label}
|
title={isAutoResolved ? `${option.label} (currently selected by Auto)` : option.label}
|
||||||
className={css({
|
className={css({
|
||||||
flex: 1,
|
flex: 1,
|
||||||
px: '2',
|
px: '2',
|
||||||
py: '1.5',
|
py: '1.5',
|
||||||
fontSize: '2xs',
|
fontSize: '2xs',
|
||||||
fontWeight: isSelected ? 'bold' : 'medium',
|
fontWeight: isSelected ? 'bold' : isAutoResolved ? 'semibold' : 'medium',
|
||||||
color: isSelected ? (isDark ? 'white' : 'white') : isDark ? 'gray.400' : 'gray.600',
|
color: isSelected
|
||||||
bg: isSelected ? 'brand.500' : 'transparent',
|
? 'white'
|
||||||
border: 'none',
|
: isAutoResolved
|
||||||
|
? isDark
|
||||||
|
? 'green.300'
|
||||||
|
: 'green.700'
|
||||||
|
: isDark
|
||||||
|
? 'gray.400'
|
||||||
|
: 'gray.600',
|
||||||
|
bg: isSelected
|
||||||
|
? 'brand.500'
|
||||||
|
: isAutoResolved
|
||||||
|
? isDark
|
||||||
|
? 'green.900/30'
|
||||||
|
: 'green.100'
|
||||||
|
: 'transparent',
|
||||||
|
border: isAutoResolved ? '1px solid' : 'none',
|
||||||
|
borderColor: isAutoResolved ? (isDark ? 'green.700' : 'green.300') : 'transparent',
|
||||||
borderTopLeftRadius: isLeftmost ? 'md' : '0',
|
borderTopLeftRadius: isLeftmost ? 'md' : '0',
|
||||||
borderBottomLeftRadius: isLeftmost ? 'md' : '0',
|
borderBottomLeftRadius: isLeftmost ? 'md' : '0',
|
||||||
borderTopRightRadius: isRightmost ? 'md' : '0',
|
borderTopRightRadius: isRightmost ? 'md' : '0',
|
||||||
|
|
@ -99,7 +120,15 @@ export function RuleThermometer({
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.15s',
|
transition: 'all 0.15s',
|
||||||
_hover: {
|
_hover: {
|
||||||
bg: isSelected ? 'brand.600' : isDark ? 'gray.600' : 'gray.200',
|
bg: isSelected
|
||||||
|
? 'brand.600'
|
||||||
|
: isAutoResolved
|
||||||
|
? isDark
|
||||||
|
? 'green.800/40'
|
||||||
|
: 'green.200'
|
||||||
|
: isDark
|
||||||
|
? 'gray.600'
|
||||||
|
: 'gray.200',
|
||||||
color: isSelected ? 'white' : isDark ? 'gray.200' : 'gray.800',
|
color: isSelected ? 'white' : isDark ? 'gray.200' : 'gray.800',
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { useTheme } from '@/contexts/ThemeContext'
|
||||||
import { RuleThermometer } from '../config-panel/RuleThermometer'
|
import { RuleThermometer } from '../config-panel/RuleThermometer'
|
||||||
import type { DisplayRules } from '../../displayRules'
|
import type { DisplayRules } from '../../displayRules'
|
||||||
import { defaultAdditionConfig } from '@/app/create/worksheets/config-schemas'
|
import { defaultAdditionConfig } from '@/app/create/worksheets/config-schemas'
|
||||||
|
import { getSkillById } from '../../skills'
|
||||||
|
|
||||||
export function ScaffoldingTab() {
|
export function ScaffoldingTab() {
|
||||||
const { formState, onChange } = useWorksheetConfig()
|
const { formState, onChange } = useWorksheetConfig()
|
||||||
|
|
@ -18,6 +19,33 @@ export function ScaffoldingTab() {
|
||||||
// Check if we're in mastery+mixed mode (needs operator-specific rules)
|
// Check if we're in mastery+mixed mode (needs operator-specific rules)
|
||||||
const isMasteryMixed = formState.mode === 'mastery' && formState.operator === 'mixed'
|
const isMasteryMixed = formState.mode === 'mastery' && formState.operator === 'mixed'
|
||||||
|
|
||||||
|
// Get resolved display rules for showing what 'auto' defers to
|
||||||
|
let resolvedDisplayRules: DisplayRules | undefined
|
||||||
|
|
||||||
|
if (formState.mode === 'mastery') {
|
||||||
|
const operator = formState.operator ?? 'addition'
|
||||||
|
|
||||||
|
if (operator === 'mixed') {
|
||||||
|
// Mixed mode: Use addition skill's recommendations for now (could show both)
|
||||||
|
const skillId = formState.currentAdditionSkillId
|
||||||
|
if (skillId) {
|
||||||
|
const skill = getSkillById(skillId as any)
|
||||||
|
resolvedDisplayRules = skill?.recommendedScaffolding
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Single operator: Use its skill's recommendations
|
||||||
|
const skillId =
|
||||||
|
operator === 'addition'
|
||||||
|
? formState.currentAdditionSkillId
|
||||||
|
: formState.currentSubtractionSkillId
|
||||||
|
|
||||||
|
if (skillId) {
|
||||||
|
const skill = getSkillById(skillId as any)
|
||||||
|
resolvedDisplayRules = skill?.recommendedScaffolding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updateRule = (key: keyof DisplayRules, value: DisplayRules[keyof DisplayRules]) => {
|
const updateRule = (key: keyof DisplayRules, value: DisplayRules[keyof DisplayRules]) => {
|
||||||
const newDisplayRules = {
|
const newDisplayRules = {
|
||||||
...displayRules,
|
...displayRules,
|
||||||
|
|
@ -199,6 +227,7 @@ export function ScaffoldingTab() {
|
||||||
value={displayRules.answerBoxes}
|
value={displayRules.answerBoxes}
|
||||||
onChange={(value) => updateRule('answerBoxes', value)}
|
onChange={(value) => updateRule('answerBoxes', value)}
|
||||||
isDark={isDark}
|
isDark={isDark}
|
||||||
|
resolvedValue={resolvedDisplayRules?.answerBoxes}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RuleThermometer
|
<RuleThermometer
|
||||||
|
|
@ -207,6 +236,7 @@ export function ScaffoldingTab() {
|
||||||
value={displayRules.placeValueColors}
|
value={displayRules.placeValueColors}
|
||||||
onChange={(value) => updateRule('placeValueColors', value)}
|
onChange={(value) => updateRule('placeValueColors', value)}
|
||||||
isDark={isDark}
|
isDark={isDark}
|
||||||
|
resolvedValue={resolvedDisplayRules?.placeValueColors}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RuleThermometer
|
<RuleThermometer
|
||||||
|
|
@ -227,6 +257,7 @@ export function ScaffoldingTab() {
|
||||||
value={displayRules.carryBoxes}
|
value={displayRules.carryBoxes}
|
||||||
onChange={(value) => updateRule('carryBoxes', value)}
|
onChange={(value) => updateRule('carryBoxes', value)}
|
||||||
isDark={isDark}
|
isDark={isDark}
|
||||||
|
resolvedValue={resolvedDisplayRules?.carryBoxes}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{(formState.operator === 'subtraction' || formState.operator === 'mixed') && (
|
{(formState.operator === 'subtraction' || formState.operator === 'mixed') && (
|
||||||
|
|
@ -236,6 +267,7 @@ export function ScaffoldingTab() {
|
||||||
value={displayRules.borrowNotation}
|
value={displayRules.borrowNotation}
|
||||||
onChange={(value) => updateRule('borrowNotation', value)}
|
onChange={(value) => updateRule('borrowNotation', value)}
|
||||||
isDark={isDark}
|
isDark={isDark}
|
||||||
|
resolvedValue={resolvedDisplayRules?.borrowNotation}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -246,6 +278,7 @@ export function ScaffoldingTab() {
|
||||||
value={displayRules.borrowingHints}
|
value={displayRules.borrowingHints}
|
||||||
onChange={(value) => updateRule('borrowingHints', value)}
|
onChange={(value) => updateRule('borrowingHints', value)}
|
||||||
isDark={isDark}
|
isDark={isDark}
|
||||||
|
resolvedValue={resolvedDisplayRules?.borrowingHints}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -255,6 +288,7 @@ export function ScaffoldingTab() {
|
||||||
value={displayRules.tenFrames}
|
value={displayRules.tenFrames}
|
||||||
onChange={(value) => updateRule('tenFrames', value)}
|
onChange={(value) => updateRule('tenFrames', value)}
|
||||||
isDark={isDark}
|
isDark={isDark}
|
||||||
|
resolvedValue={resolvedDisplayRules?.tenFrames}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue