mirror of
https://dev.azure.com/tstanciu94/Packages/_git/standard-cv
synced 2025-08-10 18:32:25 +03:00
move standard cv to npm package
This commit is contained in:
parent
1542a72462
commit
2c4fea7bf7
1
.npmrc
Normal file
1
.npmrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
@flare:registry=https://toodle.ddns.net/public-node-registry
|
10
package.json
10
package.json
@ -52,15 +52,17 @@
|
|||||||
"lint-staged": "^12.2.2",
|
"lint-staged": "^12.2.2",
|
||||||
"prettier": "^2.5.1"
|
"prettier": "^2.5.1"
|
||||||
},
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-scripts": "5.0.1"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.10.2",
|
"@fortawesome/fontawesome-free": "^5.10.2",
|
||||||
"bootstrap": "^4.3.1",
|
"bootstrap": "^4.3.1",
|
||||||
"jquery": "^3.3.1",
|
"jquery": "^3.3.1",
|
||||||
"popper.js": "^1.14.7",
|
"popper.js": "^1.14.7",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1"
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-scripts": "5.0.1"
|
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
|
40
src/components/Footer.js
Normal file
40
src/components/Footer.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
const Footer = ({ data }) => {
|
||||||
|
return (
|
||||||
|
<footer>
|
||||||
|
<small className="copyright">
|
||||||
|
{data.custom ? (
|
||||||
|
data.custom
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<a href={data.project.repository} target="_blank" rel="noreferrer">
|
||||||
|
{data.project.name}
|
||||||
|
</a>
|
||||||
|
{` | ${data.owner.message} `}
|
||||||
|
<a href={data.owner.url} target="_blank" rel="noreferrer">
|
||||||
|
{data.owner.name}
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</small>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Footer.propTypes = {
|
||||||
|
data: PropTypes.shape({
|
||||||
|
project: PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
repository: PropTypes.string
|
||||||
|
}),
|
||||||
|
owner: PropTypes.shape({
|
||||||
|
message: PropTypes.string.isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
url: PropTypes.string
|
||||||
|
}),
|
||||||
|
custom: PropTypes.node
|
||||||
|
}).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Footer;
|
66
src/components/Layout.js
Normal file
66
src/components/Layout.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import React from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import HeaderLayout from "./header/HeaderLayout";
|
||||||
|
import Education from "./section/Education";
|
||||||
|
import Work from "./section/Work";
|
||||||
|
import Projects from "./section/Projects";
|
||||||
|
import Skills from "./section/Skills";
|
||||||
|
import Honors from "./section/Honors";
|
||||||
|
import Conferences from "./section/Conferences";
|
||||||
|
import Footer from "./Footer";
|
||||||
|
|
||||||
|
const Layout = ({ data }) => {
|
||||||
|
return (
|
||||||
|
<main className="container">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-lg-4 left-side">
|
||||||
|
<HeaderLayout data={data.header} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-lg-8 right-side">
|
||||||
|
<article>
|
||||||
|
{data.article.education.visible && (
|
||||||
|
<Education data={data.article.education} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data.article.work.visible && <Work data={data.article.work} />}
|
||||||
|
|
||||||
|
{data.article.projects.visible && (
|
||||||
|
<Projects data={data.article.projects} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data.article.skills.visible && (
|
||||||
|
<Skills data={data.article.skills} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data.article.honors.visible && (
|
||||||
|
<Honors data={data.article.honors} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data.article.conferences.visible && (
|
||||||
|
<Conferences data={data.article.conferences} />
|
||||||
|
)}
|
||||||
|
</article>
|
||||||
|
{data.footer.visible && <Footer data={data.footer} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Layout.propTypes = {
|
||||||
|
data: PropTypes.shape({
|
||||||
|
header: PropTypes.object.isRequired,
|
||||||
|
article: PropTypes.shape({
|
||||||
|
education: PropTypes.object.isRequired,
|
||||||
|
work: PropTypes.object.isRequired,
|
||||||
|
projects: PropTypes.object,
|
||||||
|
skills: PropTypes.object.isRequired,
|
||||||
|
honors: PropTypes.object,
|
||||||
|
conferences: PropTypes.object
|
||||||
|
}).isRequired,
|
||||||
|
footer: PropTypes.object.isRequired
|
||||||
|
}).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
13
src/components/header/About.js
Normal file
13
src/components/header/About.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
const About = ({ data }) => {
|
||||||
|
return <p className="justify-text">{data.content}</p>;
|
||||||
|
};
|
||||||
|
|
||||||
|
About.propTypes = {
|
||||||
|
data: PropTypes.shape({
|
||||||
|
content: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired
|
||||||
|
}).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default About;
|
58
src/components/header/HeaderLayout.js
Normal file
58
src/components/header/HeaderLayout.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import React from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import Profile from "./Profile";
|
||||||
|
import ProfileName from "./ProfileName";
|
||||||
|
import SocialNetworks from "./SocialNetworks";
|
||||||
|
import About from "./About";
|
||||||
|
import { useWindowSize } from "@flare/react-hooks";
|
||||||
|
|
||||||
|
const TabletHeader = ({ data }) => {
|
||||||
|
return (
|
||||||
|
<header>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-6">
|
||||||
|
<Profile
|
||||||
|
data={data.profile}
|
||||||
|
displayProfileName={false}
|
||||||
|
className="profile-inline"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-6">
|
||||||
|
<div className="profile-inline">
|
||||||
|
<ProfileName data={data.profile} />
|
||||||
|
</div>
|
||||||
|
<SocialNetworks data={data.networks} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md about-flat">
|
||||||
|
<About data={data.about} />
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Header = ({ data }) => {
|
||||||
|
return (
|
||||||
|
<header>
|
||||||
|
<Profile data={data.profile} />
|
||||||
|
<SocialNetworks data={data.networks} />
|
||||||
|
<About data={data.about} />
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const HeaderLayout = ({ data }) => {
|
||||||
|
const { isTablet } = useWindowSize();
|
||||||
|
|
||||||
|
if (isTablet) return <TabletHeader data={data} />;
|
||||||
|
return <Header data={data} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
HeaderLayout.propTypes = {
|
||||||
|
data: PropTypes.shape({
|
||||||
|
profile: PropTypes.object.isRequired
|
||||||
|
}).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeaderLayout;
|
50
src/components/header/Profile.js
Normal file
50
src/components/header/Profile.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import PropTypes from "prop-types";
|
||||||
|
import ProfileName from "./ProfileName";
|
||||||
|
|
||||||
|
const Profile = ({ data, displayProfileName, className }) => {
|
||||||
|
const handleDownloadClick = () => {
|
||||||
|
window.umami && window.umami("cv-download");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<div className="picture">
|
||||||
|
<img src={data.picture.src} alt={data.picture.alt} />
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
className="btn btn-cv"
|
||||||
|
href={data.download.src}
|
||||||
|
role="button"
|
||||||
|
download
|
||||||
|
onClick={handleDownloadClick}
|
||||||
|
>
|
||||||
|
<i className="fas fa-file-alt"></i> {data.download.label}
|
||||||
|
</a>
|
||||||
|
{displayProfileName && <ProfileName data={data} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Profile.defaultProps = {
|
||||||
|
displayProfileName: true,
|
||||||
|
className: "profile"
|
||||||
|
};
|
||||||
|
|
||||||
|
Profile.propTypes = {
|
||||||
|
data: PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
position: PropTypes.string.isRequired,
|
||||||
|
picture: PropTypes.shape({
|
||||||
|
src: PropTypes.string.isRequired,
|
||||||
|
alt: PropTypes.string
|
||||||
|
}),
|
||||||
|
download: PropTypes.shape({
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
src: PropTypes.string.isRequired
|
||||||
|
})
|
||||||
|
}).isRequired,
|
||||||
|
displayProfileName: PropTypes.bool,
|
||||||
|
className: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Profile;
|
19
src/components/header/ProfileName.js
Normal file
19
src/components/header/ProfileName.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
const ProfileName = ({ data }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>{data.name}</h1>
|
||||||
|
<span>{data.position}</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ProfileName.propTypes = {
|
||||||
|
data: PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
position: PropTypes.string.isRequired
|
||||||
|
}).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProfileName;
|
50
src/components/header/SocialNetworks.js
Normal file
50
src/components/header/SocialNetworks.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
const SocialNetworks = ({ data }) => {
|
||||||
|
const networks = data.sort((a, b) => a.id - b.id);
|
||||||
|
const handleSocialNetworkClick = event => () => {
|
||||||
|
event && window.umami && window.umami(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="social">
|
||||||
|
{networks.map(network => (
|
||||||
|
<li key={`social-network-${network.id}`}>
|
||||||
|
<i className={network.icon}></i>{" "}
|
||||||
|
{network.sameTab ? (
|
||||||
|
<a
|
||||||
|
href={network.url}
|
||||||
|
onClick={handleSocialNetworkClick(network.event)}
|
||||||
|
>
|
||||||
|
{network.label}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<a
|
||||||
|
href={network.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
onClick={handleSocialNetworkClick(network.event)}
|
||||||
|
>
|
||||||
|
{network.label}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SocialNetworks.propTypes = {
|
||||||
|
data: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
id: PropTypes.number.isRequired,
|
||||||
|
icon: PropTypes.string.isRequired,
|
||||||
|
url: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
sameTab: PropTypes.bool,
|
||||||
|
event: PropTypes.string
|
||||||
|
})
|
||||||
|
).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SocialNetworks;
|
28
src/components/section/Conferences.js
Normal file
28
src/components/section/Conferences.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import PropTypes from "prop-types";
|
||||||
|
import SectionTitle from "./SectionTitle";
|
||||||
|
import { composeKey } from "../../utils/textUtils";
|
||||||
|
|
||||||
|
const Conferences = ({ data }) => {
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<SectionTitle icon={data.icon} label={data.label} />
|
||||||
|
<div className="justify-text">
|
||||||
|
<ul style={{ marginBottom: "-5px" }}>
|
||||||
|
{data.elements.map(conference => (
|
||||||
|
<li key={composeKey(conference)}>{conference}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Conferences.propTypes = {
|
||||||
|
data: PropTypes.shape({
|
||||||
|
icon: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
elements: PropTypes.array.isRequired
|
||||||
|
}).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Conferences;
|
59
src/components/section/Education.js
Normal file
59
src/components/section/Education.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import PropTypes from "prop-types";
|
||||||
|
import SectionTitle from "./SectionTitle";
|
||||||
|
|
||||||
|
const School = ({ name, time, title, courses, last }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="school justify-text">
|
||||||
|
<div className="upper-row">
|
||||||
|
<h3 className="school-name">{name}</h3>
|
||||||
|
<div className="time">{time}</div>
|
||||||
|
</div>
|
||||||
|
<div className="school-title">{title}</div>
|
||||||
|
{courses && (
|
||||||
|
<>
|
||||||
|
<b>{courses.label}:</b> {courses.content}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!last && <br />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Education = ({ data }) => {
|
||||||
|
const _schools = [...data.elements.sort((a, b) => a.id - b.id)];
|
||||||
|
const last = _schools.pop();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<SectionTitle icon={data.icon} label={data.label} />
|
||||||
|
|
||||||
|
{_schools.map(school => (
|
||||||
|
<School key={`school-${school.id}`} {...school} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
<School {...last} last />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Education.propTypes = {
|
||||||
|
data: PropTypes.shape({
|
||||||
|
icon: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
elements: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
time: PropTypes.string.isRequired,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
courses: PropTypes.shape({
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
content: PropTypes.string.isRequired
|
||||||
|
})
|
||||||
|
})
|
||||||
|
).isRequired
|
||||||
|
}).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Education;
|
58
src/components/section/Honors.js
Normal file
58
src/components/section/Honors.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import PropTypes from "prop-types";
|
||||||
|
import SectionTitle from "./SectionTitle";
|
||||||
|
import { composeKey } from "../../utils/textUtils";
|
||||||
|
|
||||||
|
const HonorComponent = ({ name, abbreviation, bullets, last }) => {
|
||||||
|
const ulProps = last ? { style: { marginBottom: "-5px" } } : {};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h3>
|
||||||
|
{abbreviation ? <abbr title={abbreviation}>{name}</abbr> : <>{name}</>}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<ul {...ulProps}>
|
||||||
|
{bullets.map(bullet => (
|
||||||
|
<li key={composeKey(bullet)}>{bullet}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
HonorComponent.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
abbreviation: PropTypes.string,
|
||||||
|
bullets: PropTypes.array.isRequired,
|
||||||
|
last: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
const Honors = ({ data }) => {
|
||||||
|
const _honors = [...data.elements.sort((a, b) => a.id - b.id)];
|
||||||
|
const last = _honors.pop();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="honors">
|
||||||
|
<SectionTitle icon={data.icon} label={data.label} />
|
||||||
|
{_honors.map(honor => (
|
||||||
|
<HonorComponent key={`honor-${honor.id}`} {...honor} />
|
||||||
|
))}
|
||||||
|
<HonorComponent {...last} last={true} />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Honors.propTypes = {
|
||||||
|
data: PropTypes.shape({
|
||||||
|
icon: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
elements: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
abbreviation: PropTypes.string,
|
||||||
|
bullets: PropTypes.array.isRequired
|
||||||
|
})
|
||||||
|
).isRequired
|
||||||
|
}).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Honors;
|
36
src/components/section/Projects.js
Normal file
36
src/components/section/Projects.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import PropTypes from "prop-types";
|
||||||
|
import SectionTitle from "./SectionTitle";
|
||||||
|
import Job from "./job/Job";
|
||||||
|
import { composeKey } from "../../utils/textUtils";
|
||||||
|
|
||||||
|
const Projects = ({ data }) => {
|
||||||
|
const _projects = [...data.elements.sort((a, b) => a.id - b.id)];
|
||||||
|
const last = _projects.pop();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<SectionTitle icon={data.icon} label={data.label} />
|
||||||
|
{_projects.map(project => (
|
||||||
|
<Job key={composeKey(project.name)} {...project} />
|
||||||
|
))}
|
||||||
|
<Job {...last} last />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Projects.propTypes = {
|
||||||
|
data: PropTypes.shape({
|
||||||
|
icon: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
elements: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
text: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||||
|
bullets: PropTypes.arrayOf(
|
||||||
|
PropTypes.oneOfType([PropTypes.string, PropTypes.node])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
).isRequired
|
||||||
|
}).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Projects;
|
20
src/components/section/SectionTitle.js
Normal file
20
src/components/section/SectionTitle.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
const SectionTitle = ({ icon, label }) => {
|
||||||
|
return (
|
||||||
|
<h2 className="section-title">
|
||||||
|
<span className="fa-stack fa-xs">
|
||||||
|
<i className="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i className={`fas ${icon} fa-stack-1x fa-inverse`}></i>
|
||||||
|
</span>{" "}
|
||||||
|
{label}
|
||||||
|
</h2>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SectionTitle.propTypes = {
|
||||||
|
icon: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SectionTitle;
|
51
src/components/section/Skills.js
Normal file
51
src/components/section/Skills.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import PropTypes from "prop-types";
|
||||||
|
import SectionTitle from "./SectionTitle";
|
||||||
|
|
||||||
|
const Skill = ({ type, description, last }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<b>{type}:</b> {description}
|
||||||
|
{!last && <br />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Skill.propTypes = {
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
description: PropTypes.oneOfType([PropTypes.string, PropTypes.node])
|
||||||
|
.isRequired,
|
||||||
|
last: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
const Skills = ({ data }) => {
|
||||||
|
const _skills = [...data.elements.sort((a, b) => a.id - b.id)];
|
||||||
|
const last = _skills.pop();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<SectionTitle icon={data.icon} label={data.label} />
|
||||||
|
<div className="justify-text">
|
||||||
|
{_skills.map(skill => (
|
||||||
|
<Skill key={`skill-${skill.id}`} {...skill} />
|
||||||
|
))}
|
||||||
|
<Skill {...last} last={true} />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Skills.propTypes = {
|
||||||
|
data: PropTypes.shape({
|
||||||
|
icon: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
elements: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
description: PropTypes.oneOfType([PropTypes.string, PropTypes.node])
|
||||||
|
.isRequired
|
||||||
|
})
|
||||||
|
).isRequired
|
||||||
|
}).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Skills;
|
82
src/components/section/Work.js
Normal file
82
src/components/section/Work.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import PropTypes from "prop-types";
|
||||||
|
import SectionTitle from "./SectionTitle";
|
||||||
|
import Job from "./job/Job";
|
||||||
|
import JobChapter from "./job/JobChapter";
|
||||||
|
import { composeKey } from "../../utils/textUtils";
|
||||||
|
|
||||||
|
const MultiplePositionsWork = ({ data, last }) => {
|
||||||
|
const divProps = last
|
||||||
|
? { className: "job justify-text", style: { marginBottom: "-15px" } }
|
||||||
|
: { className: "job justify-text" };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...divProps}>
|
||||||
|
<h3 className="company">{data.name}</h3>
|
||||||
|
{data.positions.map(position => (
|
||||||
|
<JobChapter key={composeKey(position.name)} {...position} trivial />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const WorkPoint = ({ data, last }) =>
|
||||||
|
data.positions ? (
|
||||||
|
<MultiplePositionsWork data={data} last={last} />
|
||||||
|
) : (
|
||||||
|
<Job {...data} last={last} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const Work = ({ data }) => {
|
||||||
|
const _work = [...data.elements];
|
||||||
|
const last = _work.pop();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<SectionTitle icon={data.icon} label={data.label} />
|
||||||
|
|
||||||
|
{_work.map(point => (
|
||||||
|
<WorkPoint key={composeKey(point.name)} data={point} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
<WorkPoint data={last} last={true} />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Work.propTypes = {
|
||||||
|
data: PropTypes.shape({
|
||||||
|
icon: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
elements: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
time: PropTypes.string,
|
||||||
|
position: PropTypes.string,
|
||||||
|
positions: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
time: PropTypes.string,
|
||||||
|
content: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
text: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||||
|
bullets: PropTypes.arrayOf(
|
||||||
|
PropTypes.oneOfType([PropTypes.string, PropTypes.node])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
).isRequired
|
||||||
|
})
|
||||||
|
),
|
||||||
|
content: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
text: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||||
|
bullets: PropTypes.arrayOf(
|
||||||
|
PropTypes.oneOfType([PropTypes.string, PropTypes.node])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
).isRequired
|
||||||
|
}).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Work;
|
33
src/components/section/job/Job.js
Normal file
33
src/components/section/job/Job.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import PropTypes from "prop-types";
|
||||||
|
import JobChapter from "./JobChapter";
|
||||||
|
|
||||||
|
const Job = ({ last, ...props }) => {
|
||||||
|
// style="margin-bottom: -15px" must pe used on last element of section
|
||||||
|
const divProps = last
|
||||||
|
? { className: "job justify-text", style: { marginBottom: "-15px" } }
|
||||||
|
: { className: "job justify-text" };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...divProps}>
|
||||||
|
<JobChapter {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Job.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
time: PropTypes.string,
|
||||||
|
position: PropTypes.string,
|
||||||
|
content: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
text: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||||
|
bullets: PropTypes.arrayOf(
|
||||||
|
PropTypes.oneOfType([PropTypes.string, PropTypes.node])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
|
chapter: PropTypes.bool,
|
||||||
|
last: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Job;
|
62
src/components/section/job/JobChapter.js
Normal file
62
src/components/section/job/JobChapter.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { composeKey } from "../../../utils/textUtils";
|
||||||
|
|
||||||
|
const JobFragment = ({ text, bullets }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{text && text}
|
||||||
|
{bullets ? (
|
||||||
|
<ul>
|
||||||
|
{bullets.map(bullet => (
|
||||||
|
<li key={composeKey(bullet)}>{bullet}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<ul></ul>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
JobFragment.propTypes = {
|
||||||
|
text: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||||
|
bullets: PropTypes.arrayOf(
|
||||||
|
PropTypes.oneOfType([PropTypes.string, PropTypes.node])
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
const JobChapter = ({ name, time, position, content, trivial }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="upper-row">
|
||||||
|
{trivial ? (
|
||||||
|
<div className="job-title">{name}</div>
|
||||||
|
) : (
|
||||||
|
<h3 className="company">{name}</h3>
|
||||||
|
)}
|
||||||
|
<div className="time">{time}</div>
|
||||||
|
</div>
|
||||||
|
{position && <div className="job-title">{position}</div>}
|
||||||
|
{content.map(fragment => (
|
||||||
|
<JobFragment key={`job-fragment-${fragment.id}`} {...fragment} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
JobChapter.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
time: PropTypes.string,
|
||||||
|
position: PropTypes.string,
|
||||||
|
content: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
text: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||||
|
bullets: PropTypes.arrayOf(
|
||||||
|
PropTypes.oneOfType([PropTypes.string, PropTypes.node])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
|
trivial: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
export default JobChapter;
|
57
src/index.js
57
src/index.js
@ -1,2 +1,55 @@
|
|||||||
const empty = "";
|
import React from "react";
|
||||||
export { empty };
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
// styles
|
||||||
|
import "bootstrap/dist/css/bootstrap.min.css";
|
||||||
|
import "@fortawesome/fontawesome-free/css/all.min.css";
|
||||||
|
import "./styles/main.css";
|
||||||
|
|
||||||
|
// jQuery first, then Popper.js, then Bootstrap JS
|
||||||
|
import "jquery/dist/jquery.slim.min.js";
|
||||||
|
import "popper.js/dist/popper.min.js";
|
||||||
|
import "bootstrap/dist/js/bootstrap.min.js";
|
||||||
|
|
||||||
|
import Layout from "./components/Layout";
|
||||||
|
import { useLink } from "@flare/react-hooks";
|
||||||
|
import { constants, useTheme, useFavicon, utils } from "./theme";
|
||||||
|
|
||||||
|
const StandardCV = ({ data }) => {
|
||||||
|
const { theme, favicon, options } = data.configuration;
|
||||||
|
const { urlTheme: enableUrlTheme } = options;
|
||||||
|
|
||||||
|
useLink(
|
||||||
|
"https://fonts.googleapis.com/css?family=Noto+Sans&display=swap",
|
||||||
|
"stylesheet"
|
||||||
|
);
|
||||||
|
|
||||||
|
const themeFromUrl = enableUrlTheme ? utils.getThemeFromUrl() : null;
|
||||||
|
const _theme = themeFromUrl ?? theme ?? constants.defaultTheme;
|
||||||
|
useTheme(_theme);
|
||||||
|
useFavicon(_theme, favicon);
|
||||||
|
|
||||||
|
return <Layout data={data} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
StandardCV.propTypes = {
|
||||||
|
data: PropTypes.shape({
|
||||||
|
configuration: PropTypes.shape({
|
||||||
|
theme: PropTypes.oneOf(constants.themes),
|
||||||
|
favicon: PropTypes.shape({
|
||||||
|
use: PropTypes.bool.isRequired,
|
||||||
|
id: PropTypes.string,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
href: PropTypes.string
|
||||||
|
}).isRequired,
|
||||||
|
options: PropTypes.shape({
|
||||||
|
urlTheme: PropTypes.bool
|
||||||
|
}).isRequired
|
||||||
|
}).isRequired,
|
||||||
|
header: PropTypes.object.isRequired,
|
||||||
|
article: PropTypes.object.isRequired,
|
||||||
|
footer: PropTypes.object.isRequired
|
||||||
|
}).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StandardCV;
|
||||||
|
200
src/styles/main.css
Normal file
200
src/styles/main.css
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
:root {
|
||||||
|
--primary-color: #71c9ce;
|
||||||
|
--secondary-color: #64b2b6;
|
||||||
|
--text-color: #3d3d3f;
|
||||||
|
--border-color: #ddd;
|
||||||
|
--background-color: #fff;
|
||||||
|
--shadow-color: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Noto Sans", sans-serif;
|
||||||
|
padding: 15px 0px;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
background-color: var(--background-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: 0 0 25px 0 var(--shadow-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
a:hover {
|
||||||
|
color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-side {
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile {
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile h1 {
|
||||||
|
padding-top: 20px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-inline {
|
||||||
|
text-align: center;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-inline h1 {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 2.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picture {
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picture img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-flat {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-text {
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cv {
|
||||||
|
padding: 10px;
|
||||||
|
color: white;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cv:hover {
|
||||||
|
color: var(--background-color);
|
||||||
|
background-color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:focus,
|
||||||
|
.btn:active {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social {
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social i {
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-side {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
padding: 40px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-left: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upper-row {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.company,
|
||||||
|
.school-name {
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-title,
|
||||||
|
.school-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: italic;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job li,
|
||||||
|
section {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.honors h3 {
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-form {
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input,
|
||||||
|
.form-group textarea {
|
||||||
|
border-radius: 0px;
|
||||||
|
border-color: var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0px;
|
||||||
|
border-color: var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 991.98px) {
|
||||||
|
.right-side {
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.left-side {
|
||||||
|
border-right: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
/* fixed sidebar */
|
||||||
|
position: -webkit-sticky;
|
||||||
|
position: sticky;
|
||||||
|
top: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.time {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
29
src/theme/constants.js
Normal file
29
src/theme/constants.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
const theme = {
|
||||||
|
TURQUOISE: "turquoise",
|
||||||
|
BLUE: "blue",
|
||||||
|
GREEN: "green",
|
||||||
|
BROWN: "brown",
|
||||||
|
ORANGE: "orange",
|
||||||
|
PURPLE: "purple",
|
||||||
|
PINK: "pink",
|
||||||
|
CORAL: "coral",
|
||||||
|
NUDE: "nude",
|
||||||
|
RAINBOW: "rainbow"
|
||||||
|
};
|
||||||
|
|
||||||
|
const themes = [
|
||||||
|
theme.TURQUOISE,
|
||||||
|
theme.BLUE,
|
||||||
|
theme.GREEN,
|
||||||
|
theme.BROWN,
|
||||||
|
theme.ORANGE,
|
||||||
|
theme.PURPLE,
|
||||||
|
theme.PINK,
|
||||||
|
theme.CORAL,
|
||||||
|
theme.NUDE,
|
||||||
|
theme.RAINBOW
|
||||||
|
];
|
||||||
|
|
||||||
|
const defaultTheme = theme.TURQUOISE;
|
||||||
|
|
||||||
|
export { defaultTheme, theme, themes };
|
21
src/theme/hooks/useFavicon.js
Normal file
21
src/theme/hooks/useFavicon.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
const useFavicon = (themeName, configuration) => {
|
||||||
|
const { use, id: faviconElementId, placeholder, href } = configuration;
|
||||||
|
if (
|
||||||
|
use &&
|
||||||
|
(!faviconElementId || !placeholder || !href || !href.includes(placeholder))
|
||||||
|
) {
|
||||||
|
throw Error("Invalid favicon configuration.");
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!use) return;
|
||||||
|
const favicon = document.getElementById(faviconElementId);
|
||||||
|
if (favicon) {
|
||||||
|
favicon.href = href.replace(placeholder, themeName);
|
||||||
|
}
|
||||||
|
}, [use, themeName, faviconElementId, placeholder, href]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useFavicon;
|
71
src/theme/hooks/useTheme.js
Normal file
71
src/theme/hooks/useTheme.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
turquoise,
|
||||||
|
blue,
|
||||||
|
brown,
|
||||||
|
pink,
|
||||||
|
purple,
|
||||||
|
orange,
|
||||||
|
coral,
|
||||||
|
nude,
|
||||||
|
green
|
||||||
|
} from "../variants";
|
||||||
|
import { theme } from "../constants";
|
||||||
|
|
||||||
|
const getTheme = name => {
|
||||||
|
switch (name) {
|
||||||
|
case theme.TURQUOISE:
|
||||||
|
return turquoise;
|
||||||
|
case theme.BLUE:
|
||||||
|
return blue;
|
||||||
|
case theme.BROWN:
|
||||||
|
return brown;
|
||||||
|
case theme.PINK:
|
||||||
|
return pink;
|
||||||
|
case theme.PURPLE:
|
||||||
|
return purple;
|
||||||
|
case theme.ORANGE:
|
||||||
|
return orange;
|
||||||
|
case theme.CORAL:
|
||||||
|
return coral;
|
||||||
|
case theme.NUDE:
|
||||||
|
return nude;
|
||||||
|
case theme.GREEN:
|
||||||
|
return green;
|
||||||
|
default:
|
||||||
|
return turquoise;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const useTheme = name => {
|
||||||
|
useEffect(() => {
|
||||||
|
const theme = getTheme(name);
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--primary-color",
|
||||||
|
theme.colors.primary
|
||||||
|
);
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--secondary-color",
|
||||||
|
theme.colors.secondary
|
||||||
|
);
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--text-color",
|
||||||
|
theme.colors.text
|
||||||
|
);
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--border-color",
|
||||||
|
theme.colors.border
|
||||||
|
);
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--background-color",
|
||||||
|
theme.colors.background
|
||||||
|
);
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--shadow-color",
|
||||||
|
theme.colors.shadow
|
||||||
|
);
|
||||||
|
}, [name]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useTheme;
|
6
src/theme/index.js
Normal file
6
src/theme/index.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import useTheme from "./hooks/useTheme";
|
||||||
|
import useFavicon from "./hooks/useFavicon";
|
||||||
|
import * as constants from "./constants";
|
||||||
|
import * as utils from "./utils";
|
||||||
|
|
||||||
|
export { constants, useTheme, useFavicon, utils };
|
6
src/theme/utils.js
Normal file
6
src/theme/utils.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export const getThemeFromUrl = () => {
|
||||||
|
const urlString = window.location.href;
|
||||||
|
const url = new URL(urlString);
|
||||||
|
const theme = url.searchParams.get("theme");
|
||||||
|
return theme;
|
||||||
|
};
|
12
src/theme/variants/blue.js
Normal file
12
src/theme/variants/blue.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const theme = {
|
||||||
|
colors: {
|
||||||
|
primary: "#65a3cc",
|
||||||
|
secondary: "#5499c7",
|
||||||
|
text: "#3d3d3f",
|
||||||
|
border: "#ddd",
|
||||||
|
background: "#fff",
|
||||||
|
shadow: "rgba(0, 0, 0, 0.1)"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default theme;
|
12
src/theme/variants/brown.js
Normal file
12
src/theme/variants/brown.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const theme = {
|
||||||
|
colors: {
|
||||||
|
primary: "#d27065",
|
||||||
|
secondary: "#cd6155",
|
||||||
|
text: "#3d3d3f",
|
||||||
|
border: "#ddd",
|
||||||
|
background: "#fff",
|
||||||
|
shadow: "rgba(0, 0, 0, 0.1)"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default theme;
|
12
src/theme/variants/coral.js
Normal file
12
src/theme/variants/coral.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const theme = {
|
||||||
|
colors: {
|
||||||
|
primary: "#ff9483",
|
||||||
|
secondary: "#F1948A",
|
||||||
|
text: "#3d3d3f",
|
||||||
|
border: "#ddd",
|
||||||
|
background: "#fff",
|
||||||
|
shadow: "rgba(0, 0, 0, 0.1)"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default theme;
|
12
src/theme/variants/green.js
Normal file
12
src/theme/variants/green.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const theme = {
|
||||||
|
colors: {
|
||||||
|
primary: "#3d884d",
|
||||||
|
secondary: "#367a45",
|
||||||
|
text: "#3d3d3f",
|
||||||
|
border: "#ddd",
|
||||||
|
background: "#fff",
|
||||||
|
shadow: "rgba(0, 0, 0, 0.1)"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default theme;
|
11
src/theme/variants/index.js
Normal file
11
src/theme/variants/index.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import turquoise from "./turquoise";
|
||||||
|
import blue from "./blue";
|
||||||
|
import brown from "./brown";
|
||||||
|
import pink from "./pink";
|
||||||
|
import purple from "./purple";
|
||||||
|
import orange from "./orange";
|
||||||
|
import coral from "./coral";
|
||||||
|
import nude from "./nude";
|
||||||
|
import green from "./green";
|
||||||
|
|
||||||
|
export { turquoise, blue, brown, pink, purple, orange, coral, nude, green };
|
12
src/theme/variants/nude.js
Normal file
12
src/theme/variants/nude.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const theme = {
|
||||||
|
colors: {
|
||||||
|
primary: "#f29e95",
|
||||||
|
secondary: "#f1948a",
|
||||||
|
text: "#3d3d3f",
|
||||||
|
border: "#ddd",
|
||||||
|
background: "#fff",
|
||||||
|
shadow: "rgba(0, 0, 0, 0.1)"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default theme;
|
12
src/theme/variants/orange.js
Normal file
12
src/theme/variants/orange.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const theme = {
|
||||||
|
colors: {
|
||||||
|
primary: "#f49f2b",
|
||||||
|
secondary: "#f39514",
|
||||||
|
text: "#3d3d3f",
|
||||||
|
border: "#ddd",
|
||||||
|
background: "#fff",
|
||||||
|
shadow: "rgba(0, 0, 0, 0.1)"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default theme;
|
12
src/theme/variants/pink.js
Normal file
12
src/theme/variants/pink.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const theme = {
|
||||||
|
colors: {
|
||||||
|
primary: "#d5819b",
|
||||||
|
secondary: "#D17390",
|
||||||
|
text: "#3d3d3f",
|
||||||
|
border: "#ddd",
|
||||||
|
background: "#fff",
|
||||||
|
shadow: "rgba(0, 0, 0, 0.1)"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default theme;
|
12
src/theme/variants/purple.js
Normal file
12
src/theme/variants/purple.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const theme = {
|
||||||
|
colors: {
|
||||||
|
primary: "#c19ad2",
|
||||||
|
secondary: "#BB8FCE",
|
||||||
|
text: "#3d3d3f",
|
||||||
|
border: "#ddd",
|
||||||
|
background: "#fff",
|
||||||
|
shadow: "rgba(0, 0, 0, 0.1)"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default theme;
|
12
src/theme/variants/turquoise.js
Normal file
12
src/theme/variants/turquoise.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const theme = {
|
||||||
|
colors: {
|
||||||
|
primary: "#71c9ce",
|
||||||
|
secondary: "#64b2b6",
|
||||||
|
text: "#3d3d3f",
|
||||||
|
border: "#ddd",
|
||||||
|
background: "#fff",
|
||||||
|
shadow: "rgba(0, 0, 0, 0.1)"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default theme;
|
9
src/utils/textUtils.js
Normal file
9
src/utils/textUtils.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const composeKey = input => {
|
||||||
|
if (typeof input === "string" || input instanceof String) {
|
||||||
|
return input.replace(/\s+/g, "-").toLowerCase(); //replace spaces with dashes
|
||||||
|
}
|
||||||
|
const key = Date.now();
|
||||||
|
return `generated-key-${key}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { composeKey };
|
Loading…
x
Reference in New Issue
Block a user