@anmiles/theme-switcher 1.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nycrc.json +27 -0
- package/CHANGELOG.md +10 -1
- package/README.md +43 -43
- package/cspell.json +18 -0
- package/dist/theme-switcher-2.0.0.js +307 -0
- package/dist/theme-switcher-2.0.0.min.js +22 -0
- package/eslint.config.mts +16 -0
- package/index.html +38 -0
- package/jest.config.js +13 -15
- package/package.json +56 -36
- package/server.mjs +11 -0
- package/src/__tests__/index.test.tsx +10 -8
- package/src/__tests__/theme.test.ts +1 -1
- package/src/components/App.tsx +13 -11
- package/src/components/Icon.tsx +3 -1
- package/src/components/ThemeSelector.tsx +3 -2
- package/src/components/__tests__/App.test.tsx +47 -48
- package/src/components/__tests__/__snapshots__/App.test.tsx.snap +60 -20
- package/src/components/icons/Dark.tsx +0 -1
- package/src/index.tsx +4 -3
- package/src/lib/__tests__/eventEmitter.test.ts +6 -6
- package/src/lib/eventEmitter.ts +5 -7
- package/src/lib/theme.ts +7 -9
- package/src/providers/__tests__/systemProvider.test.ts +16 -16
- package/src/providers/__tests__/userProvider.test.ts +4 -4
- package/src/providers/systemProvider.ts +1 -3
- package/src/providers/userProvider.ts +1 -3
- package/static/development/index.html +39 -0
- package/static/production/index.html +39 -0
- package/tsconfig.json +7 -11
- package/tsconfig.test.json +1 -1
- package/vite.config.mts +54 -0
- package/.eslintignore +0 -2
- package/.eslintrc.js +0 -10
- package/.vscode/settings.json +0 -6
- package/coverage.config.js +0 -8
- package/dev/index.html +0 -35
- package/dist/theme-switcher-1.0.2.js +0 -2194
- package/dist/theme-switcher-1.0.2.min.js +0 -2
- package/dist/theme-switcher-1.0.2.min.js.LICENSE.txt +0 -9
- package/tsconfig.build.json +0 -7
- package/webpack.config.js +0 -67
package/.nycrc.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"all": true,
|
|
3
|
+
"check-coverage" : true,
|
|
4
|
+
|
|
5
|
+
"statements" : 100,
|
|
6
|
+
"branches" : 100,
|
|
7
|
+
"lines" : 100,
|
|
8
|
+
"functions" : 100,
|
|
9
|
+
|
|
10
|
+
"report-dir" : "./coverage",
|
|
11
|
+
"temp-dir" : "./coverage",
|
|
12
|
+
|
|
13
|
+
"reporter" : [
|
|
14
|
+
"text",
|
|
15
|
+
"html"
|
|
16
|
+
],
|
|
17
|
+
"extension": [
|
|
18
|
+
".js",
|
|
19
|
+
".mjs",
|
|
20
|
+
".cjs",
|
|
21
|
+
".jsx",
|
|
22
|
+
".ts",
|
|
23
|
+
".cts",
|
|
24
|
+
".mts",
|
|
25
|
+
".tsx"
|
|
26
|
+
]
|
|
27
|
+
}
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.0.0](../../tags/v2.0.0) - 2025-05-18
|
|
9
|
+
__(BREAKING) Dropped support for NodeJS 18 (EOL). Minimum required version is now NodeJS 20.__
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Migrated to NodeJS 20.19
|
|
13
|
+
- Migrated to ESLint V9 flat configs
|
|
14
|
+
- Updated dependencies
|
|
15
|
+
- Optimized size of static bundle (5.7Kb vs. 12.3Kb)
|
|
16
|
+
|
|
8
17
|
## [1.0.2](../../tags/v1.0.2) - 2024-12-15
|
|
9
18
|
### Changed
|
|
10
19
|
- Update dist files
|
|
@@ -13,6 +22,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
13
22
|
### Changed
|
|
14
23
|
- First official release
|
|
15
24
|
|
|
16
|
-
##
|
|
25
|
+
## 0.1.0 - 2024-11-30
|
|
17
26
|
### Added
|
|
18
27
|
- First version (not covered by unit tests; not released on NPM for now)
|
package/README.md
CHANGED
|
@@ -9,80 +9,80 @@ Theme switcher for websites
|
|
|
9
9
|
### For React+TS project
|
|
10
10
|
|
|
11
11
|
1. Install package:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
```bash
|
|
13
|
+
npm install @anmiles/theme-switcher
|
|
14
|
+
```
|
|
15
15
|
|
|
16
16
|
2. Import component:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
```ts
|
|
18
|
+
import { ThemeSwitcher } from '@anmiles/theme-switcher';
|
|
19
|
+
```
|
|
20
20
|
|
|
21
21
|
3. Use component:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
```ts
|
|
23
|
+
<ThemeSwitcher float="right" />
|
|
24
|
+
```
|
|
25
|
+
where
|
|
26
|
+
- `float` _(optional)_ - position of icon and dropdown box
|
|
27
27
|
|
|
28
28
|
### For static HTML website
|
|
29
29
|
|
|
30
30
|
1. Install package:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
```bash
|
|
32
|
+
npm install @anmiles/theme-switcher
|
|
33
|
+
```
|
|
34
34
|
|
|
35
35
|
2. Copy all files from `dist` into the target website.
|
|
36
36
|
|
|
37
37
|
4. Create HTML container for theme switcher:
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
```html
|
|
40
|
+
<div class="my-selector"></div>
|
|
41
|
+
```
|
|
42
42
|
|
|
43
43
|
5. Include React library and theme switcher:
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
### Development
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
```html
|
|
48
|
+
<script type="text/javascript" src="https://unpkg.com/react@18.3.1/umd/react.development.js"></script>
|
|
49
|
+
<script type="text/javascript" src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js"></script>
|
|
50
|
+
<script type="text/javascript" src="./theme-switcher-1.0.2.js"></script>
|
|
51
|
+
```
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
### Production
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
```html
|
|
56
|
+
<script type="text/javascript" src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
|
|
57
|
+
<script type="text/javascript" src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
|
|
58
|
+
<script type="text/javascript" src="./theme-switcher-1.0.2.min.js"></script>
|
|
59
|
+
```
|
|
60
60
|
|
|
61
61
|
6. Place theme switcher into container:
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
```html
|
|
64
|
+
<script type="text/javascript">
|
|
65
|
+
new ThemeSwitcher({ float: 'right' })
|
|
66
|
+
.render(document.querySelector('.my-selector'));
|
|
67
|
+
</script>
|
|
68
|
+
```
|
|
69
|
+
where
|
|
70
|
+
- `float` _(optional)_ - position of icon and dropdown box
|
|
71
71
|
|
|
72
72
|
## Usage
|
|
73
73
|
|
|
74
74
|
Use selectors to write theme-specific styles:
|
|
75
75
|
|
|
76
76
|
```css
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
body[data-theme="light"] .selector {
|
|
78
|
+
/* css rules */
|
|
79
|
+
}
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
```css
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
body[data-theme="dark"] .selector {
|
|
84
|
+
/* css rules */
|
|
85
|
+
}
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
Or you can just write default styles for light theme and override them for dark theme using `body[data-theme="dark"]`.
|
package/cspell.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.2",
|
|
3
|
+
"enabled": true,
|
|
4
|
+
"language": "en",
|
|
5
|
+
"minWordLength": 4,
|
|
6
|
+
"enableGlobDot": true,
|
|
7
|
+
"useGitignore": true,
|
|
8
|
+
"ignorePaths": [
|
|
9
|
+
".git",
|
|
10
|
+
"package-lock.json"
|
|
11
|
+
],
|
|
12
|
+
"words": [
|
|
13
|
+
"anmiles",
|
|
14
|
+
"anatoliy",
|
|
15
|
+
"oblaukhov"
|
|
16
|
+
],
|
|
17
|
+
"flagWords": []
|
|
18
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
var ThemeSwitcher = function(exports, require$$0, require$$0$1) {
|
|
2
|
+
"use strict";
|
|
3
|
+
var __vite_style__ = document.createElement("style");
|
|
4
|
+
__vite_style__.textContent = '.themeSwitcher {\n cursor: pointer;\n position: relative;\n}\n\n.themeSwitcher > svg:hover,\n.themeSwitcher li:hover {\n filter: brightness(1.5);\n}\n\n.themeSwitcher svg {\n width: 2em;\n height: 2em;\n stroke: currentColor;\n display: block;\n}\n\n.themeSwitcher ul {\n list-style-type: none;\n position: absolute;\n left: 0;\n margin: 0.5em 0;\n padding: 0;\n gap: 0;\n overflow-y: visible;\n z-index: 1;\n}\n\n.themeSwitcher li {\n padding: 0.5em 1em;\n}\n\n.themeSwitcher[data-float="right"] ul {\n left: auto;\n right: 0;\n}\n\n.themeSwitcher li {\n display: flex;\n align-items: center;\n gap: 0.5em;\n}\n\n.themeSwitcher li svg {\n width: 1.5em;\n height: 1.5em;\n}\n\n.themeSwitcher svg.checked {\n width: 16px;\n height: 13.5px;\n}\n/*$vite$:1*/';
|
|
5
|
+
document.head.appendChild(__vite_style__);
|
|
6
|
+
var jsxRuntime = { exports: {} };
|
|
7
|
+
var reactJsxRuntime_production_min = {};
|
|
8
|
+
/**
|
|
9
|
+
* @license React
|
|
10
|
+
* react-jsx-runtime.production.min.js
|
|
11
|
+
*
|
|
12
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
13
|
+
*
|
|
14
|
+
* This source code is licensed under the MIT license found in the
|
|
15
|
+
* LICENSE file in the root directory of this source tree.
|
|
16
|
+
*/
|
|
17
|
+
var hasRequiredReactJsxRuntime_production_min;
|
|
18
|
+
function requireReactJsxRuntime_production_min() {
|
|
19
|
+
if (hasRequiredReactJsxRuntime_production_min) return reactJsxRuntime_production_min;
|
|
20
|
+
hasRequiredReactJsxRuntime_production_min = 1;
|
|
21
|
+
var f = require$$0, k = Symbol.for("react.element"), l = Symbol.for("react.fragment"), m = Object.prototype.hasOwnProperty, n = f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner, p = { key: true, ref: true, __self: true, __source: true };
|
|
22
|
+
function q(c, a, g) {
|
|
23
|
+
var b, d = {}, e = null, h = null;
|
|
24
|
+
void 0 !== g && (e = "" + g);
|
|
25
|
+
void 0 !== a.key && (e = "" + a.key);
|
|
26
|
+
void 0 !== a.ref && (h = a.ref);
|
|
27
|
+
for (b in a) m.call(a, b) && !p.hasOwnProperty(b) && (d[b] = a[b]);
|
|
28
|
+
if (c && c.defaultProps) for (b in a = c.defaultProps, a) void 0 === d[b] && (d[b] = a[b]);
|
|
29
|
+
return { $$typeof: k, type: c, key: e, ref: h, props: d, _owner: n.current };
|
|
30
|
+
}
|
|
31
|
+
reactJsxRuntime_production_min.Fragment = l;
|
|
32
|
+
reactJsxRuntime_production_min.jsx = q;
|
|
33
|
+
reactJsxRuntime_production_min.jsxs = q;
|
|
34
|
+
return reactJsxRuntime_production_min;
|
|
35
|
+
}
|
|
36
|
+
var hasRequiredJsxRuntime;
|
|
37
|
+
function requireJsxRuntime() {
|
|
38
|
+
if (hasRequiredJsxRuntime) return jsxRuntime.exports;
|
|
39
|
+
hasRequiredJsxRuntime = 1;
|
|
40
|
+
{
|
|
41
|
+
jsxRuntime.exports = requireReactJsxRuntime_production_min();
|
|
42
|
+
}
|
|
43
|
+
return jsxRuntime.exports;
|
|
44
|
+
}
|
|
45
|
+
var jsxRuntimeExports = requireJsxRuntime();
|
|
46
|
+
var client = {};
|
|
47
|
+
var hasRequiredClient;
|
|
48
|
+
function requireClient() {
|
|
49
|
+
if (hasRequiredClient) return client;
|
|
50
|
+
hasRequiredClient = 1;
|
|
51
|
+
var m = require$$0$1;
|
|
52
|
+
{
|
|
53
|
+
client.createRoot = m.createRoot;
|
|
54
|
+
client.hydrateRoot = m.hydrateRoot;
|
|
55
|
+
}
|
|
56
|
+
return client;
|
|
57
|
+
}
|
|
58
|
+
var clientExports = requireClient();
|
|
59
|
+
class EventEmitter {
|
|
60
|
+
constructor() {
|
|
61
|
+
this.listeners = {};
|
|
62
|
+
}
|
|
63
|
+
on(event, listener) {
|
|
64
|
+
var _a;
|
|
65
|
+
const listeners = (_a = this.listeners)[event] ?? (_a[event] = []);
|
|
66
|
+
listeners.push(listener);
|
|
67
|
+
}
|
|
68
|
+
off(event, listener) {
|
|
69
|
+
var _a;
|
|
70
|
+
const listeners = (_a = this.listeners)[event] ?? (_a[event] = []);
|
|
71
|
+
listeners.splice(listeners.indexOf(listener), 1);
|
|
72
|
+
}
|
|
73
|
+
emit(event, ...data) {
|
|
74
|
+
var _a;
|
|
75
|
+
(_a = this.listeners[event]) == null ? void 0 : _a.forEach((listener) => {
|
|
76
|
+
listener(...data);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const themes = ["light", "dark"];
|
|
81
|
+
const defaultTheme = "light";
|
|
82
|
+
function isTheme(arg) {
|
|
83
|
+
return typeof arg === "string" && themes.map(String).includes(arg);
|
|
84
|
+
}
|
|
85
|
+
function getThemeName(theme) {
|
|
86
|
+
switch (theme) {
|
|
87
|
+
case "light":
|
|
88
|
+
return "Light";
|
|
89
|
+
case "dark":
|
|
90
|
+
return "Dark";
|
|
91
|
+
case void 0:
|
|
92
|
+
default:
|
|
93
|
+
return "System";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
class SystemProvider extends EventEmitter {
|
|
97
|
+
get() {
|
|
98
|
+
if (!("matchMedia" in window)) {
|
|
99
|
+
return defaultTheme;
|
|
100
|
+
}
|
|
101
|
+
for (const theme of themes) {
|
|
102
|
+
const mediaQueryList = window.matchMedia(`(prefers-color-scheme: ${theme})`);
|
|
103
|
+
if (mediaQueryList.matches) {
|
|
104
|
+
return theme;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return defaultTheme;
|
|
108
|
+
}
|
|
109
|
+
watch() {
|
|
110
|
+
if (!("matchMedia" in window)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
for (const theme of themes) {
|
|
114
|
+
const mediaQueryList = window.matchMedia(`(prefers-color-scheme: ${theme})`);
|
|
115
|
+
mediaQueryList.addEventListener("change", (ev) => {
|
|
116
|
+
if (ev.matches) {
|
|
117
|
+
this.emit("change", theme);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
class UserProvider extends EventEmitter {
|
|
124
|
+
constructor() {
|
|
125
|
+
super(...arguments);
|
|
126
|
+
this.storageKey = "theme";
|
|
127
|
+
}
|
|
128
|
+
get() {
|
|
129
|
+
const theme = localStorage.getItem(this.storageKey);
|
|
130
|
+
return isTheme(theme) ? theme : void 0;
|
|
131
|
+
}
|
|
132
|
+
set(theme) {
|
|
133
|
+
if (theme) {
|
|
134
|
+
localStorage.setItem(this.storageKey, theme);
|
|
135
|
+
} else {
|
|
136
|
+
localStorage.removeItem(this.storageKey);
|
|
137
|
+
}
|
|
138
|
+
this.emit("change", theme);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function Dark() {
|
|
142
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
143
|
+
"svg",
|
|
144
|
+
{
|
|
145
|
+
viewBox: "0 0 100 100",
|
|
146
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
147
|
+
className: "dark",
|
|
148
|
+
strokeWidth: "8",
|
|
149
|
+
strokeLinecap: "round",
|
|
150
|
+
fill: "none",
|
|
151
|
+
children: [
|
|
152
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "50", cy: "50", r: "46", strokeDasharray: "180", transform: "rotate(22.5 50 50)" }),
|
|
153
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "75", cy: "25", r: "46", strokeDasharray: "108 200", transform: "rotate(67.5 75 25)" })
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
function Light() {
|
|
159
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
160
|
+
"svg",
|
|
161
|
+
{
|
|
162
|
+
viewBox: "0 0 100 100",
|
|
163
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
164
|
+
className: "light",
|
|
165
|
+
strokeWidth: "8",
|
|
166
|
+
strokeLinecap: "round",
|
|
167
|
+
fill: "none",
|
|
168
|
+
children: [
|
|
169
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "50", cy: "50", r: "20" }),
|
|
170
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M 50 86 v 10", transform: "rotate(0 50 50)" }),
|
|
171
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M 50 86 v 10", transform: "rotate(90 50 50)" }),
|
|
172
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M 50 86 v 10", transform: "rotate(180 50 50)" }),
|
|
173
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M 50 86 v 10", transform: "rotate(270 50 50)" }),
|
|
174
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M 50 86 v 15", transform: "rotate(45 50 50)" }),
|
|
175
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M 50 86 v 15", transform: "rotate(135 50 50)" }),
|
|
176
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M 50 86 v 15", transform: "rotate(225 50 50)" }),
|
|
177
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M 50 86 v 15", transform: "rotate(315 50 50)" })
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
function System() {
|
|
183
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
184
|
+
"svg",
|
|
185
|
+
{
|
|
186
|
+
viewBox: "0 0 100 100",
|
|
187
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
188
|
+
className: "system",
|
|
189
|
+
strokeWidth: "8",
|
|
190
|
+
strokeLinecap: "round",
|
|
191
|
+
fill: "none",
|
|
192
|
+
children: [
|
|
193
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "50", cy: "50", r: "46" }),
|
|
194
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
195
|
+
"path",
|
|
196
|
+
{
|
|
197
|
+
strokeWidth: "0",
|
|
198
|
+
fill: "currentColor",
|
|
199
|
+
d: "\n M 50,0\n a 50,50,0,1,1,0,100\n Z"
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
function Icon({ theme }) {
|
|
207
|
+
switch (theme) {
|
|
208
|
+
case "light":
|
|
209
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(Light, {});
|
|
210
|
+
case "dark":
|
|
211
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(Dark, {});
|
|
212
|
+
case void 0:
|
|
213
|
+
default:
|
|
214
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(System, {});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function Checked() {
|
|
218
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
219
|
+
"svg",
|
|
220
|
+
{
|
|
221
|
+
viewBox: "0 0 640 540",
|
|
222
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
223
|
+
className: "checked",
|
|
224
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
225
|
+
"path",
|
|
226
|
+
{
|
|
227
|
+
fill: "currentColor",
|
|
228
|
+
d: "\n M 12,370\n a 40,40,0,0,1,56.56,-56.56\n l 130,130\n l 370,-430\n a 40,40,0,0,1,56.56,56.56\n l -398.28,458.28\n a 40,40,0,0,1,-56.56,0\n l -140,-140\n Z"
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
function ThemeSelector({ currentUserTheme, onListItemClick }) {
|
|
235
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("ul", { "data-testid": "theme-selector", children: [...themes, void 0].map((theme) => {
|
|
236
|
+
const themeName = getThemeName(theme);
|
|
237
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
238
|
+
"li",
|
|
239
|
+
{
|
|
240
|
+
"data-testid": `theme-item-${themeName.toLowerCase()}`,
|
|
241
|
+
onClick: () => {
|
|
242
|
+
onListItemClick(theme);
|
|
243
|
+
},
|
|
244
|
+
children: [
|
|
245
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { theme }),
|
|
246
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: themeName }),
|
|
247
|
+
currentUserTheme === theme && /* @__PURE__ */ jsxRuntimeExports.jsx(Checked, {})
|
|
248
|
+
]
|
|
249
|
+
},
|
|
250
|
+
themeName
|
|
251
|
+
);
|
|
252
|
+
}) });
|
|
253
|
+
}
|
|
254
|
+
function App({ float }) {
|
|
255
|
+
const userProvider = require$$0.useMemo(() => new UserProvider(), []);
|
|
256
|
+
const systemProvider = require$$0.useMemo(() => new SystemProvider(), []);
|
|
257
|
+
const [userTheme, setUserTheme] = require$$0.useState(userProvider.get());
|
|
258
|
+
const [systemTheme, setSystemTheme] = require$$0.useState(systemProvider.get());
|
|
259
|
+
const [showList, setShowList] = require$$0.useState(false);
|
|
260
|
+
const theme = userTheme ?? systemTheme;
|
|
261
|
+
require$$0.useEffect(() => {
|
|
262
|
+
document.body.setAttribute("data-theme", theme);
|
|
263
|
+
userProvider.on("change", setUserTheme);
|
|
264
|
+
systemProvider.on("change", setSystemTheme);
|
|
265
|
+
systemProvider.watch();
|
|
266
|
+
}, [theme, userProvider, systemProvider]);
|
|
267
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
268
|
+
"div",
|
|
269
|
+
{
|
|
270
|
+
className: "themeSwitcher",
|
|
271
|
+
"data-testid": "theme-switcher",
|
|
272
|
+
"data-float": float,
|
|
273
|
+
onClick: () => {
|
|
274
|
+
setShowList(!showList);
|
|
275
|
+
},
|
|
276
|
+
children: [
|
|
277
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { theme }),
|
|
278
|
+
!showList ? null : /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
279
|
+
ThemeSelector,
|
|
280
|
+
{
|
|
281
|
+
currentUserTheme: userTheme,
|
|
282
|
+
onListItemClick: (theme2) => {
|
|
283
|
+
userProvider.set(theme2);
|
|
284
|
+
setShowList(false);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
)
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
class Element {
|
|
293
|
+
constructor(props) {
|
|
294
|
+
this.props = props;
|
|
295
|
+
}
|
|
296
|
+
render(parentNode) {
|
|
297
|
+
const root = clientExports.createRoot(parentNode);
|
|
298
|
+
root.render(
|
|
299
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(require$$0.StrictMode, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(App, { ...this.props }) })
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
exports.Element = Element;
|
|
304
|
+
exports.ThemeSwitcher = App;
|
|
305
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
306
|
+
return exports;
|
|
307
|
+
}({}, React, ReactDOM);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
var ThemeSwitcher=function(d,c,T){"use strict";var y=document.createElement("style");y.textContent=`.themeSwitcher{cursor:pointer;position:relative}.themeSwitcher>svg:hover,.themeSwitcher li:hover{filter:brightness(1.5)}.themeSwitcher svg{width:2em;height:2em;stroke:currentColor;display:block}.themeSwitcher ul{list-style-type:none;position:absolute;left:0;margin:.5em 0;padding:0;gap:0;overflow-y:visible;z-index:1}.themeSwitcher li{padding:.5em 1em}.themeSwitcher[data-float=right] ul{left:auto;right:0}.themeSwitcher li{display:flex;align-items:center;gap:.5em}.themeSwitcher li svg{width:1.5em;height:1.5em}.themeSwitcher svg.checked{width:16px;height:13.5px}
|
|
2
|
+
/*$vite$:1*/`,document.head.appendChild(y);var w={exports:{}},m={};/**
|
|
3
|
+
* @license React
|
|
4
|
+
* react-jsx-runtime.production.min.js
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
7
|
+
*
|
|
8
|
+
* This source code is licensed under the MIT license found in the
|
|
9
|
+
* LICENSE file in the root directory of this source tree.
|
|
10
|
+
*/var j;function C(){if(j)return m;j=1;var s=c,e=Symbol.for("react.element"),r=Symbol.for("react.fragment"),n=Object.prototype.hasOwnProperty,i=s.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,g={key:!0,ref:!0,__self:!0,__source:!0};function p(h,o,l){var a,x={},v=null,E=null;l!==void 0&&(v=""+l),o.key!==void 0&&(v=""+o.key),o.ref!==void 0&&(E=o.ref);for(a in o)n.call(o,a)&&!g.hasOwnProperty(a)&&(x[a]=o[a]);if(h&&h.defaultProps)for(a in o=h.defaultProps,o)x[a]===void 0&&(x[a]=o[a]);return{$$typeof:e,type:h,key:v,ref:E,props:x,_owner:i.current}}return m.Fragment=r,m.jsx=p,m.jsxs=p,m}var S;function b(){return S||(S=1,w.exports=C()),w.exports}var t=b(),u={},k;function O(){if(k)return u;k=1;var s=T;return u.createRoot=s.createRoot,u.hydrateRoot=s.hydrateRoot,u}var N=O();class _{constructor(){this.listeners={}}on(e,r){var i;((i=this.listeners)[e]??(i[e]=[])).push(r)}off(e,r){var i;const n=(i=this.listeners)[e]??(i[e]=[]);n.splice(n.indexOf(r),1)}emit(e,...r){var n;(n=this.listeners[e])==null||n.forEach(i=>{i(...r)})}}const f=["light","dark"],R="light";function P(s){return typeof s=="string"&&f.map(String).includes(s)}function I(s){switch(s){case"light":return"Light";case"dark":return"Dark";case void 0:default:return"System"}}class D extends _{get(){if(!("matchMedia"in window))return R;for(const e of f)if(window.matchMedia(`(prefers-color-scheme: ${e})`).matches)return e;return R}watch(){if("matchMedia"in window)for(const e of f)window.matchMedia(`(prefers-color-scheme: ${e})`).addEventListener("change",n=>{n.matches&&this.emit("change",e)})}}class B extends _{constructor(){super(...arguments),this.storageKey="theme"}get(){const e=localStorage.getItem(this.storageKey);return P(e)?e:void 0}set(e){e?localStorage.setItem(this.storageKey,e):localStorage.removeItem(this.storageKey),this.emit("change",e)}}function J(){return t.jsxs("svg",{viewBox:"0 0 100 100",xmlns:"http://www.w3.org/2000/svg",className:"dark",strokeWidth:"8",strokeLinecap:"round",fill:"none",children:[t.jsx("circle",{cx:"50",cy:"50",r:"46",strokeDasharray:"180",transform:"rotate(22.5 50 50)"}),t.jsx("circle",{cx:"75",cy:"25",r:"46",strokeDasharray:"108 200",transform:"rotate(67.5 75 25)"})]})}function U(){return t.jsxs("svg",{viewBox:"0 0 100 100",xmlns:"http://www.w3.org/2000/svg",className:"light",strokeWidth:"8",strokeLinecap:"round",fill:"none",children:[t.jsx("circle",{cx:"50",cy:"50",r:"20"}),t.jsx("path",{d:"M 50 86 v 10",transform:"rotate(0 50 50)"}),t.jsx("path",{d:"M 50 86 v 10",transform:"rotate(90 50 50)"}),t.jsx("path",{d:"M 50 86 v 10",transform:"rotate(180 50 50)"}),t.jsx("path",{d:"M 50 86 v 10",transform:"rotate(270 50 50)"}),t.jsx("path",{d:"M 50 86 v 15",transform:"rotate(45 50 50)"}),t.jsx("path",{d:"M 50 86 v 15",transform:"rotate(135 50 50)"}),t.jsx("path",{d:"M 50 86 v 15",transform:"rotate(225 50 50)"}),t.jsx("path",{d:"M 50 86 v 15",transform:"rotate(315 50 50)"})]})}function W(){return t.jsxs("svg",{viewBox:"0 0 100 100",xmlns:"http://www.w3.org/2000/svg",className:"system",strokeWidth:"8",strokeLinecap:"round",fill:"none",children:[t.jsx("circle",{cx:"50",cy:"50",r:"46"}),t.jsx("path",{strokeWidth:"0",fill:"currentColor",d:`
|
|
11
|
+
M 50,0
|
|
12
|
+
a 50,50,0,1,1,0,100
|
|
13
|
+
Z`})]})}function M({theme:s}){switch(s){case"light":return t.jsx(U,{});case"dark":return t.jsx(J,{});case void 0:default:return t.jsx(W,{})}}function K(){return t.jsx("svg",{viewBox:"0 0 640 540",xmlns:"http://www.w3.org/2000/svg",className:"checked",children:t.jsx("path",{fill:"currentColor",d:`
|
|
14
|
+
M 12,370
|
|
15
|
+
a 40,40,0,0,1,56.56,-56.56
|
|
16
|
+
l 130,130
|
|
17
|
+
l 370,-430
|
|
18
|
+
a 40,40,0,0,1,56.56,56.56
|
|
19
|
+
l -398.28,458.28
|
|
20
|
+
a 40,40,0,0,1,-56.56,0
|
|
21
|
+
l -140,-140
|
|
22
|
+
Z`})})}function Q({currentUserTheme:s,onListItemClick:e}){return t.jsx("ul",{"data-testid":"theme-selector",children:[...f,void 0].map(r=>{const n=I(r);return t.jsxs("li",{"data-testid":`theme-item-${n.toLowerCase()}`,onClick:()=>{e(r)},children:[t.jsx(M,{theme:r}),t.jsx("span",{children:n}),s===r&&t.jsx(K,{})]},n)})})}function L({float:s}){const e=c.useMemo(()=>new B,[]),r=c.useMemo(()=>new D,[]),[n,i]=c.useState(e.get()),[g,p]=c.useState(r.get()),[h,o]=c.useState(!1),l=n??g;return c.useEffect(()=>{document.body.setAttribute("data-theme",l),e.on("change",i),r.on("change",p),r.watch()},[l,e,r]),t.jsxs("div",{className:"themeSwitcher","data-testid":"theme-switcher","data-float":s,onClick:()=>{o(!h)},children:[t.jsx(M,{theme:l}),h?t.jsx(Q,{currentUserTheme:n,onListItemClick:a=>{e.set(a),o(!1)}}):null]})}class A{constructor(e){this.props=e}render(e){N.createRoot(e).render(t.jsx(c.StrictMode,{children:t.jsx(L,{...this.props})}))}}return d.Element=A,d.ThemeSwitcher=L,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"}),d}({},React,ReactDOM);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { configs } from '@anmiles/eslint-config';
|
|
2
|
+
import type { Linter } from 'eslint';
|
|
3
|
+
|
|
4
|
+
export default [
|
|
5
|
+
...configs.base,
|
|
6
|
+
...configs.ts,
|
|
7
|
+
...configs.jest,
|
|
8
|
+
...configs.react,
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
ignores: [
|
|
12
|
+
'coverage/*',
|
|
13
|
+
'dist/*',
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
] as Linter.Config[];
|
package/index.html
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Theme switcher</title>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<link rel="stylesheet" href="https://dev.anmiles.net/index.css" type="text/css">
|
|
7
|
+
<link rel="icon" href="https://dev.anmiles.net/favicon.ico" type="image/x-icon">
|
|
8
|
+
<style type="text/css">
|
|
9
|
+
.theme {
|
|
10
|
+
cursor: pointer;
|
|
11
|
+
float: right;
|
|
12
|
+
}
|
|
13
|
+
</style>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
|
|
17
|
+
<div class="top">
|
|
18
|
+
<div class="theme"></div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div class="box">
|
|
22
|
+
|
|
23
|
+
<p>Merry alone do it burst me songs. Sorry equal charm joy her those folly ham. In they no is many both. Recommend new contented intention improving bed performed age. Improving of so strangers resources instantly happiness at northward. Danger nearer length oppose really add now either. But ask regret eat branch fat garden. Become am he except wishes. Past so at door we walk want such sang. Feeling colonel get her garrets own.</p>
|
|
24
|
+
|
|
25
|
+
<p>Boisterous he on understood attachment as entreaties ye devonshire. In mile an form snug were been sell. Hastened admitted joy nor absolute gay its. Extremely ham any his departure for contained curiosity defective. Way now instrument had eat diminution melancholy expression sentiments stimulated. One built fat you out manor books. Mrs interested now his affronting inquietude contrasted cultivated. Lasting showing expense greater on colonel no.</p>
|
|
26
|
+
|
|
27
|
+
<p>Man request adapted spirits set pressed. Up to denoting subjects sensible feelings it indulged directly. We dwelling elegance do shutters appetite yourself diverted. Our next drew much you with rank. Tore many held age hold rose than our. She literature sentiments any contrasted. Set aware joy sense young now tears china shy.</p>
|
|
28
|
+
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<script type="module">
|
|
32
|
+
import * as ThemeSwitcher from '/src/index.tsx';
|
|
33
|
+
|
|
34
|
+
new ThemeSwitcher.Element({ float: 'right' }).render(document.querySelector('.theme'));
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
package/jest.config.js
CHANGED
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
-
preset
|
|
3
|
-
transform
|
|
4
|
-
'^.+\\.tsx?$'
|
|
5
|
-
'
|
|
6
|
-
|
|
7
|
-
tsconfig : '<rootDir>/tsconfig.test.json',
|
|
8
|
-
},
|
|
9
|
-
],
|
|
2
|
+
preset : 'ts-jest',
|
|
3
|
+
transform: {
|
|
4
|
+
'^.+\\.tsx?$': [ 'ts-jest', {
|
|
5
|
+
tsconfig: './tsconfig.test.json',
|
|
6
|
+
} ],
|
|
10
7
|
},
|
|
11
8
|
|
|
12
|
-
clearMocks
|
|
13
|
-
testEnvironment
|
|
9
|
+
clearMocks : true,
|
|
10
|
+
testEnvironment: 'jsdom',
|
|
14
11
|
|
|
15
|
-
roots
|
|
16
|
-
testMatch
|
|
12
|
+
roots : [ '<rootDir>/src' ],
|
|
13
|
+
testMatch: [ '<rootDir>/src/**/__tests__/*.test.{ts,tsx}' ],
|
|
17
14
|
|
|
18
|
-
collectCoverageFrom
|
|
15
|
+
collectCoverageFrom: [
|
|
19
16
|
'<rootDir>/src/**/*.{ts,tsx}',
|
|
20
17
|
'!<rootDir>/src/**/__tests__/**',
|
|
18
|
+
'!<rootDir>/src/**/__mocks__/**',
|
|
21
19
|
],
|
|
22
20
|
|
|
23
|
-
moduleNameMapper
|
|
24
|
-
'\\.(css|less)$'
|
|
21
|
+
moduleNameMapper: {
|
|
22
|
+
'\\.(css|less)$': '<rootDir>/src/__mocks__/css.ts',
|
|
25
23
|
},
|
|
26
24
|
};
|