Skip to content

Commit e3cf339

Browse files
authored
Batched updates (#27)
* Copy changes and new web-component experiment * nav position updates; pretty text
1 parent 698ef30 commit e3cf339

24 files changed

Lines changed: 377 additions & 94 deletions

app/components/cube/cube.css.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export const cubeWrapper = style({
7474
});
7575

7676
export const cube = style({
77-
background: vars.colorSubtleOffWhite,
77+
background: vars.palette.subtleOffWhite,
7878
minHeight: "100lvh",
7979
padding: vars.bodyMargin,
8080
"@container": {

app/components/icons/icon.css.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const icon = style({
66
display: "inline-flex",
77
width: "1em",
88
height: "1em",
9-
color: vars.colorIce,
9+
color: vars.palette.ice,
1010
alignItems: "baseline",
1111
});
1212

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { globalStyle, style } from "@vanilla-extract/css";
2+
3+
import { dimensions, vars } from "~/css/theme.css";
4+
5+
export const nav = style({
6+
display: "flex",
7+
inset: "0 0 auto 0",
8+
background: vars.palette.stone,
9+
zIndex: 5,
10+
"@media": {
11+
[dimensions.breakHorizontalMin]: {
12+
position: "fixed",
13+
},
14+
print: {
15+
display: "none",
16+
},
17+
},
18+
});
19+
20+
export const list = style({
21+
display: "flex",
22+
gap: "1rem",
23+
});
24+
25+
export const popover = style({});
26+
27+
export const item = style({
28+
height: "2em",
29+
display: "grid",
30+
padding: "0 1em",
31+
alignItems: "center",
32+
color: "white",
33+
textDecoration: "none",
34+
verticalAlign: "text-top",
35+
selectors: {
36+
"&[popoverTarget]": {
37+
anchorName: "var(--anchorName)",
38+
font: "inherit",
39+
background: "none",
40+
border: "none",
41+
appearance: "none",
42+
margin: "0",
43+
// outline: "none",
44+
},
45+
"&:hover": {
46+
background: "rgb(100 100 100 / 0.8)",
47+
},
48+
"&:visited": {
49+
color: "white",
50+
},
51+
},
52+
});
53+
54+
export const sublist = style({
55+
background: vars.palette.stone,
56+
border: "none",
57+
top: "anchor(bottom)",
58+
left: "anchor(left)",
59+
positionAnchor: "var(--anchorName)",
60+
});
61+
62+
globalStyle(`${sublist}:popover-open`, {
63+
"@supports": {
64+
"not (top: anchor(bottom))": {
65+
display: "block",
66+
position: "fixed",
67+
whiteSpace: "nowrap",
68+
},
69+
},
70+
});
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { NavLink, useLocation } from "react-router";
2+
3+
import * as styles from "./site-nav.css";
4+
5+
export const routeTypes = {
6+
link: "link",
7+
group: "group",
8+
} as const;
9+
10+
interface Link {
11+
type: typeof routeTypes.link;
12+
to: string;
13+
label: string;
14+
}
15+
16+
interface Group {
17+
type: typeof routeTypes.group;
18+
label: string;
19+
links: Array<Link>;
20+
}
21+
22+
export interface Routes {
23+
routes: Array<Link | Group>;
24+
}
25+
26+
const createPopoverId = (label: string) => `popover_${label}`;
27+
28+
/**
29+
* Simple workaround for browsers that don't support anchor positioning
30+
* Relies on some fallback css styles
31+
*/
32+
const createAnchorPositionFallbackRef =
33+
(popoverTarget: string) => (anchor: HTMLButtonElement) => {
34+
if (anchor && window && !("anchorName" in document.documentElement.style)) {
35+
const setPosition = () => {
36+
const rect = anchor.getBoundingClientRect();
37+
const popover = document.getElementById(popoverTarget);
38+
39+
if (!popover) {
40+
throw new Error(`Popover with id ${popoverTarget} not found`);
41+
}
42+
43+
popover.style.setProperty("top", `${rect.height}px`);
44+
popover.style.setProperty("left", `${rect.left}px`);
45+
};
46+
47+
const controller = new AbortController();
48+
49+
const signal = controller.signal;
50+
51+
window.addEventListener("resize", setPosition, {
52+
signal,
53+
});
54+
55+
setPosition();
56+
57+
return function removeListener() {
58+
controller.abort();
59+
};
60+
}
61+
};
62+
63+
function Items({ routes }: Routes) {
64+
const pathname = useLocation().pathname;
65+
66+
return routes.map((route) =>
67+
route.type === "link" ? (
68+
<li key={route.label}>
69+
<NavLink
70+
className={styles.item}
71+
to={route.to}
72+
// disable view transitions on same-page navigation
73+
viewTransition={pathname !== route.to}
74+
>
75+
{route.label}
76+
</NavLink>
77+
</li>
78+
) : (
79+
<li key={route.label} className={styles.popover}>
80+
<style>
81+
{
82+
/* Technically I don’t think we don‘t need this popover
83+
anchorName because they are next to each other in the dom,
84+
but I will leave it here until I have a full understanding
85+
of best practice */
86+
`
87+
.${styles.popover} {
88+
--anchorName: ${route.label};
89+
}
90+
`
91+
}
92+
</style>
93+
<button
94+
type="button"
95+
className={styles.item}
96+
popoverTarget={createPopoverId(route.label)}
97+
ref={createAnchorPositionFallbackRef(createPopoverId(route.label))}
98+
>
99+
{route.label}
100+
</button>
101+
<ul
102+
className={styles.sublist}
103+
id={createPopoverId(route.label)}
104+
popover="auto"
105+
role="menu"
106+
>
107+
<Items routes={route.links} />
108+
</ul>
109+
</li>
110+
),
111+
);
112+
}
113+
114+
export function SiteNav({ routes }: Routes) {
115+
return (
116+
<nav className={styles.nav}>
117+
<ul className={styles.list}>
118+
<Items routes={routes} />
119+
</ul>
120+
</nav>
121+
);
122+
}

app/components/typography/typography.css.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { vars } from "~/css/theme.css";
55
export const paragraph = style({
66
fontFamily: "Calluna, serif",
77
fontSize: "105%",
8+
textWrap: "pretty",
89
});
910

1011
export const variants = styleVariants({
@@ -26,9 +27,9 @@ export const variants = styleVariants({
2627

2728
export const item = style({
2829
listStyle: "outside",
29-
color: vars.colorFire,
30+
color: vars.palette.fire,
3031
});
3132

3233
export const itemText = style({
33-
color: vars.colorBody,
34+
color: vars.palette.body,
3435
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { style } from "@vanilla-extract/css";
2+
3+
import { vars } from "~/css/theme.css";
4+
5+
export const button = style({
6+
appearance: "none",
7+
border: "none",
8+
padding: "0.5em 1em",
9+
backgroundColor: vars.palette.ice,
10+
color: "var(--mb-button-color, white)",
11+
fontSize: "1rem",
12+
borderRadius: "4px",
13+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { vars } from "~/css/theme.css";
2+
3+
const template = document.createElement("template");
4+
template.innerHTML = `
5+
<button class="button">
6+
<style>.button {
7+
background-color: ${vars.palette.ice};
8+
appearance: none;
9+
color: var(--button-fg, white)
10+
}</style>
11+
<slot></slot>
12+
</button>`;
13+
14+
customElements.define(
15+
"mb-button",
16+
class MyButton extends HTMLElement {
17+
constructor() {
18+
super();
19+
this.attachShadow({ mode: "open" });
20+
21+
this.shadowRoot?.appendChild(template.content.cloneNode(true));
22+
23+
this.shadowRoot
24+
?.querySelector("button")
25+
?.addEventListener("click", () => {
26+
this.dispatchEvent(new Event("button-clicksss"));
27+
});
28+
29+
this.shadowRoot?.addEventListener("slotchange", (event) =>
30+
console.log(event),
31+
);
32+
}
33+
},
34+
);

app/css/theme.css.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import { createTheme } from "@vanilla-extract/css";
22

33
const palette = {
4-
colorBody: "rgba(0, 0, 0, 0.9)",
5-
colorSubtleOffWhite: "#fafcfa",
6-
colorIce: "#007f98",
7-
colorFire: "#c25600",
8-
colorPrince: "#c53fa6",
4+
body: "rgba(0, 0, 0, 0.9)",
5+
subtleOffWhite: "#fafcfa",
6+
ice: "#007f98",
7+
fire: "#c25600",
8+
prince: "#c53fa6",
9+
stone: "rgb(55 55 55 / 0.8)",
910
} as const;
1011

1112
export const [themeClass, vars] = createTheme({
12-
...palette,
13+
palette,
1314
bodyMargin: "2em",
1415
headerSize: "1.6rem",
1516
headerMarginTop: "0.15rem",
@@ -18,7 +19,7 @@ export const [themeClass, vars] = createTheme({
1819
columnGap: "3em",
1920
fontInfo: '"Theinhardt", sans-serif',
2021
fontHeader: '"Theinhardt", sans-serif',
21-
sectionDecoration: `0.075rem solid ${palette.colorIce}`,
22+
sectionDecoration: `0.075rem solid ${palette.ice}`,
2223
rhythmVertical: "1.35rem",
2324
rhythmVerticalHalf: "0.675rem",
2425
rhythmVertical2: "2.7rem",

app/entry.client.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { startTransition, StrictMode } from "react";
2+
import { hydrateRoot } from "react-dom/client";
3+
import { HydratedRouter } from "react-router/dom";
4+
5+
import "~/components/web-components/mb-button";
6+
7+
startTransition(() => {
8+
hydrateRoot(
9+
document,
10+
<StrictMode>
11+
<HydratedRouter />
12+
</StrictMode>,
13+
);
14+
});

app/json/cv.json

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@
3333
{
3434
"name": "Shieldpay",
3535
"position": "Lead UI Engineer",
36-
"url": "www.shieldpay.com",
37-
"startDate": "2021-02-20",
36+
"url": "https://www.shieldpay.com",
37+
"startDate": "2021-01-25",
38+
"endDate": "2025-12-15",
3839
"summary": "Hands-on code-focused lead for the UI stack managing and mentoring in a “chapter” of 4 inside larger cross-functional team. Set of React Router 7 (Remix) SSR apps using accessible (WCAG 2.1 AAA) component library deployed to AWS infrastructure with Github actions and comprehensive automated testing.",
3940
"highlights": [
4041
"Architected and built React Router 7 SSR apps using shared “Optimus” platform hosted on AWS Lambda NodeJS calling microservices via REST API.",
@@ -51,9 +52,9 @@
5152
{
5253
"name": "Pure360",
5354
"position": "Software developer",
54-
"url": "pure360.com",
55+
"url": "https://pure360.com",
5556
"startDate": "2018-11-12",
56-
"endDate": "2021-02-15",
57+
"endDate": "2021-01-22",
5758
"summary": "React/Typescript SPA with REST for handling customer creation and scheduling of multi-channel marketing campaigns. Redux for data fetching and state management, charts for data visualisation. Agile/Scrum environment.",
5859
"highlights": [
5960
"Building a graph-style decision tree with a clear simple graphic interface for multiple journeys of customer engagement",
@@ -64,7 +65,7 @@
6465
{
6566
"name": "Simpplr",
6667
"position": "JavaScript engineer",
67-
"url": "simpplr.com",
68+
"url": "https://simpplr.com",
6869
"startDate": "2018-05-01",
6970
"endDate": "2018-11-10",
7071
"summary": "Building features on two React SPAs in the corporate intranet sector. An established product using React and Redux and a greenfield product using React with Apollo/GraphQL.",
@@ -76,7 +77,7 @@
7677
{
7778
"name": "Filmstro",
7879
"position": "Software developer",
79-
"url": "filmstro.com",
80+
"url": "https://filmstro.com",
8081
"startDate": "2017-07-21",
8182
"endDate": "2018-03-23",
8283
"summary": "Building cross-platform adaptive-music desktop app Filmstro Pro: A React/Redux app inside Electron using a lower-level audio binary.",
@@ -88,7 +89,7 @@
8889
{
8990
"name": "New Era Education Ltd.",
9091
"position": "Front-end developer",
91-
"url": "neweraed.co.uk",
92+
"url": "https://www.neweratech.com/uk/education",
9293
"startDate": "2013-07-07",
9394
"endDate": "2017-07-20",
9495
"summary": "Developed websites and e-learning courses for the education sector.",
@@ -100,7 +101,7 @@
100101
{
101102
"name": "Freelance",
102103
"position": "Developer and designer",
103-
"url": "matthewbalaam.co.uk",
104+
"url": "https://matthewbalaam.co.uk",
104105
"startDate": "2009-10-10",
105106
"endDate": "2013-07-07",
106107
"summary": "Providing web design and development services to a variety of clients.",

0 commit comments

Comments
 (0)