Browse Source

feat: experience tags

pull/2/head
Muthu Kumar 3 months ago
parent
commit
5aa0ac4e6e
Failed to extract signature
  1. 2
      package.json
  2. 3950
      pnpm-lock.yaml
  3. 3
      src/assets/cross.svg
  4. 88
      src/components/Exp/Tags.tsx
  5. 1
      src/components/constants.ts
  6. 70
      src/pages/main/Exp.tsx
  7. 48
      src/pages/main/data/experience.tsx
  8. 41
      src/util/useSet.ts

2
package.json

@ -4,7 +4,7 @@
"private": true,
"type": "module",
"scripts": {
"start": "vite",
"start": "vite --host",
"build": "pnpm blog && tsc && vite build",
"serve": "vite preview",
"blog": "node scripts/blog.js"

3950
pnpm-lock.yaml

File diff suppressed because it is too large

3
src/assets/cross.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none">
<path d="M18 6L12 12M12 12L6 18M12 12L18 18M12 12L6 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>

After

Width:  |  Height:  |  Size: 252 B

88
src/components/Exp/Tags.tsx

@ -0,0 +1,88 @@
import React from "react";
import { css, cx } from "@emotion/css";
import { HookSet } from "../../util/useSet";
import { ReactComponent as Cross } from "../../assets/cross.svg";
type Tags = HookSet<string>;
const tag = css`
border: none;
cursor: pointer;
border-radius: 0.5rem;
padding: 0.5rem 0.8rem;
font-size: 0.85rem;
background: var(--card-bg);
color: var(--text-colour);
display: flex;
align-items: flex-end;
transition: all 100ms ease-in-out;
&:hover {
background: var(--card-tags-hover);
}
&.active {
background: var(--card-tags);
}
`;
export const Tag = (props: { tag: string; selected: Tags }) => {
const { selected } = props;
const active = selected.has(props.tag);
const select = () =>
selected.has(props.tag)
? selected.remove(props.tag)
: selected.add(props.tag);
return (
<button className={cx(tag, { active })} onClick={select}>
{props.tag}
<span
className={cx(
css`
width: 0;
opacity: 0;
margin-inline-start: 0;
transition: all 100ms ease-in-out;
overflow: hidden;
&.active {
opacity: 1;
width: 0.85rem;
margin-inline-start: 0.5rem;
}
`,
{ active },
)}>
<Cross
className={css`
display: ${active ? "block" : "none"};
height: 0.85rem;
width: 0.85rem;
fill: var(--text-colour);
`}
/>
</span>
</button>
);
};
export const Tags = (props: { tags: string[]; selected: Tags }) => {
const { tags, selected } = props;
return (
<div
className={css`
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
`}>
{tags.map(tag => (
<Tag tag={tag} selected={selected} />
))}
</div>
);
};

1
src/components/constants.ts

@ -0,0 +1 @@
export const offscreenWidth = "85rem";

70
src/pages/main/Exp.tsx

@ -1,11 +1,15 @@
import React, { useEffect, useState } from "react";
import { css } from "@emotion/css";
import { css, cx } from "@emotion/css";
import Container from "../../components/Container";
import { ExpUnit } from "../../components/Exp/Unit";
import { age, experience } from "./data/experience";
import { offscreenWidth } from "../../components/constants";
import { Tags } from "../../components/Exp/Tags";
import useSet from "../../util/useSet";
const Exp: React.FC = () => {
const [active, setActive] = useState(-1);
const tags = useSet<string>();
useEffect(() => {
const handler = (e: KeyboardEvent) => {
@ -34,27 +38,51 @@ const Exp: React.FC = () => {
</span>
:
</p>
<section>
<Tags
tags={["Programming", "Design", "Architecture", "Writing"]}
selected={tags}
/>
</section>
<div
className={css`
width: 100%;
--item-padding: 1.2rem;
display: grid;
grid-template-columns: repeat(auto-fit, 20rem);
gap: 1rem;
& > * {
padding-top: 3rem;
}
`}>
{experience.map((unit, i) => (
<ExpUnit
key={i}
active={i === active}
{...unit}
onClick={() => setActive(active === i ? -1 : i)}
/>
))}
className={cx(
css`
width: 100%;
--item-padding: 1.2rem;
display: grid;
grid-template-columns: repeat(auto-fit, 20rem);
gap: 1rem;
@media screen and (min-width: ${offscreenWidth}) {
transform: translateX(0);
}
& > * {
padding-top: 3rem;
}
`,
)}>
{experience
.filter(unit => !tags.size || unit.tags.some(tag => tags.has(tag)))
.map((unit, i) => (
<ExpUnit
key={i}
active={i === active}
{...unit}
onClick={() => {
if (active === i) return setActive(-1);
setActive(i);
setTimeout(() => {
const active = window.document.getElementById("active-story");
if (!active)
return console.error("Unexpected missing #active-story");
}, 300);
}}
/>
))}
</div>
</Container>
);

48
src/pages/main/data/experience.tsx

@ -1,10 +1,15 @@
import React from "react";
const sampleText = `
This is gonna have some text, probably a story about what I did here.
Apollonius of Perga rich in heavy atoms great turbulent clouds citizens of distant epochs the only home we've ever known hydrogen atoms?
Bits of moving fluff two ghostly white figures in coveralls and helmets are softly dancing a still more glorious dawn awaits hearts of the stars extraordinary claims require extraordinary evidence a very small stage in a vast cosmic arena.
`;
const sampleText = (
<div>
This is gonna have some text, probably a story about what I did here.
Apollonius of Perga rich in heavy atoms great turbulent clouds citizens of
distant epochs the only home we've ever known hydrogen atoms? Bits of moving
fluff two ghostly white figures in coveralls and helmets are softly dancing
a still more glorious dawn awaits hearts of the stars extraordinary claims
require extraordinary evidence a very small stage in a vast cosmic arena.
</div>
);
export const experience = [
{
@ -12,26 +17,27 @@ export const experience = [
location: "Chennai (formerly Tirunelveli, Ooty)",
position: "Founder (Creative Collective)",
year: "2011-19",
tags: ["Programming", "Film", "Photography", "Design", "Writing"],
description: (
<>
<li>
<div>
<p>
The Feathers was a creative collective, comprising mostly of students
and amateur artists and entertainers pursuing various creative
endeavours.
</li>
<li>
</p>
<p>
This varied from photography, typography, graphic design, short films,
music, to ad films and event coverage.
</li>
<li>
</p>
<p>
It was an early attempt at creating something out of pure passion, and
gave rise to such projects as StudioFlicks, OutFocus, and the like.
</li>
<li>
</p>
<p>
It gives me immense pride and joy to see former members go on to build
amazing careers.
</li>
</>
amazing careers and pursue interests.
</p>
</div>
),
logo: "TheFeathers.png",
},
@ -40,6 +46,7 @@ export const experience = [
location: "Remote (Coimbatore)",
position: "Co-founder & Creative Head",
year: "2013-15",
tags: ["Design", "Writing"],
description: sampleText,
logo: "StudioFlicks.png",
},
@ -48,6 +55,7 @@ export const experience = [
location: "Chennai",
position: "Architectural Intern",
year: "2014",
tags: ["Architecture"],
description: sampleText,
logo: "Vinzas.png",
},
@ -56,6 +64,7 @@ export const experience = [
location: "Chennai",
position: "Architectural Intern",
year: "2015",
tags: ["Architecture"],
description: sampleText,
logo: "BlueCube.png",
},
@ -64,6 +73,7 @@ export const experience = [
location: "Ooty",
position: "Editor / Developer",
year: "2014-17",
tags: ["Design", "Programming", "Writing"],
description: sampleText,
logo: "OutFocus.png",
},
@ -72,6 +82,7 @@ export const experience = [
location: "Chennai",
position: "Technical Content Writer",
year: "2017",
tags: ["Writing"],
description: sampleText,
logo: "Zoho.png",
},
@ -80,6 +91,7 @@ export const experience = [
location: "Chennai",
position: "Designer & Web Dev",
year: "2017",
tags: ["Design", "Programming"],
description: sampleText,
logo: "ManojExports.png",
},
@ -88,6 +100,7 @@ export const experience = [
location: "Chennai",
position: "Full Stack Developer",
year: "2018",
tags: ["Programming"],
description: sampleText,
logo: "Klenty.png",
},
@ -96,6 +109,7 @@ export const experience = [
location: "Remote (Dublin)",
position: "Full Stack Developer",
year: "2018-19",
tags: ["Programming"],
description: sampleText,
logo: "Hugosway.png",
},
@ -104,6 +118,7 @@ export const experience = [
location: "Remote (Mumbai)",
position: "Lead Web Dev & Architect",
year: "2021-22",
tags: ["Programming", "Design"],
description: sampleText,
logo: "NavanaTech.png",
},
@ -112,6 +127,7 @@ export const experience = [
location: "Chennai",
position: "Chief Maker",
year: "2019-present",
tags: ["Programming", "Design", "Writing"],
description: sampleText,
logo: "FeathersStudio.png",
},

41
src/util/useSet.ts

@ -0,0 +1,41 @@
import { useState } from "react";
export type HookSet<T> = {
size: number;
set: Set<T>;
add: (value: T) => void;
remove: (value: T) => void;
clear: () => void;
has: (value: T) => boolean;
};
function useSet<T>(initialValues: T[] = []): HookSet<T> {
const [set, setSet] = useState(new Set(initialValues));
const add = (value: T) => {
setSet(prevSet => new Set([...prevSet, value]));
};
const remove = (value: T) => {
setSet(prevSet => {
const newSet = new Set(prevSet);
newSet.delete(value);
return newSet;
});
};
const clear = () => {
setSet(new Set());
};
return {
size: set.size,
set,
add,
remove,
clear,
has: (value: T) => set.has(value),
};
}
export default useSet;
Loading…
Cancel
Save