Feat/callout icon (#394)

* add icon prop to callout component

* add story example for custom icon node

* destructure props in fn args

* add accessibility props to callout icon

* remove redundant props

* add loading state to callout

* render loader first

* fix lint

Co-authored-by: Botond Fekete <fekbot@gmail.com>
This commit is contained in:
botond 2022-05-17 15:46:03 +02:00 committed by GitHub
parent ad2d81eba3
commit 6800f22064
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 106 additions and 9 deletions

View File

@ -74,3 +74,37 @@ IconAndContent.args = {
</div> </div>
), ),
}; };
export const CustomIconAndContent = Template.bind({});
CustomIconAndContent.args = {
intent: Intent.Help,
title: 'This is what this thing does',
icon: (
<span role="img" aria-label="tick">
</span>
),
children: (
<div className="flex flex-col">
<div>With a longer explaination</div>
<Button className="block mt-8" variant="secondary">
Action
</Button>
</div>
),
};
export const Loading = Template.bind({});
Loading.args = {
intent: Intent.Help,
title: 'This is what this thing does',
isLoading: true,
children: (
<div className="flex flex-col">
<div>With a longer explaination</div>
<Button className="block mt-8" variant="secondary">
Action
</Button>
</div>
),
};

View File

@ -1,23 +1,89 @@
import type { ReactNode } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { getIntentShadow, Intent } from '../../utils/intent'; import { getIntentShadow, Intent } from '../../utils/intent';
import { Loader } from '../loader';
import type { IconName } from '../icon'; import type { IconName } from '../icon';
import { Icon } from '../icon'; import { Icon } from '../icon';
export interface CalloutProps { interface CalloutRootProps {
children?: React.ReactNode; children?: React.ReactNode;
title?: React.ReactElement | string; title?: React.ReactElement | string;
intent?: Intent; intent?: Intent;
iconName?: IconName;
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6; headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
isLoading?: boolean;
} }
interface CalloutWithoutIcon extends CalloutRootProps {
iconName?: never;
iconDescription?: never;
icon?: never;
}
interface CalloutPropsWithIconName extends CalloutRootProps {
iconName: IconName;
iconDescription?: string;
icon?: never;
}
interface CalloutPropsWithIcon extends CalloutRootProps {
iconName?: never;
iconDescription?: never;
icon: ReactNode;
}
type CalloutProps =
| CalloutWithoutIcon
| CalloutPropsWithIconName
| CalloutPropsWithIcon;
const getIconElement = ({
icon,
iconName,
iconDescription,
isLoading,
}: Pick<
CalloutProps,
'icon' | 'iconName' | 'iconDescription' | 'isLoading'
>) => {
const wrapperClassName = 'ml-8 mr-16 mt-8';
if (isLoading) {
return (
<div className={wrapperClassName}>
<Loader size="small" />
</div>
);
}
if (iconName) {
return (
<Icon
name={iconName}
className={classNames(wrapperClassName, 'fill-current')}
size={20}
aria-label={iconDescription}
aria-hidden={!iconDescription}
/>
);
}
return <div className={wrapperClassName}>{icon}</div>;
};
export function Callout({ export function Callout({
children, children,
title, title,
intent = Intent.Help, icon,
iconName, iconName,
iconDescription,
isLoading,
intent = Intent.Help,
headingLevel, headingLevel,
}: CalloutProps) { }: CalloutProps) {
const iconElement = getIconElement({
icon,
iconName,
iconDescription,
isLoading,
});
const className = classNames( const className = classNames(
'border', 'border',
'border-black', 'border-black',
@ -27,15 +93,12 @@ export function Callout({
'p-16', 'p-16',
getIntentShadow(intent), getIntentShadow(intent),
{ {
flex: !!iconName, flex: iconElement,
} }
); );
const TitleTag: keyof JSX.IntrinsicElements = headingLevel const TitleTag: keyof JSX.IntrinsicElements = headingLevel
? `h${headingLevel}` ? `h${headingLevel}`
: 'div'; : 'div';
const icon = iconName && (
<Icon name={iconName} className="fill-current ml-8 mr-16 mt-8" size={20} />
);
const body = ( const body = (
<> <>
{title && <TitleTag className="text-h5 mt-0 mb-8">{title}</TitleTag>} {title && <TitleTag className="text-h5 mt-0 mb-8">{title}</TitleTag>}
@ -44,8 +107,8 @@ export function Callout({
); );
return ( return (
<div data-testid="callout" className={className}> <div data-testid="callout" className={className}>
{icon} {iconElement}
{icon ? <div className="grow">{body}</div> : body} {iconElement ? <div className="grow">{body}</div> : body}
</div> </div>
); );
} }