Skip to content

Commit

Permalink
Enhance a11y
Browse files Browse the repository at this point in the history
  • Loading branch information
tujoworker committed Jan 5, 2024
1 parent 726fa5c commit 6c709c9
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 155 deletions.
243 changes: 119 additions & 124 deletions packages/dnb-eufemia/src/extensions/forms/Field/Number/Number.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import FieldBlock from '../../FieldBlock'
import { useDataValue } from '../../hooks'
import { FieldProps, FieldHelpProps } from '../../types'
import { pickSpacingProps } from '../../../../components/flex/utils'
import { ButtonSize } from '../../../../components/Button'
import { ButtonProps, ButtonSize } from '../../../../components/Button'
import { clamp } from '../../../../components/slider/SliderHelpers'

interface ErrorMessages {
required?: string
Expand Down Expand Up @@ -106,11 +107,11 @@ function NumberComponent(props: Props) {
const fromInput = useCallback(
({ value, numberValue }: { value: string; numberValue: number }) => {
if (value === '') {
return emptyValue
return props.emptyValue
}
return numberValue
},
[]
[props.emptyValue]
)

const maskProps: Partial<InputMaskedProps> = useMemo(() => {
Expand Down Expand Up @@ -174,153 +175,147 @@ function NumberComponent(props: Props) {
labelDescription,
labelSecondary,
value,
minimum,
maximum,
disabled,
info,
warning,
error,
help,
emptyValue,
size,
width,
handleFocus,
handleBlur,
handleChange,
} = useDataValue(preparedProps)

const onKeyDownHandler = useCallback(
({ key, event }) => {
let numberValue = null

switch (key) {
case 'ArrowUp':
numberValue = clamp((value as number) + step, minimum, maximum)
break
case 'ArrowDown':
numberValue = clamp((value as number) - step, minimum, maximum)
break
}

if (numberValue !== null) {
event.persist()
event.preventDefault()
handleChange({ numberValue })
}
},
[handleChange, maximum, minimum, step, value]
)

const fieldBlockProps = {
className: classnames('dnb-forms-field-number', className),
contentClassName: classnames(
'dnb-forms-field-number__contents',
showStepControls && 'dnb-forms-field-number__contents--has-controls',
disabled && 'dnb-forms-field-number__contents--is-disabled',
error && 'dnb-forms-field-number__contents--has-error'
),
forId: id,
layout,
label,
labelDescription,
labelSecondary,
info,
warning,
error,
disabled,
width: width === 'stretch' ? width : undefined,
contentsWidth: width !== false ? width : undefined,
...pickSpacingProps(props),
}

const increaseProps: ButtonProps = showStepControls && {
'aria-hidden': true,
className: 'dnb-button--control-after',
variant: 'secondary',
icon: 'add',
size: convertInputSizeToButtonSize(size),
tabIndex: -1,
disabled: disabled || value >= maximum,
onClick: () => {
handleChange({
numberValue: clamp((value as number) + step, minimum, maximum),
})
},
title: sharedContext?.translation.Slider.addTitle?.replace(
'%s',
String(value + step)
),
}

const decreaseProps: ButtonProps = showStepControls && {
...increaseProps,
className: 'dnb-button--control-before',
icon: 'subtract',
disabled: disabled || value <= minimum,
onClick: () => {
handleChange({
numberValue: clamp((value as number) - step, minimum, maximum),
})
},
title: sharedContext?.translation.Slider.subtractTitle?.replace(
'%s',
String(value - step)
),
}

const ariaParams = showStepControls && {
role: 'spinbutton',
'aria-valuemin': props.minimum,
'aria-valuemax': props.maximum,
'aria-valuetext': String(value),
'aria-valuemin': String(minimum),
'aria-valuemax': String(maximum),
'aria-valuenow': String(value), // without it, VO will read an invlaid value
'aria-valuetext': String(value), // without it, VO will read %
}

const onKeyDownHandler = ({ key, event }) => {
let numberValue = null
switch (key) {
case 'ArrowUp':
numberValue = clamp(
(value as number) + step,
props.minimum,
props.maximum
)
break
case 'ArrowDown':
numberValue = clamp(
(value as number) - step,
props.minimum,
props.maximum
)
break
}

if (numberValue !== null) {
event.persist()
event.preventDefault()
handleChange({ numberValue })
}
const inputProps = {
id,
name,
autoComplete,
className: classnames(
'dnb-forms-field-number__input',
`dnb-input--${size}`,
inputClassName
),
step,
placeholder,
value,
...maskProps,
align: rightAligned && 'right',
onKeyDown: onKeyDownHandler,
onFocus: handleFocus,
onBlur: handleBlur,
onChange: handleChange,
disabled,
status: error ? 'error' : undefined,
stretch: width !== undefined,
suffix:
help && !showStepControls ? (
<HelpButton title={help.title}>{help.contents}</HelpButton>
) : undefined,
...ariaParams,
}

return (
<FieldBlock
className={classnames('dnb-forms-field-number', className)}
contentClassName={classnames(
'dnb-forms-field-number__contents',
showStepControls &&
'dnb-forms-field-number__contents--has-controls',
disabled && 'dnb-forms-field-number__contents--is-disabled',
error && 'dnb-forms-field-number__contents--has-error'
)}
forId={id}
layout={layout}
label={label}
labelDescription={labelDescription}
labelSecondary={labelSecondary}
info={info}
warning={warning}
error={error}
disabled={disabled}
width={width === 'stretch' ? width : undefined}
contentsWidth={width !== false ? width : undefined}
{...pickSpacingProps(props)}
>
{showStepControls && (
<Button
className="dnb-button--control-before"
variant="secondary"
icon="subtract"
size={convertInputSizeToButtonSize(size)}
tabIndex={-1}
disabled={disabled || value <= props.minimum}
on_click={() => {
handleChange({
numberValue: clamp(
(value as number) - step,
props.minimum,
props.maximum
),
})
}}
{...ariaParams}
/>
)}
<InputMasked
id={id}
name={name}
autoComplete={autoComplete}
className={classnames(
'dnb-forms-field-number__input',
`dnb-input--${size}`,
inputClassName
)}
step={step}
placeholder={placeholder}
value={value}
{...maskProps}
align={rightAligned && 'right'}
onKeyDown={onKeyDownHandler}
on_focus={handleFocus}
on_blur={handleBlur}
on_change={handleChange}
disabled={disabled}
status={error ? 'error' : undefined}
stretch={width !== undefined}
suffix={
help && !showStepControls ? (
<HelpButton title={help.title}>{help.contents}</HelpButton>
) : undefined
}
{...ariaParams}
/>
{showStepControls && (
<Button
className="dnb-button--control-after"
variant="secondary"
icon="add"
size={convertInputSizeToButtonSize(size)}
tabIndex={-1}
disabled={disabled || value >= props.maximum}
on_click={() => {
handleChange({
numberValue: clamp(
(value as number) + step,
props.minimum,
props.maximum
),
})
}}
{...ariaParams}
/>
)}
<FieldBlock {...fieldBlockProps} asFieldset={false}>
{showStepControls && <Button {...decreaseProps} />}
<InputMasked {...inputProps} />
{showStepControls && <Button {...increaseProps} />}
{help && showStepControls && (
<HelpButton title={help.title}>{help.contents}</HelpButton>
)}
</FieldBlock>
)
}

const clamp = (value: number, min = 0, max = 100) =>
Math.min(Math.max(value, min), max)

const convertInputSizeToButtonSize = (
inputSize: InputSize
): ButtonSize => {
Expand Down
Loading

0 comments on commit 6c709c9

Please sign in to comment.