From 459e95edbd4dc2ef08a710d057a310636122f318 Mon Sep 17 00:00:00 2001 From: Innei Date: Sun, 22 Sep 2024 22:55:27 +0800 Subject: [PATCH] refactor: update Signed-off-by: Innei --- eslint.config.mjs | 72 +++++++------- index.html | 33 ++++++- package.json | 3 +- cssAsPlugin.js => plugins/css-plugin.js | 0 plugins/eslint-recursive-sort.js | 60 ++++++++++++ pnpm-lock.yaml | 19 ++++ src/atoms/route.ts | 2 +- src/components/common/ErrorElement.tsx | 2 +- src/components/hooks/common/useBizQuery.ts | 84 ---------------- .../hooks/common/useInputComposition.ts | 60 ------------ src/components/ui/loading.tsx | 2 +- src/{components => }/hooks/common/index.ts | 2 +- src/hooks/common/useDark.ts | 74 ++++++++++++++ src/hooks/common/useInputComposition.ts | 98 +++++++++++++++++++ .../hooks/common/useIsOnline.ts | 0 .../hooks/common/usePrevious.ts | 0 .../hooks/common/useRefValue.ts | 0 src/{components => }/hooks/common/useTitle.ts | 0 src/{utils => lib}/cn.ts | 0 src/{utils => lib}/defineQuery.ts | 0 src/{utils => lib}/dev.tsx | 0 src/lib/dom.ts | 23 +++++ src/{utils => lib}/jotai.ts | 0 src/lib/ns.ts | 11 +++ src/{utils => lib}/query-client.ts | 0 src/{utils => lib}/route-builder.ts | 0 src/pages/(main)/index.tsx | 6 +- src/providers/root-providers.tsx | 6 +- src/providers/setting-sync.tsx | 11 +++ src/router.tsx | 2 +- src/styles/tailwind.css | 48 +++------ src/styles/theme.css | 9 ++ tailwind.config.ts | 35 +++++-- 33 files changed, 426 insertions(+), 236 deletions(-) rename cssAsPlugin.js => plugins/css-plugin.js (100%) create mode 100644 plugins/eslint-recursive-sort.js delete mode 100644 src/components/hooks/common/useBizQuery.ts delete mode 100644 src/components/hooks/common/useInputComposition.ts rename src/{components => }/hooks/common/index.ts (74%) create mode 100644 src/hooks/common/useDark.ts create mode 100644 src/hooks/common/useInputComposition.ts rename src/{components => }/hooks/common/useIsOnline.ts (100%) rename src/{components => }/hooks/common/usePrevious.ts (100%) rename src/{components => }/hooks/common/useRefValue.ts (100%) rename src/{components => }/hooks/common/useTitle.ts (100%) rename src/{utils => lib}/cn.ts (100%) rename src/{utils => lib}/defineQuery.ts (100%) rename src/{utils => lib}/dev.tsx (100%) create mode 100644 src/lib/dom.ts rename src/{utils => lib}/jotai.ts (100%) create mode 100644 src/lib/ns.ts rename src/{utils => lib}/query-client.ts (100%) rename src/{utils => lib}/route-builder.ts (100%) create mode 100644 src/providers/setting-sync.tsx diff --git a/eslint.config.mjs b/eslint.config.mjs index 3dca5f63..8bc8abeb 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,14 +1,19 @@ // @ts-check import { defineConfig } from 'eslint-config-hyoban' +import recursiveSort from './plugins/eslint-recursive-sort' + export default defineConfig( { formatting: false, lessOpinionated: true, - ignores: [], + ignores: [ + 'src/renderer/src/hono.ts', + 'src/hono.ts', + 'packages/shared/src/hono.ts', + 'resources/**', + ], preferESM: false, - react: 'vite', - tailwindCSS: true, }, { settings: { @@ -17,45 +22,36 @@ export default defineConfig( }, }, rules: { - 'package-json/valid-package-def': 'off', - '@eslint-react/no-missing-key': 'warn', - 'no-restricted-syntax': 'off', - 'import/no-anonymous-default-export': 'off', - eqeqeq: 'warn', - 'no-console': 'warn', - '@typescript-eslint/no-unsafe-function-type': 'off', - 'no-empty': 'warn', - '@typescript-eslint/no-empty-object-type': 'warn', - 'unicorn/prefer-query-selector': 0, - 'regexp/no-super-linear-backtracking': 0, - 'regexp/no-useless-assertions': 0, - 'unicorn/no-new-array': 0, - '@typescript-eslint/method-signature-style': 0, - 'unicorn/prefer-code-point': 'warn', - 'unicorn/no-object-as-default-parameter': 'warn', - 'unused-imports/no-unused-vars': 'warn', - '@eslint-react/no-unstable-default-props': 'warn', - 'unicorn/prefer-regexp-test': 'warn', - 'no-unsafe-optional-chaining': 'warn', - 'unicorn/prefer-logical-operator-over-ternary': 'warn', - 'arrow-body-style': 0, - 'unicorn/no-array-callback-reference': 0, - 'prefer-regex-literals': 0, - 'regexp/optimal-quantifier-concatenation': 'warn', - 'unicorn/prefer-string-slice': 0, - 'array-callback-return': 0, - 'regexp/no-unused-capturing-group': 1, - 'unicorn/no-anonymous-default-export': 0, - 'unicorn/no-magic-array-flat-depth': 1, - 'react-refresh/only-export-components': 'error', + 'unicorn/prefer-math-trunc': 'off', + '@eslint-react/no-clone-element': 0, + '@eslint-react/hooks-extra/no-direct-set-state-in-use-effect': 0, + // NOTE: Disable this temporarily + 'react-compiler/react-compiler': 0, + 'no-restricted-syntax': 0, + 'no-restricted-globals': [ + 'error', + { + name: 'location', + message: + "Since you don't use the same router instance in electron and browser, you can't use the global location to get the route info. \n\n" + + 'You can use `useLocaltion` or `getReadonlyRoute` to get the route info.', + }, + ], }, }, - { - files: ['**/*/package.json', 'package.json'], + files: ['**/*.tsx'], + rules: { + '@stylistic/jsx-self-closing-comp': 'error', + }, + }, + { + files: ['locales/**/*.json'], + plugins: { + 'recursive-sort': recursiveSort, + }, rules: { - 'package-json/valid-package-def': 0, - 'package-json/valid-name': 0, + 'recursive-sort/recursive-sort': 'error', }, }, ) diff --git a/index.html b/index.html index 38f38611..3dfc4ae2 100644 --- a/index.html +++ b/index.html @@ -1,10 +1,41 @@ - + Vite App +
diff --git a/package.json b/package.json index 62e26b1f..227f41fa 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "react-dom": "18.3.1", "react-router-dom": "6.26.2", "sonner": "1.5.0", - "tailwind-merge": "2.5.2" + "tailwind-merge": "2.5.2", + "usehooks-ts": "3.1.0" }, "devDependencies": { "@egoist/tailwindcss-icons": "1.8.1", diff --git a/cssAsPlugin.js b/plugins/css-plugin.js similarity index 100% rename from cssAsPlugin.js rename to plugins/css-plugin.js diff --git a/plugins/eslint-recursive-sort.js b/plugins/eslint-recursive-sort.js new file mode 100644 index 00000000..d451554b --- /dev/null +++ b/plugins/eslint-recursive-sort.js @@ -0,0 +1,60 @@ +const sortObjectKeys = (obj) => { + if (typeof obj !== 'object' || obj === null) { + return obj + } + + if (Array.isArray(obj)) { + return obj.map((element) => sortObjectKeys(element)) + } + + return Object.keys(obj) + .sort() + .reduce((acc, key) => { + acc[key] = sortObjectKeys(obj[key]) + return acc + }, {}) +} +/** + * @type {import("eslint").ESLint.Plugin} + */ +export default { + rules: { + 'recursive-sort': { + meta: { + type: 'layout', + fixable: 'code', + }, + create(context) { + return { + Program(node) { + if (context.getFilename().endsWith('.json')) { + const sourceCode = context.getSourceCode() + const text = sourceCode.getText() + + try { + const json = JSON.parse(text) + const sortedJson = sortObjectKeys(json) + const sortedText = JSON.stringify(sortedJson, null, 2) + + if (text.trim() !== sortedText.trim()) { + context.report({ + node, + message: 'JSON keys are not sorted recursively', + fix(fixer) { + return fixer.replaceText(node, sortedText) + }, + }) + } + } catch (error) { + context.report({ + node, + message: `Invalid JSON: ${error.message}`, + }) + } + } + }, + } + }, + }, + }, +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9d762fa..fee8f79f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: tailwind-merge: specifier: 2.5.2 version: 2.5.2 + usehooks-ts: + specifier: 3.1.0 + version: 3.1.0(react@18.3.1) devDependencies: '@egoist/tailwindcss-icons': specifier: 1.8.1 @@ -2184,6 +2187,9 @@ packages: lodash.castarray@4.4.0: resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} @@ -3051,6 +3057,12 @@ packages: '@types/react': optional: true + usehooks-ts@3.1.0: + resolution: {integrity: sha512-bBIa7yUyPhE1BCc0GmR96VU/15l/9gP1Ch5mYdLcFBaFGQsdmXkvjV0TtOqW1yUd6VjIwDunm+flSciCQXujiw==} + engines: {node: '>=16.15.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -5488,6 +5500,8 @@ snapshots: lodash.castarray@4.4.0: {} + lodash.debounce@4.0.8: {} + lodash.isplainobject@4.0.6: {} lodash.merge@4.6.2: {} @@ -6329,6 +6343,11 @@ snapshots: optionalDependencies: '@types/react': 18.3.8 + usehooks-ts@3.1.0(react@18.3.1): + dependencies: + lodash.debounce: 4.0.8 + react: 18.3.1 + util-deprecate@1.0.2: {} validate-npm-package-license@3.0.4: diff --git a/src/atoms/route.ts b/src/atoms/route.ts index a3d9c3ea..a9704d9e 100644 --- a/src/atoms/route.ts +++ b/src/atoms/route.ts @@ -3,7 +3,7 @@ import { selectAtom } from 'jotai/utils' import { useMemo } from 'react' import type { Location, NavigateFunction, Params } from 'react-router-dom' -import { createAtomHooks } from '~/utils/jotai' +import { createAtomHooks } from '~/lib/jotai' interface RouteAtom { params: Readonly> diff --git a/src/components/common/ErrorElement.tsx b/src/components/common/ErrorElement.tsx index ea764e49..9cd3051c 100644 --- a/src/components/common/ErrorElement.tsx +++ b/src/components/common/ErrorElement.tsx @@ -2,7 +2,7 @@ import { repository } from '@pkg' import { useEffect, useRef } from 'react' import { isRouteErrorResponse, useRouteError } from 'react-router-dom' -import { attachOpenInEditor } from '~/utils/dev' +import { attachOpenInEditor } from '~/lib/dev' import { StyledButton } from '../ui' diff --git a/src/components/hooks/common/useBizQuery.ts b/src/components/hooks/common/useBizQuery.ts deleted file mode 100644 index 36d823d8..00000000 --- a/src/components/hooks/common/useBizQuery.ts +++ /dev/null @@ -1,84 +0,0 @@ - -import type { - InfiniteData, - QueryKey, - UseInfiniteQueryOptions, - UseInfiniteQueryResult, - UseQueryOptions, - UseQueryResult, -} from '@tanstack/react-query' -import { useInfiniteQuery, useQuery } from '@tanstack/react-query' -import type { FetchError } from 'ofetch' - -import type { DefinedQuery } from '~/utils/defineQuery' - -// TODO split normal define query and infinite define query for better type checking -export type SafeReturnType = T extends (...args: any[]) => infer R - ? R - : never - -export type CombinedObject = T & U -export function useAuthQuery< - TQuery extends DefinedQuery, - TError = FetchError, - TQueryFnData = Awaited>, - TData = TQueryFnData, ->( - query: TQuery, - options: Omit< - UseQueryOptions, - 'queryKey' | 'queryFn' - > = {}, -): CombinedObject< - UseQueryResult, - { key: TQuery['key']; fn: TQuery['fn'] } -> { - // @ts-expect-error - return Object.assign( - {}, - useQuery({ - queryKey: query.key, - queryFn: query.fn, - enabled: options.enabled !== false, - ...options, - }), - { - key: query.key, - fn: query.fn, - }, - ) -} - -export function useAuthInfiniteQuery< - T extends DefinedQuery, - E = FetchError, - FNR = Awaited>, - R = FNR, ->( - query: T, - options: Omit, 'queryKey' | 'queryFn'>, -): CombinedObject< - UseInfiniteQueryResult, FetchError>, - { key: T['key']; fn: T['fn'] } -> { - // @ts-expect-error - return Object.assign( - {}, - // @ts-expect-error - useInfiniteQuery({ - queryFn: query.fn, - queryKey: query.key, - enabled: options.enabled !== false, - ...options, - }), - { - key: query.key, - fn: query.fn, - }, - ) -} - -/** - * @deprecated use `useAuthQuery` instead - */ -export const useBizQuery = useAuthQuery diff --git a/src/components/hooks/common/useInputComposition.ts b/src/components/hooks/common/useInputComposition.ts deleted file mode 100644 index 7bd6b34b..00000000 --- a/src/components/hooks/common/useInputComposition.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { CompositionEventHandler } from 'react' -import { useCallback, useRef } from 'react' - -export const useInputComposition = ( - props: Pick< - | React.DetailedHTMLProps< - React.InputHTMLAttributes, - HTMLInputElement - > - | React.DetailedHTMLProps< - React.TextareaHTMLAttributes, - HTMLTextAreaElement - >, - 'onKeyDown' | 'onCompositionEnd' | 'onCompositionStart' - >, -) => { - const { onKeyDown, onCompositionStart, onCompositionEnd } = props - - const isCompositionRef = useRef(false) - - const handleCompositionStart: CompositionEventHandler = useCallback( - (e) => { - isCompositionRef.current = true - onCompositionStart?.(e) - }, - [onCompositionStart], - ) - - const handleCompositionEnd: CompositionEventHandler = useCallback( - (e) => { - isCompositionRef.current = false - onCompositionEnd?.(e) - }, - [onCompositionEnd], - ) - - const handleKeyDown: React.KeyboardEventHandler = useCallback( - (e) => { - onKeyDown?.(e) - - if (isCompositionRef.current) { - e.stopPropagation() - return - } - - if (e.key === 'Escape') { - e.currentTarget.blur() - e.preventDefault() - e.stopPropagation() - } - }, - [onKeyDown], - ) - - return { - onCompositionEnd: handleCompositionEnd, - onCompositionStart: handleCompositionStart, - onKeyDown: handleKeyDown, - } -} diff --git a/src/components/ui/loading.tsx b/src/components/ui/loading.tsx index 820ae5a6..141f11db 100644 --- a/src/components/ui/loading.tsx +++ b/src/components/ui/loading.tsx @@ -1,4 +1,4 @@ -import { clsxm } from '~/utils/cn' +import { clsxm } from '~/lib/cn' interface LoadingCircleProps { size: 'small' | 'medium' | 'large' diff --git a/src/components/hooks/common/index.ts b/src/hooks/common/index.ts similarity index 74% rename from src/components/hooks/common/index.ts rename to src/hooks/common/index.ts index 7d21396e..32978cd7 100644 --- a/src/components/hooks/common/index.ts +++ b/src/hooks/common/index.ts @@ -1,4 +1,4 @@ -export * from './useBizQuery' +export * from './useDark' export * from './usePrevious' export * from './useRefValue' export * from './useTitle' diff --git a/src/hooks/common/useDark.ts b/src/hooks/common/useDark.ts new file mode 100644 index 00000000..31c2561e --- /dev/null +++ b/src/hooks/common/useDark.ts @@ -0,0 +1,74 @@ +import { useAtomValue } from 'jotai' +import { atomWithStorage } from 'jotai/utils' +import { useCallback, useLayoutEffect } from 'react' +import { useMediaQuery } from 'usehooks-ts' + +import { nextFrame } from '~/lib/dom' +import { jotaiStore } from '~/lib/jotai' +import { getStorageNS } from '~/lib/ns' + +const useDarkQuery = () => useMediaQuery('(prefers-color-scheme: dark)') +type ColorMode = 'light' | 'dark' | 'system' +const themeAtom = atomWithStorage( + 'color-mode', + 'system' as ColorMode, + undefined, + { + getOnInit: true, + }, +) + +function useDarkWebApp() { + const systemIsDark = useDarkQuery() + const mode = useAtomValue(themeAtom) + return mode === 'dark' || (mode === 'system' && systemIsDark) +} +export const useIsDark = useDarkWebApp + +export const useThemeAtomValue = () => useAtomValue(themeAtom) + +const useSyncThemeWebApp = () => { + const colorMode = useAtomValue(themeAtom) + const systemIsDark = useDarkQuery() + useLayoutEffect(() => { + const realColorMode: Exclude = + colorMode === 'system' ? (systemIsDark ? 'dark' : 'light') : colorMode + document.documentElement.dataset.theme = realColorMode + disableTransition(['[role=switch]>*'])() + }, [colorMode, systemIsDark]) +} + +export const useSyncThemeark = useSyncThemeWebApp + +export const useSetTheme = () => + useCallback((colorMode: ColorMode) => { + jotaiStore.set(themeAtom, colorMode) + }, []) + +function disableTransition(disableTransitionExclude: string[] = []) { + const css = document.createElement('style') + css.append( + document.createTextNode( + ` +*${disableTransitionExclude.map((s) => `:not(${s})`).join('')} { + -webkit-transition: none !important; + -moz-transition: none !important; + -o-transition: none !important; + -ms-transition: none !important; + transition: none !important; +} + `, + ), + ) + document.head.append(css) + + return () => { + // Force restyle + ;(() => window.getComputedStyle(document.body))() + + // Wait for next tick before removing + nextFrame(() => { + css.remove() + }) + } +} diff --git a/src/hooks/common/useInputComposition.ts b/src/hooks/common/useInputComposition.ts new file mode 100644 index 00000000..c8d7b8b3 --- /dev/null +++ b/src/hooks/common/useInputComposition.ts @@ -0,0 +1,98 @@ +import type { CompositionEventHandler } from 'react' +import { useCallback, useEffect, useRef } from 'react' + +type InputElementAttributes = React.DetailedHTMLProps< + React.InputHTMLAttributes, + HTMLInputElement +> +type TextareaElementAttributes = React.DetailedHTMLProps< + React.TextareaHTMLAttributes, + HTMLTextAreaElement +> +export const useInputComposition = ( + props: Pick< + E extends HTMLInputElement + ? InputElementAttributes + : E extends HTMLTextAreaElement + ? TextareaElementAttributes + : never, + 'onKeyDown' | 'onCompositionEnd' | 'onCompositionStart' | 'onKeyDownCapture' + >, +) => { + const { onKeyDown, onCompositionStart, onCompositionEnd } = props + + const isCompositionRef = useRef(false) + + const currentInputTargetRef = useRef(null) + + const handleCompositionStart: CompositionEventHandler = useCallback( + (e) => { + currentInputTargetRef.current = e.target as E + + isCompositionRef.current = true + onCompositionStart?.(e as any) + }, + [onCompositionStart], + ) + + const handleCompositionEnd: CompositionEventHandler = useCallback( + (e) => { + currentInputTargetRef.current = null + isCompositionRef.current = false + onCompositionEnd?.(e as any) + }, + [onCompositionEnd], + ) + + const handleKeyDown: React.KeyboardEventHandler = useCallback( + (e: any) => { + // The keydown event stop emit when the composition is being entered + if (isCompositionRef.current) { + e.stopPropagation() + return + } + onKeyDown?.(e) + + if (e.key === 'Escape') { + e.preventDefault() + e.stopPropagation() + + if (!isCompositionRef.current) { + e.currentTarget.blur() + } + } + }, + [onKeyDown], + ) + + // Register a global capture keydown listener to prevent the radix `useEscapeKeydown` from working + useEffect(() => { + const handleGlobalKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape' && currentInputTargetRef.current) { + e.stopPropagation() + e.preventDefault() + } + } + + document.addEventListener('keydown', handleGlobalKeyDown, { capture: true }) + + return () => { + document.removeEventListener('keydown', handleGlobalKeyDown, { + capture: true, + }) + } + }, []) + + const ret = { + onCompositionEnd: handleCompositionEnd, + onCompositionStart: handleCompositionStart, + onKeyDown: handleKeyDown, + } + Object.defineProperty(ret, 'isCompositionRef', { + value: isCompositionRef, + enumerable: false, + }) + return ret as typeof ret & { + isCompositionRef: typeof isCompositionRef + } +} diff --git a/src/components/hooks/common/useIsOnline.ts b/src/hooks/common/useIsOnline.ts similarity index 100% rename from src/components/hooks/common/useIsOnline.ts rename to src/hooks/common/useIsOnline.ts diff --git a/src/components/hooks/common/usePrevious.ts b/src/hooks/common/usePrevious.ts similarity index 100% rename from src/components/hooks/common/usePrevious.ts rename to src/hooks/common/usePrevious.ts diff --git a/src/components/hooks/common/useRefValue.ts b/src/hooks/common/useRefValue.ts similarity index 100% rename from src/components/hooks/common/useRefValue.ts rename to src/hooks/common/useRefValue.ts diff --git a/src/components/hooks/common/useTitle.ts b/src/hooks/common/useTitle.ts similarity index 100% rename from src/components/hooks/common/useTitle.ts rename to src/hooks/common/useTitle.ts diff --git a/src/utils/cn.ts b/src/lib/cn.ts similarity index 100% rename from src/utils/cn.ts rename to src/lib/cn.ts diff --git a/src/utils/defineQuery.ts b/src/lib/defineQuery.ts similarity index 100% rename from src/utils/defineQuery.ts rename to src/lib/defineQuery.ts diff --git a/src/utils/dev.tsx b/src/lib/dev.tsx similarity index 100% rename from src/utils/dev.tsx rename to src/lib/dev.tsx diff --git a/src/lib/dom.ts b/src/lib/dom.ts new file mode 100644 index 00000000..f2d7902c --- /dev/null +++ b/src/lib/dom.ts @@ -0,0 +1,23 @@ +import type { ReactEventHandler } from "react" + +export const stopPropagation: ReactEventHandler = (e) => e.stopPropagation() + +export const preventDefault: ReactEventHandler = (e) => e.preventDefault() + +export const nextFrame = (fn: (...args: any[]) => any) => { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + fn() + }) + }) +} + +export const getElementTop = (element: HTMLElement) => { + let actualTop = element.offsetTop + let current = element.offsetParent as HTMLElement + while (current !== null) { + actualTop += current.offsetTop + current = current.offsetParent as HTMLElement + } + return actualTop +} diff --git a/src/utils/jotai.ts b/src/lib/jotai.ts similarity index 100% rename from src/utils/jotai.ts rename to src/lib/jotai.ts diff --git a/src/lib/ns.ts b/src/lib/ns.ts new file mode 100644 index 00000000..5bb6f061 --- /dev/null +++ b/src/lib/ns.ts @@ -0,0 +1,11 @@ +const ns = 'app' +export const getStorageNS = (key: string) => `${ns}:${key}` + +export const clearStorage = () => { + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i) + if (key && key.startsWith(ns)) { + localStorage.removeItem(key) + } + } +} diff --git a/src/utils/query-client.ts b/src/lib/query-client.ts similarity index 100% rename from src/utils/query-client.ts rename to src/lib/query-client.ts diff --git a/src/utils/route-builder.ts b/src/lib/route-builder.ts similarity index 100% rename from src/utils/route-builder.ts rename to src/lib/route-builder.ts diff --git a/src/pages/(main)/index.tsx b/src/pages/(main)/index.tsx index 2834835b..312e9a8f 100644 --- a/src/pages/(main)/index.tsx +++ b/src/pages/(main)/index.tsx @@ -1,3 +1,7 @@ export const Component = () => { - return 'Main' + return ( +
+

Main

+
+ ) } diff --git a/src/providers/root-providers.tsx b/src/providers/root-providers.tsx index cdbed172..096c130e 100644 --- a/src/providers/root-providers.tsx +++ b/src/providers/root-providers.tsx @@ -4,9 +4,10 @@ import { Provider } from 'jotai' import type { FC, PropsWithChildren } from 'react' import { Toaster } from '~/components/ui/sonner' -import { jotaiStore } from '~/utils/jotai' -import { queryClient } from '~/utils/query-client' +import { jotaiStore } from '~/lib/jotai' +import { queryClient } from '~/lib/query-client' +import { SettingSync } from './setting-sync' import { StableRouterProvider } from './stable-router-provider' const loadFeatures = () => @@ -23,6 +24,7 @@ export const RootProviders: FC = ({ children }) => ( + {children} diff --git a/src/providers/setting-sync.tsx b/src/providers/setting-sync.tsx new file mode 100644 index 00000000..85620e21 --- /dev/null +++ b/src/providers/setting-sync.tsx @@ -0,0 +1,11 @@ +import { useSyncThemeark } from '~/hooks/common' + +const useUISettingSync = () => { + useSyncThemeark() +} + +export const SettingSync = () => { + useUISettingSync() + + return null +} diff --git a/src/router.tsx b/src/router.tsx index 3e27fcb5..4a157cdf 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -3,7 +3,7 @@ import { createBrowserRouter } from 'react-router-dom' import { App } from './App' import { ErrorElement } from './components/common/ErrorElement' import { NotFound } from './components/common/NotFound' -import { buildGlobRoutes } from './utils/route-builder' +import { buildGlobRoutes } from './lib/route-builder' const globTree = import.meta.glob('./pages/**/*.tsx') const tree = buildGlobRoutes(globTree) diff --git a/src/styles/tailwind.css b/src/styles/tailwind.css index 6c894591..4ac19cbf 100644 --- a/src/styles/tailwind.css +++ b/src/styles/tailwind.css @@ -1,14 +1,8 @@ @tailwind base; @tailwind components; @tailwind utilities; -@tailwind base; -@tailwind components; -@tailwind utilities; html { - font-size: 14px; - line-height: 1.5; - @apply font-sans; } @@ -16,31 +10,12 @@ html body { @apply max-w-screen overflow-x-hidden; } -@media print { - html { - font-size: 12px; - } -} - /* @media (min-width: 2160px) { html { font-size: 15px; } } */ -.prose { - max-width: 100% !important; - font-size: 1.1rem; - - p { - @apply break-words; - } - - figure img { - @apply mb-0 mt-0; - } -} - *:focus { outline: none; } @@ -57,18 +32,6 @@ html body { } } -.animate-ping { - animation: ping 2s cubic-bezier(0, 0, 0.2, 1) infinite; -} - -@keyframes ping { - 75%, - 100% { - transform: scale(1.4); - opacity: 0; - } -} - /* input, textarea { font-size: max(16px, 1rem); @@ -105,3 +68,14 @@ a { background-color: theme(colors.accent); } } + +@layer base { + * { + @apply border-border; + } + body { + font-feature-settings: + 'rlig' 1, + 'calt' 1; + } +} diff --git a/src/styles/theme.css b/src/styles/theme.css index 78dd389f..0840d1c3 100644 --- a/src/styles/theme.css +++ b/src/styles/theme.css @@ -1,3 +1,12 @@ @tailwind base; @layer base { + [data-theme='light'] { + --radius: 0.5rem; + --border: 20 5.9% 90%; + } + + [data-theme='dark'] { + --radius: 0.5rem; + --border: 0 0% 22.1%; + } } diff --git a/tailwind.config.ts b/tailwind.config.ts index 77672187..a6e90215 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -4,13 +4,40 @@ import daisyui from 'daisyui' import { withTV } from 'tailwind-variants/transformer' import type { Config } from 'tailwindcss' -require('./cssAsPlugin') +require('./plugins/css-plugin') const twConfig: Config = { content: ['./src/**/*.{js,jsx,ts,tsx}'], darkMode: ['class', '[data-theme="dark"]'], safelist: [], theme: { + container: { + center: true, + padding: '2rem', + screens: { + '2xl': '1400px', + }, + }, + cursor: { + button: 'var(--cursor-button)', + select: 'var(--cursor-select)', + checkbox: 'var(--cursor-checkbox)', + link: 'var(--cursor-link)', + menu: 'var(--cursor-menu)', + radio: 'var(--cursor-radio)', + switch: 'var(--cursor-switch)', + card: 'var(--cursor-card)', + }, + + colors: { + border: 'hsl(var(--border) / )', + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)', + }, + extend: { fontFamily: { sans: 'system-ui,-apple-system,PingFang SC,"Microsoft YaHei",Segoe UI,Roboto,Helvetica,noto sans sc,hiragino sans gb,"sans-serif",Apple Color Emoji,Segoe UI Emoji,Not Color Emoji', @@ -37,12 +64,6 @@ const twConfig: Config = { maxHeight: { screen: '100vh', }, - - colors: { - themed: { - bg_opacity: 'var(--bg-opacity)', - }, - }, }, },