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
|
||||
onChange: (value: RuleMode) => void
|
||||
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 }> = [
|
||||
|
|
@ -26,8 +28,10 @@ export function RuleThermometer({
|
|||
value,
|
||||
onChange,
|
||||
isDark = false,
|
||||
resolvedValue,
|
||||
}: RuleThermometerProps) {
|
||||
const selectedIndex = RULE_OPTIONS.findIndex((opt) => opt.value === value)
|
||||
const isAutoSelected = value === 'auto'
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -74,6 +78,8 @@ export function RuleThermometer({
|
|||
>
|
||||
{RULE_OPTIONS.map((option, index) => {
|
||||
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 isRightmost = index === RULE_OPTIONS.length - 1
|
||||
|
||||
|
|
@ -82,16 +88,31 @@ export function RuleThermometer({
|
|||
key={option.value}
|
||||
type="button"
|
||||
onClick={() => onChange(option.value)}
|
||||
title={option.label}
|
||||
title={isAutoResolved ? `${option.label} (currently selected by Auto)` : option.label}
|
||||
className={css({
|
||||
flex: 1,
|
||||
px: '2',
|
||||
py: '1.5',
|
||||
fontSize: '2xs',
|
||||
fontWeight: isSelected ? 'bold' : 'medium',
|
||||
color: isSelected ? (isDark ? 'white' : 'white') : isDark ? 'gray.400' : 'gray.600',
|
||||
bg: isSelected ? 'brand.500' : 'transparent',
|
||||
border: 'none',
|
||||
fontWeight: isSelected ? 'bold' : isAutoResolved ? 'semibold' : 'medium',
|
||||
color: isSelected
|
||||
? 'white'
|
||||
: 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',
|
||||
borderBottomLeftRadius: isLeftmost ? 'md' : '0',
|
||||
borderTopRightRadius: isRightmost ? 'md' : '0',
|
||||
|
|
@ -99,7 +120,15 @@ export function RuleThermometer({
|
|||
cursor: 'pointer',
|
||||
transition: 'all 0.15s',
|
||||
_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',
|
||||
},
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { useTheme } from '@/contexts/ThemeContext'
|
|||
import { RuleThermometer } from '../config-panel/RuleThermometer'
|
||||
import type { DisplayRules } from '../../displayRules'
|
||||
import { defaultAdditionConfig } from '@/app/create/worksheets/config-schemas'
|
||||
import { getSkillById } from '../../skills'
|
||||
|
||||
export function ScaffoldingTab() {
|
||||
const { formState, onChange } = useWorksheetConfig()
|
||||
|
|
@ -18,6 +19,33 @@ export function ScaffoldingTab() {
|
|||
// Check if we're in mastery+mixed mode (needs operator-specific rules)
|
||||
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 newDisplayRules = {
|
||||
...displayRules,
|
||||
|
|
@ -199,6 +227,7 @@ export function ScaffoldingTab() {
|
|||
value={displayRules.answerBoxes}
|
||||
onChange={(value) => updateRule('answerBoxes', value)}
|
||||
isDark={isDark}
|
||||
resolvedValue={resolvedDisplayRules?.answerBoxes}
|
||||
/>
|
||||
|
||||
<RuleThermometer
|
||||
|
|
@ -207,6 +236,7 @@ export function ScaffoldingTab() {
|
|||
value={displayRules.placeValueColors}
|
||||
onChange={(value) => updateRule('placeValueColors', value)}
|
||||
isDark={isDark}
|
||||
resolvedValue={resolvedDisplayRules?.placeValueColors}
|
||||
/>
|
||||
|
||||
<RuleThermometer
|
||||
|
|
@ -227,6 +257,7 @@ export function ScaffoldingTab() {
|
|||
value={displayRules.carryBoxes}
|
||||
onChange={(value) => updateRule('carryBoxes', value)}
|
||||
isDark={isDark}
|
||||
resolvedValue={resolvedDisplayRules?.carryBoxes}
|
||||
/>
|
||||
|
||||
{(formState.operator === 'subtraction' || formState.operator === 'mixed') && (
|
||||
|
|
@ -236,6 +267,7 @@ export function ScaffoldingTab() {
|
|||
value={displayRules.borrowNotation}
|
||||
onChange={(value) => updateRule('borrowNotation', value)}
|
||||
isDark={isDark}
|
||||
resolvedValue={resolvedDisplayRules?.borrowNotation}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
@ -246,6 +278,7 @@ export function ScaffoldingTab() {
|
|||
value={displayRules.borrowingHints}
|
||||
onChange={(value) => updateRule('borrowingHints', value)}
|
||||
isDark={isDark}
|
||||
resolvedValue={resolvedDisplayRules?.borrowingHints}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
@ -255,6 +288,7 @@ export function ScaffoldingTab() {
|
|||
value={displayRules.tenFrames}
|
||||
onChange={(value) => updateRule('tenFrames', value)}
|
||||
isDark={isDark}
|
||||
resolvedValue={resolvedDisplayRules?.tenFrames}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue