feat: replace hookstate with jotai

This commit is contained in:
jinhojang6 2024-10-18 01:32:07 +09:00
parent 2dc90344d9
commit 9d0a517fa6
8 changed files with 120 additions and 80 deletions

View File

@ -25,16 +25,14 @@
"@acid-info/lsd-react": "^0.1.0-beta.3",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@hookstate/core": "^4.0.1",
"@hookstate/localstored": "^4.0.2",
"@mdx-js/loader": "^2.3.0",
"@mdx-js/react": "^2.3.0",
"@next/font": "^14.2.13",
"@next/mdx": "^13.5.5",
"@tanstack/react-query": "^5.56.2",
"@types/mdx": "^2.0.8",
"@vercel/og": "^0.5.4",
"axios": "^1.4.0",
"jotai": "^2.10.1",
"lazysizes": "^5.3.2",
"next": "^14.2.3",
"next-mdx-remote": "^4.4.1",

View File

@ -1,20 +1,21 @@
import { defaultFilterState } from '@/states/filterState'
import styled from '@emotion/styled'
import { hookstate, useHookstate } from '@hookstate/core'
import { atom, useAtom } from 'jotai'
import React, { useEffect, useRef, useState } from 'react'
import Checkbox from './Checkbox' // Import the CustomCheckbox
import Checkbox from './Checkbox'
interface DropdownProps {
title: string
options: string[]
filterType: string
onSelectionChange: (selectedOptions: string[], filterType: string) => void
filterType: keyof typeof defaultFilterState
onSelectionChange: (
selectedOptions: string[],
filterType: keyof typeof defaultFilterState,
) => void
prefill?: string[]
}
const globalState = hookstate(() =>
JSON.parse(JSON.stringify(defaultFilterState)),
)
const filterAtom = atom(defaultFilterState)
const Dropdown: React.FC<DropdownProps> = ({
title,
@ -29,17 +30,17 @@ const Dropdown: React.FC<DropdownProps> = ({
const dropdownRef = useRef<HTMLDivElement>(null)
const state = useHookstate(globalState)
const [filter, setFilter] = useAtom(filterAtom)
const defualtState = state.get()
const defaultState = defaultFilterState
useEffect(() => {
if (defualtState[filterType]?.length === selectedOptions?.length) {
if (defaultState[filterType]?.length === selectedOptions?.length) {
setUpdated(false)
} else {
setUpdated(true)
}
}, [defualtState, selectedOptions])
}, [defaultState, selectedOptions, filterType])
useEffect(() => {
if (prefill?.length) {
@ -56,16 +57,23 @@ const Dropdown: React.FC<DropdownProps> = ({
}
const handleSelect = (option: string) => {
let newSelectedOptions = []
let newSelectedOptions: any = []
if (selectedOptions.includes(option)) {
newSelectedOptions = selectedOptions.filter((o) => o !== option)
} else {
newSelectedOptions = [...selectedOptions, option]
}
setSelectedOptions(newSelectedOptions)
onSelectionChange(newSelectedOptions, filterType)
handleUpdate(newSelectedOptions)
setFilter((prev) => ({
...prev,
[filterType]: newSelectedOptions,
}))
}
const selectAll = () => {
@ -73,6 +81,11 @@ const Dropdown: React.FC<DropdownProps> = ({
onSelectionChange(options, filterType)
setUpdated(false)
setFilter((prev) => ({
...prev,
[filterType]: options,
}))
}
const clearAll = () => {
@ -80,6 +93,11 @@ const Dropdown: React.FC<DropdownProps> = ({
onSelectionChange([], filterType)
setUpdated(true)
setFilter((prev) => ({
...prev,
[filterType]: [],
}))
}
const toggleDropdown = () => {
@ -139,8 +157,6 @@ const Dropdown: React.FC<DropdownProps> = ({
)
}
// Additional styled components for Dropdown
const DropdownContainer = styled.div`
position: relative;
display: inline-block;
@ -219,7 +235,6 @@ const ScrollDiv = styled.div`
overflow-x: hidden;
box-sizing: border-box;
// white scrollbar
&::-webkit-scrollbar {
width: 10px;
}

View File

@ -1,8 +1,8 @@
import { Dropdown } from '@/components/Dropdown'
import { OperatorGrid } from '@/components/Explore/OperatorGrid'
import { defaultFilterState } from '@/states/filterState'
import { defaultFilterState, FilterState } from '@/states/filterState'
import styled from '@emotion/styled'
import { hookstate, useHookstate } from '@hookstate/core'
import { atom, useAtom } from 'jotai'
import React, { useCallback, useMemo } from 'react'
import useGetOperators from '../../../apis/operators/useGetOperators'
import {
@ -17,15 +17,12 @@ import { processOperators, shuffleOperators } from '../../../utils/operators'
interface ExploreSectionProps {}
const globalState = hookstate(() =>
JSON.parse(JSON.stringify(defaultFilterState)),
)
const filterAtom = atom<FilterState>(defaultFilterState)
const ExploreSection: React.FC<ExploreSectionProps> = () => {
const { data, isLoading } = useGetOperators()
const state = useHookstate(globalState)
const filter = state.get()
const [filter, setFilter] = useAtom(filterAtom)
const processedOperators = processOperators(
data as any,
@ -47,15 +44,17 @@ const ExploreSection: React.FC<ExploreSectionProps> = () => {
const handleFilterChange = (
selectedOptions: string[],
filterType: string,
filterType: keyof typeof filter,
) => {
// @ts-ignore
state[filterType].set(selectedOptions)
setFilter((prev) => ({
...prev,
[filterType]: selectedOptions,
}))
}
const handleResetAll = useCallback(() => {
state.set(JSON.parse(JSON.stringify(defaultFilterState)))
}, [filter])
setFilter(JSON.parse(JSON.stringify(defaultFilterState)))
}, [setFilter])
return (
<Container>
@ -104,7 +103,7 @@ const ExploreSection: React.FC<ExploreSectionProps> = () => {
prefill={filter.background.slice()}
/>
<ResetAll onClick={handleResetAll}>
Reset All <img src="/assets/close-black.svg" />
Reset All <img src="/assets/close-black.svg" alt="close-black" />
</ResetAll>
</DropdownContainer>
<OperatorGrid

View File

@ -1,7 +1,7 @@
import { ThemeProvider, ThemeProviderProps } from '@acid-info/lsd-react'
import { Global } from '@emotion/react'
import React, { useEffect } from 'react'
import useThemeState from '../../states/themeState/theme.state'
import { useThemeState } from '../../states/themeState/theme.state' // Using Jotai's useThemeState hook
import { useLSDTheme } from './themes'
export type LSDThemeProviderProps = Partial<ThemeProviderProps>
@ -15,9 +15,9 @@ export const LSDThemeProvider: React.FC<LSDThemeProviderProps> = ({
useEffect(() => {
const html = document.querySelector('html') as HTMLElement
html.setAttribute('data-theme', mode.value)
html.setAttribute('data-font-family', genericFontFamily.value)
}, [mode.value, genericFontFamily.value])
html.setAttribute('data-theme', mode) // Removed `.value` since Jotai provides direct values
html.setAttribute('data-font-family', genericFontFamily) // Removed `.value`
}, [mode, genericFontFamily]) // Removed `.value` in dependencies
return (
<ThemeProvider theme={theme.current} injectCssVars={false}>

View File

@ -33,7 +33,7 @@ const useThemeCssVars = (theme: Theme, colorMode: string) =>
)
export const useLSDTheme = () => {
const { genericFontFamily, mode } = useThemeState().get()
const { genericFontFamily, mode } = useThemeState()
const themes = useMemo(() => {
const options: CreateThemeProps = {

View File

@ -1,5 +1,4 @@
import { hookstate, State, useHookstate } from '@hookstate/core'
import { localstored } from '@hookstate/localstored'
import { atom, useAtom } from 'jotai'
import {
ARCHETYPE,
BACKGROUND,
@ -28,17 +27,20 @@ export const defaultFilterState: FilterState = {
background: BACKGROUND,
}
const filterState =
typeof window === 'undefined'
? hookstate(defaultFilterState)
: hookstate<FilterState>(defaultFilterState, localstored({ key: 'filter' }))
const filterAtom = atom<FilterState>(defaultFilterState)
const wrapFilterState = (state: State<FilterState>) => ({
filter: { ...state.value },
get: () => state.value,
setFilter: (value: FilterState) => state.set(value),
const wrapFilterState = (
filter: FilterState,
setFilter: (value: FilterState) => void,
) => ({
filter,
get: () => filter,
setFilter: (value: FilterState) => setFilter(value),
})
export const useFilterState = () => wrapFilterState(useHookstate(filterState))
export const useFilterState = () => {
const [filter, setFilter] = useAtom(filterAtom)
return wrapFilterState(filter, setFilter)
}
export default useFilterState

View File

@ -1,6 +1,5 @@
import { TypographyGenericFontFamily } from '@acid-info/lsd-react'
import { hookstate, State, useHookstate } from '@hookstate/core'
import { localstored } from '@hookstate/localstored'
import { atom, useAtom } from 'jotai'
export type ThemeState = {
mode: 'light' | 'dark'
@ -12,24 +11,61 @@ export const defaultThemeState: ThemeState = {
genericFontFamily: 'Inter',
}
const themeState =
typeof window === 'undefined'
? hookstate(defaultThemeState)
: hookstate<ThemeState>(defaultThemeState, localstored({ key: 'theme' }))
const wrapThemeState = (state: State<ThemeState>) => ({
mode: state.mode,
genericFontFamily: state.genericFontFamily,
get: () => state.value,
setMode: (value: ThemeState['mode']) => state.mode.set(value),
setGenericFontFamily: (value: ThemeState['genericFontFamily']) =>
state.genericFontFamily.set('Inter'),
toggleMode: () =>
state.mode.set(state.mode.get() === 'dark' ? 'light' : 'dark'),
const themeAtom = atom<ThemeState>(() => {
if (typeof window !== 'undefined') {
const storedTheme = localStorage.getItem('theme')
return storedTheme ? JSON.parse(storedTheme) : defaultThemeState
}
return defaultThemeState
})
export const useThemeState = () => wrapThemeState(useHookstate(themeState))
const persistentThemeAtom = atom(
(get) => get(themeAtom),
(get, set, newTheme: ThemeState) => {
set(JSON.parse(JSON.stringify(newTheme)))
if (typeof window !== 'undefined') {
localStorage.setItem('theme', JSON.stringify(newTheme))
}
},
)
export const useIsDarkTheme = () => useThemeState().mode.get() === 'dark'
export const useThemeState = () => {
const [theme, setTheme] = useAtom(persistentThemeAtom)
const setMode = (mode: ThemeState['mode']) => {
setTheme({
...theme,
mode,
})
}
const setGenericFontFamily = (
fontFamily: ThemeState['genericFontFamily'],
) => {
setTheme({
...theme,
genericFontFamily: fontFamily,
})
}
const toggleMode = () => {
setTheme({
...theme,
mode: theme.mode === 'dark' ? 'light' : 'dark',
})
}
return {
...theme,
setMode,
setGenericFontFamily,
toggleMode,
}
}
export const useIsDarkTheme = () => {
const theme = useThemeState()
return theme.mode === 'dark'
}
export default useThemeState

View File

@ -267,16 +267,6 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.51.0.tgz#6d419c240cfb2b66da37df230f7e7eef801c32fa"
integrity sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==
"@hookstate/core@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@hookstate/core/-/core-4.0.1.tgz#6744380e96ce13fe3488c926c1cbae93bbea0ff6"
integrity sha512-Uh2D8Z0z/pqOJ7t+SfC+2sj13JQcB4yFhtL+T1choCaBxTSlgOS/CKRBohgJ4cjTKoxOmTT8uSQysu3gUjX+Gw==
"@hookstate/localstored@^4.0.2":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@hookstate/localstored/-/localstored-4.0.2.tgz#6f0ef6b71baa4c28e5e8835431585500eb6a9532"
integrity sha512-81dbUH6xnwbePby21NQN8zrmjvMA8tOYeOOLZXqRmRGKnhtcokjTJoRNMZugpC9P3/NnacllBZ5ZUt43nmrKkA==
"@humanwhocodes/config-array@^0.11.11":
version "0.11.11"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844"
@ -364,11 +354,6 @@
dependencies:
glob "10.3.10"
"@next/font@^14.2.13":
version "14.2.13"
resolved "https://registry.yarnpkg.com/@next/font/-/font-14.2.13.tgz#cfe0903654e65b58e7b5cdc600399a45e028bbdb"
integrity sha512-xtleJ172yNVSVoQoMIwPHnM9zSPOzNB964mZMOFmRaLWgWMgmlhCVI42dlDSGc6d9nQ/WHCmCZaJMloS1HHGXg==
"@next/mdx@^13.5.5":
version "13.5.5"
resolved "https://registry.yarnpkg.com/@next/mdx/-/mdx-13.5.5.tgz#ce1ff29ab424f2335a02ac6a662f56938c275b5d"
@ -2562,6 +2547,11 @@ jackspeak@^2.3.5:
optionalDependencies:
"@pkgjs/parseargs" "^0.11.0"
jotai@^2.10.1:
version "2.10.1"
resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.10.1.tgz#8d5598d06fa295110de0914f10bd1d10ea229723"
integrity sha512-4FycO+BOTl2auLyF2Chvi6KTDqdsdDDtpaL/WHQMs8f3KS1E3loiUShQzAzFA/sMU5cJ0hz/RT1xum9YbG/zaA==
js-cookie@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"