@spaced-out/ui-design-system
Version:
Sense UI components library
191 lines (174 loc) • 4.87 kB
Flow
// @flow strict
import * as React from 'react';
//$FlowFixMe[untyped-import]
import Highcharts from 'highcharts';
//$FlowFixMe[untyped-import]
import accessibility from 'highcharts/modules/accessibility';
//$FlowFixMe[untyped-import]
import drilldown from 'highcharts/modules/drilldown';
//$FlowFixMe[untyped-import]
import exporting from 'highcharts/modules/exporting';
import {size180} from '../../../styles/variables/_size';
import classify from '../../../utils/classify';
import {ANCHOR_POSITION_TYPE, ButtonDropdown} from '../../ButtonDropdown';
import {
Card,
CardActions,
CardContent,
CardHeader,
CardTitle,
} from '../../Card';
import {CircularLoader} from '../../CircularLoader';
import type {MenuOption} from '../../Menu';
import css from './ChartWrapper.module.css';
export type ExportOptionType = {
key: string,
label: string,
format: string,
};
export const EXPORT_OPTIONS = Object.freeze({
download_as_png: {
key: 'download_as_png',
label: 'Download as PNG',
format: 'image/png',
},
download_as_jpeg: {
key: 'download_as_jpeg',
label: 'Download as JPEG',
format: 'image/jpeg',
},
download_as_pdf: {
key: 'download_as_pdf',
label: 'Download as PDF',
format: 'application/pdf',
},
download_as_svg: {
key: 'download_as_svg',
label: 'Download as SVG',
format: 'image/svg+xml',
},
});
// on assumption that all export options will be shown by default
const DEFAULT_EXPORT_OPTIONS = Object.keys(EXPORT_OPTIONS).map(
(exportKey) => EXPORT_OPTIONS[exportKey],
);
type CustomExportButtonProps = {
ref?: HTMLDivElement,
customExportOptions?: Array<ExportOptionType> | null,
};
const CustomExportButton = React.forwardRef<
CustomExportButtonProps,
HTMLDivElement,
>(({customExportOptions = null}: CustomExportButtonProps, ref) => {
exporting(Highcharts);
drilldown(Highcharts);
accessibility(Highcharts);
const exportOptions = customExportOptions
? customExportOptions
: DEFAULT_EXPORT_OPTIONS;
const menuOptions: Array<MenuOption> = exportOptions.map((exportOption) => {
const {key, label} = exportOption;
return {key, label};
});
const handleDownload = (exportKey: string): void => {
const exportOptionObject = exportOptions.find(
(exportOption) => exportOption.key === exportKey,
);
const exportFormat = exportOptionObject?.format || '';
if (ref.current) {
// $FlowFixMe[prop-missing]
// $FlowFixMe[incompatible-use]
ref.current.chart?.exportChart({type: exportFormat});
}
};
return (
<ButtonDropdown
ariaLabel="Export Chart Menu"
menu={{
isFluid: true,
menuDisabled: false,
options: menuOptions,
size: 'medium',
width: size180,
}}
iconLeftName="ellipsis"
onOptionSelect={(option) => handleDownload(option.key)}
size="small"
type="ghost"
anchorPosition={ANCHOR_POSITION_TYPE.bottomEnd}
/>
);
});
export type ChartWrapperClassNames = $ReadOnly<{
wrapper?: string,
header?: string,
title?: string,
actions?: string,
content?: string,
loader?: string,
}>;
type ChartWrapperProps = {
isLoading?: boolean,
title: React.Node,
children?: React.Node,
classNames?: ChartWrapperClassNames,
customExportOptions?: Array<ExportOptionType> | null,
headerActions?: React.Node,
hasEmptyData?: boolean,
emptyText?: React.Node,
};
export const ChartWrapper: React$AbstractComponent<
ChartWrapperProps,
HTMLDivElement,
> = React.forwardRef<ChartWrapperProps, HTMLDivElement>(
(
{
isLoading,
title,
children,
classNames,
customExportOptions,
headerActions,
hasEmptyData = false,
emptyText,
}: ChartWrapperProps,
ref,
): React.Node => (
<Card
classNames={{
wrapper: classify(
css.wrapper,
{[css.justifyCenter]: isLoading},
classNames?.wrapper,
),
}}
>
{isLoading ? (
<CircularLoader
size="large"
className={classify(css.loader, classNames?.loader)}
/>
) : (
<>
<CardHeader className={classNames?.header}>
<CardTitle className={classNames?.title}>{title}</CardTitle>
<CardActions
className={classify(css.chartCardActions, classNames?.actions)}
>
<CustomExportButton
ref={ref}
customExportOptions={customExportOptions}
/>
{headerActions ? headerActions : null}
</CardActions>
</CardHeader>
<CardContent
className={classify(css.chartContent, classNames?.content)}
>
{hasEmptyData && emptyText ? emptyText : children}
</CardContent>
</>
)}
</Card>
),
);