Skip to content

Commit

Permalink
palettes, providers, utils
Browse files Browse the repository at this point in the history
  • Loading branch information
GirasolXimena committed Aug 29, 2023
1 parent d33efe3 commit c3eefea
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 108 deletions.
5 changes: 4 additions & 1 deletion components/audio-player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import { useEffect } from "react";
import SoundIcon from "./mute-icon";
import styles from "../styles/audio-player.module.scss"
import useAudioContext from "hooks/useAudioContext";
import usePaletteContext from "hooks/usePaletteContext";

function AudioPlayer({ musicType, segment }) {
function AudioPlayer({ segment }) {
const { currentPalette } = usePaletteContext()
const musicType = currentPalette.audio
const { playing, startPlaying, stopPlaying } = useAudioContext();

const togglePlaying = () => {
Expand Down
21 changes: 0 additions & 21 deletions components/header-controls.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,20 @@
import AudioPlayer from "./audio-player";
import PaletteSwitcher from "./palette-switcher";
import ThemeSwitcher from "./theme-switcher";
import utilities from "../lib/util";
import styles from '../styles/layout.module.scss'
import palettes from "../styles/palettes";
import { useEffect } from "react";
import usePaletteContext from "hooks/usePaletteContext";


const { setCustomProperties } = utilities

function HeaderControls({ segment }) {
const { palette, setPalette } = usePaletteContext()

useEffect(() => {
const storedPalette = localStorage.getItem('user-palette') || 'default';
setPalette(storedPalette)
}, [setPalette])

useEffect(() => {
localStorage.setItem('user-palette', palette)
setCustomProperties(palettes[palette].properties)
}, [palette])

return (
<div className={styles.controls}>
<AudioPlayer
segment={segment}
musicType={palettes[palette].audio}
/>
<ThemeSwitcher
segment={segment}
/>
<PaletteSwitcher
segment={segment}
currentPalette={palette}
setPalette={setPalette}
/>
</div>
);
Expand Down
17 changes: 13 additions & 4 deletions components/palette-switcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import palettes from "../styles/palettes"
import styles from "../styles/palette-switcher.module.scss"
import utilities from "../lib/util"
import { useEffect, useRef } from "react"
import usePaletteContext from "hooks/usePaletteContext";

function PaletteSwitcher({ currentPalette, setPalette, segment }) {
function PaletteSwitcher({ segment }) {
const paletteSwitcherRef = useRef<HTMLButtonElement>(null);
const paletteKeys = Object.keys(palettes)
const nextPaletteIndex = paletteKeys.indexOf(currentPalette) + 1
const nextPalette = paletteKeys[nextPaletteIndex] || paletteKeys[0]
const isTransitioning = useRef(false)
const { palette, setPalette, nextPalette } = usePaletteContext()

useEffect(() => {
if (!paletteSwitcherRef.current) return
Expand All @@ -17,6 +16,16 @@ function PaletteSwitcher({ currentPalette, setPalette, segment }) {
utilities.setCustomProperties(properties, element)
}, [nextPalette])

useEffect(() => {
const storedPalette = localStorage.getItem('user-palette') || 'default';
setPalette(storedPalette)
}, [setPalette])

useEffect(() => {
localStorage.setItem('user-palette', palette)
utilities.setCustomProperties(palettes[palette].properties)
}, [palette])

const handleClick = () => {
// prevent double clicks
if (isTransitioning.current) return
Expand Down
64 changes: 33 additions & 31 deletions components/theme-switcher.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,49 @@
import { useEffect, useRef, useState } from 'react';
'use client'
import { useCallback, useEffect, useRef, useState } from 'react';
import { useTheme } from 'next-themes';
import styles from '../styles/theme-switcher.module.scss';
import iconStyles from '../styles/theme-icon.module.scss';
import ThreeStateCheckbox from './three-state-checkbox';
import ThemeIcon from './theme-icon';
import usePaletteContext from 'hooks/usePaletteContext';
import { animateProperties } from 'lib/util';
import { useUpdateEffect } from 'usehooks-ts';

function ThemeSwitcher({ segment }) {
const isTransitioning = useRef(false);
const { theme, setTheme } = useTheme();
const { theme, resolvedTheme, setTheme } = useTheme();
const { currentPalette } = usePaletteContext();
const currentTheme = useRef(resolvedTheme);
const [mounted, setMounted] = useState(false);
const [checked, setChecked] = useState(theme === 'system' ? null : theme === 'dark');

// Avoid hydration mismatch by only rendering form when mounted
const animateTheme = useCallback(() => {
const { light, dark } = currentPalette.properties;
const isLightTheme = resolvedTheme === 'light';
animateProperties(isLightTheme ? light : dark, isLightTheme ? dark : light, 'text');
animateProperties(isLightTheme ? dark : light, isLightTheme ? light : dark, 'background');
}, [resolvedTheme, currentPalette.properties]);

useUpdateEffect(() => {
if (resolvedTheme !== 'system' && resolvedTheme !== currentTheme.current) {
if(currentTheme.current !== 'system') {
animateTheme();
}
currentTheme.current = resolvedTheme;
}
}, [resolvedTheme]);

useEffect(() => {
setMounted(true);
}, []);

const onChange = (checked: Boolean | null) => {
// prevent double clicks
if (isTransitioning.current) return
let theme = ''
if (checked === null) {
theme ='system';
} else if (checked) {
theme = 'dark';
} else {
theme = 'light';
}

const bodyElem = document.body;
const onChange = (newChecked: boolean | null) => {
const newTheme = newChecked === null ? 'system' : (newChecked ? 'dark' : 'light');
setTheme(newTheme);
};

const handleTransitionEnd = (event) => {
if (event.propertyName === 'opacity' && event.srcElement.nodeName === 'BODY') {
setTheme(theme);
bodyElem.removeEventListener('transitionend', handleTransitionEnd);
bodyElem.classList.remove('transitioning');
isTransitioning.current = false
}
};
isTransitioning.current = true
bodyElem.classList.add('transitioning');
bodyElem.addEventListener('transitionend', handleTransitionEnd);
}
const getCheckedFromTheme = (themeVal: string | undefined) => {
if (themeVal === 'system') return null;
return themeVal === 'dark';
};

return mounted && (
<div className={`${styles.container} ${styles[segment]}`}>
Expand All @@ -50,7 +52,7 @@ function ThemeSwitcher({ segment }) {
onChange={onChange}
name="dark-mode-toggle"
id="toggle"
checked={checked}
checked={getCheckedFromTheme(theme)}
label={theme}
>
<ThemeIcon />
Expand Down
95 changes: 56 additions & 39 deletions lib/util/index.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,61 @@
import { animate } from "framer-motion"

export const utilities = {
toCartesianCoords: ({ x, y }) => {
const { width, height } = document.documentElement.getBoundingClientRect()
const halfWidth = width / 2
const halfHeight = height / 2
const xPos = x - halfWidth
const yPos = y - halfHeight
return {
x: xPos / width,
y: yPos / height
}
},
toPolarCoords: ({ x, y }) => {
const { width, height } = document.documentElement.getBoundingClientRect()
const halfWidth = width / 2
const halfHeight = height / 2
const xPos = x - halfWidth
const yPos = y - halfHeight
const distance = Math.sqrt(xPos * xPos + yPos * yPos)
const angle = Math.atan2(yPos, xPos)
return {
distance: distance / Math.min(width, height),
angle
}
},
setCustomProperties: (
properties: Record<string, string>,
element?: HTMLElement
) => {
const targetElement = element || document.documentElement;

Object.entries(properties).forEach(([key, value]) => {
const formattedKey = key.startsWith('--') ? key : `--${key}`;
targetElement.style.setProperty(formattedKey, value);
});
},
getCustomProperty: (property: string, element?: HTMLElement) => {
const targetElement = element || document.documentElement;
return getComputedStyle(targetElement).getPropertyValue(property);
export const toCartesianCoords = ({ x, y }) => {
const { width, height } = document.documentElement.getBoundingClientRect()
const halfWidth = width / 2
const halfHeight = height / 2
const xPos = x - halfWidth
const yPos = y - halfHeight
return {
x: xPos / width,
y: yPos / height
}
}

export const toPolarCoords = ({ x, y }) => {
const { width, height } = document.documentElement.getBoundingClientRect()
const halfWidth = width / 2
const halfHeight = height / 2
const xPos = x - halfWidth
const yPos = y - halfHeight
const distance = Math.sqrt(xPos * xPos + yPos * yPos)
const angle = Math.atan2(yPos, xPos)
return {
distance: distance / Math.min(width, height),
angle
}
}

export const setCustomProperties = (
properties: Record<string, string>,
element?: HTMLElement
) => {
const targetElement = element || document.documentElement;

Object.entries(properties).forEach(([key, value]) => {
const formattedKey = key.startsWith('--') ? key : `--${key}`;
targetElement.style.setProperty(formattedKey, value);
});
}

export const getCustomProperty = (property: string, element?: HTMLElement) => {
const targetElement = element || document.documentElement;
return getComputedStyle(targetElement).getPropertyValue(property);
}

export const animateProperties = (from, to, property) => {
animate(from, to, {
duration: 2,
onUpdate: (latest) => utilities.setCustomProperties({ [property]: latest }),
});
}

export const utilities = {
toCartesianCoords,
toPolarCoords,
setCustomProperties,
getCustomProperty,
animateProperties
}

export default utilities
2 changes: 1 addition & 1 deletion providers/composite-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { ReactNode } from "react";

const providers = [
{ provider: FrozenRouterContextProvider, props: {} },
{ provider: ThemeProvider, props: {} },
{ provider: PaletteContextProvider, props: {} },
{ provider: ThemeProvider, props: {} },
{ provider: AudioContextProvider, props: {} },
]

Expand Down
8 changes: 4 additions & 4 deletions providers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ export function Providers({ children }) {
useMouseInput();

return (
<TransitionContext key={pathname}>
<CompositeProvider>
<CompositeProvider>
<TransitionContext key={pathname}>
{children}
</CompositeProvider>
</TransitionContext>
</TransitionContext>
</CompositeProvider>
)
}

Expand Down
11 changes: 11 additions & 0 deletions providers/palette-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,35 @@ import {
createContext,
useState
} from "react";
import palettes from "styles/palettes";
import { Palette } from "types";

type PaletteContextProviderProps = {
children: ReactNode
}

type PaletteContextType = {
palette: string,
nextPalette: string,
currentPalette: Palette
setPalette: Dispatch<SetStateAction<string>>
}

export const PaletteContext = createContext<PaletteContextType | undefined>(undefined);

const PaletteContextProvider = ({ children }: PaletteContextProviderProps) => {
const [palette, setPalette] = useState('default');
const paletteKeys = Object.keys(palettes)
const nextPaletteIndex = paletteKeys.indexOf(palette) + 1
const nextPalette = paletteKeys[nextPaletteIndex] || paletteKeys[0]
const currentPalette = palettes[palette]


return (
<PaletteContext.Provider value={{
palette,
currentPalette,
nextPalette,
setPalette
}}>
{children}
Expand Down
5 changes: 3 additions & 2 deletions providers/transition-context.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FC, ReactNode } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import { AnimatePresence, animate, motion } from 'framer-motion';

type TransitionContextType = {
children: ReactNode;
Expand All @@ -20,4 +20,5 @@ const TransitionContext: FC<TransitionContextType> = ({ children, transitionKey
</AnimatePresence>
);

export default TransitionContext

export default TransitionContext;
8 changes: 4 additions & 4 deletions styles/palettes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { Palettes } from "../../types"
const palettes: Palettes = {
default: {
properties: {
light: 'gainsboro',
light: '#DCDCDC',
dark: '#323313',
primary: 'yellow',
secondary: 'magenta',
tertiary: 'cyan',
primary: '#FFFF00',
secondary: '#FF00FF',
tertiary: '#00FFFF',
font: 'noto_sans'
},
},
Expand Down
2 changes: 1 addition & 1 deletion types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type PaletteProperties = {
font: string;
};

type Palette = {
export type Palette = {
properties: PaletteProperties;
audio?: string; // the audio property is optional
};
Expand Down

0 comments on commit c3eefea

Please sign in to comment.