Files
grafana/packages/grafana-ui/src/components/Collapse/Collapse.tsx
2020-02-09 17:39:26 +01:00

141 lines
3.7 KiB
TypeScript

import React, { FunctionComponent, useContext, useState } from 'react';
import { css, cx } from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import { ThemeContext } from '../../themes/ThemeContext';
import { stylesFactory } from '../../themes/stylesFactory';
import { Icon } from '../Icon/Icon';
const getStyles = stylesFactory((theme: GrafanaTheme) => ({
collapse: css`
label: collapse;
margin-bottom: ${theme.spacing.sm};
`,
collapseBody: css`
label: collapse__body;
padding: ${theme.panelPadding}px;
`,
loader: css`
label: collapse__loader;
height: 2px;
position: relative;
overflow: hidden;
background: none;
margin: ${theme.spacing.xs};
`,
loaderActive: css`
label: collapse__loader_active;
&:after {
content: ' ';
display: block;
width: 25%;
top: 0;
top: -50%;
height: 250%;
position: absolute;
animation: loader 2s cubic-bezier(0.17, 0.67, 0.83, 0.67) 500ms;
animation-iteration-count: 100;
left: -25%;
background: ${theme.colors.blue};
}
@keyframes loader {
from {
left: -25%;
opacity: 0.1;
}
to {
left: 100%;
opacity: 1;
}
}
`,
header: css`
label: collapse__header;
padding: ${theme.spacing.sm} ${theme.spacing.md};
display: flex;
cursor: inherit;
transition: all 0.1s linear;
cursor: pointer;
`,
headerCollapsed: css`
label: collapse__header--collapsed;
cursor: pointer;
padding: ${theme.spacing.sm} ${theme.spacing.md};
`,
headerButtons: css`
label: collapse__header-buttons;
margin-right: ${theme.spacing.sm};
font-size: ${theme.typography.size.lg};
line-height: ${theme.typography.heading.h6};
display: inherit;
`,
headerButtonsCollapsed: css`
label: collapse__header-buttons--collapsed;
display: none;
`,
headerLabel: css`
label: collapse__header-label;
font-weight: ${theme.typography.weight.semibold};
margin-right: ${theme.spacing.sm};
font-size: ${theme.typography.heading.h6};
`,
}));
interface Props {
isOpen?: boolean;
label: string;
loading?: boolean;
collapsible?: boolean;
onToggle?: (isOpen: boolean) => void;
}
export const ControlledCollapse: FunctionComponent<Props> = ({ isOpen, onToggle, ...otherProps }) => {
const [open, setOpen] = useState(isOpen);
return (
<Collapse
isOpen={open}
{...otherProps}
onToggle={() => {
setOpen(!open);
if (onToggle) {
onToggle(!open);
}
}}
/>
);
};
export const Collapse: FunctionComponent<Props> = ({ isOpen, label, loading, collapsible, onToggle, children }) => {
const theme = useContext(ThemeContext);
const style = getStyles(theme);
const onClickToggle = () => {
if (onToggle) {
onToggle(!isOpen);
}
};
const panelClass = cx([style.collapse, 'panel-container']);
const loaderClass = loading ? cx([style.loader, style.loaderActive]) : cx([style.loader]);
const headerClass = collapsible ? cx([style.header]) : cx([style.headerCollapsed]);
const headerButtonsClass = collapsible ? cx([style.headerButtons]) : cx([style.headerButtonsCollapsed]);
return (
<div className={panelClass}>
<div className={headerClass} onClick={onClickToggle}>
<div className={headerButtonsClass}>
<Icon name={isOpen ? 'caret-up' : 'caret-down'} />
</div>
<div className={cx([style.headerLabel])}>{label}</div>
</div>
{isOpen && (
<div className={cx([style.collapseBody])}>
<div className={loaderClass} />
{children}
</div>
)}
</div>
);
};
Collapse.displayName = 'Collapse';