diff --git a/css-styles b/css-styles index 66ce2d4..a3f2b95 160000 --- a/css-styles +++ b/css-styles @@ -1 +1 @@ -Subproject commit 66ce2d43c99f01403c14dfb3ecdffc5c44779a7f +Subproject commit a3f2b95d422aad8a921c550b0a125f0c6fb8831a diff --git a/package.json b/package.json index fd24821..dd23405 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", @@ -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" } } diff --git a/react-components b/react-components index f78a861..84d20f9 160000 --- a/react-components +++ b/react-components @@ -1 +1 @@ -Subproject commit f78a8612a6e4d6b0076240c654d3858d809d9ab2 +Subproject commit 84d20f950ca45cad36567fca72dacb9f25bc662e diff --git a/src/components/Dock.tsx b/src/components/Dock.tsx index 799db78..7d75e9c 100644 --- a/src/components/Dock.tsx +++ b/src/components/Dock.tsx @@ -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'; @@ -16,87 +16,109 @@ export default function Dock({ toggleTheme, secondaryLinks, }: DockProps) { + const ref = useRef(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 ( -
+
{socialLinks.map(link => ( - + ))}
-
- -
- {theme === 'dark' ? 'Light Mode' : 'Dark Mode'} -
-
+
{secondaryLinks && (
{secondaryLinks.map(link => ( - + ))}
)}
); } - -export interface DockItemProps { - link: LinkType; - external?: boolean; -} - -export function DockItem({ link, external = false }: DockItemProps) { - return ( -
- - {link.icon && } - {link.socialIcon && ( - - )} - -
{link.name}
-
- ); -} diff --git a/src/components/DockItem.tsx b/src/components/DockItem.tsx new file mode 100644 index 0000000..b12767f --- /dev/null +++ b/src/components/DockItem.tsx @@ -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(null); + const targetSizeRef = useRef(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 ( +
+ {asButton ? ( + + ) : ( + + {link.icon && } + {link.socialIcon && ( + + )} + + )} +
{link.name}
+
+ ); +} + +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), + ); +} diff --git a/src/components/Grid3D.tsx b/src/components/Grid3D.tsx new file mode 100644 index 0000000..2c801fa --- /dev/null +++ b/src/components/Grid3D.tsx @@ -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(
); + } + return ( + + ); +} diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 2aa9b07..4c4910c 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -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 ( <> diff --git a/src/components/Page.tsx b/src/components/Page.tsx index 7866fa6..bcc0bde 100644 --- a/src/components/Page.tsx +++ b/src/components/Page.tsx @@ -13,7 +13,7 @@ export interface PageProps { hideDock?: boolean; hideHeader?: boolean; id?: string; - location?: Location; + location?: Location | null; metadata?: MetadataProps; title?: string; } @@ -25,7 +25,7 @@ export default function Page({ hideDock = false, hideHeader = false, id, - location, + location = typeof window !== 'undefined' ? window.location : null, metadata, title, }: PageProps) { diff --git a/src/components/Vignette.tsx b/src/components/Vignette.tsx new file mode 100644 index 0000000..731a1fe --- /dev/null +++ b/src/components/Vignette.tsx @@ -0,0 +1,6 @@ +import '../styles/vignette.css'; +import React from 'react'; + +export default function Vignette() { + return