Skip to content

Commit

Permalink
Redesign cover page and improve dock animations
Browse files Browse the repository at this point in the history
  • Loading branch information
adamgraham committed Oct 19, 2024
1 parent 6088931 commit 95c963c
Show file tree
Hide file tree
Showing 21 changed files with 471 additions and 136 deletions.
2 changes: 1 addition & 1 deletion css-styles
24 changes: 12 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
"version": "2.0.0",
"homepage": "https://adamgraham.github.io",
"private": true,
"scripts": {
"build": "gatsby build",
"clean": "gatsby clean",
"deploy": "gh-pages -d public -b public -m \"Publish build to GitHub pages\"",
"develop": "gatsby develop",
"precommit": "eslint src --fix --quiet",
"predeploy": "gatsby build",
"serve": "gatsby serve",
"start": "gatsby develop",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@zigurous/css-styles": "^3.0.0",
"@zigurous/react-components": "^3.0.0",
Expand Down Expand Up @@ -31,7 +42,7 @@
"@types/node": "^20.11.0",
"@types/react": "^18.1.0",
"@types/react-dom": "^18.1.0",
"@types/react-helmet": "^6.1.11",
"@types/react-helmet": "^6.1.0",
"eslint": "^8.18.0",
"eslint-config-prettier": "^8.5.0",
"eslint-config-standard": "^17.0.0",
Expand All @@ -46,16 +57,5 @@
"resolutions": {
"react": "^18.1.0",
"react-dom": "^18.1.0"
},
"scripts": {
"build": "gatsby build",
"clean": "gatsby clean",
"deploy": "gh-pages -d public -b public -m \"Publish build to GitHub pages\"",
"develop": "gatsby develop",
"precommit": "eslint src --fix --quiet",
"predeploy": "gatsby build",
"serve": "gatsby serve",
"start": "gatsby develop",
"typecheck": "tsc --noEmit"
}
}
2 changes: 1 addition & 1 deletion react-components
148 changes: 85 additions & 63 deletions src/components/Dock.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import '../styles/dock.css';
import { Icon, Link, SocialIcon, Theme } from '@zigurous/react-components'; // prettier-ignore
import { Link as GatsbyLink } from 'gatsby';
import React from 'react';
import { Theme, useMediaQuery } from '@zigurous/react-components'; // prettier-ignore
import React, { useEffect, useRef, useState } from 'react';
import DockItem from './DockItem';
import { dockLinks, socialLinks } from '../links';
import type { LinkType } from '../types';

Expand All @@ -16,87 +16,109 @@ export default function Dock({
toggleTheme,
secondaryLinks,
}: DockProps) {
const ref = useRef<HTMLDivElement>(null);
const canHover = useMediaQuery('(hover: hover)');
const [mouseState, setMouseState] = useState({ x: 0, y: 0, entered: false });

useEffect(() => {
if (!ref.current) return;

const mouseleave = (e: MouseEvent) => {
setMouseState({ x: e.clientX, y: e.clientY, entered: false });
};
const mousemove = (e: MouseEvent) => {
setMouseState({ x: e.clientX, y: e.clientY, entered: true });
};

if (canHover) {
ref.current.addEventListener('mousemove', mousemove);
ref.current.addEventListener('mouseleave', mouseleave);
}

return () => {
if (ref.current) {
ref.current.removeEventListener('mousemove', mousemove);
ref.current.removeEventListener('mouseleave', mouseleave);
}
};
}, [ref, canHover]);

return (
<div className="dock">
<div className="dock" ref={ref}>
<div className="dock__container" id="primary">
<div className="dock__section" id="navigation">
{dockLinks.map(link => (
<DockItem link={link} key={link.id} />
<DockItem key={link.id} link={link} mouseState={mouseState} />
))}
</div>
<div className="dock__section" id="socials">
{socialLinks.map(link => (
<DockItem link={link} key={link.id} external />
<DockItem
key={link.id}
link={link}
mouseState={mouseState}
external
/>
))}
</div>
<div className="dock__section" id="gallery">
<div className="dock__item" id="Previous">
<button
onClick={() => {
if (!document) return;
const e = new Event('previous_slide');
document.dispatchEvent(e);
}}
>
<Icon name="chevron_left" />
</button>
<div className="dock__tooltip">Previous</div>
</div>
<div className="dock__item" id="Next">
<button
onClick={() => {
if (!document) return;
const e = new Event('next_slide');
document.dispatchEvent(e);
}}
>
<Icon name="chevron_right" />
</button>
<div className="dock__tooltip">Next</div>
</div>
<DockItem
asButton
link={{
to: '',
id: 'previous',
name: 'Previous',
icon: 'chevron_left',
}}
mouseState={mouseState}
onClick={() => {
if (!document) return;
const e = new Event('previous_slide');
document.dispatchEvent(e);
}}
/>
<DockItem
asButton
link={{
to: '',
id: 'next',
name: 'Next',
icon: 'chevron_right',
}}
mouseState={mouseState}
onClick={() => {
if (!document) return;
const e = new Event('next_slide');
document.dispatchEvent(e);
}}
/>
</div>
<div className="dock__section" id="theme">
<div className="dock__item" id="Theme">
<button onClick={toggleTheme}>
<Icon name={theme === 'dark' ? 'light_mode' : 'dark_mode'} />
</button>
<div className="dock__tooltip">
{theme === 'dark' ? 'Light Mode' : 'Dark Mode'}
</div>
</div>
<DockItem
asButton
link={{
to: '',
id: 'theme',
name: theme === 'dark' ? 'Light Mode' : 'Dark Mode',
icon: theme === 'dark' ? 'light_mode' : 'dark_mode',
}}
mouseState={mouseState}
onClick={toggleTheme}
/>
</div>
</div>
{secondaryLinks && (
<div className="dock__container" id="secondary">
{secondaryLinks.map(link => (
<DockItem link={link} key={link.id || link.name} external />
<DockItem
key={link.id || link.name}
link={link}
mouseState={mouseState}
external
/>
))}
</div>
)}
</div>
);
}

export interface DockItemProps {
link: LinkType;
external?: boolean;
}

export function DockItem({ link, external = false }: DockItemProps) {
return (
<div className="dock__item" id={link.name}>
<Link
as={external ? 'a' : GatsbyLink}
external={external}
to={link.to}
unstyled
>
{link.icon && <Icon name={link.icon} />}
{link.socialIcon && (
<SocialIcon icon={link.socialIcon} innerPadding={0} size={18} />
)}
</Link>
<div className="dock__tooltip">{link.name}</div>
</div>
);
}
106 changes: 106 additions & 0 deletions src/components/DockItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { distance, Icon, inverseLerp, lerp, Link, SocialIcon, useSmoothDamp } from '@zigurous/react-components'; // prettier-ignore
import { Link as GatsbyLink } from 'gatsby';
import React, { useEffect, useRef } from 'react';
import type { LinkType } from '../types';

const settings = {
minSize: 44,
maxSize: 64,
minFontSize: 20,
maxFontSize: 30,
distance: 150,
smoothTime: 50,
};

export interface DockItemProps {
asButton?: boolean;
external?: boolean;
link: LinkType;
mouseState: { x: number; y: number; entered: boolean };
onClick?: () => void;
}

export default function DockItem({
asButton = false,
external = false,
link,
mouseState,
onClick,
}: DockItemProps) {
const ref = useRef<HTMLDivElement>(null);
const targetSizeRef = useRef<number>(settings.minSize);
const size = useSmoothDamp(
settings.minSize,
targetSizeRef,
settings.smoothTime,
);
const iconSize = size * 0.45;

useEffect(() => {
if (!ref.current) return;
if (mouseState.entered) {
targetSizeRef.current = getSize(ref.current, mouseState.x, mouseState.y);
} else {
targetSizeRef.current = settings.minSize;
}
}, [ref, mouseState]);

return (
<div
className="dock__item"
id={link.id}
ref={ref}
style={{
width: `${size}px`,
height: `${size}px`,
fontSize: `${iconSize}px`,
transform: `translateY(${-(size - settings.minSize) / 2}px)`,
}}
>
{asButton ? (
<button onClick={onClick}>
{link.icon && <Icon name={link.icon} size="inherit" />}
{link.socialIcon && (
<SocialIcon
icon={link.socialIcon}
innerPadding={0}
size={iconSize - 2}
/>
)}
</button>
) : (
<Link
as={external ? 'a' : GatsbyLink}
external={external}
to={link.to}
unstyled
>
{link.icon && <Icon name={link.icon} size="inherit" />}
{link.socialIcon && (
<SocialIcon
icon={link.socialIcon}
innerPadding={0}
size={iconSize - 2}
/>
)}
</Link>
)}
<div className="dock__tooltip">{link.name}</div>
</div>
);
}

function getSize(el: HTMLDivElement, mouseX: number, mouseY: number) {
const rect = el.getBoundingClientRect();
const d = distance(
mouseX,
mouseY,
rect.left + rect.width / 2,
rect.top + rect.height / 2,
);
return lerp(
settings.minSize,
settings.maxSize,
inverseLerp(settings.distance, 0, d),
);
}
26 changes: 26 additions & 0 deletions src/components/Grid3D.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import '../styles/grid-3d.css';
import React from 'react';

export interface Grid3DProps {
width?: number;
height?: number;
}

export default function Grid3D({ width = 30, height = 30 }: Grid3DProps) {
const cells = [];
for (let i = 0; i < width * height; i++) {
cells.push(<div className="grid-3d__cell" key={i} />);
}
return (
<div
aria-hidden="true"
className="grid-3d"
style={{
gridTemplateColumns: `repeat(${width}, 1fr)`,
gridTemplateRows: `repeat(${height}, 1fr)`,
}}
>
{cells.map(cell => cell)}
</div>
);
}
7 changes: 5 additions & 2 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import MenuGallery from './MenuGallery';
import { headerLinks } from '../links';

export interface HeaderProps {
location?: Location;
location?: Location | null;
pageTitle?: string;
}

export default function Header({ location, pageTitle }: HeaderProps) {
export default function Header({
location = typeof window !== 'undefined' ? window.location : null,
pageTitle,
}: HeaderProps) {
const [isMenuOpen, setIsMenuOpen] = useState(false);
return (
<>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface PageProps {
hideDock?: boolean;
hideHeader?: boolean;
id?: string;
location?: Location;
location?: Location | null;
metadata?: MetadataProps;
title?: string;
}
Expand All @@ -25,7 +25,7 @@ export default function Page({
hideDock = false,
hideHeader = false,
id,
location,
location = typeof window !== 'undefined' ? window.location : null,
metadata,
title,
}: PageProps) {
Expand Down
Loading

0 comments on commit 95c963c

Please sign in to comment.