mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-10 04:22:27 +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": {
|
||||
"version": "4.2.3",
|
||||
"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": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz",
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-scripts": "3.4.1",
|
||||
"react-toastify": "^5.5.0",
|
||||
"uuid": "^7.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<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="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!" />
|
||||
@ -13,7 +14,9 @@
|
||||
|
||||
<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 rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
|
||||
</head>
|
||||
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
{
|
||||
"layout": "Onyx",
|
||||
"font": {
|
||||
"family": "Montserrat"
|
||||
},
|
||||
"colors": {
|
||||
"background": "#FFF",
|
||||
"accent": "#FF5722",
|
||||
"body": "#414141"
|
||||
"background": "#FFFFFF",
|
||||
"primary": "#414141",
|
||||
"accent": "#FF5722"
|
||||
}
|
||||
}
|
||||
@ -1,23 +1,31 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
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 RightSidebar from '../RightSidebar/RightSidebar';
|
||||
|
||||
toast.configure({
|
||||
autoClose: 3000,
|
||||
closeButton: false,
|
||||
hideProgressBar: true,
|
||||
position: toast.POSITION.BOTTOM_RIGHT,
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<div className="grid grid-cols-5 items-center">
|
||||
<div className="h-screen overflow-hidden grid grid-cols-5 items-center">
|
||||
<LeftSidebar />
|
||||
|
||||
<div className="col-span-3">
|
||||
<div id="page" className="p-12 my-auto mx-auto shadow-2xl">
|
||||
<div className="z-0 h-screen col-span-3 flex justify-center items-center overflow-scroll">
|
||||
<div id="page" className="p-10 my-auto shadow-2xl overflow-scroll">
|
||||
<Onyx />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="rightSidebar" className="h-screen bg-white col-span-1 shadow-2xl overflow-scroll">
|
||||
This is the right sidebar
|
||||
</div>
|
||||
<RightSidebar />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import React, { useState, useEffect, useContext } from 'react';
|
||||
|
||||
import AppContext from '../../context/AppContext';
|
||||
import TabBar from '../../shared/TabBar';
|
||||
import ProfileTab from './tabs/Profile';
|
||||
import ObjectiveTab from './tabs/Objective';
|
||||
import WorkTab from './tabs/Work';
|
||||
import AppContext from '../../context/AppContext';
|
||||
import EducationTab from './tabs/Education';
|
||||
import AwardsTab from './tabs/Awards';
|
||||
import CertificationsTab from './tabs/Certifications';
|
||||
@ -28,7 +28,7 @@ const LeftSidebar = () => {
|
||||
const { data } = state;
|
||||
|
||||
const [currentTab, setCurrentTab] = useState('Profile');
|
||||
const onChange = (key, value) => {
|
||||
const onChange = (key, value) =>
|
||||
dispatch({
|
||||
type: 'on_input',
|
||||
payload: {
|
||||
@ -36,8 +36,8 @@ const LeftSidebar = () => {
|
||||
value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: Remove this in production environment
|
||||
useEffect(() => {
|
||||
dispatch({ type: 'populate_starter' });
|
||||
}, [dispatch]);
|
||||
@ -66,9 +66,12 @@ const LeftSidebar = () => {
|
||||
};
|
||||
|
||||
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} />
|
||||
<div className="px-6 pb-6">{renderTabs()}</div>
|
||||
<div className="px-6">{renderTabs()}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -41,6 +41,7 @@ const AwardsTab = ({ data, onChange }) => {
|
||||
last={index === data.awards.items.length - 1}
|
||||
/>
|
||||
))}
|
||||
|
||||
<AddItem dispatch={dispatch} />
|
||||
</>
|
||||
);
|
||||
|
||||
@ -42,6 +42,7 @@ const EducationTab = ({ data, onChange }) => {
|
||||
last={index === data.education.items.length - 1}
|
||||
/>
|
||||
))}
|
||||
|
||||
<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: {
|
||||
layout: 'Onyx',
|
||||
font: {
|
||||
family: '',
|
||||
},
|
||||
colors: {
|
||||
background: '',
|
||||
primary: '',
|
||||
accent: '',
|
||||
body: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -95,6 +96,12 @@ const reducer = (state, { type, payload }) => {
|
||||
return set({ ...state }, `data.${payload.key}.items`, items);
|
||||
case 'on_input':
|
||||
return set({ ...state }, payload.key, payload.value);
|
||||
case 'import_data':
|
||||
return {
|
||||
...state,
|
||||
data: payload.data,
|
||||
theme: payload.theme,
|
||||
};
|
||||
case 'populate_starter':
|
||||
return {
|
||||
...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,
|
||||
body {
|
||||
height: 100%;
|
||||
@ -33,10 +44,14 @@ body {
|
||||
height: 29.7cm;
|
||||
zoom: 0.8;
|
||||
background-color: white;
|
||||
overflow: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media print {
|
||||
html,
|
||||
body,
|
||||
@ -52,8 +67,9 @@ body {
|
||||
}
|
||||
|
||||
#page {
|
||||
width: 21cm;
|
||||
height: 29.7cm;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
||||
@ -17,7 +17,7 @@ const TabBar = ({ tabs, currentTab, setCurrentTab }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="my-6 mx-4 flex items-center">
|
||||
<div className="mx-4 mb-6 flex items-center">
|
||||
<div
|
||||
className="flex mr-1 cursor-pointer select-none text-gray-600 hover:text-gray-800"
|
||||
onClick={() => scrollBy(-100)}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
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}`}>
|
||||
{label && (
|
||||
<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
|
||||
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"
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
|
||||
@ -11,7 +11,7 @@ const Onyx = () => {
|
||||
style={{
|
||||
fontFamily: theme.font.family,
|
||||
backgroundColor: theme.colors.background,
|
||||
color: theme.colors.body,
|
||||
color: theme.colors.primary,
|
||||
}}
|
||||
>
|
||||
<div className="grid grid-cols-4 items-center">
|
||||
@ -181,7 +181,10 @@ const Onyx = () => {
|
||||
<span
|
||||
key={x}
|
||||
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}
|
||||
</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]]);
|
||||
};
|
||||
|
||||
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