diff --git a/configs/rollup.config.js b/configs/rollup.config.js
index a489f481..5d04207e 100644
--- a/configs/rollup.config.js
+++ b/configs/rollup.config.js
@@ -28,7 +28,8 @@ const TSComponentsList = [
'Button',
'Image',
'Rating',
- 'InteractiveWidget'
+ 'InteractiveWidget',
+ 'Export'
];
const getInputs = (name, dir) => {
diff --git a/package.json b/package.json
index 2c745489..0863e2f3 100644
--- a/package.json
+++ b/package.json
@@ -87,6 +87,7 @@
"@storybook/react-webpack5": "^7.6.15",
"@storybook/theming": "^7.6.15",
"@types/enzyme-adapter-react-16": "^1.0.9",
+ "@types/file-saver": "^2.0.7",
"@types/jest": "^29.5.11",
"autoprefixer": "^10.4.13",
"babel-jest": "^29.7.0",
@@ -141,6 +142,7 @@
"semantic-release": "^19.0.5",
"storybook": "^7.6.15",
"storybook-dark-mode": "^3.0.3",
+ "string-to-html": "^1.3.4",
"style-loader": "^3.3.1",
"stylelint": "^14.14.0",
"stylelint-config-prettier": "^9.0.3",
@@ -160,14 +162,18 @@
"dayjs": "^1.11.5",
"draft-js": "^0.11.7",
"draftjs-to-html": "^0.9.1",
+ "exceljs": "^4.4.0",
"file-saver": "^2.0.5",
"highcharts": "^10.2.1",
"highcharts-react-official": "^3.1.0",
"html-to-draftjs": "^1.5.0",
+ "html-to-image": "^1.11.11",
"i": "^0.3.7",
"immer": "^9.0.18",
"jodit-react": "^1.3.23",
- "npm": "^9.4.1",
+ "jspdf": "^2.5.1",
+ "jspdf-autotable": "^3.8.2",
+ "npm": "^9.9.3",
"pure-react-carousel": "^1.30.1",
"qrcode.react": "^3.1.0",
"rc-slider": "^8.6.9",
diff --git a/src/index.ts b/src/index.ts
index fa0afdcb..09fda904 100755
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,7 @@
// Atoms
export { default as TextLink } from './lib/atoms/TextLink';
export { default as Button } from './lib/atoms/Button';
+export { default as Export } from './lib/atoms/Export';
export { default as Label } from './lib/atoms/Label';
export { default as Icon } from './lib/atoms/Icon';
export { default as Switcher } from './lib/atoms/Switcher';
diff --git a/src/lib/atoms/Export/Export.stories.tsx b/src/lib/atoms/Export/Export.stories.tsx
new file mode 100644
index 00000000..903267ce
--- /dev/null
+++ b/src/lib/atoms/Export/Export.stories.tsx
@@ -0,0 +1,278 @@
+import React, { useRef, useState } from 'react';
+import toHtml from 'string-to-html';
+import { args } from '../../../../stories/assets/storybook.globals';
+
+// Helpers
+import * as ExportHelper from './exportHelper';
+
+// Components
+import Button from '../Button/Button';
+import InteractiveWidget from '../../molecules/InteractiveWidget/InteractiveWidget';
+import RichEditor from '../../organisms/RichEditor';
+import ColumnChart from '../../molecules/Charts/ColumnChart';
+
+const meta = {
+ title: 'Atoms/Export',
+ argTypes: {},
+ args: {}
+};
+
+export default meta;
+
+export const ExelFormats = () => {
+ const [currentFunction, setCurrentFunction] = useState<'xls' | 'csv' | 'xlsx'>('xlsx');
+
+ const exelHeader: ExportHelper.ITableHeader[] = [
+ {
+ header: 'Test2',
+ key: 'test2',
+ style: {
+ fontSize: 10,
+ color: '#e91e63'
+ }
+ },
+ {
+ header: 'Test5',
+ key: 'test5',
+ style: {
+ fontSize: 20,
+ color: '#e91e63',
+ bold: true
+ }
+ },
+ {
+ header: 'Zzzz',
+ key: 'test1',
+ style: {
+ fontSize: 20,
+ color: '#e91e63',
+ bold: true
+ }
+ }
+ ];
+
+ const createExel: ExportHelper.DataType[] = [
+ {
+ test1: {
+ value: 'Zzzzz',
+ style: {
+ color: '#bd8faa',
+ fontSize: 10
+ }
+ },
+ test5: 'loyality',
+
+ test2: {
+ value: 'TestWithStyle',
+ style: {
+ color: '#e91e63',
+ fontSize: 10
+ }
+ },
+ test3: 'test'
+ },
+ {
+ test1: {
+ value: 'Zzzzz2',
+ style: {
+ fontSize: 10
+ }
+ },
+ test2: 'test',
+ test4: 'test4',
+ test3: {
+ value: 'Styled value',
+ style: {
+ fontSize: 10
+ }
+ }
+ },
+ {
+ test5: 'loyality',
+ test2: 'loyality',
+ test3: {
+ value: 'ffdggdgd',
+ style: {
+ fontSize: 10,
+ bold: true
+ }
+ }
+ }
+ ];
+
+ const exportHandler = () => {
+ ExportHelper[currentFunction](createExel, exelHeader);
+ };
+
+ return (
+
+
+ Data:
{JSON.stringify(createExel, null, 2)}
+ Header:
{JSON.stringify(exelHeader, null, 2)}
+
{' '}
+
+
{' '}
+
+ );
+};
+
+export const ExportTableAsPdf = () => {
+ const exelHeader = [
+ {
+ header: 'Test2',
+ key: 'test2'
+ },
+ {
+ header: 'Test5',
+ key: 'test5'
+ }
+ ];
+
+ const createExel: Record[] = [
+ {
+ test3: 'test'
+ },
+ {
+ test2: 'test',
+ test4: 'test4'
+ },
+ {
+ test5: 'loyality',
+ test2: 'loyality'
+ }
+ ];
+
+ const exportHandler = () => {
+ ExportHelper.exportToTablePdf(createExel);
+ };
+
+ return (
+
+
+ Data:
{JSON.stringify(createExel, null, 2)}
+ Header:
{JSON.stringify(exelHeader, null, 2)}
+
+
{' '}
+
+ );
+};
+
+export const ExportAsImage = () => {
+ const ref = useRef(null);
+ const [imageType, setImageType] = useState('toJpeg');
+
+ const exportHandler = () => {
+ if (ref.current) {
+ ExportHelper.exportImage(ref.current, imageType);
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ {/**@ts-ignore */}
+
+
+
+
+
+ >
+ );
+};
+
+export const ExportAsPdf = () => {
+ const ref = useRef(null);
+
+ const exportHandler = () => {
+ if (ref.current) {
+ ExportHelper.pdf(ref.current);
+ }
+ };
+ const getData = (e) => {
+ const div = document.createElement('div');
+ div.style.width = '400px';
+ div.style.margin = '5px';
+ div.appendChild(toHtml(e));
+ ref.current = div;
+ };
+
+ return (
+
+ {/**@ts-ignore */}
+
+ {/**@ts-ignore */}
+
+
+ );
+};
diff --git a/src/lib/atoms/Export/exportHelper.ts b/src/lib/atoms/Export/exportHelper.ts
new file mode 100644
index 00000000..1009a3d7
--- /dev/null
+++ b/src/lib/atoms/Export/exportHelper.ts
@@ -0,0 +1,238 @@
+import React from 'react';
+import jsPDF from 'jspdf';
+import * as ImageExporter from 'html-to-image';
+import ExcelJS from 'exceljs';
+import { saveAs } from 'file-saver';
+import autoTable, { ColumnInput } from 'jspdf-autotable';
+//TODO: change file location after tests
+
+const createUniqueArr = (data: object[]) => {
+ return data.reduce((aggr: string[], val) => {
+ aggr = [...aggr, ...Object.keys(val)];
+ return [...new Set(aggr)];
+ }, []);
+};
+
+export const exportToTablePdf = (
+ data: Record[],
+ columns?: Record[],
+ fileName: string = 'document'
+) => {
+ try {
+ const doc = new jsPDF({
+ format: 'letter'
+ });
+
+ const pdfColumns = columns
+ ? columns.map((column) => ({ header: column.header, dataKey: column.key }))
+ : createUniqueArr(data).map((el) => ({ header: el, dataKey: el }));
+
+ autoTable(doc, {
+ columns: pdfColumns as ColumnInput[],
+ body: data
+ });
+
+ doc.save(`${fileName}.pdf`);
+ } catch (error) {
+ return error;
+ }
+};
+
+export const pdf = (HTMLelement: HTMLElement, fileName: string = 'document') => {
+ try {
+ const doc = new jsPDF({
+ format: 'a4',
+ unit: 'px'
+ });
+
+ doc.html(HTMLelement, {
+ callback(doc) {
+ doc.save(fileName);
+ },
+ x: 10,
+ y: 10
+ });
+ } catch (error) {
+ return error;
+ }
+};
+
+export type ImageFormats = 'toPng' | 'toJpeg';
+export const exportImage = async (
+ HTMLelement: HTMLElement,
+ format: ImageFormats = 'toPng',
+ fileName: string = 'document'
+) => {
+ try {
+ const request = await ImageExporter[format](HTMLelement, { cacheBust: true, height: 1000 });
+ const link = document.createElement('a');
+ link.download = fileName;
+ link.href = request;
+ link.click();
+ } catch (error) {
+ return error;
+ }
+};
+
+interface IDataWithStyle {
+ style: {
+ fontSize?: number;
+ color?: `#${string}`;
+ bold?: boolean;
+ italic?: boolean;
+ underline?: boolean;
+ background?: `#${string}`;
+ };
+ value: string | number;
+}
+export interface ITableHeader extends Partial> {
+ style?: IDataWithStyle['style'];
+ header: string;
+ key: string;
+}
+export type DataType = Record;
+
+type AllIndexType = Set>;
+
+const transformData = {
+ font: {},
+ fontSize(size: number) {
+ this.font.size = size;
+
+ return { font: this.font };
+ },
+ color(color: `#${string}`) {
+ if (!this.font.color) {
+ this.font.color = {};
+ }
+ this.font.color = { argb: color.replace('#', '') };
+ return { font: this.font };
+ },
+ bold(isBold: boolean) {
+ this.font.bold = isBold;
+ return { font: this.font };
+ },
+ italic(isItalic: boolean) {
+ this.font.italic = isItalic;
+ return { font: this.font };
+ },
+ underline(isUnderline: boolean) {
+ this.font.underline = isUnderline;
+ return { font: this.font };
+ },
+ background: (color: `#${string}`) => ({
+ fill: {
+ type: 'pattern',
+ pattern: 'solid',
+ fgColor: { argb: color.replace('#', '') }
+ }
+ })
+};
+
+const transformedData = (transformedStyles: Record>) => {
+ Object.keys(transformedStyles).forEach((el) => {
+ let styles = transformedStyles[el].style;
+ transformData.font = {};
+ Object.keys(styles).forEach((key) => {
+ if (typeof transformData[key] === 'function') {
+ transformedStyles[el].style = {
+ ...transformedStyles[el].style,
+ ...transformData[key](styles[key])
+ };
+ }
+ delete transformedStyles[el].style[key];
+ });
+ });
+};
+
+const tableFormats = async (
+ data: DataType[],
+ header?: ITableHeader[],
+ fileName: string = 'document',
+ type: 'xlsx' | 'csv' = 'xlsx'
+) => {
+ try {
+ let transformedStyles = {};
+ const workbook = new ExcelJS.Workbook();
+ const worksheet = workbook.addWorksheet();
+ const allRows: Record = {};
+ const dataFromHeader: string[] = [];
+ const allIndex: AllIndexType = new Set();
+ const transformAllIndex = (allIndex: AllIndexType) => {
+ allIndex.forEach((el) => {
+ if (typeof el.element === 'object') {
+ allRows[el.rowIndex as number].getCell(el.colIndex as number).style =
+ transformedStyles[el.element.value].style;
+ }
+ });
+ };
+ const fillTransformedStyles = (key: string | number, style: IDataWithStyle['style']) => {
+ transformedStyles = {
+ ...transformedStyles,
+ [key]: {
+ ...transformedStyles[key],
+ style
+ }
+ };
+ };
+ if (!header) {
+ const createHeder = createUniqueArr(data).map((val) => {
+ dataFromHeader.push(val);
+ return { header: val, key: val };
+ });
+ worksheet.columns = createHeder;
+ } else {
+ const headerWithoutStyles = header.map((el) => {
+ dataFromHeader.push(el.key!);
+ if (el.style && el.key) {
+ fillTransformedStyles(el.key, el.style);
+ }
+ return el;
+ });
+ transformedData(transformedStyles);
+ worksheet.columns = headerWithoutStyles as Partial[];
+ worksheet.getRow(1).eachCell((cell, colNumber) => {
+ const getCol = worksheet.getColumn(colNumber);
+ if (getCol.key && transformedStyles.hasOwnProperty(getCol.key)) {
+ cell.style = { ...cell.style, ...transformedStyles[getCol.key].style };
+ }
+ });
+ }
+
+ data.forEach((items, rowIndex) => {
+ let rows = {};
+ dataFromHeader.forEach((element, colIndex) => {
+ const isObject = typeof items[element] === 'object';
+ rows[element] = isObject ? (items[element] as IDataWithStyle).value : items[element];
+ allIndex.add({ element: items[element], colIndex: colIndex + 1, rowIndex: rowIndex });
+
+ if (isObject) {
+ const currentElement = items[element] as IDataWithStyle;
+ fillTransformedStyles(currentElement.value, currentElement.style);
+ }
+ });
+ const row = worksheet.addRow(rows);
+ allRows[rowIndex] = row;
+ });
+ transformedData(transformedStyles);
+ transformAllIndex(allIndex);
+ let buffer: ExcelJS.Buffer;
+ if (type === 'csv') {
+ buffer = await workbook.csv.writeBuffer();
+ } else {
+ buffer = await workbook.xlsx.writeBuffer();
+ }
+ const blob = new Blob([buffer], {
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+ });
+ saveAs(blob, `${fileName}.${type}`);
+ } catch (error) {
+ return error;
+ }
+};
+
+export const xlsx = (data: DataType[], header?: ITableHeader[], documentName?: string) =>
+ tableFormats(data, header, documentName);
+
+export const csv = (data: DataType[], header?: ITableHeader[], documentName?: string) =>
+ tableFormats(data, header, documentName, 'csv');
diff --git a/src/lib/atoms/Export/index.tsx b/src/lib/atoms/Export/index.tsx
new file mode 100644
index 00000000..1836aff7
--- /dev/null
+++ b/src/lib/atoms/Export/index.tsx
@@ -0,0 +1 @@
+export * as default from './exportHelper';