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:
parent
ad2d81eba3
commit
6800f22064
@ -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>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user