Browse Source

feat: Exp expansion to reveal story (wip)

pull/2/head
Muthu Kumar 12 months ago
parent
commit
8be10c1c55
Failed to extract signature
  1. 101
      src/components/Exp/Content.tsx
  2. 66
      src/components/Exp/Story.tsx
  3. 70
      src/components/Exp/Unit.tsx
  4. 10
      src/components/Exp/types.ts
  5. 178
      src/pages/main/Exp.tsx

101
src/components/Exp/Content.tsx

@ -0,0 +1,101 @@
import { css } from "@emotion/css";
import React from "react";
import { setupDynamicGradient } from "../../util";
import { Experience } from "./types";
const Circle: React.FC = () => (
<div className="timeline-segment" aria-hidden>
<div
className={css`
width: 200vw;
height: 1px;
background: #333333;
left: -50vw;
position: absolute;
top: calc(-3rem + 0.2rem / 2);
z-index: 0;
`}></div>
<div
className={css`
width: 0.25rem;
height: 0.25rem;
background: #333333;
background: #ffffff;
border-radius: 100%;
position: absolute;
top: -3rem;
left: 0;
z-index: 100;
`}></div>
</div>
);
const btn = css`
display: flex;
flex-direction: column;
gap: 0.6rem;
cursor: pointer;
padding: 1rem var(--item-padding);
border-radius: 0.5rem;
position: relative;
height: max-content;
width: 100%;
background-color: transparent;
border: none;
text-align: left;
display: inherit;
& > * {
z-index: 10;
}
@media (pointer: fine) {
&:hover {
background-color: var(--card-hover);
z-index: 1000;
box-shadow: 0 0 25rem 2rem rgba(190, 190, 190, 0.1);
}
}
& .timeline-segment {
position: absolute;
}
& .position {
color: var(--text-colour);
}
& .year,
& h5 {
font-size: 0.8rem;
font-weight: 300;
color: var(--text-subdued);
}
& h5 {
font-weight: 400;
margin-block-start: 0.2rem;
}
`;
export const Content = ({
onClick,
title,
year,
position,
location,
}: Experience) => {
return (
<button className={btn} onClick={onClick} ref={setupDynamicGradient}>
<div className="dynamic-gradient" />
<Circle />
<h4>
{title}
<span className="year"> · ({year})</span>
</h4>
<span className="position">{position}</span>
<h5>{location}</h5>
</button>
);
};

66
src/components/Exp/Story.tsx

@ -0,0 +1,66 @@
import React from "react";
import { css, cx } from "@emotion/css";
import { Experience } from "./types";
const story = css`
position: absolute;
left: 0;
width: 100%;
border-radius: 0.5rem;
display: flex;
overflow: hidden;
& .contents {
padding: 1.5rem;
line-height: 1.25rem;
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 2rem;
margin-block-start: 1rem;
height: var(--story-height);
& ul {
max-height: 100%;
margin: 0;
column-count: 3;
column-gap: 2rem;
color: var(--text-subdued);
font-weight: 400;
& li + li {
margin-block-start: 0.5rem;
}
& li::marker {
content: "";
font-weight: 800;
padding-top: 1rem;
}
}
}
`;
export const Story = ({ description, logo }: Experience) => {
return (
<div className={cx(story, "story")}>
<div className="contents">
<img
src={`/assets/logos/` + logo}
className={cx(
"story-logo",
css`
height: 4rem;
width: 4rem;
background: rgba(40, 40, 40);
border-radius: 100%;
`,
)}
/>
<ul>{description}</ul>
</div>
</div>
);
};

70
src/components/Exp/Unit.tsx

@ -0,0 +1,70 @@
import React from "react";
import { css, cx } from "@emotion/css";
import { Story } from "./Story";
import { Experience } from "./types";
import { Content } from "./Content";
const expUnit = css`
--final-height: 20rem;
--unit-height: 9rem;
--story-height: calc(var(--final-height) - var(--unit-height));
--transition-time: 300ms;
& > * {
line-height: 1em;
font-size: 1rem;
}
& button {
border: 1px solid transparent;
transition: all calc(var(--transition-time) * 2);
}
&.active button {
background-color: var(--card-active);
border: 1px solid var(--card-active-border);
box-shadow: 0 0 50rem 0 rgba(190, 190, 190, 0.5);
z-index: 800;
& .year,
& h5 {
color: var(--text-colour);
}
}
margin-block-end: 0.5rem;
/* -- Animation stuff -- */
height: var(--unit-height);
transition: height var(--transition-time) ease-in-out;
& .story {
opacity: 0;
transition: opacity var(--transition-time) ease-in-out;
transition-delay: 0;
}
&.active {
height: var(--final-height);
transition-delay: 0;
transition-delay: var(--transition-time);
.story {
opacity: 1;
transition: opacity calc(var(--transition-time) * 2) ease-in-out;
transition-delay: var(--transition-time);
}
}
/* -- */
`;
export const ExpUnit = (props: Experience) => {
return (
<div className={cx(expUnit, { active: props.active })}>
<Content {...props} />
<Story {...props} />
</div>
);
};

10
src/components/Exp/types.ts

@ -0,0 +1,10 @@
export interface Experience {
active: boolean;
title: string;
location: string;
position: string;
year: string;
description: React.ReactElement | string;
logo: string;
onClick?: (e: React.MouseEvent) => void;
}

178
src/pages/main/Exp.tsx

@ -1,163 +1,22 @@
import React from "react";
import React, { useEffect, useState } from "react";
import { css } from "@emotion/css";
import Container from "../../components/Container";
import { ExpUnit } from "../../components/Exp/Unit";
import { age, experience } from "./data/experience";
const exp = [
{
title: "The Feathers",
location: "Chennai (formerly Tirunelveli and Ooty)",
position: "Founder",
year: "2011-19",
},
{
title: "StudioFlicks",
location: "Remote (Coimbatore)",
position: "Co-founder & Creative Head",
year: "2013-15",
},
{
title: "Vinzas",
location: "Chennai",
position: "Architectural Intern",
year: "2014",
},
{
title: "Blue Cube",
location: "Chennai",
position: "Architectural Intern",
year: "2015",
},
{
title: "OutFocus Magazine",
location: "Ooty",
position: "Editor / developer",
year: "2014-17",
},
{
title: "Zoho",
location: "Chennai",
position: "Technical Content Writer",
year: "2017",
},
{
title: "Manoj Exports",
location: "Chennai",
position: "Designer & web dev",
year: "2017",
},
{
title: "Klenty",
location: "Chennai",
position: "Full stack developer",
year: "2018",
},
{
title: "Hugo's Way",
location: "Remote (Dublin)",
position: "Full stack developer",
year: "2018-19",
},
{
title: "Navana Tech",
location: "Remote (Mumbai)",
position: "Lead webdev & architect",
year: "2021-22",
},
{
title: "Feathers Studio",
location: "Chennai",
position: "Chief Maker",
year: "2019-present",
},
].reverse();
const Circle: React.FC = () => (
<div>
<div
className={css`
width: 200vw;
height: 1px;
background: #333333;
left: -50vw;
position: absolute;
top: calc(2rem + 0.25rem / 2);
/* centre it to the circle */
transform: translateY(-50%);
z-index: 0;
`}></div>
<div
className={css`
width: 0.25rem;
height: 0.25rem;
background: #ffffff;
border-radius: 100%;
position: absolute;
top: 2rem;
left: 0;
z-index: 100;
`}></div>
</div>
);
type Experience = {
title: string;
location?: string;
position: string;
year: string;
};
const Exp: React.FC = () => {
const [active, setActive] = useState(-1);
const ExpUnit: React.FC<Experience> = ({ title, location, position, year }) => {
return (
<div
className={css`
position: relative;
display: flex;
flex-direction: column;
gap: 0.6rem;
& * {
line-height: 1em;
}
& h5 {
color: var(--text-subdued);
font-weight: 400;
font-size: 0.9rem;
}
`}>
<Circle />
<h4>{title}</h4>
<div>
<span
className={css`
color: var(--text-colour);
`}>
{position}
</span>
{" . "}
<span
className={css`
font-weight: 300;
`}>
{year}
</span>
</div>
<h5>{location}</h5>
</div>
);
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key === "Escape") setActive(-1);
};
const getAge = (date: string) => {
var today = new Date();
var birthDate = new Date(date);
var age = today.getFullYear() - birthDate.getFullYear();
var m = today.getMonth() - birthDate.getMonth();
if (m < 0) return age - 1;
if (m === 0 && today.getDate() < birthDate.getDate()) return age - 1;
return age;
};
window.addEventListener("keydown", handler);
const age = getAge("27 May 1995");
return () => window.removeEventListener("keydown", handler);
}, []);
const Exp: React.FC = () => {
return (
<Container>
<h2>
@ -169,7 +28,6 @@ const Exp: React.FC = () => {
Here are some places Ive worked at{" "}
<span
className={css`
/* font-size: 0.8rem; */
color: var(--text-subdued);
`}>
(recent first)
@ -179,17 +37,25 @@ const Exp: React.FC = () => {
<div
className={css`
width: 100%;
--item-padding: 1.2rem;
/* offset padding */
transform: translateX(calc(var(--item-padding) * -1));
display: grid;
grid-template-columns: repeat(auto-fit, 20rem);
gap: 1rem;
& > * {
padding-top: 4rem;
padding-top: 3rem;
}
`}>
{exp.map(unit => (
<ExpUnit key={unit.title} {...unit} />
{experience.map((unit, i) => (
<ExpUnit
key={i}
active={i === active}
{...unit}
onClick={() => setActive(active === i ? -1 : i)}
/>
))}
</div>
</Container>

Loading…
Cancel
Save