refactor(worksheets): constrain display preview width
Add width constraints to preview box to prevent excessive width: - Use fit-content to size based on content - Add maxWidth 100% for responsiveness 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,128 @@ interface DisplayOptionsPreviewProps {
|
||||
formState: WorksheetFormState
|
||||
}
|
||||
|
||||
interface MathSentenceProps {
|
||||
operands: number[]
|
||||
operator: string
|
||||
onChange: (operands: number[]) => void
|
||||
labels?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Flexible math sentence component supporting operators with arity 1-3
|
||||
* Examples:
|
||||
* Arity 1 (unary): [64] with "√" → "√64"
|
||||
* Arity 2 (binary): [45, 27] with "+" → "45 + 27"
|
||||
* Arity 3 (ternary): [5, 10, 15] with "between" → "5 < 10 < 15"
|
||||
*/
|
||||
function MathSentence({ operands, operator, onChange, labels }: MathSentenceProps) {
|
||||
const handleOperandChange = (index: number, value: string) => {
|
||||
const numValue = Number.parseInt(value, 10)
|
||||
if (!Number.isNaN(numValue) && numValue >= 0 && numValue <= 99) {
|
||||
const newOperands = [...operands]
|
||||
newOperands[index] = numValue
|
||||
onChange(newOperands)
|
||||
}
|
||||
}
|
||||
|
||||
const renderInput = (value: number, index: number) => (
|
||||
<input
|
||||
key={index}
|
||||
type="number"
|
||||
min="0"
|
||||
max="99"
|
||||
value={value}
|
||||
onChange={(e) => handleOperandChange(index, e.target.value)}
|
||||
aria-label={labels?.[index] || `operand ${index + 1}`}
|
||||
className={css({
|
||||
width: '3.5em',
|
||||
px: '1',
|
||||
py: '0.5',
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'medium',
|
||||
textAlign: 'center',
|
||||
border: '1px solid',
|
||||
borderColor: 'transparent',
|
||||
rounded: 'sm',
|
||||
outline: 'none',
|
||||
transition: 'border-color 0.2s',
|
||||
_hover: {
|
||||
borderColor: 'gray.300',
|
||||
},
|
||||
_focus: {
|
||||
borderColor: 'brand.500',
|
||||
ring: '1px',
|
||||
ringColor: 'brand.200',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
// Render based on arity
|
||||
if (operands.length === 1) {
|
||||
// Unary operator (prefix): √64 or ±5
|
||||
return (
|
||||
<div
|
||||
data-component="math-sentence"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '1',
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'medium',
|
||||
})}
|
||||
>
|
||||
<span>{operator}</span>
|
||||
{renderInput(operands[0], 0)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (operands.length === 2) {
|
||||
// Binary operator (infix): 45 + 27
|
||||
return (
|
||||
<div
|
||||
data-component="math-sentence"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '1',
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'medium',
|
||||
})}
|
||||
>
|
||||
{renderInput(operands[0], 0)}
|
||||
<span>{operator}</span>
|
||||
{renderInput(operands[1], 1)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (operands.length === 3) {
|
||||
// Ternary operator: 5 < 10 < 15 or similar
|
||||
return (
|
||||
<div
|
||||
data-component="math-sentence"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '1',
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'medium',
|
||||
})}
|
||||
>
|
||||
{renderInput(operands[0], 0)}
|
||||
<span>{operator}</span>
|
||||
{renderInput(operands[1], 1)}
|
||||
<span>{operator}</span>
|
||||
{renderInput(operands[2], 2)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
async function fetchExample(options: {
|
||||
showCarryBoxes: boolean
|
||||
showAnswerBoxes: boolean
|
||||
@@ -17,6 +139,8 @@ async function fetchExample(options: {
|
||||
showCellBorder: boolean
|
||||
showTenFrames: boolean
|
||||
showTenFramesForAll: boolean
|
||||
addend1: number
|
||||
addend2: number
|
||||
}): Promise<string> {
|
||||
const response = await fetch('/api/create/worksheets/addition/example', {
|
||||
method: 'POST',
|
||||
@@ -36,6 +160,9 @@ async function fetchExample(options: {
|
||||
}
|
||||
|
||||
export function DisplayOptionsPreview({ formState }: DisplayOptionsPreviewProps) {
|
||||
// Local state for operands (not debounced - we want immediate feedback)
|
||||
const [operands, setOperands] = useState([45, 27])
|
||||
|
||||
// Debounce the display options to avoid hammering the server
|
||||
const [debouncedOptions, setDebouncedOptions] = useState({
|
||||
showCarryBoxes: formState.showCarryBoxes ?? true,
|
||||
@@ -45,6 +172,8 @@ export function DisplayOptionsPreview({ formState }: DisplayOptionsPreviewProps)
|
||||
showCellBorder: formState.showCellBorder ?? true,
|
||||
showTenFrames: formState.showTenFrames ?? false,
|
||||
showTenFramesForAll: formState.showTenFramesForAll ?? false,
|
||||
addend1: operands[0],
|
||||
addend2: operands[1],
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
@@ -57,6 +186,8 @@ export function DisplayOptionsPreview({ formState }: DisplayOptionsPreviewProps)
|
||||
showCellBorder: formState.showCellBorder ?? true,
|
||||
showTenFrames: formState.showTenFrames ?? false,
|
||||
showTenFramesForAll: formState.showTenFramesForAll ?? false,
|
||||
addend1: operands[0],
|
||||
addend2: operands[1],
|
||||
})
|
||||
}, 300) // 300ms debounce
|
||||
|
||||
@@ -69,6 +200,7 @@ export function DisplayOptionsPreview({ formState }: DisplayOptionsPreviewProps)
|
||||
formState.showCellBorder,
|
||||
formState.showTenFrames,
|
||||
formState.showTenFramesForAll,
|
||||
operands,
|
||||
])
|
||||
|
||||
const { data: svg, isLoading } = useQuery({
|
||||
@@ -81,7 +213,7 @@ export function DisplayOptionsPreview({ formState }: DisplayOptionsPreviewProps)
|
||||
<div
|
||||
data-component="display-options-preview"
|
||||
className={css({
|
||||
p: '4',
|
||||
p: '3',
|
||||
bg: 'white',
|
||||
rounded: 'xl',
|
||||
border: '2px solid',
|
||||
@@ -89,18 +221,34 @@ export function DisplayOptionsPreview({ formState }: DisplayOptionsPreviewProps)
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '2',
|
||||
width: 'fit-content',
|
||||
maxWidth: '100%',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'xs',
|
||||
fontWeight: 'semibold',
|
||||
color: 'gray.500',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 'wider',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
})}
|
||||
>
|
||||
Preview
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'xs',
|
||||
fontWeight: 'semibold',
|
||||
color: 'gray.500',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 'wider',
|
||||
})}
|
||||
>
|
||||
Preview
|
||||
</div>
|
||||
<MathSentence
|
||||
operands={operands}
|
||||
operator="+"
|
||||
onChange={setOperands}
|
||||
labels={['addend', 'addend']}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
|
||||
Reference in New Issue
Block a user