chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:36 +08:00
commit 8e2a6eb840
10194 changed files with 1593658 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
+7
View File
@@ -0,0 +1,7 @@
# nx-dev-ui-theme
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test nx-dev-ui-theme` to execute the unit tests via [Jest](https://jestjs.io).
+4
View File
@@ -0,0 +1,4 @@
import { baseConfig, reactHooksV7Off } from '../../eslint.config.mjs';
import nx from '@nx/eslint-plugin';
export default [...baseConfig, ...nx.configs['flat/react'], ...reactHooksV7Off];
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
displayName: 'nx-dev-ui-theme',
preset: '../../jest.preset.js',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/nx-dev/ui-theme',
};
+8
View File
@@ -0,0 +1,8 @@
{
"name": "@nx/nx-dev-ui-theme",
"version": "0.0.1",
"type": "commonjs",
"private": true,
"main": "./src/index.ts",
"types": "./src/index.ts"
}
+8
View File
@@ -0,0 +1,8 @@
{
"name": "nx-dev-ui-theme",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "nx-dev/ui-theme/src",
"projectType": "library",
"tags": [],
"targets": {}
}
+2
View File
@@ -0,0 +1,2 @@
export * from './lib/theme-switcher.component';
export * from './lib/theme.provider';
@@ -0,0 +1,104 @@
'use client';
import {
Label,
Listbox,
ListboxButton,
ListboxOption,
ListboxOptions,
Transition,
} from '@headlessui/react';
import {
ComputerDesktopIcon,
MoonIcon,
SunIcon,
} from '@heroicons/react/24/outline';
import cx from 'classnames';
import { ElementType, Fragment } from 'react';
import { Theme, useTheme } from './theme.provider';
export function ThemeSwitcher(): JSX.Element {
const [theme, setTheme] = useTheme();
const themeMap = {
dark: {
className: 'group-hover:text-white',
icon: <MoonIcon className="h-4 w-4" />,
},
light: {
className: 'group-hover:text-yellow-500',
icon: <SunIcon className="h-4 w-4" />,
},
system: {
className: 'group-hover:text-blue-500',
icon: <ComputerDesktopIcon className="h-4 w-4" />,
},
};
const availableThemes: { label: string; value: Theme; icon: ElementType }[] =
[
{
label: 'Light',
value: 'light',
icon: SunIcon,
},
{
label: 'Dark',
value: 'dark',
icon: MoonIcon,
},
{
label: 'System',
value: 'system',
icon: ComputerDesktopIcon,
},
];
return (
<div className="inline-block">
<div className="group relative flex h-full w-full items-center px-1">
<Listbox value={theme} onChange={setTheme}>
<Label className="sr-only">Theme</Label>
<ListboxButton
type="button"
className={cx(
'inline-flex p-2 text-sm font-medium opacity-60 transition-opacity group-hover:opacity-100',
themeMap[theme].className
)}
>
{themeMap[theme].icon}
</ListboxButton>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<ListboxOptions className="absolute -right-10 top-full z-50 mt-2 w-36 origin-top-right divide-y divide-zinc-100 rounded-md bg-white shadow-lg ring-1 ring-black/5 focus:outline-none dark:divide-zinc-800 dark:bg-zinc-900 dark:ring-white/5">
{availableThemes.map((t) => (
<ListboxOption key={t.value} value={t.value} as={Fragment}>
{({ focus, selected }) => (
<li
className={cx(
'flex cursor-pointer items-center gap-2 px-4 py-2 text-sm',
{
'bg-zinc-100 dark:bg-zinc-800/60': focus,
'text-blue-500 dark:text-blue-500': focus || selected,
'text-zinc-700 dark:text-zinc-400': !focus,
}
)}
>
<t.icon aria-hidden="true" className="h-4 w-4 shrink-0" />
{t.label}
</li>
)}
</ListboxOption>
))}
</ListboxOptions>
</Transition>
</Listbox>
</div>
</div>
);
}
@@ -0,0 +1,61 @@
'use client';
import { useMemo, useSyncExternalStore } from 'react';
export type Theme = 'light' | 'dark' | 'system';
export function useTheme(): [Theme, (theme: Theme) => void] {
const [subscribe, getSnapshot, getServerSnapshot] = useMemo(() => {
return [
(notify: () => void) => {
const handleStorageEvent = (evt: StorageEvent) => {
if (evt.key === 'theme') {
changeDocumentClassName();
notify();
}
};
const changeDocumentClassName = () => {
if (
localStorage['theme'] === 'dark' ||
((!localStorage['theme'] || localStorage['theme'] === 'system') &&
window.matchMedia('(prefers-color-scheme: dark)').matches)
) {
document.documentElement.classList.add('dark', 'changing-theme');
} else {
document.documentElement.classList.remove('dark', 'changing-theme');
}
window.setTimeout(() => {
document.documentElement.classList.remove('changing-theme');
});
};
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', changeDocumentClassName);
window.addEventListener('storage', handleStorageEvent);
changeDocumentClassName();
return () => {
mediaQuery.removeEventListener('change', changeDocumentClassName);
window.removeEventListener('storage', handleStorageEvent);
};
},
() => window.localStorage['theme'] ?? 'system',
() => 'system',
];
}, []);
return [
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot),
(theme: Theme) => {
localStorage['theme'] = theme;
// For current window
window.dispatchEvent(
new StorageEvent('storage', {
key: 'theme',
newValue: theme,
})
);
},
];
}
+22
View File
@@ -0,0 +1,22 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}
+23
View File
@@ -0,0 +1,23 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"types": ["node"],
"lib": ["dom"],
"composite": true,
"declaration": true
},
"files": [],
"exclude": [
"jest.config.ts",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}
+19
View File
@@ -0,0 +1,19 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "dist/spec",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"**/*.test.ts",
"**/*.spec.ts",
"**/*.test.tsx",
"**/*.spec.tsx",
"**/*.test.js",
"**/*.spec.js",
"**/*.test.jsx",
"**/*.spec.jsx",
"**/*.d.ts"
]
}