Browse Source

feat: add filter and unit-based experience routing

pull/2/head
Muthu Kumar 5 months ago
parent
commit
761ab66d98
Failed to extract signature
  1. 10
      src/components/Exp/Tags.tsx
  2. 26
      src/index.tsx
  3. 31
      src/pages/main/Exp.tsx
  4. 11
      src/pages/main/data/experience.tsx
  5. 57
      src/util/useSearchParams.ts
  6. 41
      src/util/useSet.ts

10
src/components/Exp/Tags.tsx

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { css, cx } from "@emotion/css"; import { css, cx } from "@emotion/css";
import { HookSet } from "../../util/useSet"; import { HookSet } from "../../util/useSearchParams";
import { ReactComponent as Cross } from "../../assets/cross.svg"; import { ReactComponent as Cross } from "../../assets/cross.svg";
type Tags = HookSet<string>; type Tags = HookSet<string>;
@ -32,10 +32,10 @@ export const Tag = (props: { tag: string; selected: Tags }) => {
const { selected } = props; const { selected } = props;
const active = selected.has(props.tag); const active = selected.has(props.tag);
const select = () => const select = () => {
selected.has(props.tag) if (selected.has(props.tag)) selected.remove(props.tag);
? selected.remove(props.tag) else selected.add(props.tag);
: selected.add(props.tag); };
return ( return (
<button className={cx(tag, { active })} onClick={select}> <button className={cx(tag, { active })} onClick={select}>

26
src/index.tsx

@ -25,23 +25,15 @@ function App() {
return null; return null;
} }
switch (normalised) { if (normalised === "/") return <Home />;
case "/": if (normalised === "/experience") return <Exp />;
return <Home />; if (normalised.startsWith("/experience/")) return <Exp />;
case "/experience": if (normalised === "/projects") return <Projects />;
return <Exp />; if (normalised === "/contact") return <Contact />;
case "/projects": if (normalised === "/live") return <Live />;
return <Projects />; if (normalised === "/blog") return <BlogHome />;
case "/contact": if (location.startsWith("/blog")) return <BlogPost />;
return <Contact />; return <NotFound />;
case "/live":
return <Live />;
case "/blog":
// return <BlogHome />;
default:
// if (location.startsWith("/blog")) return <BlogPost />;
return <NotFound />;
}
} }
createRoot(document.getElementById("root")!).render( createRoot(document.getElementById("root")!).render(

31
src/pages/main/Exp.tsx

@ -5,15 +5,25 @@ import { ExpUnit } from "../../components/Exp/Unit";
import { age, experience } from "./data/experience"; import { age, experience } from "./data/experience";
import { offscreenWidth } from "../../components/constants"; import { offscreenWidth } from "../../components/constants";
import { Tags } from "../../components/Exp/Tags"; import { Tags } from "../../components/Exp/Tags";
import useSet from "../../util/useSet"; import useSearchParams from "../../util/useSearchParams";
import useLocation from "wouter/use-location";
const exp_route = /^\/experience\/?[^\/]*$/;
const Exp: React.FC = () => { const Exp: React.FC = () => {
const [active, setActive] = useState(-1); const [location, navigate] = useLocation();
const tags = useSet<string>(); const tags = useSearchParams("tag");
if (!exp_route.test(location)) {
navigate("/experience", { replace: true });
return null;
}
const slug = location.replace("/experience/", "").replace("/", "");
useEffect(() => { useEffect(() => {
const handler = (e: KeyboardEvent) => { const handler = (e: KeyboardEvent) => {
if (e.key === "Escape") setActive(-1); if (slug) if (e.key === "Escape") window.history.back();
}; };
window.addEventListener("keydown", handler); window.addEventListener("keydown", handler);
@ -68,18 +78,11 @@ const Exp: React.FC = () => {
.map((unit, i) => ( .map((unit, i) => (
<ExpUnit <ExpUnit
key={i} key={i}
active={i === active} active={slug === unit.slug}
{...unit} {...unit}
onClick={() => { onClick={() => {
if (active === i) return setActive(-1); if (slug === unit.slug) return navigate("/experience");
navigate(`/experience/${unit.slug}`);
setActive(i);
setTimeout(() => {
const active = window.document.getElementById("active-story");
if (!active)
return console.error("Unexpected missing #active-story");
}, 300);
}} }}
/> />
))} ))}

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

@ -14,6 +14,7 @@ const sampleText = (
export const experience = [ export const experience = [
{ {
title: "The Feathers", title: "The Feathers",
slug: "thefeathers",
location: "Chennai (formerly Tirunelveli, Ooty)", location: "Chennai (formerly Tirunelveli, Ooty)",
position: "Founder (Creative Collective)", position: "Founder (Creative Collective)",
year: "2011-19", year: "2011-19",
@ -43,6 +44,7 @@ export const experience = [
}, },
{ {
title: "StudioFlicks", title: "StudioFlicks",
slug: "studioflicks",
location: "Remote (Coimbatore)", location: "Remote (Coimbatore)",
position: "Co-founder & Creative Head", position: "Co-founder & Creative Head",
year: "2013-15", year: "2013-15",
@ -52,6 +54,7 @@ export const experience = [
}, },
{ {
title: "Vinzas", title: "Vinzas",
slug: "vinzas",
location: "Chennai", location: "Chennai",
position: "Architectural Intern", position: "Architectural Intern",
year: "2014", year: "2014",
@ -61,6 +64,7 @@ export const experience = [
}, },
{ {
title: "Blue Cube", title: "Blue Cube",
slug: "bluecube",
location: "Chennai", location: "Chennai",
position: "Architectural Intern", position: "Architectural Intern",
year: "2015", year: "2015",
@ -70,6 +74,7 @@ export const experience = [
}, },
{ {
title: "OutFocus Magazine", title: "OutFocus Magazine",
slug: "outfocus",
location: "Ooty", location: "Ooty",
position: "Editor / Developer", position: "Editor / Developer",
year: "2014-17", year: "2014-17",
@ -79,6 +84,7 @@ export const experience = [
}, },
{ {
title: "Zoho", title: "Zoho",
slug: "zoho",
location: "Chennai", location: "Chennai",
position: "Technical Content Writer", position: "Technical Content Writer",
year: "2017", year: "2017",
@ -88,6 +94,7 @@ export const experience = [
}, },
{ {
title: "Manoj Exports", title: "Manoj Exports",
slug: "manoj",
location: "Chennai", location: "Chennai",
position: "Designer & Web Dev", position: "Designer & Web Dev",
year: "2017", year: "2017",
@ -97,6 +104,7 @@ export const experience = [
}, },
{ {
title: "Klenty", title: "Klenty",
slug: "klenty",
location: "Chennai", location: "Chennai",
position: "Full Stack Developer", position: "Full Stack Developer",
year: "2018", year: "2018",
@ -106,6 +114,7 @@ export const experience = [
}, },
{ {
title: "Hugo's Way", title: "Hugo's Way",
slug: "hugosway",
location: "Remote (Dublin)", location: "Remote (Dublin)",
position: "Full Stack Developer", position: "Full Stack Developer",
year: "2018-19", year: "2018-19",
@ -115,6 +124,7 @@ export const experience = [
}, },
{ {
title: "Navana Tech", title: "Navana Tech",
slug: "navana",
location: "Remote (Mumbai)", location: "Remote (Mumbai)",
position: "Lead Web Dev & Architect", position: "Lead Web Dev & Architect",
year: "2021-22", year: "2021-22",
@ -124,6 +134,7 @@ export const experience = [
}, },
{ {
title: "Feathers Studio", title: "Feathers Studio",
slug: "feathers-studio",
location: "Chennai", location: "Chennai",
position: "Chief Maker", position: "Chief Maker",
year: "2019-present", year: "2019-present",

57
src/util/useSearchParams.ts

@ -0,0 +1,57 @@
import { useState } from "react";
import useLocation from "wouter/use-location";
export type HookSet<T> = {
set: Set<T>;
size: number;
add: (value: T) => void;
remove: (value: T) => void;
clear: () => void;
has: (value: T) => boolean;
};
const flatten = (set: Set<string>, key: string) =>
Array.from(set)
.map(param => `${key}=${encodeURIComponent(param)}`)
.join("&");
export function useSearchParams(key: string): HookSet<string> {
const [, navigate] = useLocation();
const [params, setParams] = useState(
() => new Set(new URLSearchParams(window.location.search).getAll(key)),
);
const add = (value: string) => {
setParams(prevSet => {
const newSet = new Set(prevSet);
newSet.add(value);
navigate("?" + flatten(newSet, key), { replace: true });
return newSet;
});
};
const remove = (value: string) => {
setParams(prevSet => {
const newSet = new Set(prevSet);
newSet.delete(value);
navigate("?" + flatten(newSet, key), { replace: true });
return newSet;
});
};
const clear = () => {
setParams(new Set());
navigate("?", { replace: true });
};
return {
set: params,
size: params.size,
add,
remove,
clear,
has: (value: string) => params.has(value),
};
}
export default useSearchParams;

41
src/util/useSet.ts

@ -1,41 +0,0 @@
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