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

feat(number-input): new decorator prop #18020

Original file line number Diff line number Diff line change
Expand Up @@ -5670,6 +5670,9 @@ Map {
"className": Object {
"type": "string",
},
"decorator": Object {
"type": "node",
},
"defaultValue": Object {
"args": Array [
Array [
Expand Down Expand Up @@ -5743,9 +5746,7 @@ Map {
],
"type": "oneOf",
},
"slug": Object {
"type": "node",
},
"slug": [Function],
"step": Object {
"type": "number",
},
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/components/Form/Form.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ export const withAILabel = (args) => {
<Stack gap={7} className="form-example">
<Form aria-label="sample form" className="ai-label-form">
<Stack gap={7}>
<NumberInput {...numberInputProps} slug={aiLabel} {...rest} />
<NumberInput {...numberInputProps} decorator={aiLabel} {...rest} />
<DatePicker datePickerType="single">
<DatePickerInput
placeholder="mm/dd/yyyy"
Expand Down Expand Up @@ -400,7 +400,7 @@ export const withAILabel = (args) => {
<FluidNumberInput
{...numberInputProps}
id="fluid-number-input"
slug={aiLabel}
decorator={aiLabel}
{...rest}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import NumberInputSkeleton from './NumberInput.Skeleton';
import Button from '../Button';
import { AILabel, AILabelContent, AILabelActions } from '../AILabel';
import { IconButton } from '../IconButton';
import { View, FolderOpen, Folders } from '@carbon/icons-react';
import { View, FolderOpen, Folders, Information } from '@carbon/icons-react';
import { Tooltip } from '../Tooltip';

export default {
title: 'Components/NumberInput',
Expand Down Expand Up @@ -77,7 +78,22 @@ export const withAILabel = () => (
label="NumberInput label"
helperText="Optional helper text."
invalidText="Number is not valid"
slug={aiLabel}
decorator={aiLabel}
/>

<br />
<NumberInput
min={-100}
max={100}
value={50}
label="NumberInput label"
helperText="Optional helper text."
invalidText="Number is not valid"
decorator={
<Tooltip label={'hello'}>
<Information />
</Tooltip>
}
/>
</div>
);
Expand Down Expand Up @@ -150,6 +166,11 @@ Playground.argTypes = {
disable: true,
},
},
slug: {
table: {
disable: true,
},
},
translateWithId: {
table: {
disable: true,
Expand Down
56 changes: 46 additions & 10 deletions packages/react/src/components/NumberInput/NumberInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import React, {
useState,
useEffect,
FC,
ReactElement,
} from 'react';
import { useMergedRefs } from '../../internal/useMergedRefs';
import { useNormalizedInputProps as normalize } from '../../internal/useNormalizedInputProps';
Expand Down Expand Up @@ -63,6 +64,11 @@ export interface NumberInputProps
*/
className?: string;

/**
* **Experimental**: Provide a `decorator` component to be rendered inside the `TextInput` component
*/
decorator?: ReactNode;

/**
* Optional starting value for uncontrolled state
*/
Expand Down Expand Up @@ -171,6 +177,7 @@ export interface NumberInputProps
size?: 'sm' | 'md' | 'lg';

/**
* @deprecated please use `decorator` instead.
* **Experimental**: Provide a `Slug` component to be rendered inside the `TextInput` component
*/
slug?: ReactNode;
Expand Down Expand Up @@ -201,6 +208,7 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
const {
allowEmpty = false,
className: customClassName,
decorator,
disabled = false,
disableWheel: disableWheelProp = false,
defaultValue = 0,
Expand Down Expand Up @@ -279,6 +287,7 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
const wrapperClasses = cx(`${prefix}--number__input-wrapper`, {
[`${prefix}--number__input-wrapper--warning`]: normalizedProps.warn,
[`${prefix}--number__input-wrapper--slug`]: slug,
[`${prefix}--number__input-wrapper--decorator`]: decorator,
});
const iconClasses = cx({
[`${prefix}--number__invalid`]:
Expand Down Expand Up @@ -376,18 +385,27 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
}
}

// Slug is always size `mini`
let normalizedSlug;
if (slug && slug['type']?.displayName === 'AILabel') {
normalizedSlug = React.cloneElement(slug as React.ReactElement<any>, {
size: 'mini',
});
// AILabel always size `mini`
let normalizedDecorator = slug ?? decorator;
if (
normalizedDecorator &&
normalizedDecorator['type']?.displayName === 'AILabel'
) {
normalizedDecorator = React.cloneElement(
normalizedDecorator as React.ReactElement<any>,
{
size: 'mini',
}
);
}

// Need to update the internal value when the revert button is clicked
let isRevertActive;
if (slug && slug['type']?.displayName === 'AILabel') {
isRevertActive = normalizedSlug.props.revertActive;
if (
normalizedDecorator &&
normalizedDecorator['type']?.displayName === 'AILabel'
) {
isRevertActive = (normalizedDecorator as ReactElement).props.revertActive;
}

useEffect(() => {
Expand Down Expand Up @@ -449,7 +467,16 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
type="number"
value={value}
/>
{normalizedSlug}
{slug ? (
normalizedDecorator
) : decorator ? (
<div
className={`${prefix}--number__input-inner-wrapper--decorator`}>
{normalizedDecorator}
</div>
) : (
''
)}
{Icon ? <Icon className={iconClasses} /> : null}
{!hideSteppers && (
<div className={`${prefix}--number__controls`}>
Expand Down Expand Up @@ -505,6 +532,11 @@ NumberInput.propTypes = {
*/
className: PropTypes.string,

/**
* **Experimental**: Provide a `decorator` component to be rendered inside the `NumberInput` component
*/
decorator: PropTypes.node,

/**
* Optional starting value for uncontrolled state
*/
Expand Down Expand Up @@ -610,7 +642,11 @@ NumberInput.propTypes = {
/**
* **Experimental**: Provide a `Slug` component to be rendered inside the `NumberInput` component
*/
slug: PropTypes.node,
slug: deprecate(
PropTypes.node,
'The `slug` prop for `NumberInput` is no longer needed and has ' +
'been deprecated in v11 in favor of the new `decorator` prop. It will be moved in the next major release.'
),

/**
* Specify how much the values should increase/decrease upon clicking on up/down button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,24 @@ describe('NumberInput', () => {
expect(screen.getByLabelText('test-label')).toHaveValue(5);
});

it('should respect slug prop', () => {
it('should respect decorator prop', () => {
render(
<NumberInput label="test-label" id="test" decorator={<AILabel />} />
);

expect(
screen.getByRole('button', { name: 'AI - Show information' })
).toBeInTheDocument();
});

it('should respect the deprecated slug prop', () => {
const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});
render(<NumberInput label="test-label" id="test" slug={<AILabel />} />);

expect(
screen.getByRole('button', { name: 'AI - Show information' })
).toBeInTheDocument();
spy.mockRestore();
});

it('should allow an empty string as input to the underlying <input>', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,10 @@
}

// AILabel styles
.#{$prefix}--number-input--fluid
.#{$prefix}--number__input-wrapper--decorator
.#{$prefix}--number__input-inner-wrapper--decorator
> *,
.#{$prefix}--number-input--fluid
.#{$prefix}--number__input-wrapper--slug
.#{$prefix}--ai-label,
Expand All @@ -287,6 +291,14 @@
inset-block-start: convert.to-rem(43px);
}

.#{$prefix}--number-input--fluid.#{$prefix}--number-input--fluid--invalid
.#{$prefix}--number__input-wrapper--decorator
.#{$prefix}--number__input-inner-wrapper--decorator
> *,
.#{$prefix}--number-input--fluid
.#{$prefix}--number__input-wrapper--decorator.#{$prefix}--number__input-wrapper--warning
.#{$prefix}--number__input-inner-wrapper--decorator
> *,
.#{$prefix}--number-input--fluid.#{$prefix}--number-input--fluid--invalid
.#{$prefix}--number__input-wrapper--slug
.#{$prefix}--ai-label,
Expand All @@ -302,6 +314,15 @@
inset-inline-end: convert.to-rem(88px);
}

.#{$prefix}--number-input--fluid
.#{$prefix}--number__input-wrapper--decorator
input,
.#{$prefix}--number-input--fluid.#{$prefix}--number-input--fluid--invalid
.#{$prefix}--number__input-wrapper--decorator
input[data-invalid],
.#{$prefix}--number-input--fluid
.#{$prefix}--number__input-wrapper--decorator.#{$prefix}--number__input-wrapper--warning
input,
.#{$prefix}--number-input--fluid
.#{$prefix}--number__input-wrapper--slug
input,
Expand All @@ -314,6 +335,10 @@
padding-inline-end: convert.to-rem(120px);
}

.#{$prefix}--number-input--fluid
.#{$prefix}--number__input-wrapper--decorator:has(
.#{$prefix}--ai-label
):not(:has(.#{$prefix}--ai-label--revert)),
.#{$prefix}--number-input--fluid
.#{$prefix}--number__input-wrapper--slug:has(.#{$prefix}--ai-label):not(
:has(.#{$prefix}--ai-label--revert)
Expand Down
Loading
Loading