lib/components/src/tooltip/ListItem.tsx
import React, { FunctionComponent, ReactNode, ComponentProps } from 'react';
import { styled } from '@storybook/theming';
import memoize from 'memoizerific';
import { transparentize } from 'polished';
export interface TitleProps {
active?: boolean;
loading?: boolean;
disabled?: boolean;
}
const Title = styled(({ active, loading, disabled, ...rest }: TitleProps) => <span {...rest} />)<{
active: boolean;
loading: boolean;
disabled: boolean;
}>(
({ theme }) => ({
color: theme.color.defaultText,
// Previously was theme.typography.weight.normal but this weight does not exists in Theme
fontWeight: theme.typography.weight.regular,
}),
({ active, theme }) =>
active
? {
color: theme.color.primary,
fontWeight: theme.typography.weight.bold,
}
: {},
({ loading, theme }) =>
loading
? {
display: 'inline-block',
flex: 'none',
...theme.animation.inlineGlow,
}
: {},
({ disabled, theme }) =>
disabled
? {
color: transparentize(0.7, theme.color.defaultText),
}
: {}
);
export interface RightProps {
active?: boolean;
}
const Right = styled.span<RightProps>(
{
'& svg': {
transition: 'all 200ms ease-out',
opacity: 0,
height: 12,
width: 12,
margin: '3px 0',
verticalAlign: 'top',
},
'& path': {
fill: 'inherit',
},
},
({ active, theme }) =>
active
? {
'& svg': {
opacity: 1,
},
'& path': {
fill: theme.color.primary,
},
}
: {}
);
const Center = styled.span({
flex: 1,
textAlign: 'left',
display: 'inline-flex',
'& > * + *': {
paddingLeft: 10,
},
});
export interface CenterTextProps {
active?: boolean;
disabled?: boolean;
}
const CenterText = styled.span<CenterTextProps>(
{
flex: 1,
textAlign: 'center',
},
({ active, theme }) =>
active
? {
color: theme.color.primary,
}
: {},
({ theme, disabled }) =>
disabled
? {
color: theme.color.mediumdark,
}
: {}
);
export interface LeftProps {
active?: boolean;
}
const Left = styled.span<LeftProps>(({ active, theme }) =>
active
? {
'& svg': {
opacity: 1,
},
'& path': {
fill: theme.color.primary,
},
}
: {}
);
export interface ItemProps {
disabled?: boolean;
}
const Item = styled.a<ItemProps>(
({ theme }) => ({
fontSize: theme.typography.size.s1,
transition: 'all 150ms ease-out',
color: transparentize(0.5, theme.color.defaultText),
textDecoration: 'none',
cursor: 'pointer',
justifyContent: 'space-between',
lineHeight: '18px',
padding: '7px 15px',
display: 'flex',
alignItems: 'center',
'& > * + *': {
paddingLeft: 10,
},
'&:hover': {
background: theme.background.hoverable,
},
'&:hover svg': {
opacity: 1,
},
}),
({ disabled }) =>
disabled
? {
cursor: 'not-allowed',
}
: {}
);
const getItemProps = memoize(100)((onClick, href, LinkWrapper) => {
const result = {};
if (onClick) {
Object.assign(result, {
onClick,
});
}
if (href) {
Object.assign(result, {
href,
});
}
if (LinkWrapper && href) {
Object.assign(result, {
to: href,
as: LinkWrapper,
});
}
return result;
});
export type LinkWrapperType = FunctionComponent<any>;
export interface ListItemProps extends Omit<ComponentProps<typeof Item>, 'href' | 'title'> {
loading?: boolean;
left?: ReactNode;
title?: ReactNode;
center?: ReactNode;
right?: ReactNode;
active?: boolean;
disabled?: boolean;
href?: string;
LinkWrapper?: LinkWrapperType;
}
const ListItem: FunctionComponent<ListItemProps> = ({
loading,
left,
title,
center,
right,
active,
disabled,
href,
onClick,
LinkWrapper,
...rest
}) => {
const itemProps = getItemProps(onClick, href, LinkWrapper);
const commonProps = { active, disabled };
return (
<Item {...commonProps} {...rest} {...itemProps}>
{left && <Left {...commonProps}>{left}</Left>}
{title || center ? (
<Center>
{title && (
<Title {...commonProps} loading={loading}>
{title}
</Title>
)}
{center && <CenterText {...commonProps}>{center}</CenterText>}
</Center>
) : null}
{right && <Right {...commonProps}>{right}</Right>}
</Item>
);
};
ListItem.defaultProps = {
loading: false,
left: null,
title: <span>Loading state</span>,
center: null,
right: null,
active: false,
disabled: false,
href: null,
LinkWrapper: null,
onClick: null,
};
export default ListItem;