@kids-reporter/routing-ui 0.1.0-alpha.5 → 0.1.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/input.d.ts +6 -2
- package/dist/components/input.d.ts.map +1 -1
- package/dist/components/input.js +30 -9
- package/dist/components/input.js.map +1 -1
- package/dist/header/shared-components.d.ts.map +1 -1
- package/dist/header/shared-components.js +2 -1
- package/dist/header/shared-components.js.map +1 -1
- package/dist/styles.css +11 -0
- package/package.json +6 -5
- package/.prettierignore +0 -17
- package/babel.config.cjs +0 -31
- package/eslint.config.mjs +0 -56
- package/prettier.config.mjs +0 -13
- package/scripts/build.sh +0 -18
- package/src/components/button.tsx +0 -108
- package/src/components/index.tsx +0 -2
- package/src/components/input.tsx +0 -171
- package/src/constants/default-values.tsx +0 -153
- package/src/footer.tsx +0 -149
- package/src/header/desktop-header.tsx +0 -132
- package/src/header/header-context.tsx +0 -82
- package/src/header/index.tsx +0 -104
- package/src/header/is-logged-in-setter.tsx +0 -27
- package/src/header/menu/header-menu-item-group.tsx +0 -37
- package/src/header/menu/header-menu-item.tsx +0 -132
- package/src/header/menu/index.tsx +0 -205
- package/src/header/mobile-back-button-href-setter.tsx +0 -22
- package/src/header/mobile-header.tsx +0 -77
- package/src/header/post-title-setter.tsx +0 -22
- package/src/header/shared-components.tsx +0 -325
- package/src/hooks/index.ts +0 -3
- package/src/hooks/use-is-at-top.ts +0 -23
- package/src/hooks/use-media-query.ts +0 -57
- package/src/hooks/use-scroll-level.ts +0 -52
- package/src/icons/index.tsx +0 -378
- package/src/index.ts +0 -11
- package/src/styles.css +0 -354
- package/src/types/index.ts +0 -10
- package/src/utils/cn.ts +0 -41
- package/src/utils/generate-social-media-config.ts +0 -75
- package/src/utils/index.ts +0 -2
- package/tsconfig.json +0 -33
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import { cva } from 'class-variance-authority'
|
|
3
|
-
import { useEffect, useRef, useState } from 'react'
|
|
4
|
-
|
|
5
|
-
import { Button, Input } from '../components'
|
|
6
|
-
import {
|
|
7
|
-
ClearIcon,
|
|
8
|
-
HamburgerIcon,
|
|
9
|
-
HamburgerIconSmall,
|
|
10
|
-
SearchIcon,
|
|
11
|
-
SettingsIcon,
|
|
12
|
-
} from '../icons'
|
|
13
|
-
import type { MenuItem } from '../types'
|
|
14
|
-
import { cn } from '../utils/cn'
|
|
15
|
-
|
|
16
|
-
const searchFormVariants = cva(
|
|
17
|
-
'ease-in-out h-full transition-all duration-300',
|
|
18
|
-
{
|
|
19
|
-
variants: {
|
|
20
|
-
mode: {
|
|
21
|
-
inline: 'h-11 w-full',
|
|
22
|
-
popover: 'top-0 -right-4 w-66 absolute overflow-hidden opacity-0',
|
|
23
|
-
},
|
|
24
|
-
isSearchOpen: {
|
|
25
|
-
true: '',
|
|
26
|
-
false: '',
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
compoundVariants: [
|
|
30
|
-
{
|
|
31
|
-
mode: 'popover',
|
|
32
|
-
isSearchOpen: true,
|
|
33
|
-
class: 'w-66 pointer-events-auto opacity-100',
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
mode: 'popover',
|
|
37
|
-
isSearchOpen: false,
|
|
38
|
-
class: 'pointer-events-none',
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
}
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
const searchDropdownVariants = cva(
|
|
45
|
-
'rounded-xl mt-2 w-66 ease-in-out h-0 p-0 z-50 bg-neutral-white opacity-0 transition-all duration-200',
|
|
46
|
-
{
|
|
47
|
-
variants: {
|
|
48
|
-
mode: {
|
|
49
|
-
inline: '',
|
|
50
|
-
popover: 'top-12 -right-4 shadow-custom p-4 absolute',
|
|
51
|
-
},
|
|
52
|
-
isSearchOpen: {
|
|
53
|
-
true: '',
|
|
54
|
-
false: '',
|
|
55
|
-
},
|
|
56
|
-
isFocused: {
|
|
57
|
-
true: '',
|
|
58
|
-
false: '',
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
compoundVariants: [
|
|
62
|
-
{
|
|
63
|
-
mode: 'popover',
|
|
64
|
-
isSearchOpen: true,
|
|
65
|
-
isFocused: true,
|
|
66
|
-
class: 'p-4 h-min opacity-100',
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
mode: 'popover',
|
|
70
|
-
isSearchOpen: false,
|
|
71
|
-
class: 'pointer-events-none',
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
mode: 'inline',
|
|
75
|
-
isFocused: true,
|
|
76
|
-
class:
|
|
77
|
-
'translate-y-0 pt-6 mt-0 bg-neutral-transparent h-min w-full opacity-100',
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
mode: 'inline',
|
|
81
|
-
isFocused: false,
|
|
82
|
-
class: '-translate-y-3 pointer-events-none w-full',
|
|
83
|
-
},
|
|
84
|
-
],
|
|
85
|
-
}
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
export function LogoLink({ compactMode = false }: { compactMode?: boolean }) {
|
|
89
|
-
return (
|
|
90
|
-
<a href="/" className="flex items-center" rel="home">
|
|
91
|
-
<img
|
|
92
|
-
src="/assets/images/brand-icon.svg"
|
|
93
|
-
alt="少年報導者 The Reporter for Kids"
|
|
94
|
-
loading="eager"
|
|
95
|
-
className={cn(
|
|
96
|
-
'h-5 tablet:h-6 desktop:h-8 ease-in-out w-auto transition-all duration-500',
|
|
97
|
-
compactMode && 'desktop:h-[26px]'
|
|
98
|
-
)}
|
|
99
|
-
width={293}
|
|
100
|
-
height={32}
|
|
101
|
-
/>
|
|
102
|
-
</a>
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
type SearchInputSectionProps =
|
|
107
|
-
| {
|
|
108
|
-
mode: 'popover'
|
|
109
|
-
isSearchOpen: boolean
|
|
110
|
-
tags: string[]
|
|
111
|
-
searchPlaceholder: string
|
|
112
|
-
}
|
|
113
|
-
| {
|
|
114
|
-
mode: 'inline'
|
|
115
|
-
tags: string[]
|
|
116
|
-
searchPlaceholder: string
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function SearchInputSection(props: SearchInputSectionProps) {
|
|
120
|
-
const ref = useRef<HTMLInputElement>(null)
|
|
121
|
-
const [isFocused, setIsFocused] = useState(false)
|
|
122
|
-
const [searchValue, setSearchValue] = useState('')
|
|
123
|
-
|
|
124
|
-
const mode = props.mode
|
|
125
|
-
const isSearchOpen = mode === 'popover' && props.isSearchOpen
|
|
126
|
-
const tags = props.tags
|
|
127
|
-
const searchPlaceholder = props.searchPlaceholder
|
|
128
|
-
|
|
129
|
-
useEffect(() => {
|
|
130
|
-
if (mode === 'inline') {
|
|
131
|
-
return
|
|
132
|
-
}
|
|
133
|
-
if (isSearchOpen) {
|
|
134
|
-
ref.current?.focus()
|
|
135
|
-
setIsFocused(true)
|
|
136
|
-
document.body.classList.add('no-scroll')
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
setIsFocused(false)
|
|
140
|
-
document.body.classList.remove('no-scroll')
|
|
141
|
-
}, [mode, isSearchOpen])
|
|
142
|
-
|
|
143
|
-
return (
|
|
144
|
-
<div
|
|
145
|
-
onFocus={() => setIsFocused(true)}
|
|
146
|
-
onBlur={() => setIsFocused(false)}
|
|
147
|
-
className={mode === 'inline' ? 'relative w-full' : 'h-11'}
|
|
148
|
-
>
|
|
149
|
-
<form
|
|
150
|
-
role="search"
|
|
151
|
-
method="get"
|
|
152
|
-
action="/search"
|
|
153
|
-
className={searchFormVariants({
|
|
154
|
-
mode,
|
|
155
|
-
isSearchOpen: mode === 'popover' ? isSearchOpen : undefined,
|
|
156
|
-
})}
|
|
157
|
-
>
|
|
158
|
-
<Input
|
|
159
|
-
placeholder={searchPlaceholder}
|
|
160
|
-
name="q"
|
|
161
|
-
title="Search for..."
|
|
162
|
-
aria-label="Search for..."
|
|
163
|
-
className="w-[99%]"
|
|
164
|
-
inputRef={ref}
|
|
165
|
-
onChange={setSearchValue}
|
|
166
|
-
value={searchValue}
|
|
167
|
-
/>
|
|
168
|
-
</form>
|
|
169
|
-
<div
|
|
170
|
-
className={searchDropdownVariants({
|
|
171
|
-
mode,
|
|
172
|
-
isSearchOpen: mode === 'popover' ? isSearchOpen : undefined,
|
|
173
|
-
isFocused,
|
|
174
|
-
})}
|
|
175
|
-
>
|
|
176
|
-
<h3 className="font-bold mb-3 prose-p3 text-neutral-700">熱門搜尋</h3>
|
|
177
|
-
<div className="gap-2.5 flex flex-wrap">
|
|
178
|
-
{tags.map((keyword) => (
|
|
179
|
-
<a
|
|
180
|
-
key={keyword}
|
|
181
|
-
className="px-3 py-1 font-bold cursor-pointer rounded-full bg-neutral-200 prose-p2 text-neutral-900 transition-colors duration-200 hover:bg-red-500 hover:text-neutral-white"
|
|
182
|
-
href={`/search?q=${encodeURIComponent(keyword)}`}
|
|
183
|
-
>
|
|
184
|
-
#{keyword}
|
|
185
|
-
</a>
|
|
186
|
-
))}
|
|
187
|
-
</div>
|
|
188
|
-
</div>
|
|
189
|
-
</div>
|
|
190
|
-
)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export function ActionButtons({
|
|
194
|
-
hideCtaButtons = false,
|
|
195
|
-
tags,
|
|
196
|
-
searchPlaceholder,
|
|
197
|
-
subscribeUrl,
|
|
198
|
-
}: {
|
|
199
|
-
hideCtaButtons?: boolean
|
|
200
|
-
tags: string[]
|
|
201
|
-
searchPlaceholder: string
|
|
202
|
-
subscribeUrl: string
|
|
203
|
-
}) {
|
|
204
|
-
const [isSearchOpen, setIsSearchOpen] = useState(false)
|
|
205
|
-
|
|
206
|
-
const containerRef = useRef<HTMLDivElement>(null)
|
|
207
|
-
const buttonRef = useRef<HTMLButtonElement>(null)
|
|
208
|
-
|
|
209
|
-
useEffect(() => {
|
|
210
|
-
const handleClickOutside = (event: MouseEvent) => {
|
|
211
|
-
const containerElement = containerRef.current
|
|
212
|
-
const buttonElement = buttonRef.current
|
|
213
|
-
if (!containerElement || !buttonElement) return
|
|
214
|
-
if (
|
|
215
|
-
!containerElement.contains(event.target as Node) &&
|
|
216
|
-
!buttonElement.contains(event.target as Node)
|
|
217
|
-
) {
|
|
218
|
-
setIsSearchOpen(false)
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
document.addEventListener('mousedown', handleClickOutside)
|
|
223
|
-
return () => {
|
|
224
|
-
document.removeEventListener('mousedown', handleClickOutside)
|
|
225
|
-
}
|
|
226
|
-
}, [])
|
|
227
|
-
|
|
228
|
-
return (
|
|
229
|
-
<div className="relative flex items-center">
|
|
230
|
-
<div className="mr-6 relative flex items-center" ref={containerRef}>
|
|
231
|
-
{/* CTA Buttons - Base layer */}
|
|
232
|
-
{!hideCtaButtons && !isSearchOpen && (
|
|
233
|
-
<div className="gap-4 flex items-center">
|
|
234
|
-
<Button variant="secondary" size={32} asChild>
|
|
235
|
-
<a href="/about#post">投稿</a>
|
|
236
|
-
</Button>
|
|
237
|
-
<Button variant="primary" size={32} asChild>
|
|
238
|
-
<a href={subscribeUrl} target="_blank" rel="noopener noreferrer">
|
|
239
|
-
訂閱
|
|
240
|
-
</a>
|
|
241
|
-
</Button>
|
|
242
|
-
</div>
|
|
243
|
-
)}
|
|
244
|
-
|
|
245
|
-
<SearchInputSection
|
|
246
|
-
isSearchOpen={isSearchOpen}
|
|
247
|
-
mode="popover"
|
|
248
|
-
tags={tags}
|
|
249
|
-
searchPlaceholder={searchPlaceholder}
|
|
250
|
-
/>
|
|
251
|
-
</div>
|
|
252
|
-
|
|
253
|
-
<button
|
|
254
|
-
className="w-8 h-8 mr-4 flex cursor-pointer items-center justify-center rounded-full text-neutral-600 transition-all duration-200 hover:text-neutral-800"
|
|
255
|
-
aria-label="搜尋"
|
|
256
|
-
onClick={() => setIsSearchOpen(!isSearchOpen)}
|
|
257
|
-
ref={buttonRef}
|
|
258
|
-
>
|
|
259
|
-
{isSearchOpen ? <ClearIcon /> : <SearchIcon />}
|
|
260
|
-
</button>
|
|
261
|
-
<button
|
|
262
|
-
className="w-8 h-8 flex cursor-pointer items-center justify-center rounded-full text-neutral-600 transition-all duration-200 hover:text-neutral-800"
|
|
263
|
-
aria-label="設定"
|
|
264
|
-
>
|
|
265
|
-
<SettingsIcon />
|
|
266
|
-
</button>
|
|
267
|
-
</div>
|
|
268
|
-
)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export function BottomNavigation({
|
|
272
|
-
onHamburgerOverlayOpen,
|
|
273
|
-
menuItems,
|
|
274
|
-
}: {
|
|
275
|
-
onHamburgerOverlayOpen: () => void
|
|
276
|
-
menuItems: MenuItem[]
|
|
277
|
-
}) {
|
|
278
|
-
return (
|
|
279
|
-
<div className="py-2 px-4 flex w-full items-center justify-between border-y border-neutral-border">
|
|
280
|
-
<HamburgerButton onHamburgerOverlayOpen={onHamburgerOverlayOpen} small />
|
|
281
|
-
|
|
282
|
-
{menuItems.reduce((acc, item, index) => {
|
|
283
|
-
return [
|
|
284
|
-
...acc,
|
|
285
|
-
<div key={item.label} className="flex items-center">
|
|
286
|
-
<a
|
|
287
|
-
href={item.href}
|
|
288
|
-
className="py-1 font-bold! h-6 flex items-center prose-p1 text-neutral-900 transition-colors hover:text-red-400"
|
|
289
|
-
>
|
|
290
|
-
{item.label}
|
|
291
|
-
</a>
|
|
292
|
-
</div>,
|
|
293
|
-
...(index < menuItems.length - 1
|
|
294
|
-
? [
|
|
295
|
-
<div
|
|
296
|
-
key={`separator-${index}`}
|
|
297
|
-
className="h-4 mx-2 w-px bg-neutral-border"
|
|
298
|
-
/>,
|
|
299
|
-
]
|
|
300
|
-
: []),
|
|
301
|
-
]
|
|
302
|
-
}, [] as React.ReactNode[])}
|
|
303
|
-
</div>
|
|
304
|
-
)
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
export function HamburgerButton({
|
|
308
|
-
onHamburgerOverlayOpen,
|
|
309
|
-
small = false,
|
|
310
|
-
}: {
|
|
311
|
-
onHamburgerOverlayOpen: () => void
|
|
312
|
-
small?: boolean
|
|
313
|
-
}) {
|
|
314
|
-
return (
|
|
315
|
-
<button
|
|
316
|
-
className={cn(
|
|
317
|
-
'rounded-sm ease-in-out flex cursor-pointer items-center justify-center transition-all duration-300 hover:[&>svg>path:nth-child(1)]:fill-blue-500 hover:[&>svg>path:nth-child(2)]:fill-red-500 hover:[&>svg>path:nth-child(3)]:fill-yellow-500 hover:[&>svg>rect:nth-child(1)]:fill-blue-500 hover:[&>svg>rect:nth-child(2)]:fill-red-500 hover:[&>svg>rect:nth-child(3)]:fill-yellow-500',
|
|
318
|
-
small ? 'w-6 h-6' : 'w-8 h-8'
|
|
319
|
-
)}
|
|
320
|
-
onClick={onHamburgerOverlayOpen}
|
|
321
|
-
>
|
|
322
|
-
{small ? <HamburgerIconSmall /> : <HamburgerIcon />}
|
|
323
|
-
</button>
|
|
324
|
-
)
|
|
325
|
-
}
|
package/src/hooks/index.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import { useEffect, useState } from 'react'
|
|
3
|
-
|
|
4
|
-
const useIsAtTop = (threshold = 35) => {
|
|
5
|
-
const [isAtTop, setIsAtTop] = useState(true)
|
|
6
|
-
|
|
7
|
-
useEffect(() => {
|
|
8
|
-
const checkIsAtTop = () => {
|
|
9
|
-
setIsAtTop(window.scrollY <= threshold)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
checkIsAtTop()
|
|
13
|
-
window.addEventListener('scroll', checkIsAtTop, { passive: true })
|
|
14
|
-
|
|
15
|
-
return () => {
|
|
16
|
-
window.removeEventListener('scroll', checkIsAtTop)
|
|
17
|
-
}
|
|
18
|
-
}, [threshold])
|
|
19
|
-
|
|
20
|
-
return isAtTop
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export default useIsAtTop
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import { useEffect, useLayoutEffect, useState } from 'react'
|
|
3
|
-
|
|
4
|
-
const useIsomorphicLayoutEffect =
|
|
5
|
-
typeof window !== 'undefined' ? useLayoutEffect : useEffect
|
|
6
|
-
|
|
7
|
-
type UseMediaQueryOptions = {
|
|
8
|
-
defaultValue?: boolean
|
|
9
|
-
initializeWithValue?: boolean
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const IS_SERVER = typeof window === 'undefined'
|
|
13
|
-
|
|
14
|
-
function useMediaQuery(
|
|
15
|
-
query: string,
|
|
16
|
-
{
|
|
17
|
-
defaultValue = false,
|
|
18
|
-
initializeWithValue = true,
|
|
19
|
-
}: UseMediaQueryOptions = {}
|
|
20
|
-
): boolean {
|
|
21
|
-
const getMatches = (query: string): boolean => {
|
|
22
|
-
if (IS_SERVER) {
|
|
23
|
-
return defaultValue
|
|
24
|
-
}
|
|
25
|
-
return window.matchMedia(query).matches
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const [matches, setMatches] = useState<boolean>(() => {
|
|
29
|
-
if (initializeWithValue && !IS_SERVER) {
|
|
30
|
-
return getMatches(query)
|
|
31
|
-
}
|
|
32
|
-
return defaultValue
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
// Handles the change event of the media query.
|
|
36
|
-
function handleChange() {
|
|
37
|
-
setMatches(getMatches(query))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
useIsomorphicLayoutEffect(() => {
|
|
41
|
-
const matchMedia = window.matchMedia(query)
|
|
42
|
-
|
|
43
|
-
// Triggered at the first client-side load and if query changes
|
|
44
|
-
handleChange()
|
|
45
|
-
|
|
46
|
-
// Use modern addEventListener/removeEventListener
|
|
47
|
-
matchMedia.addEventListener('change', handleChange)
|
|
48
|
-
|
|
49
|
-
return () => {
|
|
50
|
-
matchMedia.removeEventListener('change', handleChange)
|
|
51
|
-
}
|
|
52
|
-
}, [query])
|
|
53
|
-
|
|
54
|
-
return matches
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export default useMediaQuery
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import throttle from 'lodash/throttle'
|
|
3
|
-
import { useEffect, useState } from 'react'
|
|
4
|
-
|
|
5
|
-
export enum ScrollLevel {
|
|
6
|
-
UP = 'up',
|
|
7
|
-
DOWN_MINI = 'down-mini',
|
|
8
|
-
DOWN_HIDDEN = 'down-hidden',
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
enum ScrollDirection {
|
|
12
|
-
UP = 'up',
|
|
13
|
-
DOWN = 'down',
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const useScrollLevel = ({
|
|
17
|
-
scrollDownDistance = 10,
|
|
18
|
-
throttleThreshold = 200,
|
|
19
|
-
} = {}) => {
|
|
20
|
-
const [scrollLevel, setScrollLevel] = useState<ScrollLevel>(ScrollLevel.UP)
|
|
21
|
-
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
let lastScrollY = window.scrollY
|
|
24
|
-
const updateScrollLevel = throttle(() => {
|
|
25
|
-
if (Math.abs(window.scrollY - lastScrollY) < scrollDownDistance) {
|
|
26
|
-
return
|
|
27
|
-
}
|
|
28
|
-
const direction =
|
|
29
|
-
window.scrollY > lastScrollY ? ScrollDirection.DOWN : ScrollDirection.UP
|
|
30
|
-
let level = ScrollLevel.UP
|
|
31
|
-
if (direction === ScrollDirection.DOWN) {
|
|
32
|
-
level =
|
|
33
|
-
scrollLevel === ScrollLevel.UP
|
|
34
|
-
? ScrollLevel.DOWN_MINI
|
|
35
|
-
: ScrollLevel.DOWN_HIDDEN
|
|
36
|
-
}
|
|
37
|
-
if (level !== scrollLevel) {
|
|
38
|
-
setScrollLevel(level)
|
|
39
|
-
}
|
|
40
|
-
lastScrollY = window.scrollY > 0 ? window.scrollY : 0
|
|
41
|
-
}, throttleThreshold)
|
|
42
|
-
|
|
43
|
-
window.addEventListener('scroll', updateScrollLevel, { passive: true })
|
|
44
|
-
return () => {
|
|
45
|
-
window.removeEventListener('scroll', updateScrollLevel)
|
|
46
|
-
}
|
|
47
|
-
}, [scrollLevel, scrollDownDistance, throttleThreshold])
|
|
48
|
-
|
|
49
|
-
return scrollLevel
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export default useScrollLevel
|