Skip to content

Commit

Permalink
fix: toc
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <[email protected]>
  • Loading branch information
Innei committed Jul 9, 2023
1 parent cc0cc47 commit 0c3a6eb
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 73 deletions.
11 changes: 5 additions & 6 deletions global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ declare global {
} & Props
>

export type Component<P = {}> = FC<
{
className?: string
} & P &
PropsWithChildren
>
export type Component<P = {}> = FC<ComponentType & P>

export type ComponentType = {
className?: string
} & PropsWithChildren

// TODO should remove in next TypeScript version
interface Document {
Expand Down
72 changes: 63 additions & 9 deletions src/components/widgets/shared/ArticleRightAside.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,73 @@
import React from 'react'
'use client'

import React, { useEffect, useRef, useState } from 'react'
import clsx from 'clsx'
import type { TocAsideRef } from '../toc'

import { useViewport } from '~/atoms'

import { TocAside } from '../toc'
import { ReadIndicator } from './ReadIndicator'

export const ArticleRightAside: Component = ({ children }) => {
const asideRef = useRef<TocAsideRef>(null)
const [isScrollToBottom, setIsScrollToBottom] = useState(false)
const [isScrollToTop, setIsScrollToTop] = useState(false)
const [canScroll, setCanScroll] = useState(false)
const h = useViewport((v) => v.h)
useEffect(() => {
const $ = asideRef.current?.getContainer()
if (!$) return

// if $ can not scroll, return null
if ($.scrollHeight <= $.clientHeight + 2) return

setCanScroll(true)

const handler = () => {
// to bottom
if ($.scrollTop + $.clientHeight + 20 > $.scrollHeight) {
setIsScrollToBottom(true)
setIsScrollToTop(false)
}

// if scroll to top,
// set isScrollToTop to true
else if ($.scrollTop === 0) {
setIsScrollToTop(true)
setIsScrollToBottom(false)
} else {
setIsScrollToBottom(false)
setIsScrollToTop(false)
}
}
$.addEventListener('scroll', handler)

handler()

return () => {
$.removeEventListener('scroll', handler)
}
}, [h])

return (
<aside className="sticky top-2 h-[calc(100vh-6rem-4.5rem-150px)]">
<TocAside
as="div"
className="top-[120px] ml-4"
treeClassName="absolute h-full min-h-[120px]"
accessory={ReadIndicator}
/>
<aside className="sticky top-[120px] mt-[120px] h-[calc(100vh-6rem-4.5rem-150px-120px)]">
<div className="relative h-full">
<TocAside
as="div"
className="static ml-4"
treeClassName={clsx(
'absolute h-full min-h-[120px] overflow-auto',
isScrollToBottom && 'mask-t',
isScrollToTop && 'mask-b',
canScroll && !isScrollToBottom && !isScrollToTop && 'mask-both',
)}
accessory={ReadIndicator}
ref={asideRef}
/>
</div>
{React.cloneElement(children as any, {
className: 'ml-4 translate-y-full',
className: 'ml-4 translate-y-[calc(100%+24px)]',
})}
</aside>
)
Expand Down
112 changes: 64 additions & 48 deletions src/components/widgets/toc/TocAside.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
'use client'

import React, { useEffect, useMemo, useRef } from 'react'
import React, {
forwardRef,
useEffect,
useImperativeHandle,
useMemo,
useRef,
} from 'react'

import { throttle } from '~/lib/_'
import { clsxm } from '~/lib/helper'
Expand All @@ -17,56 +23,66 @@ export interface TocSharedProps {

as?: React.ElementType
}
export const TocAside: Component<TocAsideProps & TocSharedProps> = ({
className,
children,
treeClassName,
accessory,
as: As = 'aside',
}) => {
const containerRef = useRef<HTMLUListElement>(null)
const $article = useWrappedElement()
export interface TocAsideRef {
getContainer: () => HTMLUListElement | null
}
export const TocAside = forwardRef<
TocAsideRef,
TocAsideProps & TocSharedProps & ComponentType
>(
(
{ className, children, treeClassName, accessory, as: As = 'aside' },
ref,
) => {
const containerRef = useRef<HTMLUListElement>(null)
const $article = useWrappedElement()

useImperativeHandle(ref, () => ({
getContainer: () => containerRef.current,
}))

if (typeof $article === 'undefined') {
throw new Error('<Toc /> must be used in <WrappedElementProvider />')
}
const $headings = useMemo(() => {
if (!$article) {
return []
if (typeof $article === 'undefined') {
throw new Error('<Toc /> must be used in <WrappedElementProvider />')
}
const $headings = useMemo(() => {
if (!$article) {
return []
}

return [
...$article.querySelectorAll('h1,h2,h3,h4,h5,h6'),
] as HTMLHeadingElement[]
}, [$article])
return [
...$article.querySelectorAll('h1,h2,h3,h4,h5,h6'),
] as HTMLHeadingElement[]
}, [$article])

useEffect(() => {
const setMaxWidth = throttle(() => {
if (containerRef.current) {
containerRef.current.style.maxWidth = `${
window.innerWidth -
containerRef.current.getBoundingClientRect().x -
30
}px`
}
}, 14)
setMaxWidth()
useEffect(() => {
const setMaxWidth = throttle(() => {
if (containerRef.current) {
containerRef.current.style.maxWidth = `${
window.innerWidth -
containerRef.current.getBoundingClientRect().x -
30
}px`
}
}, 14)
setMaxWidth()

window.addEventListener('resize', setMaxWidth)
return () => {
window.removeEventListener('resize', setMaxWidth)
}
}, [])
window.addEventListener('resize', setMaxWidth)
return () => {
window.removeEventListener('resize', setMaxWidth)
}
}, [])

return (
<As className={clsxm('st-toc z-[3]', 'relative font-sans', className)}>
<TocTree
$headings={$headings}
containerRef={containerRef}
className={clsxm('absolute max-h-[75vh]', treeClassName)}
accessory={accessory}
/>
{children}
</As>
)
}
return (
<As className={clsxm('st-toc z-[3]', 'relative font-sans', className)}>
<TocTree
$headings={$headings}
containerRef={containerRef}
className={clsxm('absolute max-h-[75vh]', treeClassName)}
accessory={accessory}
/>
{children}
</As>
)
},
)
TocAside.displayName = 'TocAside'
9 changes: 0 additions & 9 deletions src/components/widgets/toc/TocItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,6 @@ export const TocItem: FC<{

const $ref = useRef<HTMLAnchorElement>(null)

// useEffect(() => {
// if (!active) {
// return
// }
// if (!getIsInteractive()) return
// const state = history.state
// history.replaceState(state, '', `#${anchorId}`)
// }, [active, anchorId])

useEffect(() => {
if (active) {
$ref.current?.scrollIntoView({ behavior: 'smooth' })
Expand Down
26 changes: 25 additions & 1 deletion src/styles/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
background-size: 0% 1.5px;
background-repeat: no-repeat;
/* NOTE: this won't work with background images */
text-shadow: 0.05em 0 theme(colors.base-100),
text-shadow:
0.05em 0 theme(colors.base-100),
-0.05em 0 theme(colors.base-100);
transition: all 500ms ease;

Expand Down Expand Up @@ -114,3 +115,26 @@
}
}
}

.mask-both {
mask-image: linear-gradient(
rgba(255, 255, 255, 0) 0%,
rgb(255, 255, 255) 10%,
rgb(255, 255, 255) 90%,
rgba(255, 255, 255, 0) 100%
);
}

.mask-b {
mask-image: linear-gradient(
rgb(255, 255, 255) 90%,
rgba(255, 255, 255, 0) 100%
);
}

.mask-t {
mask-image: linear-gradient(
rgba(255, 255, 255, 0) 0%,
rgb(255, 255, 255) 10%
);
}

1 comment on commit 0c3a6eb

@vercel
Copy link

@vercel vercel bot commented on 0c3a6eb Jul 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

shiro – ./

springtide.vercel.app
innei.in
shiro-innei.vercel.app
shiro-git-main-innei.vercel.app

Please sign in to comment.