mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-10 12:32:28 +10:00
90% completed, only final touches left
This commit is contained in:
16
package-lock.json
generated
16
package-lock.json
generated
@ -3400,6 +3400,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"classnames": {
|
||||||
|
"version": "2.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
|
||||||
|
"integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
|
||||||
|
},
|
||||||
"clean-css": {
|
"clean-css": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
|
||||||
@ -11617,6 +11622,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-toastify": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-jsVme7jALIFGRyQsri/g4YTsRuaaGI70T6/ikjwZMB4mwTZaCWqj5NqxhGrRStKlJc5npXKKvKeqTiRGQl78LQ==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.4.2",
|
||||||
|
"classnames": "^2.2.6",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"react-transition-group": "^4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-transition-group": {
|
"react-transition-group": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz",
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
"react-scripts": "3.4.1",
|
"react-scripts": "3.4.1",
|
||||||
|
"react-toastify": "^5.5.0",
|
||||||
"uuid": "^7.0.2"
|
"uuid": "^7.0.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -4,7 +4,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport"
|
||||||
|
content="width=device-width, initial-scale=.5, maximum-scale=12.0, minimum-scale=.25, user-scalable=yes" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta name="description"
|
<meta name="description"
|
||||||
content="The resume generator you've been waiting for. Completely private, secure and customizable. Pick a layout, pick colors, enter your information and voila!" />
|
content="The resume generator you've been waiting for. Completely private, secure and customizable. Pick a layout, pick colors, enter your information and voila!" />
|
||||||
@ -13,7 +14,9 @@
|
|||||||
|
|
||||||
<title>Reactive Resume</title>
|
<title>Reactive Resume</title>
|
||||||
|
|
||||||
|
<!-- Google Fonts -->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
|
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"layout": "Onyx",
|
||||||
"font": {
|
"font": {
|
||||||
"family": "Montserrat"
|
"family": "Montserrat"
|
||||||
},
|
},
|
||||||
"colors": {
|
"colors": {
|
||||||
"background": "#FFF",
|
"background": "#FFFFFF",
|
||||||
"accent": "#FF5722",
|
"primary": "#414141",
|
||||||
"body": "#414141"
|
"accent": "#FF5722"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,23 +1,31 @@
|
|||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
|
|
||||||
import Onyx from '../../templates/onyx/Onyx';
|
import Onyx from '../../templates/onyx';
|
||||||
import LeftSidebar from '../LeftSidebar/LeftSidebar';
|
import LeftSidebar from '../LeftSidebar/LeftSidebar';
|
||||||
|
import RightSidebar from '../RightSidebar/RightSidebar';
|
||||||
|
|
||||||
|
toast.configure({
|
||||||
|
autoClose: 3000,
|
||||||
|
closeButton: false,
|
||||||
|
hideProgressBar: true,
|
||||||
|
position: toast.POSITION.BOTTOM_RIGHT,
|
||||||
|
});
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-5 items-center">
|
<div className="h-screen overflow-hidden grid grid-cols-5 items-center">
|
||||||
<LeftSidebar />
|
<LeftSidebar />
|
||||||
|
|
||||||
<div className="col-span-3">
|
<div className="z-0 h-screen col-span-3 flex justify-center items-center overflow-scroll">
|
||||||
<div id="page" className="p-12 my-auto mx-auto shadow-2xl">
|
<div id="page" className="p-10 my-auto shadow-2xl overflow-scroll">
|
||||||
<Onyx />
|
<Onyx />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="rightSidebar" className="h-screen bg-white col-span-1 shadow-2xl overflow-scroll">
|
<RightSidebar />
|
||||||
This is the right sidebar
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import React, { useState, useEffect, useContext } from 'react';
|
import React, { useState, useEffect, useContext } from 'react';
|
||||||
|
|
||||||
|
import AppContext from '../../context/AppContext';
|
||||||
import TabBar from '../../shared/TabBar';
|
import TabBar from '../../shared/TabBar';
|
||||||
import ProfileTab from './tabs/Profile';
|
import ProfileTab from './tabs/Profile';
|
||||||
import ObjectiveTab from './tabs/Objective';
|
import ObjectiveTab from './tabs/Objective';
|
||||||
import WorkTab from './tabs/Work';
|
import WorkTab from './tabs/Work';
|
||||||
import AppContext from '../../context/AppContext';
|
|
||||||
import EducationTab from './tabs/Education';
|
import EducationTab from './tabs/Education';
|
||||||
import AwardsTab from './tabs/Awards';
|
import AwardsTab from './tabs/Awards';
|
||||||
import CertificationsTab from './tabs/Certifications';
|
import CertificationsTab from './tabs/Certifications';
|
||||||
@ -28,7 +28,7 @@ const LeftSidebar = () => {
|
|||||||
const { data } = state;
|
const { data } = state;
|
||||||
|
|
||||||
const [currentTab, setCurrentTab] = useState('Profile');
|
const [currentTab, setCurrentTab] = useState('Profile');
|
||||||
const onChange = (key, value) => {
|
const onChange = (key, value) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'on_input',
|
type: 'on_input',
|
||||||
payload: {
|
payload: {
|
||||||
@ -36,8 +36,8 @@ const LeftSidebar = () => {
|
|||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
|
// TODO: Remove this in production environment
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch({ type: 'populate_starter' });
|
dispatch({ type: 'populate_starter' });
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
@ -66,9 +66,12 @@ const LeftSidebar = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="leftSidebar" className="h-screen bg-white col-span-1 shadow-2xl overflow-y-scroll">
|
<div
|
||||||
|
id="leftSidebar"
|
||||||
|
className="z-10 py-6 h-screen bg-white col-span-1 shadow-2xl overflow-y-scroll"
|
||||||
|
>
|
||||||
<TabBar tabs={tabs} currentTab={currentTab} setCurrentTab={setCurrentTab} />
|
<TabBar tabs={tabs} currentTab={currentTab} setCurrentTab={setCurrentTab} />
|
||||||
<div className="px-6 pb-6">{renderTabs()}</div>
|
<div className="px-6">{renderTabs()}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -41,6 +41,7 @@ const AwardsTab = ({ data, onChange }) => {
|
|||||||
last={index === data.awards.items.length - 1}
|
last={index === data.awards.items.length - 1}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<AddItem dispatch={dispatch} />
|
<AddItem dispatch={dispatch} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -42,6 +42,7 @@ const EducationTab = ({ data, onChange }) => {
|
|||||||
last={index === data.education.items.length - 1}
|
last={index === data.education.items.length - 1}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<AddItem dispatch={dispatch} />
|
<AddItem dispatch={dispatch} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
53
src/components/RightSidebar/RightSidebar.js
Normal file
53
src/components/RightSidebar/RightSidebar.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import React, { useState, useContext } from 'react';
|
||||||
|
|
||||||
|
import AppContext from '../../context/AppContext';
|
||||||
|
import TabBar from '../../shared/TabBar';
|
||||||
|
import LayoutTab from './tabs/Layout';
|
||||||
|
import ColorsTab from './tabs/Colors';
|
||||||
|
import FontsTab from './tabs/Fonts';
|
||||||
|
import ActionsTab from './tabs/Actions';
|
||||||
|
|
||||||
|
const tabs = ['Layout', 'Colors', 'Fonts', 'Actions'];
|
||||||
|
|
||||||
|
const RightSidebar = () => {
|
||||||
|
const context = useContext(AppContext);
|
||||||
|
const { state, dispatch } = context;
|
||||||
|
const { data, theme } = state;
|
||||||
|
|
||||||
|
const [currentTab, setCurrentTab] = useState('Actions');
|
||||||
|
const onChange = (key, value) =>
|
||||||
|
dispatch({
|
||||||
|
type: 'on_input',
|
||||||
|
payload: {
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderTabs = () => {
|
||||||
|
switch (currentTab) {
|
||||||
|
case 'Layout':
|
||||||
|
return <LayoutTab theme={theme} />;
|
||||||
|
case 'Colors':
|
||||||
|
return <ColorsTab theme={theme} onChange={onChange} />;
|
||||||
|
case 'Fonts':
|
||||||
|
return <FontsTab theme={theme} onChange={onChange} />;
|
||||||
|
case 'Actions':
|
||||||
|
return <ActionsTab data={data} theme={theme} dispatch={dispatch} />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="rightSidebar"
|
||||||
|
className="z-10 py-6 h-screen bg-white col-span-1 shadow-2xl overflow-y-scroll"
|
||||||
|
>
|
||||||
|
<TabBar tabs={tabs} currentTab={currentTab} setCurrentTab={setCurrentTab} />
|
||||||
|
<div className="px-6">{renderTabs()}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RightSidebar;
|
||||||
124
src/components/RightSidebar/tabs/Actions.js
Normal file
124
src/components/RightSidebar/tabs/Actions.js
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
/* eslint-disable jsx-a11y/anchor-has-content */
|
||||||
|
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||||
|
import React, { useRef } from 'react';
|
||||||
|
|
||||||
|
const ActionsTab = ({ data, theme, dispatch }) => {
|
||||||
|
const fileInputRef = useRef(null);
|
||||||
|
|
||||||
|
const importJson = event => {
|
||||||
|
const fr = new FileReader();
|
||||||
|
fr.addEventListener('load', () => {
|
||||||
|
const importedObject = JSON.parse(fr.result);
|
||||||
|
dispatch({ type: 'import_data', payload: importedObject });
|
||||||
|
});
|
||||||
|
fr.readAsText(event.target.files[0]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportToJson = () => {
|
||||||
|
const backupObj = { data, theme };
|
||||||
|
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(backupObj))}`;
|
||||||
|
const dlAnchor = document.getElementById('downloadAnchor');
|
||||||
|
dlAnchor.setAttribute('href', dataStr);
|
||||||
|
dlAnchor.setAttribute('download', `RxResumeBackup_${Date.now()}.json`);
|
||||||
|
dlAnchor.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetEverything = () => {
|
||||||
|
dispatch({ type: 'reset' });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="shadow text-center text-sm p-5">
|
||||||
|
Changes you make to your resume are saved automatically to your browser's local
|
||||||
|
storage. No data gets out, hence your information is completely secure.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr className="my-6" />
|
||||||
|
|
||||||
|
<div className="shadow text-center p-5">
|
||||||
|
<h6 className="font-bold text-sm mb-2">Import/Export</h6>
|
||||||
|
|
||||||
|
<p className="text-sm">
|
||||||
|
You can import or export your data in JSON format. With this, you can edit and print your
|
||||||
|
resume from any device. Save this file for later use.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<input ref={fileInputRef} type="file" className="hidden" onChange={importJson} />
|
||||||
|
<a id="downloadAnchor" className="hidden" />
|
||||||
|
|
||||||
|
<div className="mt-4 grid grid-cols-2 col-gap-6">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => fileInputRef.current.click()}
|
||||||
|
className="bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-5 rounded"
|
||||||
|
>
|
||||||
|
<div className="flex justify-center items-center">
|
||||||
|
<i className="material-icons mr-2 font-bold text-base">publish</i>
|
||||||
|
<span className="text-sm">Import</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={exportToJson}
|
||||||
|
className="bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-5 rounded"
|
||||||
|
>
|
||||||
|
<div className="flex justify-center items-center">
|
||||||
|
<i className="material-icons mr-2 font-bold text-base">get_app</i>
|
||||||
|
<span className="text-sm">Export</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr className="my-6" />
|
||||||
|
|
||||||
|
<div className="shadow text-center p-5">
|
||||||
|
<h6 className="font-bold text-sm mb-2">Print Your Resume</h6>
|
||||||
|
|
||||||
|
<div className="text-sm">
|
||||||
|
You can simply press <pre className="inline font-bold">Cmd/Ctrl + P</pre> at any time
|
||||||
|
while you're in the app to print your resume, but here's a fancy button to do
|
||||||
|
the same thing, just 'cause.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => window.print()}
|
||||||
|
className="mt-4 w-1/2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium py-2 px-5 rounded"
|
||||||
|
>
|
||||||
|
<div className="flex justify-center items-center">
|
||||||
|
<i className="material-icons mr-2 font-bold text-base">print</i>
|
||||||
|
<span className="text-sm">Print</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr className="my-6" />
|
||||||
|
|
||||||
|
<div className="shadow text-center p-5">
|
||||||
|
<h6 className="font-bold text-sm mb-2">Reset Everything!</h6>
|
||||||
|
|
||||||
|
<div className="text-sm">
|
||||||
|
This action will reset all your data and remove backups made to your browser's local
|
||||||
|
storage as well, so please make sure you have exported your information before you reset
|
||||||
|
everything.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={resetEverything}
|
||||||
|
className="mt-4 w-1/2 bg-red-600 hover:bg-red-700 text-white text-sm font-medium py-2 px-5 rounded"
|
||||||
|
>
|
||||||
|
<div className="flex justify-center items-center">
|
||||||
|
<i className="material-icons mr-2 font-bold text-base">refresh</i>
|
||||||
|
<span className="text-sm">Reset</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ActionsTab;
|
||||||
108
src/components/RightSidebar/tabs/Colors.js
Normal file
108
src/components/RightSidebar/tabs/Colors.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import TextField from '../../../shared/TextField';
|
||||||
|
import { copyToClipboard } from '../../../utils';
|
||||||
|
|
||||||
|
const colorOptions = [
|
||||||
|
'#f44336',
|
||||||
|
'#E91E63',
|
||||||
|
'#9C27B0',
|
||||||
|
'#673AB7',
|
||||||
|
'#3F51B5',
|
||||||
|
'#2196F3',
|
||||||
|
'#03A9F4',
|
||||||
|
'#00BCD4',
|
||||||
|
'#009688',
|
||||||
|
'#4CAF50',
|
||||||
|
'#8BC34A',
|
||||||
|
'#CDDC39',
|
||||||
|
'#FFEB3B',
|
||||||
|
'#FFC107',
|
||||||
|
'#FF9800',
|
||||||
|
'#FF5722',
|
||||||
|
'#795548',
|
||||||
|
'#9E9E9E',
|
||||||
|
'#607D8B',
|
||||||
|
'#FAFAFA',
|
||||||
|
'#212121',
|
||||||
|
'#263238',
|
||||||
|
];
|
||||||
|
|
||||||
|
const ColorsTab = ({ theme, onChange }) => {
|
||||||
|
const copyColorToClipboard = color => {
|
||||||
|
copyToClipboard(color);
|
||||||
|
toast(`Color ${color} copied to clipboard.`, {
|
||||||
|
bodyClassName: 'text-center text-gray-800 py-2',
|
||||||
|
});
|
||||||
|
onChange('theme.colors.accent', color);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="uppercase tracking-wide text-gray-600 text-xs font-semibold mb-4">
|
||||||
|
Color Options
|
||||||
|
</div>
|
||||||
|
<div className="mb-6 grid grid-cols-8 col-gap-2 row-gap-3">
|
||||||
|
{colorOptions.map(color => (
|
||||||
|
<div
|
||||||
|
key={color}
|
||||||
|
className="cursor-pointer rounded-full border border-gray-200 h-6 w-6 hover:opacity-75"
|
||||||
|
style={{ backgroundColor: color }}
|
||||||
|
onClick={() => copyColorToClipboard(color)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr className="my-6" />
|
||||||
|
|
||||||
|
<div className="my-6 grid grid-cols-6 items-end">
|
||||||
|
<div
|
||||||
|
className="rounded-full w-8 h-8 mb-2 border-2"
|
||||||
|
style={{ backgroundColor: theme.colors.background }}
|
||||||
|
/>
|
||||||
|
<div className="col-span-5">
|
||||||
|
<TextField
|
||||||
|
disabled
|
||||||
|
label="Background Color"
|
||||||
|
placeholder="#FFFFFF"
|
||||||
|
value={theme.colors.background}
|
||||||
|
onChange={v => onChange('theme.colors.background', v)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="my-6 grid grid-cols-6 items-end">
|
||||||
|
<div
|
||||||
|
className="rounded-full w-8 h-8 mb-2 border-2"
|
||||||
|
style={{ backgroundColor: theme.colors.primary }}
|
||||||
|
/>
|
||||||
|
<div className="col-span-5">
|
||||||
|
<TextField
|
||||||
|
label="Primary Color"
|
||||||
|
placeholder="#FFFFFF"
|
||||||
|
value={theme.colors.primary}
|
||||||
|
onChange={v => onChange('theme.colors.primary', v)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="my-6 grid grid-cols-6 items-end">
|
||||||
|
<div
|
||||||
|
className="rounded-full w-8 h-8 mb-2 border-2"
|
||||||
|
style={{ backgroundColor: theme.colors.accent }}
|
||||||
|
/>
|
||||||
|
<div className="col-span-5">
|
||||||
|
<TextField
|
||||||
|
label="Accent Color"
|
||||||
|
placeholder="#FFFFFF"
|
||||||
|
value={theme.colors.accent}
|
||||||
|
onChange={v => onChange('theme.colors.accent', v)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ColorsTab;
|
||||||
35
src/components/RightSidebar/tabs/Fonts.js
Normal file
35
src/components/RightSidebar/tabs/Fonts.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const fontOptions = [
|
||||||
|
'Lato',
|
||||||
|
'Merriweather',
|
||||||
|
'Montserrat',
|
||||||
|
'Open Sans',
|
||||||
|
'Raleway',
|
||||||
|
'Roboto',
|
||||||
|
'Rubik',
|
||||||
|
'Source Sans Pro',
|
||||||
|
'Titillium Web',
|
||||||
|
'Ubuntu',
|
||||||
|
];
|
||||||
|
|
||||||
|
const FontsTab = ({ theme, onChange }) => {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 gap-6">
|
||||||
|
{fontOptions.map(x => (
|
||||||
|
<div
|
||||||
|
key={x}
|
||||||
|
style={{ fontFamily: x }}
|
||||||
|
onClick={() => onChange('theme.font.family', x)}
|
||||||
|
className={`w-full rounded border py-4 shadow text-xl text-center ${
|
||||||
|
theme.font.family === x ? 'border-gray-500' : 'border-transparent'
|
||||||
|
} hover:border-gray-400 cursor-pointer`}
|
||||||
|
>
|
||||||
|
{x}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FontsTab;
|
||||||
31
src/components/RightSidebar/tabs/Layout.js
Normal file
31
src/components/RightSidebar/tabs/Layout.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Onyx, { Image as OnyxPreview } from '../../../templates/onyx';
|
||||||
|
|
||||||
|
const layouts = [
|
||||||
|
{
|
||||||
|
name: 'Onyx',
|
||||||
|
component: Onyx,
|
||||||
|
preview: OnyxPreview,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const LayoutTab = ({ theme }) => {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-2 gap-6">
|
||||||
|
{layouts.map(x => (
|
||||||
|
<div key={x.name} className="text-center">
|
||||||
|
<img
|
||||||
|
className={`rounded cursor-pointer object-cover border shadow hover:shadow-md ${
|
||||||
|
theme.layout === x.name ? 'border-gray-500' : 'border-transparent '
|
||||||
|
} hover:border-gray-400 cursor-pointer`}
|
||||||
|
src={x.preview}
|
||||||
|
alt={x.name}
|
||||||
|
/>
|
||||||
|
<p className="mt-1 text-sm font-medium">{x.name}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LayoutTab;
|
||||||
@ -62,13 +62,14 @@ const initialState = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
|
layout: 'Onyx',
|
||||||
font: {
|
font: {
|
||||||
family: '',
|
family: '',
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
background: '',
|
background: '',
|
||||||
|
primary: '',
|
||||||
accent: '',
|
accent: '',
|
||||||
body: '',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -95,6 +96,12 @@ const reducer = (state, { type, payload }) => {
|
|||||||
return set({ ...state }, `data.${payload.key}.items`, items);
|
return set({ ...state }, `data.${payload.key}.items`, items);
|
||||||
case 'on_input':
|
case 'on_input':
|
||||||
return set({ ...state }, payload.key, payload.value);
|
return set({ ...state }, payload.key, payload.value);
|
||||||
|
case 'import_data':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
data: payload.data,
|
||||||
|
theme: payload.theme,
|
||||||
|
};
|
||||||
case 'populate_starter':
|
case 'populate_starter':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|||||||
@ -1,3 +1,14 @@
|
|||||||
|
/* Google Fonts */
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap');
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap');
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap');
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap');
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Raleway:wght@400;500;600;700&display=swap');
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Merriweather:wght@400;700&display=swap');
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;500;700&display=swap');
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Rubik:wght@400;500&display=swap');
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Titillium+Web:wght@400;600;700&display=swap');
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -33,10 +44,14 @@ body {
|
|||||||
height: 29.7cm;
|
height: 29.7cm;
|
||||||
zoom: 0.8;
|
zoom: 0.8;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
overflow: scroll;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@page {
|
||||||
|
size: A4;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
@ -52,8 +67,9 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#page {
|
#page {
|
||||||
|
width: 21cm;
|
||||||
|
height: 29.7cm;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
@ -17,7 +17,7 @@ const TabBar = ({ tabs, currentTab, setCurrentTab }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="my-6 mx-4 flex items-center">
|
<div className="mx-4 mb-6 flex items-center">
|
||||||
<div
|
<div
|
||||||
className="flex mr-1 cursor-pointer select-none text-gray-600 hover:text-gray-800"
|
className="flex mr-1 cursor-pointer select-none text-gray-600 hover:text-gray-800"
|
||||||
onClick={() => scrollBy(-100)}
|
onClick={() => scrollBy(-100)}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const TextField = ({ label, placeholder, value, onChange, className }) => (
|
const TextField = ({ label, placeholder, value, onChange, className, disabled }) => (
|
||||||
<div className={`w-full flex flex-col ${className}`}>
|
<div className={`w-full flex flex-col ${className}`}>
|
||||||
{label && (
|
{label && (
|
||||||
<label className="uppercase tracking-wide text-gray-600 text-xs font-semibold mb-2">
|
<label className="uppercase tracking-wide text-gray-600 text-xs font-semibold mb-2">
|
||||||
@ -10,6 +10,7 @@ const TextField = ({ label, placeholder, value, onChange, className }) => (
|
|||||||
<input
|
<input
|
||||||
className="appearance-none block w-full bg-gray-200 text-gray-800 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
|
className="appearance-none block w-full bg-gray-200 text-gray-800 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
|
||||||
type="text"
|
type="text"
|
||||||
|
disabled={disabled}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={e => onChange(e.target.value)}
|
onChange={e => onChange(e.target.value)}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ const Onyx = () => {
|
|||||||
style={{
|
style={{
|
||||||
fontFamily: theme.font.family,
|
fontFamily: theme.font.family,
|
||||||
backgroundColor: theme.colors.background,
|
backgroundColor: theme.colors.background,
|
||||||
color: theme.colors.body,
|
color: theme.colors.primary,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-4 items-center">
|
<div className="grid grid-cols-4 items-center">
|
||||||
@ -181,7 +181,10 @@ const Onyx = () => {
|
|||||||
<span
|
<span
|
||||||
key={x}
|
key={x}
|
||||||
className="text-xs rounded-full px-3 py-1 font-medium my-2 mr-2"
|
className="text-xs rounded-full px-3 py-1 font-medium my-2 mr-2"
|
||||||
style={{ backgroundColor: theme.colors.body, color: theme.colors.background }}
|
style={{
|
||||||
|
backgroundColor: theme.colors.primary,
|
||||||
|
color: theme.colors.background,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{x}
|
{x}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
5
src/templates/onyx/index.js
Normal file
5
src/templates/onyx/index.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import Onyx from './Onyx';
|
||||||
|
import image from './preview.png';
|
||||||
|
|
||||||
|
export const Image = image;
|
||||||
|
export default Onyx;
|
||||||
BIN
src/templates/onyx/preview.png
Normal file
BIN
src/templates/onyx/preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 407 KiB |
@ -8,4 +8,25 @@ const move = (array, element, delta) => {
|
|||||||
array.splice(indexes[0], 2, array[indexes[1]], array[indexes[0]]);
|
array.splice(indexes[0], 2, array[indexes[1]], array[indexes[0]]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { move };
|
const copyToClipboard = text => {
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.style.position = 'fixed';
|
||||||
|
textArea.style.top = 0;
|
||||||
|
textArea.style.left = 0;
|
||||||
|
textArea.style.width = '2em';
|
||||||
|
textArea.style.height = '2em';
|
||||||
|
textArea.style.padding = 0;
|
||||||
|
textArea.style.border = 'none';
|
||||||
|
textArea.style.outline = 'none';
|
||||||
|
textArea.style.boxShadow = 'none';
|
||||||
|
textArea.style.background = 'transparent';
|
||||||
|
textArea.value = text;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
textArea.select();
|
||||||
|
const successful = document.execCommand('copy');
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
return successful;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { move, copyToClipboard };
|
||||||
|
|||||||
Reference in New Issue
Block a user