|
| 1 | +<!-- Copyright 2020 Adobe. All rights reserved. |
| 2 | +This file is licensed to you under the Apache License, Version 2.0 (the "License"); |
| 3 | +you may not use this file except in compliance with the License. You may obtain a copy |
| 4 | +of the License at http://www.apache.org/licenses/LICENSE-2.0 |
| 5 | +Unless required by applicable law or agreed to in writing, software distributed under |
| 6 | +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS |
| 7 | +OF ANY KIND, either express or implied. See the License for the specific language |
| 8 | +governing permissions and limitations under the License. --> |
| 9 | + |
| 10 | +import {Layout} from '@react-spectrum/docs'; |
| 11 | +export default Layout; |
| 12 | + |
| 13 | +import docs from 'docs:@react-aria/numberfield'; |
| 14 | +import statelyDocs from 'docs:@react-stately/numberfield'; |
| 15 | +import {HeaderInfo, FunctionAPI, TypeContext, InterfaceType, TypeLink} from '@react-spectrum/docs'; |
| 16 | +import packageData from '@react-aria/numberfield/package.json'; |
| 17 | +import Anatomy from './anatomy.svg'; |
| 18 | + |
| 19 | +```jsx import |
| 20 | +import {useNumberField} from '@react-aria/numberfield'; |
| 21 | +``` |
| 22 | + |
| 23 | +--- |
| 24 | +category: Forms |
| 25 | +keywords: [number field, input, form, aria] |
| 26 | +--- |
| 27 | + |
| 28 | +# useNumberField |
| 29 | + |
| 30 | +<p>{docs.exports.useNumberField.description}</p> |
| 31 | + |
| 32 | +<HeaderInfo |
| 33 | + packageData={packageData} |
| 34 | + componentNames={['useNumberField']} |
| 35 | + sourceData={[ |
| 36 | + {type: 'W3C', url: 'https://www.w3.org/TR/wai-aria-practices-1.2/#spinbutton'} |
| 37 | + ]} /> |
| 38 | + |
| 39 | +## API |
| 40 | + |
| 41 | +<FunctionAPI function={docs.exports.useNumberField} links={docs.links} /> |
| 42 | + |
| 43 | +## Features |
| 44 | + |
| 45 | +Number fields can be built with `<input type="number">`, but the behavior is very inconsistent across |
| 46 | +browsers and platforms, it supports limited localized formatting options, and it is challenging to style |
| 47 | +the stepper buttons. `useNumberField` helps achieve accessible number fields that support internationalized |
| 48 | +formatting options and can be styled as needed. |
| 49 | + |
| 50 | +* Support for internationalized number formatting and parsing including decimals, percentages, currency values, and units |
| 51 | +* Support for the Latin, Arabic, and Han decimal numbering systems in [over 30 locales](internationalization.html#supported-locales) |
| 52 | +* Automatically detects the numbering system used and supports parsing numbers not in the default numbering system for the locale |
| 53 | +* Support for multiple currency formats including symbol, code, and name in standard or accounting notation |
| 54 | +* Validates keyboard entry as the user types so that only valid numeric input according to the locale and numbering system is accepted |
| 55 | +* Handles composed input from input method editors, e.g. Pinyin |
| 56 | +* Automatically selects an appropriate software keyboard for mobile according to the current platform and allowed values |
| 57 | +* Supports rounding to a configurable number of fraction digits |
| 58 | +* Support for clamping the value between a configurable minimum and maximum, and snapping to a step value |
| 59 | +* Support for stepper buttons and arrow keys to increment and decrement the value according to the step value |
| 60 | +* Supports pressing and holding the stepper buttons to continuously increment or decrement |
| 61 | +* Handles floating point rounding errors when incrementing, decrementing, and snapping to step |
| 62 | +* Supports using the scroll wheel to increment and decrement the value |
| 63 | +* Exposed to assistive technology as a text field with a custom localized role description using ARIA |
| 64 | +* Follows the [spinbutton](https://www.w3.org/TR/wai-aria-practices-1.2/#spinbutton) ARIA pattern |
| 65 | +* Works around bugs in VoiceOver with the spinbutton role |
| 66 | +* Uses an ARIA live region to ensure that value changes are announced |
| 67 | + |
| 68 | +## Anatomy |
| 69 | + |
| 70 | +<Anatomy /> |
| 71 | + |
| 72 | +Number fields consist of an input element that shows the current value and allows the user to type a new value, |
| 73 | +optional stepper buttons to increment and decrement the value, a group containing the input and stepper buttons, |
| 74 | +and a label. |
| 75 | + |
| 76 | +`useNumberField` returns props for each of these, which you should spread onto the appropriate elements: |
| 77 | + |
| 78 | +<TypeContext.Provider value={docs.links}> |
| 79 | + <InterfaceType properties={docs.links[docs.exports.useNumberField.return.id].properties} /> |
| 80 | +</TypeContext.Provider> |
| 81 | + |
| 82 | +State is managed by the <TypeLink links={statelyDocs.links} type={statelyDocs.exports.useNumberFieldState} /> |
| 83 | +hook from `@react-stately/numberfield`. The state object should be passed as an option to `useNumberField` |
| 84 | + |
| 85 | +If there is no visual label, an `aria-label` or `aria-labelledby` prop must be passed instead |
| 86 | +to identify the element to screen readers. |
| 87 | + |
| 88 | +## Example |
| 89 | + |
| 90 | +The following example shows how to build a simple number field. It includes an input element where the user can |
| 91 | +type a number, along with increment and decrement buttons on either side. The buttons are built using |
| 92 | +the [useButton](useButton.html) hook, using the props returned by `useNumberField`. |
| 93 | + |
| 94 | +**Note:** Due to [a bug](https://bugs.webkit.org/show_bug.cgi?id=219188) in Safari on macOS, pointer events may not |
| 95 | +be dispatched after a `<button>` element is disabled while the mouse is pressed. This may require the user to click |
| 96 | +twice when incrementing or decrementing the value from the minimum or maximum value. While out of scope for this example, |
| 97 | +you may wish to use a `<div>` element instead of a `<button>` to avoid this issue. See the [useButton docs](useButton.html#custom-element-type) |
| 98 | +for an example of a button with a custom element type. |
| 99 | + |
| 100 | +```tsx example export=true |
| 101 | +import {useNumberFieldState} from '@react-stately/numberfield'; |
| 102 | +import {useLocale} from '@react-aria/i18n'; |
| 103 | +import {useButton} from '@react-aria/button'; |
| 104 | + |
| 105 | +function NumberField(props) { |
| 106 | + let {locale} = useLocale(); |
| 107 | + let state = useNumberFieldState({...props, locale}); |
| 108 | + let inputRef = React.useRef(); |
| 109 | + let { |
| 110 | + labelProps, |
| 111 | + groupProps, |
| 112 | + inputProps, |
| 113 | + incrementButtonProps, |
| 114 | + decrementButtonProps |
| 115 | + } = useNumberField(props, state, inputRef); |
| 116 | + |
| 117 | + let {buttonProps: incrementProps} = useButton(incrementButtonProps); |
| 118 | + let {buttonProps: decrementProps} = useButton(decrementButtonProps); |
| 119 | + |
| 120 | + return ( |
| 121 | + <div> |
| 122 | + <label {...labelProps}>{props.label}</label> |
| 123 | + <div {...groupProps}> |
| 124 | + <button {...decrementProps}>-</button> |
| 125 | + <input {...inputProps} ref={inputRef} /> |
| 126 | + <button {...incrementProps}>+</button> |
| 127 | + </div> |
| 128 | + </div> |
| 129 | + ); |
| 130 | +} |
| 131 | + |
| 132 | +<NumberField |
| 133 | + label="Price" |
| 134 | + defaultValue={6} |
| 135 | + formatOptions={{ |
| 136 | + style: 'currency', |
| 137 | + currency: 'USD' |
| 138 | + }} /> |
| 139 | +``` |
| 140 | + |
| 141 | +## Usage |
| 142 | + |
| 143 | +The following examples show how to use the `NumberField` component created in the above example. |
| 144 | + |
| 145 | +### Controlled |
| 146 | + |
| 147 | +By default, `NumberField` is uncontrolled. However, when using the `value` prop, it becomes controlled. |
| 148 | +This allows you to store the current value in your own state, and use it elsewhere. |
| 149 | + |
| 150 | +The `onChange` event is triggered whenever the number value updates. This happens when the user types a |
| 151 | +value and blurs the input, or when incrementing or decrementing the value. It does not happen as the user |
| 152 | +types because partial input may not be parseable to a valid number. |
| 153 | + |
| 154 | +```tsx example |
| 155 | +function Example() { |
| 156 | + let [value, setValue] = React.useState(6); |
| 157 | + |
| 158 | + return ( |
| 159 | + <> |
| 160 | + <NumberField |
| 161 | + label="Controlled value" |
| 162 | + value={value} |
| 163 | + onChange={setValue} /> |
| 164 | + <div>Current value prop: {value}</div> |
| 165 | + </> |
| 166 | + ); |
| 167 | +} |
| 168 | +``` |
| 169 | + |
| 170 | +### Decimals |
| 171 | + |
| 172 | +The default formatting style for `NumberField` is decimal, but you can configure various aspects via the `formatOptions` |
| 173 | +prop. All options supported by [Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat) |
| 174 | +are supported, including configuration of minimum and maximum fraction digits, sign display, grouping separators, etc. |
| 175 | +Currently only standard notation is supported, though scientific, engineering, and compact notation may be supported in the future. |
| 176 | + |
| 177 | +The following example uses the `signDisplay` option to include the plus sign for positive numbers, for example to display |
| 178 | +an offset from some value. In addition, it always displays a minimum of 1 digit after the decimal point, and allows up to |
| 179 | +2 fraction digits. If the user enters more than 2 fraction digits, the result will be rounded. |
| 180 | + |
| 181 | +```tsx example |
| 182 | +<NumberField |
| 183 | + label="Adjust exposure" |
| 184 | + defaultValue={0} |
| 185 | + formatOptions={{ |
| 186 | + signDisplay: 'exceptZero', |
| 187 | + minimumFractionDigits: 1, |
| 188 | + maximumFractionDigits: 2 |
| 189 | + }} /> |
| 190 | +``` |
| 191 | + |
| 192 | +### Percentages |
| 193 | + |
| 194 | +The `style: 'percent'` option can be passed to the `formatOptions` prop to treat the value as a percentage. In this mode, |
| 195 | +the value is multiplied by 100 before it is displayed, i.e. `0.45` is displayed as `45%`. The reverse is also true: when the |
| 196 | +user enters a value, the `onChange` event will be triggered with the entered value divided by 100. When the percent option |
| 197 | +is enabled, the default step automatically changes to `0.01` such that incrementing and decrementing occurs by `1%`. This can |
| 198 | +be overridden with the `step` prop. [See below](#step-values) for details. |
| 199 | + |
| 200 | +```tsx example |
| 201 | +<NumberField |
| 202 | + label="Sales tax" |
| 203 | + defaultValue={0.05} |
| 204 | + formatOptions={{ |
| 205 | + style: 'percent' |
| 206 | + }} /> |
| 207 | +``` |
| 208 | + |
| 209 | +### Currency values |
| 210 | + |
| 211 | +The `style: 'currency'` option can be passed to the `formatOptions` prop to treat the value as a currency value. The `currency` |
| 212 | +option must also be passed to set the currency code (e.g. `USD`) to use. In addition, the `currencyDisplay` option can be |
| 213 | +used to choose whether to display the currency symbol, currency code, or currency name. Finally, the `currencySign` option |
| 214 | +can be set to `accounting` to use accounting notation for negative numbers, which uses parentheses rather than a minus sign |
| 215 | +in some locales. |
| 216 | + |
| 217 | +If you need to allow the user to change the currency, you should include a separate dropdown next to the number field. |
| 218 | +The number field itself will not determine the currency from the user input. |
| 219 | + |
| 220 | +```tsx example |
| 221 | +<NumberField |
| 222 | + label="Transaction amount" |
| 223 | + defaultValue={45} |
| 224 | + formatOptions={{ |
| 225 | + style: 'currency', |
| 226 | + currency: 'EUR', |
| 227 | + currencyDisplay: 'code', |
| 228 | + currencySign: 'accounting' |
| 229 | + }} /> |
| 230 | +``` |
| 231 | + |
| 232 | +### Units |
| 233 | + |
| 234 | +The `style: 'unit'` option can be passed to the `formatOptions` prop to format the value with a unit of measurement. The `unit` |
| 235 | +option must also be passed to set which unit to use (e.g. `inch`). In addition, the `unitDisplay` option can be used to choose |
| 236 | +whether to display the unit in long, short, or narrow format. |
| 237 | + |
| 238 | +If you need to allow the user to change the unit, you should include a separate dropdown next to the number field. |
| 239 | +The number field itself will not determine the unit from the user input. |
| 240 | + |
| 241 | +**Note:** the unit style is not currently supported in Safari. A [polyfill](https://formatjs.io/docs/polyfills/intl-numberformat/) |
| 242 | +may be necessary. |
| 243 | + |
| 244 | +```tsx example |
| 245 | +<NumberField |
| 246 | + label="Package width" |
| 247 | + defaultValue={4} |
| 248 | + formatOptions={{ |
| 249 | + style: 'unit', |
| 250 | + unit: 'inch', |
| 251 | + unitDisplay: 'long' |
| 252 | + }} /> |
| 253 | +``` |
| 254 | + |
| 255 | +### Minimum and maximum values |
| 256 | + |
| 257 | +The `minValue` and `maxValue` props can be used to limit the entered value to a specific range. The value will be clamped |
| 258 | +when the user blurs the input field. In addition, the increment and decrement buttons will be disabled when the value is |
| 259 | +within one `step` value from the bounds ([see below](#step-values) for info about steps). Ranges can be open ended by only |
| 260 | +providing either `minValue` or `maxValue` rather than both. |
| 261 | + |
| 262 | +If a valid range is known ahead of time, it is a good idea to provide it to `NumberField` so it can optimize the experience. |
| 263 | +For example, when the minimum value is greater than or equal to zero, it is possible to use a numeric keyboard on iOS rather |
| 264 | +than a full text keyboard (necessary to enter a minus sign). |
| 265 | + |
| 266 | +```tsx example |
| 267 | +<NumberField |
| 268 | + label="Enter your age" |
| 269 | + minValue={0} /> |
| 270 | +``` |
| 271 | + |
| 272 | +### Step values |
| 273 | + |
| 274 | +The `step` prop can be used to snap the value to certain increments. If there is a `minValue` defined, the steps are calculated |
| 275 | +starting from the minimum. For example, if `minValue={2}`, and `step={3}`, the valid step values would be 2, 5, 8, 11, etc. If no |
| 276 | +`minValue` is defined, the steps are calculated starting from zero and extending in both directions. In other words, such that the |
| 277 | +values are evenly divisible by the step. If no `step` is defined, any decimal value may be typed, but incrementing and decrementing |
| 278 | +snaps the value to an integer. |
| 279 | + |
| 280 | +If the user types a value that is between two steps and blurs the input, the value will be snapped to the nearest step. When |
| 281 | +incrementing or decrementing, the value is snapped to the nearest step that is higher or lower, respectively. |
| 282 | +When incrementing or decrementing from an empty field, the value starts at the `minValue` or `maxValue`, respectively, if defined. |
| 283 | +Otherwise, the value starts from `0`. |
| 284 | + |
| 285 | +```tsx example |
| 286 | +<NumberField |
| 287 | + label="Step" |
| 288 | + step={10} /> |
| 289 | +<NumberField |
| 290 | + label="Step + minValue" |
| 291 | + minValue={2} |
| 292 | + step={3} /> |
| 293 | +<NumberField |
| 294 | + label="Step + minValue + maxValue" |
| 295 | + minValue={2} |
| 296 | + maxValue={21} |
| 297 | + step={3} /> |
| 298 | +``` |
| 299 | + |
| 300 | +### Disabled and read only |
| 301 | + |
| 302 | +The `isDisabled` and `isReadOnly` props can be used prevent the user from editing the value of the number field. |
| 303 | +The difference is that `isReadOnly` still allows the input to be focused, while `isDisabled` prevents all user interaction. |
| 304 | + |
| 305 | +```tsx example |
| 306 | +<NumberField label="Disabled" isDisabled value={25} /> |
| 307 | +<NumberField label="Read only" isReadOnly value={32} /> |
| 308 | +``` |
| 309 | + |
| 310 | +## Internationalization |
| 311 | + |
| 312 | +`useNumberField` handles many aspects of internationalization automatically, including formatting and parsing numbers according |
| 313 | +to the current locale and numbering system. In addition, the increment and decrement buttons have localized ARIA labels. |
| 314 | +You are responsible for localizing the label text passed into the number field. |
| 315 | + |
| 316 | +### RTL |
| 317 | + |
| 318 | +In right-to-left languages, the number field should be mirrored. The order of the input and buttons should be flipped, and the input |
| 319 | +text should be right aligned instead of left aligned. Ensure that your CSS accounts for this. |
0 commit comments