chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
@@ -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).
|
||||
@@ -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];
|
||||
@@ -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',
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
);
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user