Skip to content

Commit

Permalink
added draggable+closable sidebar
Browse files Browse the repository at this point in the history
  • Loading branch information
LooLzzz committed Sep 4, 2024
1 parent 17a3813 commit 8e95c46
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 96 deletions.
Binary file modified bun.lockb
Binary file not shown.
202 changes: 110 additions & 92 deletions src/components/Sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,16 @@ import {
Title,
useMantineColorScheme
} from '@mantine/core'
import { useMouse } from '@mantine/hooks'
import { Link, LinkProps, useLocation, useMatchRoute } from '@tanstack/react-router'
import { useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'

import { MoonStarsIcon, SunIcon } from '@/assets'
import { useSwipe, useWordleStore } from '@/hooks'
import { useDraggable, useWordleStore, type PositionDelta } from '@/hooks'
import { secondsToHms } from '@/utils'
import { useMouse } from '@mantine/hooks'

interface SidebarProps extends DrawerRootProps {
interface SidebarProps extends DrawerRootProps { }

}

const NavRouterLink = ({ label, to, ...props }: LinkProps & NavLinkProps) => {
const matchRoute = useMatchRoute()
Expand Down Expand Up @@ -55,18 +54,34 @@ const Sidebar = ({ opened, onClose, ...props }: SidebarProps) => {
state.resetStore,
state.time,
])
const draggableRef = useRef<HTMLDivElement>(null)
const { colorScheme, toggleColorScheme } = useMantineColorScheme()
const [hovered, setHovered] = useState(false)
const mouse = useMouse()
const location = useLocation()
const [drawerOffsetX, setDrawerOffsetX] = useState(0)

const handleResetStore = () => {
resetStore()
onClose()
}

useSwipe({
left: onClose
const handleDragStop = ({ dx }: PositionDelta) => {
if (dx < -100) {
onClose()
setTimeout(() => {
setDrawerOffsetX(0)
}, 200)
} else {
setDrawerOffsetX(0)
}
}
const handleDrag = ({ dx }: PositionDelta) => {
setDrawerOffsetX(dx)
}
const { dragging } = useDraggable(draggableRef, {
onDrag: handleDrag,
onStop: handleDragStop
})

useEffect(() => {
Expand Down Expand Up @@ -102,94 +117,97 @@ const Sidebar = ({ opened, onClose, ...props }: SidebarProps) => {
{...props}
>
<Drawer.Overlay />
<Drawer.Content>
<Stack h='100%' gap={0}>
<Drawer.Header>
<Drawer.Title>
<Title order={3}>Settings</Title>
</Drawer.Title>
<Drawer.CloseButton />
</Drawer.Header>

<Drawer.Body flex={1}>
<Stack h='100%'>
<Text>
Session Time: <Code>{secondsToHms(time)}</Code>
</Text>

<Divider />

<NavLink
active
variant='subtle'
label='Toggle Dark Mode'
h='2rem'
onClick={toggleColorScheme}
leftSection={
colorScheme === 'dark'
? <MoonStarsIcon strokeWidth={2.5} width='1rem' color='var(--mantine-color-blue-6)' />
: <SunIcon strokeWidth={2.5} width='1rem' color='var(--mantine-color-yellow-7)' />
}
style={{
borderRadius: 'var(--mantine-radius-md)',
border: '0px'
}}
/>
{/* <Switch
color='dark.4'
label='Dark mode'
size='sm'
checked={colorScheme === 'dark'}
onChange={toggleColorScheme}
onLabel={<MoonStarsIcon strokeWidth={2.5} width='1rem' color='var(--mantine-color-blue-6)' />}
offLabel={<SunIcon strokeWidth={2.5} width='1rem' color='var(--mantine-color-yellow-7)' />}
/> */}

<Stack gap={0}>
<NavRouterLink
to='/'
label='Wordle'
/>
<NavRouterLink
to='/helper'
label='Helper'
/>
</Stack>

<Space flex={1} />

<Button onClick={handleResetStore}>
Restart Game
</Button>
<Box
ref={draggableRef}

<Divider />

<Box c='dimmed' fz='xs' style={{ textAlign: 'center' }}>
<Text>
{'Made with ❤️ by '}
<Anchor
href='https://github.com/LooLzzz'
target='_blank'
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
LooLzzz
</Anchor>
</Text>
>
<Drawer.Content
style={{
transform: `translateX(${Math.min(drawerOffsetX, 0)}px)`,
transitionProperty: !opened || dragging ? undefined : 'transform',
transitionDuration: !opened || dragging ? undefined : '0.2s',
transitionTimingFunction: !opened || dragging ? undefined : 'ease',
}}
>
<Stack h='100%' gap={0}>
<Drawer.Header>
<Drawer.Title>
<Title order={3}>Settings</Title>
</Drawer.Title>
<Drawer.CloseButton />
</Drawer.Header>

<Drawer.Body flex={1}>
<Stack h='100%'>
<Text>
{'Give it a ⭐ on '}
<Anchor
href='https://github.com/LooLzzz/better-wordle/stargazers'
target='_blank'
>
GitHub
</Anchor>
Session Time: <Code>{secondsToHms(time)}</Code>
</Text>
</Box>
</Stack>
</Drawer.Body>
</Stack>
</Drawer.Content>

<Divider />

<NavLink
active
variant='subtle'
label='Toggle Dark Mode'
h='2rem'
onClick={toggleColorScheme}
leftSection={
colorScheme === 'dark'
? <MoonStarsIcon strokeWidth={2.5} width='1rem' color='var(--mantine-color-blue-6)' />
: <SunIcon strokeWidth={2.5} width='1rem' color='var(--mantine-color-yellow-7)' />
}
style={{
borderRadius: 'var(--mantine-radius-md)',
border: '0px'
}}
/>

<Stack gap={0}>
<NavRouterLink
to='/'
label='Wordle'
/>
<NavRouterLink
to='/helper'
label='Helper'
/>
</Stack>

<Space flex={1} />

<Button onClick={handleResetStore}>
Restart Game
</Button>

<Divider />

<Box c='dimmed' fz='xs' style={{ textAlign: 'center' }}>
<Text>
{'Made with ❤️ by '}
<Anchor
href='https://github.com/LooLzzz'
target='_blank'
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
LooLzzz
</Anchor>
</Text>
<Text>
{'Give it a ⭐ on '}
<Anchor
href='https://github.com/LooLzzz/better-wordle/stargazers'
target='_blank'
>
GitHub
</Anchor>
</Text>
</Box>
</Stack>
</Drawer.Body>
</Stack>
</Drawer.Content>
</Box>
</Drawer.Root>
</>
)
Expand Down
2 changes: 2 additions & 0 deletions src/components/WordsGuesser/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ const WordsGuesser = () => {
<Text pb='md'>Better luck next time! 🍀</Text>
<Center>
<Image
radius='md'
style={{ boxShadow: 'var(--mantine-shadow-xs)' }}
w={250}
src={gitgudImageSrc}
/>
Expand Down
118 changes: 118 additions & 0 deletions src/hooks/drag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { RefObject, useEffect, useState } from 'react'

interface PositionDelta {
dx: number
dy: number
}

interface Position {
x: number
y: number
}

interface DraggableState extends PositionDelta {
dragging: boolean
}

interface DraggableOptions {
onStart?: (state: Position) => void
onDrag?: (state: PositionDelta) => void
onStop?: (state: PositionDelta) => void
}


const useDraggable = (ref: RefObject<HTMLElement>, options: DraggableOptions = {}): DraggableState => {
const [{ dx, dy }, setOffset] = useState({ dx: 0, dy: 0 })
const [dragging, setDragging] = useState(false)

const handleMouseDown = (e: MouseEvent) => {
const startPos = {
x: e.clientX - dx,
y: e.clientY - dy,
}
options.onStart?.(startPos)

const handleMouseMove = (e: MouseEvent) => {
const ele = ref.current
if (!ele) {
return
}

const dx = e.clientX - startPos.x
const dy = e.clientY - startPos.y
setDragging(true)
setOffset({ dx, dy })
options.onDrag?.({ dx, dy })
}

const handleMouseUp = (e: MouseEvent) => {
document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseup', handleMouseUp)
setDragging(false)
setOffset({ dx: 0, dy: 0 })

const dx = e.clientX - startPos.x
const dy = e.clientY - startPos.y
options.onStop?.({ dx, dy })
}

document.addEventListener('mousemove', handleMouseMove)
document.addEventListener('mouseup', handleMouseUp)
}

const handleTouchStart = (e: TouchEvent) => {
const touch = e.touches[0]
const startPos = {
x: touch.clientX - dx,
y: touch.clientY - dy,
}
options.onStart?.(startPos)

const handleTouchMove = (e: TouchEvent) => {
const ele = ref.current
if (!ele) {
return
}

const touch = e.touches[0]
const dx = touch.clientX - startPos.x
const dy = touch.clientY - startPos.y
setDragging(true)
setOffset({ dx, dy })
options.onDrag?.({ dx, dy })
}

const handleTouchEnd = (e: TouchEvent) => {
document.removeEventListener('touchmove', handleTouchMove)
document.removeEventListener('touchend', handleTouchEnd)
setDragging(false)
setOffset({ dx: 0, dy: 0 })

const touch = e.changedTouches[0]
const dx = touch.clientX - startPos.x
const dy = touch.clientY - startPos.y
options.onStop?.({ dx, dy })
}

document.addEventListener('touchmove', handleTouchMove)
document.addEventListener('touchend', handleTouchEnd)
}

useEffect(() => {
ref.current?.addEventListener('mousedown', handleMouseDown)
ref.current?.addEventListener('touchstart', handleTouchStart)
return () => {
ref.current?.removeEventListener('mousedown', handleMouseDown)
ref.current?.removeEventListener('touchstart', handleTouchStart)
}
}, [ref.current])

return { dx, dy, dragging }
}

export {
useDraggable as default,
type DraggableState,
type Position,
type PositionDelta
}
8 changes: 7 additions & 1 deletion src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
export {
default as useDraggable,
type DraggableState,
type Position,
type PositionDelta
} from './drag'
export { default as useWordleStore } from './store'
export { useSwipe } from './swipe'
export { default as useSwipe } from './swipe'
4 changes: 1 addition & 3 deletions src/hooks/swipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,4 @@ const useSwipe = ({ left, right, up, down }: SwipeOptions = {}) => {
})
}

export {
useSwipe
}
export default useSwipe

0 comments on commit 8e95c46

Please sign in to comment.