Skip to content

feat(numberinput): add type='text' and locale-based formatting#19471

Merged
kennylam merged 16 commits into
carbon-design-system:mainfrom
tay1orjones:numberinput-exploration
May 23, 2025
Merged

feat(numberinput): add type='text' and locale-based formatting#19471
kennylam merged 16 commits into
carbon-design-system:mainfrom
tay1orjones:numberinput-exploration

Conversation

@tay1orjones

@tay1orjones tay1orjones commented May 22, 2025

Copy link
Copy Markdown
Member

Closes #18763
Closes #7819
Closes #18755

Changelog

New

  • Support for locale based user input and formatting in NumberInput w/ @internationalized/number
  • Props to NumberInput: type, locale, formatOptions, pattern, inputMode
  • A large new suite of tests covering all this
  • German locale option to the switcher in the storybook toolbar
  • documentLang utilities for tracking updates to documentElement.lang

Changed

  • Updated stories and Overview page documentation

Testing / Reviewing

  • Go to the NumberInput / With Type of Text story
  • Test formatting and parsing behavior to ensure it:
    • Adds locale-specific thousands separator, , for en-US, . for de-DE, to input such as 5000
    • Allows input of locale-based decimal separator, . for en-US, , for de-DE such as 5000.55/5000,55
  • Ensure formatting only occurs on blur the input
  • Check the stepper buttons with different configurations
  • Try out the rounding behavior - 5000.5599999999 should be corrected to 5000.56
  • Validate everything visually looks correct, I had to duplicate a ton of selectors that were using input[type='text']
    • e.g If you put in a number outsize of min or max the invalid styling should all come through
  • Validate controls work as expected in storybook, API docs make sense
  • Read through the Overview page updates

PR Checklist

As the author of this PR, before marking ready for review, confirm you:

  • Reviewed every line of the diff
  • Updated documentation and storybook examples
  • Wrote passing tests that cover this change
  • Addressed any impact on accessibility (a11y)
  • Tested for cross-browser consistency
  • Validated that this code is ready for review and status checks should pass

More details can be found in the pull request guide

@tay1orjones tay1orjones requested a review from a team as a code owner May 22, 2025 20:44
@tay1orjones tay1orjones requested a review from a team May 22, 2025 20:44
@tay1orjones tay1orjones requested a review from a team as a code owner May 22, 2025 20:44
Comment on lines +324 to +329
/**
* The currently parsed number value.
* Only used when type=text
* Updated based on the `value` as the user types.
*/
const [numberValue, setNumberValue, isControlled] = useControllableState({

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding new state variables instead of using the existing ones is intentional. I tried to be really clear and separate out the type='number' and type='text' code paths from one another to avoid regressions.

@netlify

This comment was marked as off-topic.

Comment on lines +434 to +457
if (type === 'number') {
const state = {
value:
allowEmpty && event.target.value === ''
? ''
: Number(event.target.value),
direction: value < event.target.value ? 'up' : 'down',
};
setValue(state.value);

if (onChange) {
onChange(event, state);
}
return;
}

if (type === 'text') {
const _value =
allowEmpty && event.target.value === '' ? '' : event.target.value;
setNumberValue(numberParser.parse(_value));
setInputValue(_value);
// The onChange prop isn't called here because it will be called on blur
// or on click of a stepper, after the number is parsed and formatted
// according to the locale.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here - I know this is verbose and a bit duplicative but trying to clearly separate these code paths to avoid confusion and potential regressions.

type="number"
value={value}
type={type}
value={type === 'number' ? value : inputValue}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the crux of how the code paths are separated.

Comment on lines +10 to +11
export * from '@internationalized/number';
export type * from '@internationalized/number';

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentionally avoiding a peerDependency by doing this, to avoid issues like we've had with react-is.

@netlify

netlify Bot commented May 22, 2025

Copy link
Copy Markdown

Deploy Preview for v11-carbon-react ready!

Name Link
🔨 Latest commit d06155b
🔍 Latest deploy log https://app.netlify.com/projects/v11-carbon-react/deploys/6830b05052a6d300086d80b5
😎 Deploy Preview https://deploy-preview-19471--v11-carbon-react.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov

codecov Bot commented May 22, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 92.68293% with 6 lines in your changes missing coverage. Please review.

Project coverage is 84.91%. Comparing base (5d78de4) to head (d06155b).
Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...s/react/src/components/NumberInput/NumberInput.tsx 92.40% 5 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #19471      +/-   ##
==========================================
+ Coverage   84.87%   84.91%   +0.04%     
==========================================
  Files         372      373       +1     
  Lines       14438    14504      +66     
  Branches     4701     4742      +41     
==========================================
+ Hits        12254    12316      +62     
- Misses       2038     2041       +3     
- Partials      146      147       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kennylam kennylam requested a review from emyarod May 23, 2025 14:19

@kennylam kennylam left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kennylam

Copy link
Copy Markdown
Member

With type=text and locale=de-DE, I'm not seeing the decimal change, i.e. 5000.55 changes to 5,000.55 on blur (rounding works though, as well as US-style formatting 50000.559999 becomes 50,000.56). Is there another control that needs to be used in order to see the change?

@tay1orjones tay1orjones left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated a couple FluidNumberInput selectors that were causing that visual regression.

Ready for a re-review 👍

withAILabel.argTypes = { ...sharedArgTypes };

export const WithTypeOfText = (args) => {
const locale = useDocumentLang();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kennylam I forgot we needed to track changes to lang instead of just reading it once. I had so far been testing by changing the locale and then refreshing the page. The component now rerenders when the locale changes though.

2025-05-23.at.12.42.18-NUMBERINPUT-Google.Chrome.mp4

This new hook is powered by some new documentLang utilities that we can test out here in storybook for now. Eventually we should be able to reuse them when we implement this in web-components. It could also be the beginning of how we could add locale/lang detection to NumberInput so the consumer wouldn't have to pass a locale at all.

@ariellalgilmore ariellalgilmore left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

amazing!!!!

@kennylam kennylam left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM nice one @tay1orjones!

@kennylam kennylam added this pull request to the merge queue May 23, 2025
@github-project-automation github-project-automation Bot moved this to 🚦 In Review in Design System May 23, 2025
Merged via the queue into carbon-design-system:main with commit 781e9d0 May 23, 2025
36 of 37 checks passed
@github-project-automation github-project-automation Bot moved this from 🚦 In Review to ✅ Done in Design System May 23, 2025
@maradwan26 maradwan26 mentioned this pull request May 26, 2025
3 tasks
@tay1orjones tay1orjones self-assigned this Jun 3, 2025
AlexanderMelox pushed a commit to AlexanderMelox/carbon that referenced this pull request Jun 10, 2025
…bon-design-system#19471)

* fix(numberinput): add type of text, updated styles, example localization

* wip

* wip

* feat(numberinput): add type='text' and locale-based formatting

* chore: remove test stories

* chore: yarn dedupe

* docs(numberinput): update type prop comments

* fix(numberinput): make inputMode optional

* docs(numberinput): clean up stories

* fix(numberinput): revert min/max on story for avt tests

* chore: min/max arg types for default story

* fix(fluidnumberinput): remove duplicate style selector, correct invalid selector

* feat(utilities): add documentLang utilities to track html lang updates
heloiselui pushed a commit to guidari/carbon that referenced this pull request Jul 11, 2025
…bon-design-system#19471)

* fix(numberinput): add type of text, updated styles, example localization

* wip

* wip

* feat(numberinput): add type='text' and locale-based formatting

* chore: remove test stories

* chore: yarn dedupe

* docs(numberinput): update type prop comments

* fix(numberinput): make inputMode optional

* docs(numberinput): clean up stories

* fix(numberinput): revert min/max on story for avt tests

* chore: min/max arg types for default story

* fix(fluidnumberinput): remove duplicate style selector, correct invalid selector

* feat(utilities): add documentLang utilities to track html lang updates
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

Consider using type="text" in NumberInput NumberInput should not have pattern attribute NumberInput does not support comma as a decimal separator

4 participants