diff --git a/packages/ibm-products-styles/src/components/Card/_card.scss b/packages/ibm-products-styles/src/components/Card/_card.scss index d7d2c28ead..7be0074587 100644 --- a/packages/ibm-products-styles/src/components/Card/_card.scss +++ b/packages/ibm-products-styles/src/components/Card/_card.scss @@ -5,7 +5,7 @@ // LICENSE file in the root directory of this source tree. // -// NOTE: Please do not remove the duplicate `slug` and `ai-label` classes. We need both until slug is fully deprecated +// NOTE: Please do not remove the duplicate `slug` and `decorator` classes. We need both until slug is fully deprecated // Standard imports. @use '@carbon/styles/scss/theme' as *; @@ -144,25 +144,39 @@ $block-class: #{c4p-settings.$pkg-prefix}--card; position: relative; } -.#{$block-class} .#{c4p-settings.$carbon-prefix}--slug { +.#{$block-class} .#{c4p-settings.$carbon-prefix}--slug, +.#{$block-class} .#{c4p-settings.$carbon-prefix}--ai-label { position: absolute; top: $spacing-05; right: $spacing-05; } +.#{$block-class}__header__inner-wrapper--decorator { + position: absolute; + top: $spacing-05; + right: $spacing-05; + + .#{c4p-settings.$carbon-prefix}--slug, + .#{c4p-settings.$carbon-prefix}--ai-label { + position: relative; + top: unset; + right: unset; + } +} + .#{$block-class}__header-container--has-slug, -.#{$block-class}__header-container--has-ai-label { +.#{$block-class}__header-container--has-decorator { width: 100%; padding-right: $spacing-07; } .#{$block-class}__header-container--has-slug.#{$block-class}__header-container--has-actions, -.#{$block-class}__header-container--has-ai-label.#{$block-class}__header-container--has-actions { +.#{$block-class}__header-container--has-decorator.#{$block-class}__header-container--has-actions { padding-right: $spacing-08; } .#{$block-class}__header-container--has-slug.#{$block-class}__header-container--large-tile-or-label, -.#{$block-class}__header-container--has-ai-label.#{$block-class}__header-container--large-tile-or-label { +.#{$block-class}__header-container--has-decorator.#{$block-class}__header-container--large-tile-or-label { padding-right: $spacing-09; } diff --git a/packages/ibm-products/src/components/Card/Card.tsx b/packages/ibm-products/src/components/Card/Card.tsx index 20c3eda738..f26e51bba2 100644 --- a/packages/ibm-products/src/components/Card/Card.tsx +++ b/packages/ibm-products/src/components/Card/Card.tsx @@ -83,14 +83,14 @@ interface CardProp extends PropsWithChildren { /** * Clickable tiles only accept a boolean value of true and display a hollow slug. - * @deprecated please use the `aiLabel` prop + * @deprecated please use the `decorator` prop */ slug?: ReactNode | boolean; /** - * Optional prop that is intended for any scenario where something is being generated by AI to reinforce AI transparency, accountability, and explainability at the UI level. + * Optional prop that allows you to pass any component. */ - aiLabel?: ReactNode | boolean; + decorator?: ReactNode | boolean; status?: 'complete' | 'incomplete'; title?: ReactNode; @@ -103,7 +103,7 @@ export const Card = forwardRef( // The component props, in alphabetical order (for consistency). actionIcons = Object.freeze([]), actionsPlacement = 'bottom', - aiLabel, + decorator, metadata = Object.freeze([]), children, className, @@ -262,7 +262,10 @@ export const Card = forwardRef( [`${blockClass}__clickable`]: clickable, [`${blockClass}__media-left`]: mediaPosition === 'left', [`${blockClass}--has-slug`]: !!slug, - [`${blockClass}--has-ai-label`]: !!aiLabel, + [`${blockClass}--has-decorator`]: + !!decorator && decorator['type']?.displayName !== 'AILabel', + [`${blockClass}--has-ai-label`]: + !!decorator && decorator['type']?.displayName === 'AILabel', }, className ), @@ -287,7 +290,7 @@ export const Card = forwardRef( const getHeaderProps = () => ({ actions: actionsPlacement === 'top' ? getActions() : '', - aiLabel, + decorator, noActionIcons: getIcons().length > 0 && actionsPlacement === 'top' ? false : true, actionsPlacement, @@ -395,13 +398,13 @@ Card.propTypes = { }) ), actionsPlacement: PropTypes.oneOf(['top', 'bottom']), - /** - * Optional prop that is intended for any scenario where something is being generated by AI to reinforce AI transparency, accountability, and explainability at the UI level. - */ - aiLabel: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]), children: PropTypes.node, className: PropTypes.string, clickZone: PropTypes.oneOf(['one', 'two', 'three']), + /** + * Optional prop that allows you to pass any component. + */ + decorator: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]), /**@ts-ignore */ description: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), disabled: PropTypes.bool, @@ -455,7 +458,7 @@ Card.propTypes = { /** * **Experimental:** For all cases a `Slug` component can be provided. * Clickable tiles only accept a boolean value of true and display a hollow slug. - * @deprecated please use the `aiLabel` prop + * @deprecated please use the `decorator` prop */ slug: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]), diff --git a/packages/ibm-products/src/components/Card/CardHeader.test.js b/packages/ibm-products/src/components/Card/CardHeader.test.js index e0e57ff907..4fd17e9cba 100644 --- a/packages/ibm-products/src/components/Card/CardHeader.test.js +++ b/packages/ibm-products/src/components/Card/CardHeader.test.js @@ -8,6 +8,7 @@ import { render } from '@testing-library/react'; import React from 'react'; import { CardHeader } from '.'; +import { AILabel } from '@carbon/react'; import { pkg } from '../../settings'; const { name } = CardHeader; @@ -50,4 +51,13 @@ describe(name, () => { const { getByText } = render(); expect(getByText('action 1')).toBeVisible(); }); + + it('should respect decorator prop', () => { + const { container } = render(} />); + expect( + container.querySelector( + `.${blockClass}__header__inner-wrapper--decorator` + ) + ).not.toBeNull(); + }); }); diff --git a/packages/ibm-products/src/components/Card/CardHeader.tsx b/packages/ibm-products/src/components/Card/CardHeader.tsx index c261f5a3a0..65bb665edc 100644 --- a/packages/ibm-products/src/components/Card/CardHeader.tsx +++ b/packages/ibm-products/src/components/Card/CardHeader.tsx @@ -20,9 +20,9 @@ const defaults = { interface CardHeaderProps { actions?: ReactNode; /** - * Optional prop that is intended for any scenario where something is being generated by AI to reinforce AI transparency, accountability, and explainability at the UI level. + * Optional prop that allows you to pass any component. */ - aiLabel?: ReactNode | boolean; + decorator?: ReactNode | boolean; description?: ReactNode; hasActions?: boolean; /** @@ -46,7 +46,7 @@ interface CardHeaderProps { /** * **Experimental:** For all cases a `Slug` component can be provided. * Clickable tiles only accept a boolean value of true and display a hollow slug. - * @deprecated please use the `aiLabel` prop + * @deprecated please use the `decorator` prop */ slug?: ReactNode; @@ -56,7 +56,7 @@ interface CardHeaderProps { export const CardHeader = ({ actions, - aiLabel, + decorator, noActionIcons, onPrimaryButtonClick, onSecondaryButtonClick, @@ -107,17 +107,17 @@ export const CardHeader = ({ ); - let normalizedAiLabel: React.ReactElement | null = null; - if (aiLabel || slug) { + let normalizedDecorator: React.ReactElement | null = null; + if (decorator || slug) { if ( inClickableCard || - typeof aiLabel === 'boolean' || + typeof decorator === 'boolean' || typeof slug === 'boolean' ) { - normalizedAiLabel = hollowAiIcon; + normalizedDecorator = hollowAiIcon; } else { - const element = aiLabel || slug; - normalizedAiLabel = React.cloneElement( + const element = decorator || slug; + normalizedDecorator = React.cloneElement( element as React.ReactElement, { size: @@ -133,7 +133,7 @@ export const CardHeader = ({ className={cx([ `${headerClass}-container`, { [`${headerClass}-container--has-slug`]: !!slug }, - { [`${headerClass}-container--has-ai-label`]: !!aiLabel }, + { [`${headerClass}-container--has-decorator`]: !!decorator }, { [`${headerClass}-container--has-actions`]: !!hasActions }, { [`${headerClass}-container--large-tile-or-label`]: @@ -180,7 +180,15 @@ export const CardHeader = ({ )} )} - {normalizedAiLabel} + {slug ? ( + normalizedDecorator + ) : decorator ? ( +
+ {normalizedDecorator} +
+ ) : ( + '' + )} ); @@ -189,9 +197,9 @@ export const CardHeader = ({ CardHeader.propTypes = { actions: PropTypes.oneOfType([PropTypes.array, PropTypes.node]), /** - * Optional prop that is intended for any scenario where something is being generated by AI to reinforce AI transparency, accountability, and explainability at the UI level. + * Optional prop that allows you to pass any component. */ - aiLabel: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]), + decorator: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]), description: PropTypes.oneOfType([ PropTypes.string, PropTypes.object, @@ -219,7 +227,7 @@ CardHeader.propTypes = { /** * **Experimental:** For all cases a `Slug` component can be provided. * Clickable tiles only accept a boolean value of true and display a hollow slug. - * @deprecated please use the `aiLabel` prop + * @deprecated please use the `decorator` prop */ slug: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]), diff --git a/packages/ibm-products/src/components/ExpressiveCard/ExpressiveCard.docs-page.js b/packages/ibm-products/src/components/ExpressiveCard/ExpressiveCard.docs-page.js index 18937b7bc9..14e041fbba 100644 --- a/packages/ibm-products/src/components/ExpressiveCard/ExpressiveCard.docs-page.js +++ b/packages/ibm-products/src/components/ExpressiveCard/ExpressiveCard.docs-page.js @@ -53,7 +53,7 @@ const DocsPage = () => ( {renderedContent}} + decorator={{renderedContent}} title="Title">

expressive card body content block. description inviting the user to take action on the card. @@ -64,7 +64,7 @@ const DocsPage = () => ( }, { description: - 'Clickable tiles only accept a boolean value of true for the aiLabel property.', + 'Clickable tiles only accept a boolean value of true for the decorator property.', source: { language: 'html', code: ` @@ -73,7 +73,7 @@ const DocsPage = () => ( primaryButtonText="Primary" onClick={() => {}} onKeyDown={() => {}} - aiLabel={true} + decorator={true} title="Title">

expressive card body content block. description inviting the user to take action on the card. diff --git a/packages/ibm-products/src/components/ExpressiveCard/ExpressiveCard.stories.jsx b/packages/ibm-products/src/components/ExpressiveCard/ExpressiveCard.stories.jsx index 5ed6d1fbe8..64abe02de8 100644 --- a/packages/ibm-products/src/components/ExpressiveCard/ExpressiveCard.stories.jsx +++ b/packages/ibm-products/src/components/ExpressiveCard/ExpressiveCard.stories.jsx @@ -7,39 +7,60 @@ import React from 'react'; import styles from './_storybook-styles.scss?inline'; // import index in case more files are added later. -import { ArrowRight, Cloud, Add } from '@carbon/react/icons'; +import { ArrowRight, Cloud, Add, Information } from '@carbon/react/icons'; import { AspectRatio, Column, Grid, usePrefix, - unstable__Slug as Slug, - unstable__SlugContent as SlugContent, + AILabel, + AILabelContent, + Toggletip, + ToggletipButton, + ToggletipContent, } from '@carbon/react'; import { ExpressiveCard } from '.'; import DocsPage from './ExpressiveCard.docs-page'; import { action } from '@storybook/addon-actions'; -const sampleSlug = ( - - -

-

AI Explained

-

84%

-

Confidence score

-

- This is not really Lorem Ipsum but the spell checker did not like the - previous text with it's non-words which is why this unwieldy - sentence, should one choose to call it that, here. -

-
-

Model type

-

Foundation model

-
- - -); +const sampleDecorator = (decorator) => { + switch (decorator) { + case 1: + return ( + + +
+

AI Explained

+

84%

+

Confidence score

+

+ This is not really Lorem Ipsum but the spell checker did not + like the previous text with it's non-words which is why + this unwieldy sentence, should one choose to call it that, here. +

+
+

Model type

+

Foundation model

+
+
+
+ ); + case 2: + return ( + + + + + +

Custom content here

+
+
+ ); + default: + return; + } +}; export default { title: 'IBM Products/Components/Cards/ExpressiveCard', @@ -82,22 +103,23 @@ export default { labels: { 0: 'No AI slug', 1: 'with AI Slug', - 2: 'with hollow slug (boolean)', }, default: 0, }, - options: [0, 1, 2], + options: [false, true], }, - aiLabel: { + decorator: { control: { type: 'select', labels: { 0: 'No AI label', 1: 'with AI label', + 2: 'With non AI Label component', + 3: 'with hollow AI label (boolean)', }, default: 0, }, - options: [false, true], + options: [0, 1, 2, 3], }, }, decorators: [ @@ -126,13 +148,24 @@ const defaultProps = { }; const Template = (opts) => { - const { children, columnSizeSm, columnSizeMd, columnSizeLg, slug, ...args } = - opts; + const { + children, + columnSizeSm, + columnSizeMd, + columnSizeLg, + decorator, + ...args + } = opts; return ( - + {children} @@ -147,7 +180,7 @@ const MediaTemplate = (opts) => { columnSizeMd, columnSizeLg, mediaRatio = '1x1', - slug, + decorator, ...args } = opts; return ( @@ -155,7 +188,9 @@ const MediaTemplate = (opts) => { {mediaRatio}} - slug={slug && (slug === 2 || sampleSlug)} + decorator={ + decorator && (decorator === 3 || sampleDecorator(decorator)) + } {...args} > {children} diff --git a/packages/ibm-products/src/components/ExpressiveCard/ExpressiveCard.tsx b/packages/ibm-products/src/components/ExpressiveCard/ExpressiveCard.tsx index 9a4de0db85..2faec964d8 100644 --- a/packages/ibm-products/src/components/ExpressiveCard/ExpressiveCard.tsx +++ b/packages/ibm-products/src/components/ExpressiveCard/ExpressiveCard.tsx @@ -28,10 +28,6 @@ export interface ExpressiveCardProps extends PropsWithChildren { * Icons that are displayed on card. Refer to design documentation for implementation guidelines. Note- href will supersede onClick */ actionIcons?: ActionIcon[]; - /** - * Optional prop that is intended for any scenario where something is being generated by AI to reinforce AI transparency, accountability, and explainability at the UI level. - */ - aiLabel?: ReactNode | boolean; /** * Content that shows in the body of the card */ @@ -40,6 +36,10 @@ export interface ExpressiveCardProps extends PropsWithChildren { * Optional user provided class */ className?: string; + /** + * Optional prop that allows you to pass any component. + */ + decorator?: ReactNode | boolean; /** * Optional header description */ @@ -107,7 +107,7 @@ export interface ExpressiveCardProps extends PropsWithChildren { /** * **Experimental:** For all cases a `Slug` component can be provided. * Clickable tiles only accept a boolean value of true and display a hollow slug. - * @deprecated please use the `aiLabel` prop + * @deprecated please use the `decorator` prop */ slug?: ReactNode | boolean; @@ -152,10 +152,6 @@ ExpressiveCard.propTypes = { href: PropTypes.string, }) ), - /** - * Optional prop that is intended for any scenario where something is being generated by AI to reinforce AI transparency, accountability, and explainability at the UI level. - */ - aiLabel: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]), /** * Content that shows in the body of the card */ @@ -164,6 +160,10 @@ ExpressiveCard.propTypes = { * Optional user provided class */ className: PropTypes.string, + /** + * Optional prop that allows you to pass any component. + */ + decorator: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]), /** * Optional header description */ @@ -242,7 +242,7 @@ ExpressiveCard.propTypes = { /** * **Experimental:** For all cases a `Slug` component can be provided. * Clickable tiles only accept a boolean value of true and display a hollow slug. - * @deprecated please use the `aiLabel` prop + * @deprecated please use the `decorator` prop */ slug: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]), diff --git a/packages/ibm-products/src/components/ProductiveCard/ProductiveCard.docs-page.js b/packages/ibm-products/src/components/ProductiveCard/ProductiveCard.docs-page.js index 0ed7f84866..7b70135e77 100644 --- a/packages/ibm-products/src/components/ProductiveCard/ProductiveCard.docs-page.js +++ b/packages/ibm-products/src/components/ProductiveCard/ProductiveCard.docs-page.js @@ -54,7 +54,7 @@ const DocsPage = () => ( onClick={() => {}} onKeyDown={() => {}} primaryButtonText="Ghost button" - aiLabel={... || true} + decorator={... || true} title="Title" > @@ -72,7 +72,7 @@ const DocsPage = () => ( }, { description: - 'Clickable tiles only accept a boolean value of true for the aiLabel property.', + 'Clickable tiles only accept a boolean value of true for the decorator property.', source: { language: 'html', code: ` @@ -80,7 +80,7 @@ const DocsPage = () => ( onClick={() => {}} onKeyDown={() => {}} primaryButtonText="Ghost button" - aiLabel={true} + decorator={true} title="Title" > diff --git a/packages/ibm-products/src/components/ProductiveCard/ProductiveCard.stories.jsx b/packages/ibm-products/src/components/ProductiveCard/ProductiveCard.stories.jsx index bea6afca41..7a903a788f 100644 --- a/packages/ibm-products/src/components/ProductiveCard/ProductiveCard.stories.jsx +++ b/packages/ibm-products/src/components/ProductiveCard/ProductiveCard.stories.jsx @@ -7,13 +7,16 @@ import React from 'react'; import styles from './_storybook-styles.scss?inline'; // import index in case more files are added later. -import { TrashCan, Edit } from '@carbon/react/icons'; +import { TrashCan, Edit, Information } from '@carbon/react/icons'; import { Grid, Column, usePrefix, - unstable__Slug as Slug, - unstable__SlugContent as SlugContent, + AILabel, + AILabelContent, + Toggletip, + ToggletipButton, + ToggletipContent, } from '@carbon/react'; import { ProductiveCard } from '.'; import DocsPage from './ProductiveCard.docs-page'; @@ -21,25 +24,43 @@ import { action } from '@storybook/addon-actions'; const storyClass = 'productive-card-stories'; -const sampleSlug = ( - - -
-

AI Explained

-

84%

-

Confidence score

-

- This is not really Lorem Ipsum but the spell checker did not like the - previous text with it's non-words which is why this unwieldy - sentence, should one choose to call it that, here. -

-
-

Model type

-

Foundation model

-
-
-
-); +const sampleDecorator = (decorator) => { + switch (decorator) { + case 1: + return ( + + +
+

AI Explained

+

84%

+

Confidence score

+

+ This is not really Lorem Ipsum but the spell checker did not + like the previous text with it's non-words which is why + this unwieldy sentence, should one choose to call it that, here. +

+
+

Model type

+

Foundation model

+
+
+
+ ); + case 2: + return ( + + + + + +

Custom content here

+
+
+ ); + default: + return; + } +}; export default { title: 'IBM Products/Components/Cards/ProductiveCard', @@ -76,22 +97,23 @@ export default { labels: { 0: 'No AI slug', 1: 'with AI Slug', - 2: 'with hollow slug (boolean)', }, default: 0, }, - options: [0, 1], + options: [false, true], }, - aiLabel: { + decorator: { control: { type: 'select', labels: { 0: 'No AI label', 1: 'with AI label', + 2: 'With non AI Label component', + 3: 'with hollow AI label (boolean)', }, default: 0, }, - options: [false, true], + options: [0, 1, 2, 3], }, }, decorators: [ @@ -137,12 +159,23 @@ const defaultProps = { }; const Template = (opts) => { - const { children, columnSizeSm, columnSizeMd, columnSizeLg, slug, ...args } = - opts; + const { + children, + columnSizeSm, + columnSizeMd, + columnSizeLg, + decorator, + ...args + } = opts; return ( - + {children} diff --git a/packages/ibm-products/src/components/ProductiveCard/ProductiveCard.tsx b/packages/ibm-products/src/components/ProductiveCard/ProductiveCard.tsx index a2b4bbef91..c1bd9dccaf 100644 --- a/packages/ibm-products/src/components/ProductiveCard/ProductiveCard.tsx +++ b/packages/ibm-products/src/components/ProductiveCard/ProductiveCard.tsx @@ -43,9 +43,9 @@ export interface ProductiveCardProps extends PropsWithChildren { */ actionIcons?: ActionIcon[]; /** - * Optional prop that is intended for any scenario where something is being generated by AI to reinforce AI transparency, accountability, and explainability at the UI level. + * Optional prop that allows you to pass any component. */ - aiLabel?: ReactNode | boolean; + decorator?: ReactNode | boolean; /** * Determines if the action icons are on the top or bottom of the card @@ -134,7 +134,7 @@ export interface ProductiveCardProps extends PropsWithChildren { /** * **Experimental:** For all cases a `Slug` component can be provided. * Clickable tiles only accept a boolean value of true and display a hollow slug. - * @deprecated please use the `aiLabel` prop + * @deprecated please use the `decorator` prop */ slug?: ReactNode | boolean; @@ -204,10 +204,6 @@ ProductiveCard.propTypes = { * Determines if the action icons are on the top or bottom of the card */ actionsPlacement: PropTypes.oneOf(['top', 'bottom']), - /** - * Optional prop that is intended for any scenario where something is being generated by AI to reinforce AI transparency, accountability, and explainability at the UI level. - */ - aiLabel: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]), /** * Content that shows in the body of the card */ @@ -220,6 +216,10 @@ ProductiveCard.propTypes = { * Designates which zones of the card are clickable. Refer to design documentation for implementation guidelines */ clickZone: PropTypes.oneOf(['one', 'two', 'three']), + /** + * Optional prop that allows you to pass any component. + */ + decorator: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]), /** * Optional header description */