mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
141 lines
3.7 KiB
TypeScript
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';
|