Browse Source

feat: experience tags

pull/2/head
Muthu Kumar 5 months ago
parent
commit
5aa0ac4e6e
Failed to extract signature
  1. 2
      package.json
  2. 3912
      pnpm-lock.yaml
  3. 3
      src/assets/cross.svg
  4. 88
      src/components/Exp/Tags.tsx
  5. 1
      src/components/constants.ts
  6. 38
      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, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "vite", "start": "vite --host",
"build": "pnpm blog && tsc && vite build", "build": "pnpm blog && tsc && vite build",
"serve": "vite preview", "serve": "vite preview",
"blog": "node scripts/blog.js" "blog": "node scripts/blog.js"

3912
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";

38
src/pages/main/Exp.tsx

@ -1,11 +1,15 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { css } from "@emotion/css"; import { css, cx } from "@emotion/css";
import Container from "../../components/Container"; import Container from "../../components/Container";
import { ExpUnit } from "../../components/Exp/Unit"; import { ExpUnit } from "../../components/Exp/Unit";
import { age, experience } from "./data/experience"; 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 Exp: React.FC = () => {
const [active, setActive] = useState(-1); const [active, setActive] = useState(-1);
const tags = useSet<string>();
useEffect(() => { useEffect(() => {
const handler = (e: KeyboardEvent) => { const handler = (e: KeyboardEvent) => {
@ -34,8 +38,15 @@ const Exp: React.FC = () => {
</span> </span>
: :
</p> </p>
<section>
<Tags
tags={["Programming", "Design", "Architecture", "Writing"]}
selected={tags}
/>
</section>
<div <div
className={css` className={cx(
css`
width: 100%; width: 100%;
--item-padding: 1.2rem; --item-padding: 1.2rem;
@ -43,16 +54,33 @@ const Exp: React.FC = () => {
grid-template-columns: repeat(auto-fit, 20rem); grid-template-columns: repeat(auto-fit, 20rem);
gap: 1rem; gap: 1rem;
@media screen and (min-width: ${offscreenWidth}) {
transform: translateX(0);
}
& > * { & > * {
padding-top: 3rem; padding-top: 3rem;
} }
`}> `,
{experience.map((unit, i) => ( )}>
{experience
.filter(unit => !tags.size || unit.tags.some(tag => tags.has(tag)))
.map((unit, i) => (
<ExpUnit <ExpUnit
key={i} key={i}
active={i === active} active={i === active}
{...unit} {...unit}
onClick={() => setActive(active === i ? -1 : i)} 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> </div>

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

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