Skip to content

Commit

Permalink
feat: to top
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <tukon479@gmail.com>
  • Loading branch information
Innei committed Jun 18, 2023
1 parent f413e23 commit 3e0c815
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 22 deletions.
5 changes: 5 additions & 0 deletions src/app/notes/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { MarkdownImageRecordProvider } from '~/providers/article/markdown-image-
import { useSetCurrentNoteId } from '~/providers/note/current-note-id-provider'
import { NoteLayoutRightSidePortal } from '~/providers/note/right-side-provider'
import { parseDate } from '~/utils/datetime'
import { springScrollToTop } from '~/utils/scroller'

import styles from './page.module.css'

Expand Down Expand Up @@ -162,5 +163,9 @@ const Markdownrenderers: { [name: string]: Partial<MarkdownToJSX.Rule> } = {

export default PageDataHolder(PageImpl, () => {
const { id } = useParams() as { id: string }

useEffect(() => {
springScrollToTop()
}, [id])
return useNoteByNidQuery(id)
})
2 changes: 1 addition & 1 deletion src/components/ui/fab/BackToTopFAB.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useViewport } from '~/atoms'
import { usePageScrollLocationSelector } from '~/providers/root/page-scroll-info-provider'
import { springScrollToTop } from '~/utils/spring'
import { springScrollToTop } from '~/utils/scroller'

import { FABBase } from './FABContainer'

Expand Down
27 changes: 22 additions & 5 deletions src/components/widgets/toc/Toc.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { memo, useEffect, useMemo, useRef, useState } from 'react'
import { memo, useCallback, useEffect, useMemo, useRef } from 'react'
import { motion } from 'framer-motion'
import { atom, useAtom } from 'jotai'
import type { ITocItem } from './TocItem'

import { RightToLeftTransitionView } from '~/components/ui/transition/RightToLeftTransitionView'
import { throttle } from '~/lib/_'
import { useArticleElement } from '~/providers/article/article-element-provider'
import { clsxm } from '~/utils/helper'
import { springScrollToElement } from '~/utils/scroller'

import { TocItem } from './TocItem'

Expand All @@ -28,6 +30,7 @@ export const Toc: Component<TocProps> = ({ useAsWeight, className }) => {
...$article.querySelectorAll('h1,h2,h3,h4,h5,h6'),
] as HTMLHeadingElement[]
}, [$article])

const toc: ITocItem[] = useMemo(() => {
return Array.from($headings).map((el, idx) => {
const depth = +el.tagName.slice(1)
Expand Down Expand Up @@ -76,8 +79,18 @@ export const Toc: Component<TocProps> = ({ useAsWeight, className }) => {
[toc],
)

const activeId = useActiveId($headings)
const [activeId, setActiveId] = useActiveId($headings)

const handleScrollTo = useCallback(
(i: number, $el: HTMLElement | null, anchorId: string) => {
if ($el) {
springScrollToElement($el, -100).then(() => {
setActiveId(anchorId)
})
}
},
[],
)
return (
<aside className={clsxm('st-toc z-[3]', 'relative font-sans', className)}>
<ul
Expand All @@ -92,6 +105,7 @@ export const Toc: Component<TocProps> = ({ useAsWeight, className }) => {
isActive={heading.anchorId === activeId}
key={heading.title}
rootDepth={rootDepth}
onClick={handleScrollTo}
/>
)
})}
Expand All @@ -104,7 +118,7 @@ const MemoedItem = memo<{
isActive: boolean
heading: ITocItem
rootDepth: number
onClick?: (i: number) => void
onClick?: (i: number, $el: HTMLElement | null, anchorId: string) => void
// containerRef: any
}>((props) => {
const {
Expand Down Expand Up @@ -151,8 +165,10 @@ const MemoedItem = memo<{

MemoedItem.displayName = 'MemoedItem'

const tocActiveIdAtom = atom<string | null>(null)

function useActiveId($headings: HTMLHeadingElement[]) {
const [activeId, setActiveId] = useState<string | null>()
const [activeId, setActiveId] = useAtom(tocActiveIdAtom)
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
Expand All @@ -177,5 +193,6 @@ function useActiveId($headings: HTMLHeadingElement[]) {
observer.disconnect()
}
}, [$headings])
return activeId

return [activeId, setActiveId] as const
}
6 changes: 2 additions & 4 deletions src/components/widgets/toc/TocAutoScroll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { useEffect } from 'react'

import { useArticleElement } from '~/providers/article/article-element-provider'

import { escapeSelector } from './escapeSelector'

export const TocAutoScroll: Component = () => {
const articleElement = useArticleElement()

Expand All @@ -22,7 +24,3 @@ export const TocAutoScroll: Component = () => {

return null
}

function escapeSelector(selector: string) {
return selector.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, '\\$&')
}
12 changes: 5 additions & 7 deletions src/components/widgets/toc/TocItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import type { FC, MouseEvent } from 'react'

import { useArticleElement } from '~/providers/article/article-element-provider'
import { clsxm } from '~/utils/helper'
import { springScrollToElement } from '~/utils/spring'

import { escapeSelector } from './escapeSelector'

const styles = tv({
base: clsxm(
Expand All @@ -30,7 +31,7 @@ export const TocItem: FC<{
depth: number
active: boolean
rootDepth: number
onClick?: (i: number) => void
onClick?: (i: number, $el: HTMLElement | null, anchorId: string) => void
index: number
// containerRef?: RefObject<HTMLDivElement>
}> = memo((props) => {
Expand Down Expand Up @@ -71,14 +72,11 @@ export const TocItem: FC<{
onClick={useCallback(
(e: MouseEvent) => {
e.preventDefault()
onClick?.(index)
const $el = $article?.querySelector(
`#${anchorId}`,
`#${escapeSelector(anchorId)}`,
) as any as HTMLElement

if ($el) {
springScrollToElement($el, -100)
}
onClick?.(index, $el, anchorId)
},
[onClick, index, $article, anchorId],
)}
Expand Down
3 changes: 3 additions & 0 deletions src/components/widgets/toc/escapeSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function escapeSelector(selector: string) {
return selector.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, '\\$&')
}
12 changes: 7 additions & 5 deletions src/utils/spring.ts → src/utils/scroller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,31 @@ import { microdampingPreset } from '~/constants/spring'
export const springScrollTo = (y: number) => {
const scrollTop =
// FIXME latest version framer will ignore keyframes value `0`
(document.documentElement.scrollTop || document.body.scrollTop) + 1
document.documentElement.scrollTop || document.body.scrollTop
const animation = animateValue({
keyframes: [scrollTop, y],
keyframes: [scrollTop + 1, y],
autoplay: true,
...microdampingPreset,

onUpdate(latest) {
console.log(latest, 'latest')
if (latest <= 0) {
animation.stop()
}
window.scrollTo(0, latest)
},
})
return animation
}

export const springScrollToTop = () => {
springScrollTo(0)
return springScrollTo(0)
}

export const springScrollToElement = (element: HTMLElement, delta = 40) => {
const y = calculateElementTop(element)

const to = y + delta
springScrollTo(to)
return springScrollTo(to)
}

const calculateElementTop = (el: HTMLElement) => {
Expand Down

0 comments on commit 3e0c815

Please sign in to comment.