Skip to content

Commit

Permalink
feat(text-area/input): add new decorator prop (carbon-design-system#1…
Browse files Browse the repository at this point in the history
…8050)

* feat(text-area/input): add new decorator prop

* fix(format): update

* fix(stories): remove and adjust styles

* chore: format

---------

Co-authored-by: Nikhil Tomar <[email protected]>
Co-authored-by: Riddhi Bansal <[email protected]>
  • Loading branch information
3 people authored Nov 14, 2024
1 parent 1009bab commit a71c1b4
Show file tree
Hide file tree
Showing 12 changed files with 264 additions and 32 deletions.
14 changes: 8 additions & 6 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -8521,6 +8521,9 @@ Map {
],
"type": "oneOf",
},
"decorator": Object {
"type": "node",
},
"defaultValue": Object {
"args": Array [
Array [
Expand Down Expand Up @@ -8581,9 +8584,7 @@ Map {
"rows": Object {
"type": "number",
},
"slug": Object {
"type": "node",
},
"slug": [Function],
"value": Object {
"args": Array [
Array [
Expand Down Expand Up @@ -8856,6 +8857,9 @@ Map {
"className": Object {
"type": "string",
},
"decorator": Object {
"type": "node",
},
"defaultValue": Object {
"args": Array [
Array [
Expand Down Expand Up @@ -8924,9 +8928,7 @@ Map {
],
"type": "oneOf",
},
"slug": Object {
"type": "node",
},
"slug": [Function],
"type": Object {
"type": "string",
},
Expand Down
8 changes: 4 additions & 4 deletions packages/react/src/components/Form/Form.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,8 @@ export const withAILabel = (args) => {
{...rest}
/>
</DatePicker>
<TextInput {...TextInputProps} slug={aiLabel} {...rest} />
<TextArea {...textareaProps} slug={aiLabel} {...rest} />
<TextInput {...TextInputProps} decorator={aiLabel} {...rest} />
<TextArea {...textareaProps} decorator={aiLabel} {...rest} />
<Dropdown
id="default"
titleText="Dropdown title"
Expand Down Expand Up @@ -408,15 +408,15 @@ export const withAILabel = (args) => {
<FluidTextInput
{...TextInputProps}
id="fluid-text-input"
slug={aiLabel}
decorator={aiLabel}
{...rest}
/>
</div>
<div style={{ display: 'flex' }}>
<FluidTextArea
{...textareaProps}
id="fluid-text-area"
slug={aiLabel}
decorator={aiLabel}
{...rest}
/>
</div>
Expand Down
10 changes: 8 additions & 2 deletions packages/react/src/components/TextArea/TextArea.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
import React from 'react';

import { WithLayer } from '../../../.storybook/templates/WithLayer';
import { View, FolderOpen, Folders } from '@carbon/icons-react';
import { View, FolderOpen, Folders, Information } from '@carbon/icons-react';
import Button from '../Button';
import { AILabel, AILabelContent, AILabelActions } from '../AILabel';
import { IconButton } from '../IconButton';
import { default as TextArea, TextAreaSkeleton } from './';
import { Tooltip } from '../Tooltip';

export default {
title: 'Components/TextArea',
Expand All @@ -26,6 +27,11 @@ export default {
disable: true,
},
},
slug: {
table: {
disable: true,
},
},
},
};

Expand Down Expand Up @@ -88,7 +94,7 @@ export const withAILabel = () => (
helperText="Optional helper text"
rows={4}
id="text-area-5"
slug={aiLabel}
decorator={aiLabel}
/>
);

Expand Down
49 changes: 41 additions & 8 deletions packages/react/src/components/TextArea/TextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export interface TextAreaProps
*/
cols?: number;

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

/**
* Optionally provide the default value of the `<textarea>`
*/
Expand Down Expand Up @@ -130,6 +135,7 @@ export interface TextAreaProps
rows?: number;

/**
* @deprecated please use `decorator` instead.
* **Experimental**: Provide a `Slug` component to be rendered inside the `TextArea` component
*/
slug?: ReactNode;
Expand Down Expand Up @@ -158,6 +164,7 @@ export interface TextAreaProps
const TextArea = React.forwardRef((props: TextAreaProps, forwardRef) => {
const {
className,
decorator,
disabled = false,
id,
labelText,
Expand Down Expand Up @@ -329,6 +336,7 @@ const TextArea = React.forwardRef((props: TextAreaProps, forwardRef) => {
[`${prefix}--text-area__wrapper--readonly`]: other.readOnly,
[`${prefix}--text-area__wrapper--warn`]: warn,
[`${prefix}--text-area__wrapper--slug`]: slug,
[`${prefix}--text-area__wrapper--decorator`]: decorator,
});

const labelClasses = classNames(`${prefix}--label`, {
Expand Down Expand Up @@ -466,12 +474,20 @@ const TextArea = React.forwardRef((props: TextAreaProps, forwardRef) => {
/>
);

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

return (
Expand All @@ -490,7 +506,15 @@ const TextArea = React.forwardRef((props: TextAreaProps, forwardRef) => {
/>
)}
{input}
{normalizedSlug}
{slug ? (
normalizedDecorator
) : decorator ? (
<div className={`${prefix}--text-area__inner-wrapper--decorator`}>
{normalizedDecorator}
</div>
) : (
''
)}
<span
className={`${prefix}--text-area__counter-alert`}
role="alert"
Expand Down Expand Up @@ -528,6 +552,11 @@ TextArea.propTypes = {
*/
counterMode: PropTypes.oneOf(['character', 'word']),

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

/**
* Optionally provide the default value of the `<textarea>`
*/
Expand Down Expand Up @@ -625,7 +654,11 @@ TextArea.propTypes = {
/**
* **Experimental**: Provide a `Slug` component to be rendered inside the `TextArea` component
*/
slug: PropTypes.node,
slug: deprecate(
PropTypes.node,
'The `slug` prop for `TextArea` has ' +
'been deprecated in favor of the new `decorator` prop. It will be removed in the next major release.'
),

/**
* Provide the current value of the `<textarea>`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,21 @@ describe('TextArea', () => {
expect(screen.getByText('0/500')).toBeInTheDocument();
});

it('should respect slug prop', () => {
it('should respect decorator prop', () => {
render(
<TextArea
id="textarea-1"
labelText="TextArea label"
decorator={<AILabel />}
/>
);
expect(
screen.getByRole('button', { name: 'AI - Show information' })
).toBeInTheDocument();
});

it('should respect deprecated slug prop', () => {
const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});
render(
<TextArea
id="textarea-1"
Expand All @@ -206,6 +220,7 @@ describe('TextArea', () => {
expect(
screen.getByRole('button', { name: 'AI - Show information' })
).toBeInTheDocument();
spy.mockRestore();
});

describe('behaves as expected - Component API', () => {
Expand Down
10 changes: 8 additions & 2 deletions packages/react/src/components/TextInput/TextInput.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
import React from 'react';
import { WithLayer } from '../../../.storybook/templates/WithLayer';
import FluidForm from '../FluidForm';
import { View, FolderOpen, Folders } from '@carbon/icons-react';
import { View, FolderOpen, Folders, Information } from '@carbon/icons-react';
import Button from '../Button';
import { AILabel, AILabelContent, AILabelActions } from '../AILabel';
import { IconButton } from '../IconButton';
import mdx from './TextInput.mdx';

import { default as TextInput, TextInputSkeleton } from '../TextInput';
import { Tooltip } from '../Tooltip';

export default {
title: 'Components/TextInput',
Expand All @@ -33,6 +34,11 @@ export default {
disable: true,
},
},
slug: {
table: {
disable: true,
},
},
},
};

Expand Down Expand Up @@ -113,7 +119,7 @@ export const withAILabel = () => (
labelText="Text input label"
helperText="Optional help text"
id="text-input-ai-label"
slug={aiLabel}
decorator={aiLabel}
/>
);

Expand Down
50 changes: 42 additions & 8 deletions packages/react/src/components/TextInput/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export interface TextInputProps
*/
className?: string;

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

/**
* Optionally provide the default value of the `<input>`
*/
Expand Down Expand Up @@ -128,6 +133,7 @@ export interface TextInputProps
size?: 'sm' | 'md' | 'lg' | 'xl';

/**
* @deprecated please use `decorator` instead.
* **Experimental**: Provide a `Slug` component to be rendered inside the `TextInput` component
*/
slug?: ReactNode;
Expand Down Expand Up @@ -156,6 +162,7 @@ export interface TextInputProps
const TextInput = React.forwardRef(function TextInput(
{
className,
decorator,
disabled = false,
helperText,
hideLabel,
Expand Down Expand Up @@ -264,6 +271,7 @@ const TextInput = React.forwardRef(function TextInput(
{
[`${prefix}--text-input__field-wrapper--warning`]: normalizedProps.warn,
[`${prefix}--text-input__field-wrapper--slug`]: slug,
[`${prefix}--text-input__field-wrapper--decorator`]: decorator,
}
);
const iconClasses = classNames({
Expand Down Expand Up @@ -343,12 +351,20 @@ const TextInput = React.forwardRef(function TextInput(
}, [ariaAnnouncement, prevAnnouncement]);
const Icon = normalizedProps.icon as any;

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

return (
Expand All @@ -367,7 +383,16 @@ const TextInput = React.forwardRef(function TextInput(
data-invalid={normalizedProps.invalid || null}>
{Icon && <Icon className={iconClasses} />}
{input}
{normalizedSlug}
{slug ? (
normalizedDecorator
) : decorator ? (
<div
className={`${prefix}--text-input__field-inner-wrapper--decorator`}>
{normalizedDecorator}
</div>
) : (
''
)}
<span
className={`${prefix}--text-input__counter-alert`}
role="alert"
Expand All @@ -394,6 +419,11 @@ TextInput.propTypes = {
*/
className: PropTypes.string,

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

/**
* Optionally provide the default value of the `<input>`
*/
Expand Down Expand Up @@ -490,7 +520,11 @@ TextInput.propTypes = {
/**
* **Experimental**: Provide a `Slug` component to be rendered inside the `TextInput` component
*/
slug: PropTypes.node,
slug: deprecate(
PropTypes.node,
'The `slug` prop for `TextInput` has ' +
'been deprecated in favor of the new `decorator` prop. It will be removed in the next major release.'
),

/**
* Specify the type of the `<input>`
Expand Down
Loading

0 comments on commit a71c1b4

Please sign in to comment.