diff --git a/package.json b/package.json index 5b234dd..186fc3e 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "md5": "^2.3.0", "ofetch": "^1.4.1", "overlayscrollbars": "^2.10.0", - "overlayscrollbars-react": "^0.5.6", "react": "^18.3.1", "react-dom": "^18.3.1", "react-use": "^17.5.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ab4bb3..cf2b0c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,18 +92,12 @@ importers: md5: specifier: ^2.3.0 version: 2.3.0 - motion: - specifier: ^10.18.0 - version: 10.18.0 ofetch: specifier: ^1.4.1 version: 1.4.1 overlayscrollbars: specifier: ^2.10.0 version: 2.10.0 - overlayscrollbars-react: - specifier: ^0.5.6 - version: 0.5.6(overlayscrollbars@2.10.0)(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -1549,24 +1543,6 @@ packages: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true - '@motionone/animation@10.18.0': - resolution: {integrity: sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw==} - - '@motionone/dom@10.18.0': - resolution: {integrity: sha512-bKLP7E0eyO4B2UaHBBN55tnppwRnaE3KFfh3Ps9HhnAkar3Cb69kUCJY9as8LrccVYKgHA+JY5dOQqJLOPhF5A==} - - '@motionone/easing@10.18.0': - resolution: {integrity: sha512-VcjByo7XpdLS4o9T8t99JtgxkdMcNWD3yHU/n6CLEz3bkmKDRZyYQ/wmSf6daum8ZXqfUAgFeCZSpJZIMxaCzg==} - - '@motionone/generators@10.18.0': - resolution: {integrity: sha512-+qfkC2DtkDj4tHPu+AFKVfR/C30O1vYdvsGYaR13W/1cczPrrcjdvYCj0VLFuRMN+lP1xvpNZHCRNM4fBzn1jg==} - - '@motionone/types@10.17.1': - resolution: {integrity: sha512-KaC4kgiODDz8hswCrS0btrVrzyU2CSQKO7Ps90ibBVSQmjkrt2teqta6/sOG59v7+dPnKMAg13jyqtMKV2yJ7A==} - - '@motionone/utils@10.18.0': - resolution: {integrity: sha512-3XVF7sgyTSI2KWvTf6uLlBJ5iAgRgmvp3bpuOiQJvInd4nZ19ET8lX5unn30SlmRH7hXbBbH+Gxd0m0klJ3Xtw==} - '@napi-rs/pinyin-android-arm-eabi@1.7.5': resolution: {integrity: sha512-dnurqJdedbU8D1Ngudf10nXvc4BbVSe4ki9U2LUZoGMDGa069t4c87BHRXvcy2By3YpOozORv9/QTMxAQFVOLg==} engines: {node: '>= 10.0'} @@ -3998,9 +3974,6 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hey-listen@1.0.8: - resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==} - hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} @@ -4635,9 +4608,6 @@ packages: mockdate@3.0.5: resolution: {integrity: sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==} - motion@10.18.0: - resolution: {integrity: sha512-MVAZZmwM/cp77BrNe1TxTMldxRPjwBNHheU5aPToqT4rJdZxLiADk58H+a0al5jKLxkB0OdgNq6DiVn11cjvIQ==} - mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -4804,12 +4774,6 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - overlayscrollbars-react@0.5.6: - resolution: {integrity: sha512-E5To04bL5brn9GVCZ36SnfGanxa2I2MDkWoa4Cjo5wol7l+diAgi4DBc983V7l2nOk/OLJ6Feg4kySspQEGDBw==} - peerDependencies: - overlayscrollbars: ^2.0.0 - react: '>=16.8.0' - overlayscrollbars@2.10.0: resolution: {integrity: sha512-diNMeEafWTE0A4GJfwRpdBp2rE/BEvrhptBdBcDu8/UeytWcdCy9Td8tZWnztJeJ26f8/uHCWfPnPUC/dtgJdw==} @@ -7569,41 +7533,6 @@ snapshots: - encoding - supports-color - '@motionone/animation@10.18.0': - dependencies: - '@motionone/easing': 10.18.0 - '@motionone/types': 10.17.1 - '@motionone/utils': 10.18.0 - tslib: 2.8.0 - - '@motionone/dom@10.18.0': - dependencies: - '@motionone/animation': 10.18.0 - '@motionone/generators': 10.18.0 - '@motionone/types': 10.17.1 - '@motionone/utils': 10.18.0 - hey-listen: 1.0.8 - tslib: 2.8.0 - - '@motionone/easing@10.18.0': - dependencies: - '@motionone/utils': 10.18.0 - tslib: 2.8.0 - - '@motionone/generators@10.18.0': - dependencies: - '@motionone/types': 10.17.1 - '@motionone/utils': 10.18.0 - tslib: 2.8.0 - - '@motionone/types@10.17.1': {} - - '@motionone/utils@10.18.0': - dependencies: - '@motionone/types': 10.17.1 - hey-listen: 1.0.8 - tslib: 2.8.0 - '@napi-rs/pinyin-android-arm-eabi@1.7.5': optional: true @@ -10427,8 +10356,6 @@ snapshots: dependencies: function-bind: 1.1.2 - hey-listen@1.0.8: {} - hookable@5.5.3: {} hosted-git-info@2.8.9: {} @@ -11045,13 +10972,6 @@ snapshots: mockdate@3.0.5: {} - motion@10.18.0: - dependencies: - '@motionone/animation': 10.18.0 - '@motionone/dom': 10.18.0 - '@motionone/types': 10.17.1 - '@motionone/utils': 10.18.0 - mri@1.2.0: {} mrmime@2.0.0: {} @@ -11303,11 +11223,6 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - overlayscrollbars-react@0.5.6(overlayscrollbars@2.10.0)(react@18.3.1): - dependencies: - overlayscrollbars: 2.10.0 - react: 18.3.1 - overlayscrollbars@2.10.0: {} p-limit@2.3.0: diff --git a/src/components/column/card.tsx b/src/components/column/card.tsx index 74ecbfa..9274292 100644 --- a/src/components/column/card.tsx +++ b/src/components/column/card.tsx @@ -159,7 +159,7 @@ function NewsCard({ id, setHandleRef }: NewsCardProps) { options={{ overflow: { x: "hidden" }, }} - defer={false} + defer >
{!!data?.items?.length && (sources[id].type === "hottest" ? : )} diff --git a/src/components/common/overlay-scrollbar.tsx b/src/components/common/overlay-scrollbar/index.tsx similarity index 65% rename from src/components/common/overlay-scrollbar.tsx rename to src/components/common/overlay-scrollbar/index.tsx index 9515a35..789d7cc 100644 --- a/src/components/common/overlay-scrollbar.tsx +++ b/src/components/common/overlay-scrollbar/index.tsx @@ -1,9 +1,10 @@ -import type { UseOverlayScrollbarsParams } from "overlayscrollbars-react" -import { useOverlayScrollbars } from "overlayscrollbars-react" import type { HTMLProps, PropsWithChildren } from "react" import { defu } from "defu" import { useMount } from "react-use" +import { useOverlayScrollbars } from "./useOverlayScrollbars" +import type { UseOverlayScrollbarsParams } from "./useOverlayScrollbars" import { goToTopAtom } from "~/atoms" +import "./style.css" type Props = HTMLProps & UseOverlayScrollbarsParams const defaultScrollbarParams: UseOverlayScrollbarsParams = { @@ -15,7 +16,7 @@ const defaultScrollbarParams: UseOverlayScrollbarsParams = { defer: true, } -export function OverlayScrollbar({ disabled, children, options, events, defer, ...props }: PropsWithChildren) { +export function OverlayScrollbar({ disabled, children, options, events, defer, className, ...props }: PropsWithChildren) { const ref = useRef(null) const scrollbarParams = useMemo(() => defu >({ options, @@ -23,7 +24,7 @@ export function OverlayScrollbar({ disabled, children, options, events, defer, . defer, }, defaultScrollbarParams), [options, events, defer]) - const [initialize] = useOverlayScrollbars(scrollbarParams) + const [initialize, instance] = useOverlayScrollbars(scrollbarParams) useMount(() => { if (!disabled) { @@ -37,15 +38,25 @@ export function OverlayScrollbar({ disabled, children, options, events, defer, . } }) + useEffect(() => { + if (ref.current) { + if (instance && instance?.state().destroyed) { + ref.current.classList.remove("scrollbar-hidden") + } else { + ref.current.classList.add("scrollbar-hidden") + } + } + }, [instance]) + return ( -
+
{/* 只能有一个 element */}
{children}
) } -export function GlobalOverlayScrollbar({ children, ...props }: PropsWithChildren>) { +export function GlobalOverlayScrollbar({ children, className, ...props }: PropsWithChildren>) { const ref = useRef(null) const lastTrigger = useRef(0) const timer = useRef() @@ -67,7 +78,7 @@ export function GlobalOverlayScrollbar({ children, ...props }: PropsWithChildren ) } }, [setGoToTop]) - const [initialize] = useOverlayScrollbars({ + const [initialize, instance] = useOverlayScrollbars({ options: { scrollbars: { autoHide: "scroll", @@ -76,7 +87,7 @@ export function GlobalOverlayScrollbar({ children, ...props }: PropsWithChildren events: { scroll: (_, e) => onScroll(e), }, - defer: false, + defer: true, }) useMount(() => { @@ -95,8 +106,18 @@ export function GlobalOverlayScrollbar({ children, ...props }: PropsWithChildren } }) + useEffect(() => { + if (ref.current) { + if (instance && instance?.state().destroyed) { + ref.current.classList.remove("scrollbar-hidden") + } else { + ref.current?.classList.add("scrollbar-hidden") + } + } + }, [instance]) + return ( -
+
{children}
) diff --git a/src/components/common/overlay-scrollbar/style.css b/src/components/common/overlay-scrollbar/style.css new file mode 100644 index 0000000..d02a465 --- /dev/null +++ b/src/components/common/overlay-scrollbar/style.css @@ -0,0 +1,12 @@ +::-webkit-scrollbar-thumb { + border-radius: 8px; + -webkit-border-radius: 8px; +} + +.scrollbar-hidden { + scrollbar-width: none; +} +.scrollbar-hidden::-webkit-scrollbar { + width: 0px; + height: 0px; +} \ No newline at end of file diff --git a/src/components/common/overlay-scrollbar/useOverlayScrollbars.ts b/src/components/common/overlay-scrollbar/useOverlayScrollbars.ts new file mode 100644 index 0000000..b0890a8 --- /dev/null +++ b/src/components/common/overlay-scrollbar/useOverlayScrollbars.ts @@ -0,0 +1,152 @@ +import type { ComponentPropsWithoutRef, ElementRef, ElementType, ForwardedRef } from "react" +import { useEffect, useMemo, useRef } from "react" +import { OverlayScrollbars } from "overlayscrollbars" +import type { EventListeners, InitializationTarget, PartialOptions } from "overlayscrollbars" + +type OverlayScrollbarsComponentBaseProps = + ComponentPropsWithoutRef & { + /** Tag of the root element. */ + element?: T + /** OverlayScrollbars options. */ + options?: PartialOptions | false | null + /** OverlayScrollbars events. */ + events?: EventListeners | false | null + /** Whether to defer the initialization to a point in time when the browser is idle. (or to the next frame if `window.requestIdleCallback` is not supported) */ + defer?: boolean | IdleRequestOptions + } + +type OverlayScrollbarsComponentProps = + OverlayScrollbarsComponentBaseProps & { + ref?: ForwardedRef> + } + +interface OverlayScrollbarsComponentRef { + /** Returns the OverlayScrollbars instance or null if not initialized. */ + osInstance: () => OverlayScrollbars | null + /** Returns the root element. */ + getElement: () => ElementRef | null +} + +type Defer = [ + requestDefer: (callback: () => any, options?: OverlayScrollbarsComponentProps["defer"]) => void, + cancelDefer: () => void, +] + +export interface UseOverlayScrollbarsParams { + /** OverlayScrollbars options. */ + options?: OverlayScrollbarsComponentProps["options"] + /** OverlayScrollbars events. */ + events?: OverlayScrollbarsComponentProps["events"] + /** Whether to defer the initialization to a point in time when the browser is idle. (or to the next frame if `window.requestIdleCallback` is not supported) */ + defer?: OverlayScrollbarsComponentProps["defer"] +} + +export type UseOverlayScrollbarsInitialization = (target: InitializationTarget) => void + +export type UseOverlayScrollbarsInstance = () => ReturnType< + OverlayScrollbarsComponentRef["osInstance"] +> + +function createDefer(): Defer { + let idleId: number + let rafId: number + const wnd = window + const idleSupported = typeof wnd.requestIdleCallback === "function" + const rAF = wnd.requestAnimationFrame + const cAF = wnd.cancelAnimationFrame + const rIdle = idleSupported ? wnd.requestIdleCallback : rAF + const cIdle = idleSupported ? wnd.cancelIdleCallback : cAF + const clear = () => { + cIdle(idleId) + cAF(rafId) + } + + return [ + (callback, options) => { + clear() + idleId = rIdle( + idleSupported + ? () => { + clear() + // inside idle its best practice to use rAF to change DOM for best performance + rafId = rAF(callback) + } + : callback, + typeof options === "object" ? options : { timeout: 2233 }, + ) + }, + clear, + ] +} + +/** + * Hook for advanced usage of OverlayScrollbars. (When the OverlayScrollbarsComponent is not enough) + * @param params Parameters for customization. + * @returns A tuple with two values: + * The first value is the initialization function, it takes one argument which is the `InitializationTarget`. + * The second value is a function which returns the current OverlayScrollbars instance or `null` if not initialized. + */ +export function useOverlayScrollbars(params?: UseOverlayScrollbarsParams): [UseOverlayScrollbarsInitialization, OverlayScrollbars | null ] { + const { options, events, defer } = params || {} + const [requestDefer, cancelDefer] = useMemo(createDefer, []) + // const instanceRef = useRef>(null) + const [instance, setInstance] = useState>(null) + const deferRef = useRef(defer) + const optionsRef = useRef(options) + const eventsRef = useRef(events) + + useEffect(() => { + deferRef.current = defer + }, [defer]) + + useEffect(() => { + optionsRef.current = options + + if (OverlayScrollbars.valid(instance)) { + instance.options(options || {}, true) + } + }, [options, instance]) + + useEffect(() => { + eventsRef.current = events + + if (OverlayScrollbars.valid(instance)) { + instance.on(events || {}, true) + } + }, [events, instance]) + + useEffect( + () => () => { + cancelDefer() + instance?.destroy() + }, + [cancelDefer, instance, setInstance], + ) + + return useMemo( + () => [ + (target) => { + // if already initialized do nothing + const presentInstance = instance + if (OverlayScrollbars.valid(presentInstance)) { + return + } + + const currDefer = deferRef.current + const currOptions = optionsRef.current || {} + const currEvents = eventsRef.current || {} + const init = () => { + setInstance(OverlayScrollbars(target, currOptions, currEvents)) + } + + if (currDefer) { + requestDefer(init, currDefer) + } else { + init() + } + }, + instance, + ], + [instance, requestDefer], + ) +} diff --git a/src/components/common/search-bar/index.tsx b/src/components/common/search-bar/index.tsx index 58a7a1d..daf125d 100644 --- a/src/components/common/search-bar/index.tsx +++ b/src/components/common/search-bar/index.tsx @@ -86,7 +86,7 @@ export function SearchBar() { placeholder="搜索你想要的" />
- + 没有找到,可以前往 Github 提 issue { diff --git a/vite.config.ts b/vite.config.ts index f06e6a4..165dcbd 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,13 +4,14 @@ import react from "@vitejs/plugin-react-swc" import { TanStackRouterVite } from "@tanstack/router-plugin/vite" import unocss from "unocss/vite" import unimport from "unimport/unplugin" +import dotenv from "dotenv" import nitro from "./nitro.config" import { projectDir } from "./shared/dir" import pwa from "./pwa.config" -// dotenv.config({ -// path: join(projectDir, ".env.server"), -// }) +dotenv.config({ + path: join(projectDir, ".env.server"), +}) export default defineConfig({ resolve: {