@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
170 lines (152 loc) • 5.32 kB
JavaScript
import React, {useState} from 'react'
import PropTypes from 'prop-types'
import {SimpleSelect} from '@instructure/quiz-common/components/SimpleSelect/index'
import {Flex} from '@instructure/quiz-common/components/Flex/index'
import {Decimal} from '@instructure/quiz-i18n/util/Decimal'
import {ScientificNumberInput} from '@instructure/quiz-number-input/components/ScientificNumberInput'
import {decimals, significantDigits} from 'instructure-validations'
import t from '@instructure/quiz-i18n/format-message'
import {DECIMALS, SIGNIFICANT_DIGITS} from './constants'
function PreciseResponse({
id,
locale,
messages,
numericTypeSelect,
precision,
precisionType,
onChange,
value: valueProp,
}) {
const [value, setValue] = useState(() => {
if (valueProp) {
try {
return Decimal.toLocaleString(valueProp, locale)
} catch {
return valueProp
}
}
return ''
})
const parsedValue = () => {
return Decimal.parse(valueProp)
}
const minPrecision = () => {
const val = parsedValue()
if (precisionType === SIGNIFICANT_DIGITS) {
return val.isNaN() ? 1 : significantDigits(val.toString())
}
return val.isNaN() ? 0 : decimals(val.toString())
}
const maxPrecision = () => {
// These are the limits for toPrecision and toFixed in Chrome
if (precisionType === SIGNIFICANT_DIGITS) {
const val = parseFloat(valueProp)
return val === 0 ? 1 : 21
}
return 100
}
const handlePrecisionChange = (event, _, normalized) => {
// When precision changes, update the value to match the new precision (add
// or remove trailing zeros)
let newPrecision = normalized
let newValue = valueProp
const valueDecimal = parsedValue()
if (newPrecision != null && !valueDecimal.isNaN()) {
const precisionNumber = parseInt(newPrecision, 10)
newValue =
precisionType === SIGNIFICANT_DIGITS
? valueDecimal.toPrecision(precisionNumber, locale)
: valueDecimal.toFixed(precisionNumber, locale)
}
setValue(newValue)
onChange(event, {id, precision: newPrecision, value: newValue})
}
const handlePrecisionTypeChange = (event, {value: newValue}) => {
const newPrecisionType = newValue
let newPrecision = precision
if (valueProp) {
newPrecision = precisionFromValue(valueProp, newPrecisionType).toString()
}
onChange(event, {id, precision: newPrecision, precisionType: newPrecisionType})
}
const handleValueChange = (event, newValue, normalized) => {
// When the answer value changes, update the precision to match the
// precision of the answer
let newPrecision = precision
if (normalized != null) {
newPrecision = precisionFromValue(normalized, precisionType).toString()
}
setValue(newValue)
onChange(event, {id, precision: newPrecision, value: normalized})
}
return (
<Flex alignItems="start">
<Flex.Item shouldGrow shouldShrink margin="0 xx-small">
{numericTypeSelect}
</Flex.Item>
<Flex.Item shouldGrow shouldShrink margin="0 xx-small">
<ScientificNumberInput
renderLabel={t('Answer')}
locale={locale}
messages={messages.value}
onChange={handleValueChange}
value={value}
aria-valuetext={`${value} ${t('Answer')}`}
data-automation="sdk-numeric-precise-answer-number-input"
isRequired={true}
/>
</Flex.Item>
<Flex.Item shouldGrow shouldShrink margin="0 xx-small">
<ScientificNumberInput
renderLabel={t('Precision')}
locale={locale}
max={maxPrecision()}
messages={messages.precision}
min={minPrecision()}
onChange={handlePrecisionChange}
step={1}
value={precision}
aria-valuetext={`${precision} ${t('Precision')}`}
data-automation="sdk-numeric-precise-precision-number-input"
isRequired={true}
/>
</Flex.Item>
<Flex.Item shouldGrow shouldShrink margin="0 xx-small">
<SimpleSelect
renderLabel={t('Type')}
onChange={handlePrecisionTypeChange}
value={precisionType}
>
<SimpleSelect.Option
id="precise-response-select-option-significant-digits"
value={SIGNIFICANT_DIGITS}
>
{t('Significant digits')}
</SimpleSelect.Option>
<SimpleSelect.Option id="precise-response-select-option-decimals" value={DECIMALS}>
{t('Decimal places')}
</SimpleSelect.Option>
</SimpleSelect>
</Flex.Item>
</Flex>
)
}
PreciseResponse.propTypes = {
id: PropTypes.string.isRequired,
locale: PropTypes.string.isRequired,
messages: PropTypes.objectOf(PropTypes.array),
numericTypeSelect: PropTypes.node.isRequired,
precision: PropTypes.string,
precisionType: PropTypes.oneOf([SIGNIFICANT_DIGITS, DECIMALS]).isRequired,
onChange: PropTypes.func.isRequired,
value: PropTypes.string,
}
PreciseResponse.defaultProps = {
messages: void 0,
precision: void 0,
value: void 0,
}
export default PreciseResponse
function precisionFromValue(value, precisionType) {
return precisionType === SIGNIFICANT_DIGITS ? significantDigits(value) : decimals(value)
}