Skip to content

Commit

Permalink
Convert to TypeScript and add ESLint/Prettier 🤌 (#3)
Browse files Browse the repository at this point in the history
Among the conversion and linting there are additional changes included:

* Set proper useEffect deps
* Header: Fix initial arrow animation (unnecessary animating happened under the Splash)
* Header: Remove unnecessary animation props that didn't have effect and do minor animation tweaks
* Header: Remove the marquee "transition" key since it isn't a variant
* Header: Rename NavItem -> HeaderNavItem
* Header: Fix useMedia ssr error
* Fix vercel/next.js#65161
* Relocate locomotive-scroll wrapper to components with proper types
* Add build step script for Vercel, not active currently
* Figure: Don't spread props since the invalid blur* attrs could be applied (vercel/next.js#56511)
* Figure: Remove unnecessary props and add ones that could be needed
* Figure: Add alt text to video also
* Tweak tokens
* Slightly tweak some content strings and styles
* Refactor unnecessary stuff away from useInView hook
* Bunch of file extension changes and sort tweaks
* Relocate and fix component script
* Sort keys, jsx props and destructured keys
* Sitemap: Better key for projects
* utility.js -> utils.ts
  • Loading branch information
joonassandell authored May 4, 2024
1 parent 8db0be5 commit 168d5fb
Show file tree
Hide file tree
Showing 195 changed files with 7,673 additions and 6,652 deletions.
52 changes: 52 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"$schema": "https://json.schemastore.org/eslintrc",
"extends": [
"next/core-web-vitals",
"plugin:import/recommended",
"plugin:prettier/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:typescript-sort-keys/recommended"
],
"plugins": [
"sort-imports-es6-autofix",
"sort-keys-fix",
"sort-destructure-keys"
],
"rules": {
"@typescript-eslint/consistent-type-imports": [
"error",
{
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}
],
"@typescript-eslint/consistent-type-exports": "error",
"@typescript-eslint/prefer-nullish-coalescing": "error",
"@typescript-eslint/no-explicit-any": "off",
"import/newline-after-import": "error",
"sort-imports-es6-autofix/sort-imports-es6": [
2,
{
"ignoreCase": true
}
],
"sort-keys-fix/sort-keys-fix": "error",
"sort-destructure-keys/sort-destructure-keys": [
2,
{ "caseSensitive": false }
],
"react/no-unescaped-entities": "off",
"react/jsx-sort-props": "error"
},
"overrides": [
{
"files": ["*.ts", "*.tsx"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"project": ["tsconfig.json"],
"sourceType": "module"
}
}
]
}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ yarn-error.log*
.env.test.local
.env.production.local

# TypeScript
*.tsbuildinfo
next-env.d.ts

# Misc
.DS_Store
*.pem
Expand Down
121 changes: 67 additions & 54 deletions components/App/App.jsx → components/App/App.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,62 @@
import { createContext, useContext, useEffect, useState, useRef } from 'react';
import { AnimatePresence } from 'framer-motion';
import { isBrowser } from '@/lib/utility';
import { Splash } from '@/components/Splash';
import { AnimatePresence, domAnimation, LazyMotion } from 'framer-motion';
import {
type AppContextProps,
AppHead,
type AppProps,
type AppStateProps,
} from './';
import { createContext, useContext, useEffect, useRef, useState } from 'react';
import { EASE_CSS, SLOW_NETWORK_DELAY } from '@/lib/config';
import { Header } from '@/components/Header';
import { LocomotiveScrollProvider } from '@/lib/react-locomotive-scroll';
import { isBrowser } from '@/lib/utils';
import { LocomotiveScrollProvider } from '@/components/LocomotiveScroll';
import { Splash } from '@/components/Splash';
import { useRouter } from 'next/router';
import { LazyMotion, domAnimation } from 'framer-motion';
import Script from 'next/script';
import { AppHead } from './';
import { EASE_CSS, SLOW_NETWORK_DELAY } from '@/lib/config';
import NProgress from 'nprogress';
import Script from 'next/script';

const DISABLE_LOADING = process.env.NEXT_PUBLIC_DISABLE_LOADING;
const GOOGLE_ANALYTICS = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS;
const PRODUCTION = process.env.NODE_ENV === 'production';

const AppContext = createContext({
detect: {},
html: isBrowser && document.documentElement,
loading: DISABLE_LOADING ? false : true,
loadingEnd: DISABLE_LOADING ? true : false,
transition: false, // 'template', false, true
transitionInitial: false,
});
let scrollOnUpdateOnce = false;

export const App = ({ Component, pageProps }) => {
const appContext = useAppContext();
const [appState, setAppState] = useState(appContext);
const AppContext = createContext<AppContextProps | undefined>(undefined);

export const App = ({ Component, pageProps }: AppProps) => {
const [appState, setAppState] = useState<AppStateProps>({
detect: {},
html: (isBrowser && document.documentElement) as AppStateProps['html'],
loading: DISABLE_LOADING ? false : true,
loadingEnd: DISABLE_LOADING ? true : false,
transition: false,
transitionInitial: false,
});
const { html, loading, loadingEnd, transition } = appState;
const { asPath, beforePopState, push, events } = useRouter();
const [animationComplete, setAnimationComplete] = useState();
const { asPath, beforePopState, events, push } = useRouter();
const [animationComplete, setAnimationComplete] = useState<
string | undefined
>();
const containerRef = useRef(null);

/* ======
* App set state functions
* ====== */

const setTransition = value => {
const setTransition = (value: AppStateProps['transition']) => {
setAppState(prevState => ({
...prevState,
transition: value,
}));
};

const setTransitionInitial = value => {
const setTransitionInitial = (value: AppStateProps['transitionInitial']) => {
setAppState(prevState => ({
...prevState,
transitionInitial: value,
}));
};

const setLoadingEnd = value => {
const setLoadingEnd = (value: AppStateProps['loadingEnd']) => {
setAppState(prevState => ({
...prevState,
loadingEnd: value,
Expand All @@ -64,11 +69,13 @@ export const App = ({ Component, pageProps }) => {

useEffect(() => {
if (process.env.NODE_ENV === 'production') {
console.info('Made by me with Next.js, Framer Motion and tears. 🥲');
console.info(
'Made by me with Next.js, TypeScript, Framer Motion and tears. 🥲',
);
}

(async () => {
const { isIphoneSafari, isWindows, hasTouch, hasThemeColor } =
const { hasThemeColor, hasTouch, isIphoneSafari, isWindows } =
await import('@/lib/detect');
if (isWindows) html.classList.add('is-windows');
if (hasThemeColor) html.classList.add('has-themeColor');
Expand All @@ -91,15 +98,15 @@ export const App = ({ Component, pageProps }) => {
}));

return () => window.removeEventListener('resize', rootHeight);
}, []);
}, [html]);

/* ======
* Various
* ====== */

useEffect(() => {
if (loadingEnd) html.classList.remove('is-loading');
}, [loadingEnd]);
}, [loadingEnd, html]);

useEffect(() => {
if (transition) {
Expand All @@ -114,13 +121,13 @@ export const App = ({ Component, pageProps }) => {
setTimeout(() => html.classList.remove('is-transition:withDelay'), 300);
setTimeout(() => html.classList.remove(hackClass), 300);
}
}, [transition]);
}, [transition, html]);

/**
* Add loader with nprogress for slow networks
*/
useEffect(() => {
let timeout;
let timeout: ReturnType<typeof setTimeout>;

NProgress.configure({
easing: EASE_CSS,
Expand All @@ -146,14 +153,15 @@ export const App = ({ Component, pageProps }) => {
events.off('routeChangeError', changeComplete);
events.off('routeChangeComplete', changeComplete);
};
}, []);
}, [events]);

/**
* Set template transition by default when navigating back/forward
*/
const [popStateTimeout, setPopStateTimeout] = useState(null);
const [popStateTimeout, setPopStateTimeout] =
useState<ReturnType<typeof setTimeout>>();
useEffect(() => {
beforePopState(({ url, as }) => {
beforePopState(({ as, url }) => {
if (transition === 'template') {
setPopStateTimeout(
setTimeout(() => {
Expand All @@ -168,7 +176,7 @@ export const App = ({ Component, pageProps }) => {
}
});
return () => popStateTimeout && clearTimeout(popStateTimeout);
}, [transition]);
}, [transition, beforePopState, push, popStateTimeout]);

/**
* Set initial transitions ready for animation (e.g. for Hero)
Expand All @@ -181,8 +189,8 @@ export const App = ({ Component, pageProps }) => {
* Send GA page views
*/
useEffect(() => {
if (PRODUCTION) {
const handleRouteChange = url => {
if (PRODUCTION && GOOGLE_ANALYTICS) {
const handleRouteChange = (url: string) => {
window.gtag('config', GOOGLE_ANALYTICS, {
page_path: url,
});
Expand Down Expand Up @@ -225,6 +233,21 @@ export const App = ({ Component, pageProps }) => {
>
<LocomotiveScrollProvider
containerRef={containerRef}
location={animationComplete}
onLocationChange={scroll => {
scroll.scroll.stop && scroll.start();
scroll.scrollTo(0, { disableLerp: true, duration: 0 });
if (transition) setTransition(false);
}}
onUpdate={scroll => {
if (!DISABLE_LOADING) {
if (!scrollOnUpdateOnce && !loadingEnd) scroll.stop();
if (!scrollOnUpdateOnce && loadingEnd) {
scrollOnUpdateOnce = true;
scroll.start();
}
}
}}
options={{
class: '@',
draggingClass: 'is-drag',
Expand All @@ -238,26 +261,12 @@ export const App = ({ Component, pageProps }) => {
smooth: true,
smoothClass: 'is-smooth',
tablet: {
breakpoint: 1024,
smooth: true,
},
touchMultiplier: 4,
}}
location={animationComplete}
watch={[loadingEnd]}
onLocationChange={scroll => {
scroll.scroll.stop && scroll.start();
scroll.scrollTo(0, { duration: 0, disableLerp: true });
if (transition) setTransition(false);
}}
onUpdate={scroll => {
if (!DISABLE_LOADING) {
if (!scrollOnUpdateOnce && !loadingEnd) scroll.stop();
if (!scrollOnUpdateOnce && loadingEnd) {
scrollOnUpdateOnce = true;
scroll.start();
}
}
}}
>
<div className="App">
<Header navTitle={pageProps.navTitle} />
Expand All @@ -279,4 +288,8 @@ export const App = ({ Component, pageProps }) => {
);
};

export const useAppContext = () => useContext(AppContext);
export const useAppContext = () => {
const context = useContext(AppContext);
if (context) return context;
throw new Error('useAppContext must be used within App');
};
21 changes: 21 additions & 0 deletions components/App/App.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { AppProps as NextAppProps } from 'next/app';

export interface AppProps extends Omit<NextAppProps, 'router'> {}

export interface AppStateProps {
detect: {
[key: string]: boolean;
};
html: Document['documentElement'];
loading: boolean;
loadingEnd: boolean;
transition: boolean | 'template';
transitionInitial: boolean;
}

export interface AppContextProps {
appState: AppStateProps;
setLoadingEnd: (value: AppStateProps['loadingEnd']) => void;
setTransition: (value: AppStateProps['transition']) => void;
setTransitionInitial: (value: AppStateProps['transitionInitial']) => void;
}
Loading

0 comments on commit 168d5fb

Please sign in to comment.