feature: add subscription manage

This commit is contained in:
2025-04-30 01:59:14 +08:00
parent 9fdb778330
commit 4301f1dbab
128 changed files with 2286 additions and 740 deletions

View File

@@ -0,0 +1,40 @@
import { useEffect } from 'react';
import { useStateRef } from './use-state-ref.ts';
export interface UseDebouncedSkeletonProps {
minSkeletonDuration?: number;
loading?: boolean;
}
export function useDebouncedSkeleton({
minSkeletonDuration = 100,
loading,
}: UseDebouncedSkeletonProps) {
const [showSkeleton, setShowSkeleton, showSkeletonRef] = useStateRef(loading);
useEffect(() => {
if (loading && !showSkeleton) {
setShowSkeleton(true);
}
if (!loading && showSkeleton) {
const timeout = setTimeout(() => {
if (showSkeletonRef.current) {
setShowSkeleton(false);
}
}, minSkeletonDuration);
return () => {
clearTimeout(timeout);
};
}
}, [
loading,
showSkeleton,
setShowSkeleton,
minSkeletonDuration,
showSkeletonRef,
]);
return {
showSkeleton,
};
}

View File

@@ -0,0 +1,18 @@
import { useCallback, useInsertionEffect, useRef } from 'react';
export function useEvent<
const T extends (
...args: // eslint-disable-next-line @typescript-eslint/no-explicit-any
any[]
) => void,
>(fn: T): T {
const ref = useRef<T | null>(fn);
useInsertionEffect(() => {
ref.current = fn;
}, [fn]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return useCallback((...args: any) => {
const latestFn = ref.current!;
return latestFn(...args);
}, []) as unknown as T;
}

View File

@@ -0,0 +1,19 @@
import * as React from "react"
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
return !!isMobile
}

View File

@@ -0,0 +1,28 @@
import {
type Dispatch,
type RefObject,
type SetStateAction,
useCallback,
useRef,
useState,
} from 'react';
export function useStateRef<T>(
initialValue: T
): [T, Dispatch<SetStateAction<T>>, RefObject<T>] {
const [state, _setState] = useState(initialValue);
const ref = useRef(initialValue);
const setState = useCallback((value: T | ((prev: T) => T)) => {
let nextValue: T;
if (typeof value === 'function') {
nextValue = (value as (prev: T) => T)(ref.current);
} else {
nextValue = value;
}
ref.current = nextValue;
_setState(nextValue);
}, []);
return [state, setState, ref] as const;
}