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

[base-ui] Create useNumberInput and NumberInput #36119

Merged
merged 70 commits into from
Aug 3, 2023
Merged
Changes from 1 commit
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
c3c9b97
[base] Add useNumberInput
mj12albert Feb 1, 2023
a8418f0
Add aria attributes, more types.
mj12albert Feb 27, 2023
c5f9b59
Add NumberInputUnstyled
mj12albert Feb 23, 2023
3a9b8f9
Redo change handlers
mj12albert Feb 28, 2023
fb52a63
Add increment and decrement buttons
mj12albert Mar 1, 2023
3e19da1
Keyboard handlers and shift multiplier
mj12albert Mar 2, 2023
40bee99
Apply code review changes
mj12albert Mar 3, 2023
df5b8e8
Extract number check util
mj12albert Mar 3, 2023
de0cb54
Separate directories
mj12albert Mar 3, 2023
39c1e2c
Update docs
mj12albert Mar 3, 2023
7fc5122
NumberInput introduction demo
mj12albert Mar 3, 2023
c508ec3
Store quantity picker demo
mj12albert Mar 9, 2023
fdf42c9
Add a demo using useNumberInput
mj12albert Mar 22, 2023
0734764
Update NumberInputUnstyled to support useClassNamesOverride
mj12albert Mar 22, 2023
e95f643
Add second hook demo
mj12albert Mar 23, 2023
b0ea58c
Fix compatibility with ComponentPageTabs
mj12albert Apr 12, 2023
23d9d36
Write docs page
mj12albert Apr 12, 2023
ce96976
Add explanation of min, max, step props
mj12albert Apr 12, 2023
e187960
Add explanation of shiftMultiplier prop
mj12albert Apr 13, 2023
7b733c5
Simplify hook demo
mj12albert Apr 17, 2023
e9267ff
Design revisions
mj12albert Apr 18, 2023
e424858
Format demos
mj12albert Apr 18, 2023
1a67068
Expand introduction paragraph
mj12albert Apr 19, 2023
1f8162f
Update onValueChange signature
mj12albert Apr 19, 2023
11bff81
Align dirty value updater naming
mj12albert Apr 21, 2023
188e02e
Remove Unstyled suffix
mj12albert Apr 28, 2023
8450b0b
Tweak stepping behavior
mj12albert May 1, 2023
e0c81e2
Add readOnly prop
mj12albert May 2, 2023
1b9ed63
Align with latest API changes
mj12albert May 11, 2023
dc6f1ee
Add tests
mj12albert May 15, 2023
8c8e8a8
Add onValueChange test
mj12albert May 15, 2023
7a774be
Add keyboard interaction tests
mj12albert May 15, 2023
e4a73d9
Add stepper button tests
mj12albert May 16, 2023
18868e4
Test out @testing-library/user-event
mj12albert May 17, 2023
b3161e6
Update keyboard interaction tests
mj12albert May 17, 2023
bef43f2
Add more stepper button tests
mj12albert May 17, 2023
64d459f
Refactor tests using user-event
mj12albert May 17, 2023
10e8c33
Improve tab order tests
mj12albert May 17, 2023
0f2b804
Cleanup
mj12albert May 18, 2023
e6c57a5
Remove spinbutton role, add aria-controls
mj12albert May 19, 2023
68b2641
Fix NumberInput test missing slots
mj12albert May 19, 2023
eb93cbb
A few visual tweaks on the Demos
zanivan May 22, 2023
bcf5a67
yarn docs:typescript:formatted
zanivan May 22, 2023
f5e8422
Add unstable prefix
mj12albert May 24, 2023
db39c57
Rename change handlers
mj12albert May 29, 2023
ccd3199
Add use-client
mj12albert Jul 26, 2023
63c0d44
Docs fixes
mj12albert Jul 26, 2023
6ba86c5
Add Tailwind and plain CSS versions of basic demo
mj12albert Jul 26, 2023
1058f01
Update intro demo
mj12albert Jul 27, 2023
b281e29
Update QuantityInput demo
mj12albert Jul 27, 2023
98d9217
Update CompactNumberInput demo
mj12albert Jul 27, 2023
7317788
Update hook demo
mj12albert Jul 28, 2023
8ea2349
Drop the component prop and single letter type names
mj12albert Jul 28, 2023
2ac68aa
Update api docs
mj12albert Jul 28, 2023
2563733
Generate proptypes
mj12albert Jul 28, 2023
fd7ba0d
Update api docs
mj12albert Jul 28, 2023
6ea8267
Update readOnly behavior
mj12albert Jul 31, 2023
b887e89
Fix generating import statements for unstable items in Base API pages
mj12albert Aug 1, 2023
3b9aac2
Fix arrows in NumberInputIntroduction
mj12albert Aug 1, 2023
19b5af2
Fix all html arrows in the demos
mj12albert Aug 1, 2023
087e442
Misc fixes
mj12albert Aug 1, 2023
9e545a0
Type onBlur more explicitly
mj12albert Aug 1, 2023
414442f
Update api docs
mj12albert Aug 1, 2023
105c8c1
Styling fixes for demos
mj12albert Aug 1, 2023
ca3b51b
More styling fixes
mj12albert Aug 1, 2023
25d8a68
Another styling fix
mj12albert Aug 2, 2023
652c650
Fix component import name
mj12albert Aug 2, 2023
087e1f2
Fix svg sizing in buttons for mobile Safari
mj12albert Aug 2, 2023
1735597
Fix variable names
mj12albert Aug 2, 2023
420053d
Change product to productId in md
mj12albert Aug 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add second hook demo
mj12albert committed Aug 1, 2023
commit e95f643400480c9be1974236845313407ae60306
171 changes: 171 additions & 0 deletions docs/data/base/components/number-input/UseNumberInputCompact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import * as React from 'react';
import useNumberInput from '@mui/base/useNumberInput';
import { styled } from '@mui/system';
import { unstable_useForkRef as useForkRef } from '@mui/utils';

const CompactNumberInput = React.forwardRef(function CompactNumberInput(props, ref) {
const {
getRootProps,
getInputProps,
getIncrementButtonProps,
getDecrementButtonProps,
} = useNumberInput(props);

const inputProps = getInputProps();

inputProps.ref = useForkRef(inputProps.ref, ref);

return (
<StyledInputRoot {...getRootProps()}>
<StyledStepperButton
className="increment"
{...getIncrementButtonProps()}
tabIndex={0}
>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 0 24 24"
width="24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path
d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"
fill="currentColor"
/>
</svg>
</StyledStepperButton>
<StyledStepperButton
className="decrement"
{...getDecrementButtonProps()}
tabIndex={0}
>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 0 24 24"
width="24"
>
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"
fill="currentColor"
/>
</svg>
</StyledStepperButton>
<HiddenInput {...props} {...inputProps} />
</StyledInputRoot>
);
});

export default function UseNumberInputCompact() {
const [value, setValue] = React.useState();

return (
<Layout>
<CompactNumberInput
aria-label="Compact number input"
placeholder="Type a number…"
value={value}
onValueChange={(val) => setValue(val)}
/>
<pre>Current value: {value ?? ' '}</pre>
</Layout>
);
}

const blue = {
100: '#DAECFF',
200: '#80BFFF',
400: '#3399FF',
500: '#007FFF',
600: '#0072E5',
};

const grey = {
50: '#F3F6F9',
100: '#E7EBF0',
200: '#E0E3E7',
300: '#CDD2D7',
400: '#B2BAC2',
500: '#A0AAB4',
600: '#6F7E8C',
700: '#3E5060',
800: '#2D3843',
900: '#1A2027',
};

const StyledInputRoot = styled('div')(
({ theme }) => `
font-family: IBM Plex Sans, sans-serif;
font-size: 0.875rem;
font-weight: 400;
line-height: 1.5;
display: grid;
grid-template-columns: 2.5rem;
grid-template-rows: 2rem 2rem;
row-gap: 2px;
border-radius: 0.5rem;
border-style: solid;
border-width: 2px;
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
background: ${theme.palette.mode === 'dark' ? grey[600] : grey[50]};
border-color: ${theme.palette.mode === 'dark' ? grey[500] : grey[200]};
box-shadow: 0px 2px 2px ${theme.palette.mode === 'dark' ? grey[800] : grey[50]};

&:hover {
border-color: ${theme.palette.mode === 'dark' ? blue[600] : blue[400]};
box-shadow:
0 0 0 1px ${theme.palette.mode === 'dark' ? blue[600] : blue[400]},
0px 2px 2px ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
}
`,
);

const HiddenInput = styled('input')`
visibility: hidden;
position: absolute;
`;

const StyledStepperButton = styled('button')(
({ theme }) => `
display: flex;
flex-flow: row nowrap;
justify-content: center;
align-items: center;

font-size: 0.875rem;
box-sizing: border-box;
border: 0;
color: inherit;

&:hover {
cursor: pointer;
background: ${theme.palette.mode === 'dark' ? blue[600] : blue[400]};
color: ${grey[50]};
}

&:focus-visible {
outline: 0;
background: ${theme.palette.mode === 'dark' ? blue[400] : blue[200]};
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[50]};
}

&.increment {
border-top-left-radius: 0.35rem;
border-top-right-radius: 0.35rem;
}

&.decrement {
border-bottom-left-radius: 0.35rem;
border-bottom-right-radius: 0.35rem;
}
`,
);

const Layout = styled('div')`
display: flex;
flex-flow: row nowrap;
align-items: center;
column-gap: 2rem;
`;
175 changes: 175 additions & 0 deletions docs/data/base/components/number-input/UseNumberInputCompact.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import * as React from 'react';
import useNumberInput, { UseNumberInputParameters } from '@mui/base/useNumberInput';
import { styled } from '@mui/system';
import { unstable_useForkRef as useForkRef } from '@mui/utils';

const CompactNumberInput = React.forwardRef(function CompactNumberInput(
props: UseNumberInputParameters & React.InputHTMLAttributes<HTMLInputElement>,
ref: React.ForwardedRef<HTMLInputElement>,
) {
const {
getRootProps,
getInputProps,
getIncrementButtonProps,
getDecrementButtonProps,
} = useNumberInput(props);

const inputProps = getInputProps();

inputProps.ref = useForkRef(inputProps.ref, ref);

return (
<StyledInputRoot {...getRootProps()}>
<StyledStepperButton
className="increment"
{...getIncrementButtonProps()}
tabIndex={0}
>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 0 24 24"
width="24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path
d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"
fill="currentColor"
/>
</svg>
</StyledStepperButton>
<StyledStepperButton
className="decrement"
{...getDecrementButtonProps()}
tabIndex={0}
>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 0 24 24"
width="24"
>
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"
fill="currentColor"
/>
</svg>
</StyledStepperButton>
<HiddenInput {...props} {...inputProps} />
</StyledInputRoot>
);
});

export default function UseNumberInputCompact() {
const [value, setValue] = React.useState<number | undefined>();

return (
<Layout>
<CompactNumberInput
aria-label="Compact number input"
placeholder="Type a number…"
value={value}
onValueChange={(val) => setValue(val)}
/>

<pre>Current value: {value ?? ' '}</pre>
</Layout>
);
}

const blue = {
100: '#DAECFF',
200: '#80BFFF',
400: '#3399FF',
500: '#007FFF',
600: '#0072E5',
};

const grey = {
50: '#F3F6F9',
100: '#E7EBF0',
200: '#E0E3E7',
300: '#CDD2D7',
400: '#B2BAC2',
500: '#A0AAB4',
600: '#6F7E8C',
700: '#3E5060',
800: '#2D3843',
900: '#1A2027',
};

const StyledInputRoot = styled('div')(
({ theme }) => `
font-family: IBM Plex Sans, sans-serif;
font-size: 0.875rem;
font-weight: 400;
line-height: 1.5;
display: grid;
grid-template-columns: 2.5rem;
grid-template-rows: 2rem 2rem;
row-gap: 2px;
border-radius: 0.5rem;
border-style: solid;
border-width: 2px;
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
background: ${theme.palette.mode === 'dark' ? grey[600] : grey[50]};
border-color: ${theme.palette.mode === 'dark' ? grey[500] : grey[200]};
box-shadow: 0px 2px 2px ${theme.palette.mode === 'dark' ? grey[800] : grey[50]};

&:hover {
border-color: ${theme.palette.mode === 'dark' ? blue[600] : blue[400]};
box-shadow:
0 0 0 1px ${theme.palette.mode === 'dark' ? blue[600] : blue[400]},
0px 2px 2px ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
}
danilo-leal marked this conversation as resolved.
Show resolved Hide resolved
`,
);

const HiddenInput = styled('input')`
visibility: hidden;
position: absolute;
`;

const StyledStepperButton = styled('button')(
({ theme }) => `
display: flex;
flex-flow: row nowrap;
justify-content: center;
align-items: center;

font-size: 0.875rem;
box-sizing: border-box;
border: 0;
color: inherit;
danilo-leal marked this conversation as resolved.
Show resolved Hide resolved

&:hover {
cursor: pointer;
background: ${theme.palette.mode === 'dark' ? blue[600] : blue[400]};
danilo-leal marked this conversation as resolved.
Show resolved Hide resolved
color: ${grey[50]};
}

&:focus-visible {
outline: 0;
background: ${theme.palette.mode === 'dark' ? blue[400] : blue[200]};
danilo-leal marked this conversation as resolved.
Show resolved Hide resolved
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[50]};
}

&.increment {
border-top-left-radius: 0.35rem;
border-top-right-radius: 0.35rem;
}

&.decrement {
border-bottom-left-radius: 0.35rem;
border-bottom-right-radius: 0.35rem;
}
`,
);

const Layout = styled('div')`
display: flex;
flex-flow: row nowrap;
align-items: center;
column-gap: 2rem;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Layout>
<CompactNumberInput
aria-label="Compact number input"
placeholder="Type a number…"
value={value}
onValueChange={(val) => setValue(val)}
/>

<pre>Current value: {value ?? ' '}</pre>
</Layout>
8 changes: 8 additions & 0 deletions docs/data/base/components/number-input/number-input.md
Original file line number Diff line number Diff line change
@@ -46,4 +46,12 @@ Here's an example of a component built using the hook alone:

### Quantity Input

The "purchase quantity" input component from the MUI store:

{{"demo": "QuantityInput.js", "defaultCodeOpen": false, "bg": "gradient"}}

### Steppers only

A compact stepper-only component using the hook:

{{"demo": "UseNumberInputCompact.js", "defaultCodeOpen": false, "bg": "gradient"}}
danilo-leal marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 4 additions & 0 deletions docs/data/base/pagesApi.js
Original file line number Diff line number Diff line change
@@ -72,6 +72,10 @@ module.exports = [
title: 'useMenuButton',
},
{ pathname: '/base-ui/react-menu/hooks-api/#use-menu-item', title: 'useMenuItem' },
{
pathname: '/base-ui/react-number-input/hooks-api/#use-number-input',
title: 'useNumberInput',
},
{ pathname: '/base-ui/react-select/hooks-api/#use-option', title: 'useOption' },
{ pathname: '/base-ui/react-select/hooks-api/#use-select', title: 'useSelect' },
{ pathname: '/base-ui/react-slider/hooks-api/#use-slider', title: 'useSlider' },
12 changes: 10 additions & 2 deletions docs/pages/base-ui/react-number-input/[docsTab]/index.js
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import AppFrame from 'docs/src/modules/components/AppFrame';
import * as pageProps from 'docs/data/base/components/number-input/number-input.md?@mui/markdown';
import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
import NumberInputUnstyledApiJsonPageContent from '../../api/number-input-unstyled.json';
import useNumberInputApiJsonPageContent from '../../api/use-number-input.json';

export default function Page(props) {
const { userLanguage, ...other } = props;
@@ -29,12 +30,19 @@ export const getStaticProps = () => {
);
const NumberInputUnstyledApiDescriptions = mapApiPageTranslations(NumberInputUnstyledApiReq);

const useNumberInputApiReq = require.context(
'docs/translations/api-docs/use-number-input',
false,
/use-number-input.*.json$/,
);
const useNumberInputApiDescriptions = mapApiPageTranslations(useNumberInputApiReq);

return {
props: {
componentsApiDescriptions: { NumberInputUnstyled: NumberInputUnstyledApiDescriptions },
componentsApiPageContents: { NumberInputUnstyled: NumberInputUnstyledApiJsonPageContent },
hooksApiDescriptions: {},
hooksApiPageContents: {},
hooksApiDescriptions: { useNumberInput: useNumberInputApiDescriptions },
hooksApiPageContents: { useNumberInput: useNumberInputApiJsonPageContent },
},
};
};
Original file line number Diff line number Diff line change
@@ -6,9 +6,14 @@
"disabled": "If <code>true</code>, the component is disabled. The prop defaults to the value (<code>false</code>) inherited from the parent FormControl component.",
"error": "If <code>true</code>, the <code>input</code> will indicate an error by setting the <code>aria-invalid</code> attribute on the input and the <code>Mui-error</code> class on the root element.",
"id": "The id of the <code>input</code> element.",
"max": "The maximum value.",
"min": "The minimum value.",
"onValueChange": "Callback fired after the value is clamped and changes. Called with <code>undefined</code> when the value is unset.",
"required": "If <code>true</code>, the <code>input</code> element is required. The prop defaults to the value (<code>false</code>) inherited from the parent FormControl component.",
"slotProps": "The props used for each slot inside the NumberInput.",
"slots": "The components used for each slot inside the InputBase. Either a string to use a HTML element or a component. See <a href=\"#slots\">Slots API</a> below for more details."
"slots": "The components used for each slot inside the InputBase. Either a string to use a HTML element or a component. See <a href=\"#slots\">Slots API</a> below for more details.",
"step": "The amount that the value changes on each increment or decrement.",
"value": "The current value. Use when the component is controlled."
},
"classDescriptions": {}
}
2 changes: 2 additions & 0 deletions docs/translations/translations.json
Original file line number Diff line number Diff line change
@@ -278,6 +278,7 @@
"/base-ui/react-menu/components-api/#menu-item": "MenuItem",
"/base-ui/react-modal/components-api/#modal": "Modal",
"/base-ui/react-no-ssr/components-api/#no-ssr": "NoSsr",
"/base-ui/react-number-input/components-api/#number-input-unstyled": "NumberInputUnstyled",
"/base-ui/react-select/components-api/#option": "Option",
"/base-ui/react-select/components-api/#option-group": "OptionGroup",
"/base-ui/react-popper/components-api/#popper": "Popper",
@@ -301,6 +302,7 @@
"/base-ui/react-menu/hooks-api/#use-menu": "useMenu",
"/base-ui/react-menu/hooks-api/#use-menu-button": "useMenuButton",
"/base-ui/react-menu/hooks-api/#use-menu-item": "useMenuItem",
"/base-ui/react-number-input/hooks-api/#use-number-input": "useNumberInput",
"/base-ui/react-select/hooks-api/#use-option": "useOption",
"/base-ui/react-select/hooks-api/#use-select": "useSelect",
"/base-ui/react-slider/hooks-api/#use-slider": "useSlider",
2 changes: 1 addition & 1 deletion packages/mui-base/src/useNumberInput/useNumberInput.ts
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ const parseInput = (v: string): string => {
*
* API:
*
* - [useNumberInput API](https://mui.com/base/api/use-number-input/)
* - [useNumberInput API](https://mui.com/base/react-number-input/hooks-api/#use-number-input)
*/
export default function useNumberInput(
parameters: UseNumberInputParameters,