Skip to content

Commit

Permalink
feat(tile): add new decorator prop (#18109)
Browse files Browse the repository at this point in the history
* feat(tag): add new decorator prop

* fix(format): update

* test(tile): check selectable tile on ailabel

* test(tile): decorator clickable

* fix(clickable): decorator

* fix(format): update

* fix(api): update and tests

* test(tile): uncomment

* Apply suggestions from code review

Co-authored-by: Alison Joseph <[email protected]>

* fix(featureflag): update tile stories

* chore(tag): remove extra stories

---------

Co-authored-by: Alison Joseph <[email protected]>
  • Loading branch information
ariellalgilmore and alisonjoseph authored Nov 20, 2024
1 parent fc24500 commit b53f566
Show file tree
Hide file tree
Showing 8 changed files with 376 additions and 91 deletions.
41 changes: 29 additions & 12 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,19 @@ Map {
"clicked": Object {
"type": "bool",
},
"decorator": Object {
"args": Array [
Array [
Object {
"type": "bool",
},
Object {
"type": "node",
},
],
],
"type": "oneOfType",
},
"disabled": Object {
"type": "bool",
},
Expand Down Expand Up @@ -3216,6 +3229,9 @@ Map {
"className": Object {
"type": "string",
},
"decorator": Object {
"type": "node",
},
"expanded": Object {
"type": "bool",
},
Expand All @@ -3232,9 +3248,7 @@ Map {
"onKeyUp": Object {
"type": "func",
},
"slug": Object {
"type": "node",
},
"slug": [Function],
"tabIndex": Object {
"type": "number",
},
Expand Down Expand Up @@ -6607,6 +6621,9 @@ Map {
"className": Object {
"type": "string",
},
"decorator": Object {
"type": "node",
},
"disabled": Object {
"type": "bool",
},
Expand All @@ -6626,9 +6643,7 @@ Map {
"required": Object {
"type": "bool",
},
"slug": Object {
"type": "node",
},
"slug": [Function],
"tabIndex": Object {
"type": "number",
},
Expand Down Expand Up @@ -6980,6 +6995,9 @@ Map {
"className": Object {
"type": "string",
},
"decorator": Object {
"type": "node",
},
"disabled": Object {
"type": "bool",
},
Expand All @@ -7003,9 +7021,7 @@ Map {
"selected": Object {
"type": "bool",
},
"slug": Object {
"type": "node",
},
"slug": [Function],
"tabIndex": Object {
"type": "number",
},
Expand Down Expand Up @@ -9010,13 +9026,14 @@ Map {
"className": Object {
"type": "string",
},
"decorator": Object {
"type": "node",
},
"hasRoundedCorners": Object {
"type": "bool",
},
"light": [Function],
"slug": Object {
"type": "node",
},
"slug": [Function],
},
"render": [Function],
},
Expand Down
14 changes: 13 additions & 1 deletion packages/react/src/components/RadioTile/RadioTile-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,26 @@ describe('RadioTile', () => {
});
});

it('should check AILabel exists on radio tile and is xs', async () => {
it('should check decorator prop and if AILabel exists on radio tile and is xs', async () => {
render(
<RadioTile value="standard" decorator={<AILabel />}>
{' '}
Option 1{' '}
</RadioTile>
);
expect(screen.getByRole('button')).toHaveClass(`cds--ai-label__button--xs`);
});

it('should check deprecated slug prop and if AILabel exists on radio tile and is xs', async () => {
const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});
render(
<RadioTile value="standard" slug={<AILabel />}>
{' '}
Option 1{' '}
</RadioTile>
);
expect(screen.getByRole('button')).toHaveClass(`cds--ai-label__button--xs`);
spy.mockRestore();
});

//Feature flag : enable-v12-tile-radio-icons
Expand Down
52 changes: 43 additions & 9 deletions packages/react/src/components/RadioTile/RadioTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ export interface RadioTileProps {
*/
className?: string;

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

/**
* Specify whether the `RadioTile` should be disabled.
*/
Expand Down Expand Up @@ -79,6 +84,7 @@ export interface RadioTileProps {
) => void;

/**
* @deprecated please use `decorator` instead.
* **Experimental**: Provide a `Slug` component to be rendered inside the `SelectableTile` component
*/
slug?: React.ReactNode;
Expand All @@ -103,6 +109,7 @@ const RadioTile = React.forwardRef(function RadioTile(
{
children,
className: customClassName,
decorator,
disabled,
light,
checked,
Expand Down Expand Up @@ -131,6 +138,8 @@ const RadioTile = React.forwardRef(function RadioTile(
[`${prefix}--tile--disabled`]: disabled,
[`${prefix}--tile--slug`]: slug,
[`${prefix}--tile--slug-rounded`]: slug && hasRoundedCorners,
[`${prefix}--tile--decorator`]: decorator,
[`${prefix}--tile--decorator-rounded`]: decorator && hasRoundedCorners,
}
);
const v12TileRadioIcons = useFeatureFlag('enable-v12-tile-radio-icons');
Expand Down Expand Up @@ -161,12 +170,20 @@ const RadioTile = React.forwardRef(function RadioTile(
}
}

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

return (
Expand All @@ -188,7 +205,15 @@ const RadioTile = React.forwardRef(function RadioTile(
<label {...rest} htmlFor={inputId} className={className}>
<span className={`${prefix}--tile__checkmark`}>{icon()}</span>
<Text className={`${prefix}--tile-content`}>{children}</Text>
{normalizedSlug}
{slug ? (
normalizedDecorator
) : decorator ? (
<div className={`${prefix}--tile--inner-decorator`}>
{normalizedDecorator}
</div>
) : (
''
)}
</label>
</div>
);
Expand All @@ -212,6 +237,11 @@ RadioTile.propTypes = {
*/
className: PropTypes.string,

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

/**
* Specify whether the `RadioTile` should be disabled.
*/
Expand Down Expand Up @@ -255,9 +285,13 @@ RadioTile.propTypes = {
required: PropTypes.bool,

/**
* **Experimental**: Provide a `Slug` component to be rendered inside the `SelectableTile` component
* **Experimental**: Provide a `Slug` component to be rendered inside the `RadioTile` component
*/
slug: PropTypes.node,
slug: deprecate(
PropTypes.node,
'The `slug` prop for `RadioTile` has ' +
'been deprecated in favor of the new `decorator` prop. It will be removed in the next major release.'
),

/**
* Specify the tab index of the underlying `<input>`.
Expand Down
65 changes: 59 additions & 6 deletions packages/react/src/components/Tile/Tile-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,19 @@ describe('Tile', () => {
expect(screen.getByText('Default tile')).toHaveClass('custom-tile-class');
});

it('should respect slug prop', () => {
it('should respect decorator prop', () => {
render(<Tile decorator={<AILabel />}>Default tile</Tile>);
expect(
screen.getByRole('button', { name: 'AI - Show information' })
).toBeInTheDocument();
});
it('should respect deprecated slug prop', () => {
const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});
render(<Tile slug={<AILabel />}>Default tile</Tile>);
expect(
screen.getByRole('button', { name: 'AI - Show information' })
).toBeInTheDocument();
spy.mockRestore();
});
});

Expand Down Expand Up @@ -90,13 +98,22 @@ describe('Tile', () => {
expect(screen.getByTestId('test')).toBeInTheDocument();
});

it('should respect slug prop', () => {
render(<ClickableTile slug={<AILabel />}>Default tile</ClickableTile>);
it('should respect decorator prop', () => {
render(
<ClickableTile decorator={<AILabel />}>Default tile</ClickableTile>
);
expect(document.querySelector(`.${prefix}--cds--ai-label`));
});

it('should respect deprecated slug prop', () => {
const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});
render(<ClickableTile slug>Default tile</ClickableTile>);

// eslint-disable-next-line testing-library/no-node-access
expect(document.querySelector('svg')).toHaveClass(
`${prefix}--tile--slug-icon`
`${prefix}--tile--ai-label-icon`
);
spy.mockRestore();
});
it('should call onKeyDown', async () => {
const onKeyDown = jest.fn();
Expand Down Expand Up @@ -161,7 +178,24 @@ describe('Tile', () => {
expect(id1).toHaveFocus();
});

it('should respect slug prop', () => {
it('should respect decorator prop', async () => {
const onClick = jest.fn();
const { container } = render(
<SelectableTile decorator={<AILabel />} id="tile-1" onClick={onClick}>
Default tile
</SelectableTile>
);
const aiLabel = screen.getByRole('button', {
name: 'AI - Show information',
});
expect(aiLabel).toBeInTheDocument();
const tile = container.firstChild;
await userEvent.click(aiLabel);
expect(tile).not.toHaveClass(`${prefix}--tile--is-selected`);
});

it('should respect deprecated slug prop', () => {
const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});
render(
<SelectableTile slug={<AILabel />} id="tile-1">
Default tile
Expand All @@ -170,6 +204,7 @@ describe('Tile', () => {
expect(
screen.getByRole('button', { name: 'AI - Show information' })
).toBeInTheDocument();
spy.mockRestore();
});
});

Expand Down Expand Up @@ -305,7 +340,24 @@ describe('Tile', () => {
);
});

it('should respect slug prop', () => {
it('should respect decorator prop', () => {
render(
<ExpandableTile decorator={<AILabel />}>
<TileAboveTheFoldContent>
<div>TestAbove</div>
</TileAboveTheFoldContent>
<TileBelowTheFoldContent>
<div>TestBelow</div>
</TileBelowTheFoldContent>
</ExpandableTile>
);
expect(
screen.getByRole('button', { name: 'AI - Show information' })
).toBeInTheDocument();
});

it('should respect deprecated slug prop', () => {
const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});
render(
<ExpandableTile slug={<AILabel />}>
<TileAboveTheFoldContent>
Expand All @@ -319,6 +371,7 @@ describe('Tile', () => {
expect(
screen.getByRole('button', { name: 'AI - Show information' })
).toBeInTheDocument();
spy.mockRestore();
});
});

Expand Down
10 changes: 5 additions & 5 deletions packages/react/src/components/Tile/Tile.featureflag.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,9 +337,9 @@ export const _WithAILabel = {
type: 'boolean',
},
},
slug: {
decorator: {
description:
'**Experimental**: Provide an `AILabel` component to be rendered inside the component',
'**Experimental**: Provide a `decorator` component to be rendered inside the component',
},
},
render: (args) => (
Expand All @@ -353,23 +353,23 @@ export const _WithAILabel = {
className="ai-label-radio-tile"
id="radio-tile-4"
value="standard"
slug={aiLabel}
decorator={aiLabel}
{...args}>
Option 1
</RadioTile>
<RadioTile
className="ai-label-radio-tile"
id="radio-tile-5"
value="default-selected"
slug={aiLabel}
decorator={aiLabel}
{...args}>
Option 2
</RadioTile>
<RadioTile
className="ai-label-radio-tile"
id="radio-tile-6"
value="selected"
slug={aiLabel}
decorator={aiLabel}
{...args}>
Option 3
</RadioTile>
Expand Down
Loading

0 comments on commit b53f566

Please sign in to comment.