Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NumberField] Component and Hook #186

Merged
merged 9 commits into from
Apr 11, 2024
Merged

Conversation

atomiks
Copy link
Contributor

@atomiks atomiks commented Mar 15, 2024

Closes #185
Closes #224

Preview: https://deploy-preview-186--base-ui.netlify.app/base-ui/react-number-field/

TODO:

  • Complete tests
  • Documentation

  • When merged, use the mergeEventHandlers / mergeProps function written by Michal to significantly simplify the event handler definitions.
  • Remove the “scrub past“ distance when a max/min is specified, so it doesn't take too long to "get back".
  • Fix the “magnifier” appearing on iOS when press-holding the stepper buttons.
  • Add teleportDistance (or other name) prop to loop closer to the input compared to the viewport.
  • Fix bug when there’s a “-“ and you can’t type “-“ again when all content has been selected to be overwritten (e.g. paste).
  • Fix virtual cursor being too big when zooming (cmd+ or pinch-zoom). It should invert the scale to match the OS size. [Update: was unable to figure out how to reliably detect native browser zoom. Only pinch zoom is handled.]
  • Fix 1px sensitivity to 2px for scrub area, and add sensitivity prop.
  • Add Home and End key support if min/max are specified.
  • Add displayName to Contexts.
  • Ensure all imports use the path rather than the top-level barrel.
  • Cache Intl.NumberFormat usage.
  • Match various nits/conventions pointed out by Olivier.
  • Dynamically change the inputMode="numeric" prop for iOS when appropriate, which doesn't show a negative sign in the software keyboard, it must be "text".
  • Research if spinbutton ARIA props become necessary for iOS when inputMode is "text".
  • The virtual cursor should be automatically portalled to the body.
  • Move ScrubArea logic into the useNumberField hook, or possibly a separate hook. Depends how granular the tree-shaking could possibly be for this functionality.
  • Automatically remove floating point errors

@atomiks atomiks marked this pull request as draft March 15, 2024 12:04
@atomiks atomiks added the component: number field This is the name of the generic UI component, not the React module! label Mar 15, 2024
@colmtuite colmtuite requested a review from a team March 15, 2024 13:22
@oliviertassinari
Copy link
Member

Rebased on HEAD. It seems to fix Circle CI, I don't know why.

@atomiks atomiks closed this Mar 20, 2024
@atomiks atomiks deleted the feat/NumberField branch March 20, 2024 20:25
@atomiks atomiks restored the feat/NumberField branch March 20, 2024 20:26
@atomiks atomiks reopened this Mar 20, 2024
@atomiks atomiks force-pushed the feat/NumberField branch 10 times, most recently from 71b21fe to 4aa63f6 Compare March 22, 2024 10:37
@michaldudak
Copy link
Member

It might be subjective, but the scrubber feels too sensitive. Changing value on every pixel of pointer movement may be too fast. Figma seems to update the value every two pixels (or perhaps they take pixel density into consideration?). We could make this configurable by adding the sensitivity prop to the ScrubArea (being the number of pixels the pointer should move before the value is updated)

@atomiks
Copy link
Contributor Author

atomiks commented Mar 22, 2024

@michaldudak yeah you're right, previously it was 2px but I must have changed something that reverted it back to 1px. Will fix.

Copy link
Contributor

@colmtuite colmtuite left a comment

Choose a reason for hiding this comment

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

Tested on Chrome, Safari, Safari iOS, and Chrome iOS. Feedback/thoughts so far:

  • ScrubAreaCursor is missing from the docs anatomy section.
  • Seems like we don't have the teleport threshold thingy prop on ScrubAreaCursor yet? Are you planning to add that?
  • Should scrubbing start from where the pointer was when scrubbing began, or when the pointer direction changes during scrubbing? Idk how to explain wtf I'm on about here 🤣 It's noticeable when min and max are set. If you set max, then scrub way beyond the max value, you need to scrub all the way back down before the value decrements. Perhaps it could begin decrementing immediately when the pointer direction changes?
  • The largeStep logic seems odd to me. I expected it to just increment/decrement the value by whatever the largeStep value is?
  • smallStep doesn't seem to be working for me. Nothing happens when I hold cmd and then increment/decrement.

Still to test: formatting options.

@atomiks atomiks marked this pull request as ready for review March 29, 2024 03:59
Copy link
Member

@mnajdova mnajdova left a comment

Choose a reason for hiding this comment

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

Looks really good 👌

packages/mui-base/src/NumberField/NumberField.tsx Outdated Show resolved Hide resolved
value,
onChange,
allowWheelScrub,
render: renderProp,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
render: renderProp,
render = defaultRender,

I can't see renderProp being used anywhere else. I see this patterns used everywhere. If there isn't a valid reason I would propose going with this one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We went with the ?? fallback because it avoids requiring a default when generating docs which is too difficult with the generic BaseUiCommonComponentProps due to the polymorphic components.

} = props;
const render = renderProp ?? defaultRender;

const numberField = useNumberField(props);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const numberField = useNumberField(props);
const numberField = useNumberField(props);

We usually invoke the hooks with the ownerState, this makes sure that the default values for the props are also included. It may not make a difference in this component, but if we change the default value of some prop, it may break unexpectedly.

Copy link
Contributor Author

@atomiks atomiks Mar 31, 2024

Choose a reason for hiding this comment

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

In this case, ownerState is a partially derived version of useNumberField's return value, so it can't be called with it. The default values of the hook just match the component.

@@ -0,0 +1 @@
export { default as clamp } from '@mui/utils/clamp';
Copy link
Member

Choose a reason for hiding this comment

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

Can we do the utils changes in a separate PR? It will make the review easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think at this stage, it's easier if the utils are kept in this PR. To keep it working before rebasing, I'd have to import directly from @mui/utils temporarily, right? I can remember to do utils separately for next time.

Comment on lines 114 to 113
// We need to hide the input value during SSR to avoid a potential mismatch between the server
// rendered value and the client rendered value when their locales differ.
Copy link
Member

Choose a reason for hiding this comment

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

Flash from server's formatting to the user's one. If the server matches the client, there's no flash. This indeed may be better.

👍

@atomiks atomiks force-pushed the feat/NumberField branch 2 times, most recently from 99d4bb0 to 7866e25 Compare April 2, 2024 08:25
@colmtuite
Copy link
Contributor

Decisions from a recent call:

  • Agreed to just change the scrub min/max thing. There was one other issue mentioned, but James couldn't remember what it was. So the plan is to change it, then test again.
  • Do some research to see if cmd/ctrl is the best key for smallStep. Perhaps alt/option is better?
  • Internationalisation: passing functions and stuff needs to be added.

@atomiks
Copy link
Contributor Author

atomiks commented Apr 2, 2024

@colmtuite

  • Already changed
  • Given only Chakra seems to have this, I couldn't find any info about it. I don't think it matters really — I asked Claude and its first thought was Alt not Meta, if that has any bearing. But it said it doesn't really matter overall.

Internationalisation: passing functions and stuff needs to be added.

Can you elaborate more here? The format prop doesn't accept a function in that it only accepts Intl.NumberFormatOptions, but do you want to enable them to format the input however they like as well? (I believe Chakra does this.) I think this can be done if they specify value manually on NumberField.Input as it will override our internal prop, but this may cause issues (that I will need to investigate). If they do that as well, it will force them to make the component controlled since they'll need access to the number value.

We might be missing props similar to validate (accepts a function) and validationBehavior (string) from react-aria, however.

@colmtuite
Copy link
Contributor

@atomiks

I asked Claude and its first thought was Alt

Ok yeah my first thought was Alt also. If that's the only info we have, and both point to Alt, I guess let's go with Alt.

do you want to enable them to format the input however they like as well

No this was just a note I took from our call, not sure what you meant when you were talking about formatting, internationalisation, and functions. I remember you saying some piece of work around internationalisation is still incomplete?

@atomiks
Copy link
Contributor Author

atomiks commented Apr 3, 2024

@colmtuite

Ok yeah my first thought was Alt also. If that's the only info we have, and both point to Alt, I guess let's go with Alt.

Alright, will change 👍

I remember you saying some piece of work around internationalisation is still incomplete?

Yeah there were some bugs remaining with the parsing and validation functions, but I should have fixed most of them in the recent commits. However these are only internal and not part of any API.

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Apr 9, 2024
return (
<NumberField value={value} onChange={setValue}>
<NumberField.Group>
<NumberField.Decrement>&minus;</NumberField.Decrement>
Copy link
Member

Choose a reason for hiding this comment

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

I'm curious, why not -?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

&minus is wider and looks more in proportion with + 😅

docs/data/base/components/number-field/number-field.md Outdated Show resolved Hide resolved
import { mergeReactProps } from '../utils/mergeReactProps';

/**
* @ignore - internal hook.
Copy link
Member

Choose a reason for hiding this comment

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

Don't we want to expose it like the other hooks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No as it's just an internal hook to place some of the functionality in its own hook

packages/mui-base/src/NumberField/utils/validate.test.ts Outdated Show resolved Hide resolved
packages/mui-base/src/useNumberField/useNumberField.ts Outdated Show resolved Hide resolved
Bifurcate tests

Docs stylelint

Attempt test fix

Ignore perf test in JSDOM

Use skip

Docs

Format

Update docs

Relocate ScrubArea logic

Memo return value

Unit tests for parsing and formatting

Skip test in browser

Tests

Relocate pointerDownEvent

Remove floating point errors

Docs

Update allowed chars

Bug fixes and feedback

Remove autoFocus destructure

Remove clamp requirement logic

Allow currency symbols

Move context menu handler

Avoid calling handlers while disabled

Unsubscribe from visualViewport

Add description to useNumberField

Docs

Use mergeReactProps

Use altKey for smallStep

Fix cursor rendering
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Apr 9, 2024
@atomiks atomiks merged commit f68970d into mui:master Apr 11, 2024
16 checks passed
@atomiks atomiks deleted the feat/NumberField branch April 11, 2024 02:21
michaldudak pushed a commit to michaldudak/base-ui that referenced this pull request Apr 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: number field This is the name of the generic UI component, not the React module! new feature New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[number field] Disable focus on mobile [number field] Implement NumberField with new API
8 participants