@code-coaching/vuetiful 0.23.2 → 0.24.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/dist/types/components/atoms/VLightSwitch.vue.d.ts +5 -1
- package/dist/types/services/dark-mode.service.d.ts +13 -13
- package/dist/types/services/index.d.ts +2 -2
- package/dist/types/utils/colors/colors.service.d.ts +69 -0
- package/dist/types/utils/index.d.ts +5 -1
- package/dist/types/utils/theme/theme.service.d.ts +8 -23
- package/dist/types/utils/theme/themes.d.ts +35 -0
- package/dist/vuetiful.es.mjs +447 -146
- package/dist/vuetiful.umd.js +71 -16
- package/package.json +1 -1
- package/src/components/atoms/VLightSwitch.test.ts +61 -12
- package/src/components/atoms/VLightSwitch.vue +13 -19
- package/src/components/molecules/VTabs/VTab.test.ts +21 -0
- package/src/directives/clipboard.test.ts +2 -2
- package/src/services/dark-mode.service.test.ts +58 -210
- package/src/services/dark-mode.service.ts +32 -51
- package/src/services/drawer.service.test.ts +4 -4
- package/src/services/highlight.service.test.ts +3 -3
- package/src/services/index.ts +2 -2
- package/src/services/rail.service.test.ts +2 -2
- package/src/utils/colors/colors.service.ts +293 -0
- package/src/utils/index.ts +5 -1
- package/src/utils/platform/platform.service.test.ts +3 -3
- package/src/utils/theme/callback.test.ts +9 -5
- package/src/utils/theme/remove.test.ts +7 -5
- package/src/utils/theme/theme-switcher.vue +34 -37
- package/src/utils/theme/theme.service.test.ts +160 -58
- package/src/utils/theme/theme.service.ts +139 -78
- package/src/utils/theme/themes.ts +122 -0
- package/dist/types/components/index.test.d.ts +0 -1
- package/dist/types/index.test.d.ts +0 -1
- package/dist/types/utils/index.test.d.ts +0 -1
- package/src/components/index.test.ts +0 -10
- package/src/index.test.ts +0 -26
- package/src/utils/index.test.ts +0 -11
package/package.json
CHANGED
|
@@ -1,20 +1,69 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
|
-
import { expect, test, vi } from 'vitest';
|
|
2
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
3
|
+
import { ref } from 'vue';
|
|
3
4
|
import { VLightSwitch } from '.';
|
|
4
|
-
import { useDarkMode } from '@/services';
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const applyMode = vi.fn();
|
|
7
|
+
const chosenMode = ref('dark');
|
|
8
|
+
const MODE = {
|
|
9
|
+
LIGHT: 'light',
|
|
10
|
+
DARK: 'dark',
|
|
11
|
+
};
|
|
12
|
+
vi.mock('../../services/dark-mode.service', () => ({
|
|
13
|
+
useDarkMode: () => ({
|
|
14
|
+
applyMode,
|
|
15
|
+
chosenMode,
|
|
16
|
+
MODE,
|
|
17
|
+
}),
|
|
18
|
+
}));
|
|
7
19
|
|
|
8
|
-
|
|
20
|
+
describe('VLightSwitch', () => {
|
|
21
|
+
test('toggle dark to light', async () => {
|
|
22
|
+
const wrapper = mount(VLightSwitch);
|
|
23
|
+
await wrapper.trigger('click');
|
|
24
|
+
expect(applyMode).toHaveBeenCalledWith(MODE.LIGHT);
|
|
25
|
+
});
|
|
9
26
|
|
|
10
|
-
test('
|
|
11
|
-
|
|
12
|
-
|
|
27
|
+
test('toggle light to dark', async () => {
|
|
28
|
+
chosenMode.value = 'light';
|
|
29
|
+
const wrapper = mount(VLightSwitch);
|
|
30
|
+
await wrapper.trigger('click');
|
|
31
|
+
expect(applyMode).toHaveBeenCalledWith(MODE.DARK);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('svg dark/light', async () => {
|
|
35
|
+
chosenMode.value = MODE.DARK;
|
|
36
|
+
const wrapper = mount(VLightSwitch);
|
|
37
|
+
const path = wrapper.find('path');
|
|
38
|
+
expect(path.attributes().d).toBe(wrapper.vm.svgPath.moon);
|
|
39
|
+
|
|
40
|
+
chosenMode.value = MODE.LIGHT;
|
|
41
|
+
await wrapper.vm.$nextTick();
|
|
42
|
+
expect(path.attributes().d).toBe(wrapper.vm.svgPath.sun);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('keydown Enter', async () => {
|
|
46
|
+
const wrapper = mount(VLightSwitch);
|
|
47
|
+
const preventDefaultSpy = vi.fn();
|
|
48
|
+
const event = { code: 'Enter', preventDefault: preventDefaultSpy };
|
|
49
|
+
await wrapper.trigger('keydown', event);
|
|
50
|
+
expect(applyMode).toHaveBeenCalledWith(MODE.LIGHT);
|
|
51
|
+
expect(preventDefaultSpy).toHaveBeenCalled();
|
|
52
|
+
});
|
|
13
53
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
54
|
+
test('keydown Space', async () => {
|
|
55
|
+
const wrapper = mount(VLightSwitch);
|
|
56
|
+
const preventDefaultSpy = vi.fn();
|
|
57
|
+
const event = { code: 'Space', preventDefault: preventDefaultSpy };
|
|
58
|
+
await wrapper.trigger('keydown', event);
|
|
59
|
+
expect(applyMode).toHaveBeenCalledWith(MODE.LIGHT);
|
|
60
|
+
expect(preventDefaultSpy).toHaveBeenCalled();
|
|
61
|
+
});
|
|
18
62
|
|
|
19
|
-
|
|
63
|
+
test('keydown other', async () => {
|
|
64
|
+
applyMode.mockClear();
|
|
65
|
+
const wrapper = mount(VLightSwitch);
|
|
66
|
+
await wrapper.trigger('keydown', { key: 'a' });
|
|
67
|
+
expect(applyMode).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
20
69
|
});
|
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
:class="`lightswitch-track ${classesTrack}`"
|
|
4
4
|
@click="onToggleHandler"
|
|
5
5
|
@keydown="onKeyDown"
|
|
6
|
-
on:keyup
|
|
7
|
-
on:keypress
|
|
8
6
|
role="switch"
|
|
9
7
|
aria-label="Light Switch"
|
|
10
|
-
:aria-checked="
|
|
11
|
-
:title="`Toggle ${
|
|
8
|
+
:aria-checked="chosenMode === MODE.LIGHT"
|
|
9
|
+
:title="`Toggle ${chosenMode === MODE.DARK ? 'Dark' : 'Light'} Mode`"
|
|
12
10
|
tabindex="0"
|
|
13
11
|
>
|
|
14
12
|
<div :class="`lightswitch-thumb ${classesThumb}`">
|
|
@@ -17,7 +15,7 @@
|
|
|
17
15
|
xmlns="http://www.w3.org/2000/svg"
|
|
18
16
|
viewBox="0 0 512 512"
|
|
19
17
|
>
|
|
20
|
-
<path fill="currentColor" :d="
|
|
18
|
+
<path fill="currentColor" :d="chosenMode === MODE.LIGHT ? svgPath.sun : svgPath.moon" />
|
|
21
19
|
</svg>
|
|
22
20
|
</div>
|
|
23
21
|
</div>
|
|
@@ -25,7 +23,7 @@
|
|
|
25
23
|
|
|
26
24
|
<script lang="ts">
|
|
27
25
|
import { CssClasses, useDarkMode } from '@/index';
|
|
28
|
-
import {
|
|
26
|
+
import { ComputedRef, computed, defineComponent } from 'vue';
|
|
29
27
|
|
|
30
28
|
export default defineComponent({
|
|
31
29
|
props: {
|
|
@@ -63,11 +61,7 @@ export default defineComponent({
|
|
|
63
61
|
},
|
|
64
62
|
},
|
|
65
63
|
setup(props, { attrs }) {
|
|
66
|
-
const {
|
|
67
|
-
|
|
68
|
-
onMounted(() => {
|
|
69
|
-
initializeMode();
|
|
70
|
-
});
|
|
64
|
+
const { applyMode, chosenMode, MODE } = useDarkMode();
|
|
71
65
|
|
|
72
66
|
const cTransition = `transition-all duration-[200ms]`;
|
|
73
67
|
const cTrack = 'cursor-pointer';
|
|
@@ -80,9 +74,8 @@ export default defineComponent({
|
|
|
80
74
|
};
|
|
81
75
|
|
|
82
76
|
const onToggleHandler = () => {
|
|
83
|
-
const toggle =
|
|
84
|
-
|
|
85
|
-
setModeCurrent(toggle);
|
|
77
|
+
const toggle = chosenMode.value === MODE.LIGHT ? MODE.DARK : MODE.LIGHT;
|
|
78
|
+
applyMode(toggle);
|
|
86
79
|
};
|
|
87
80
|
|
|
88
81
|
type OnKeyDownEvent = KeyboardEvent & {
|
|
@@ -95,11 +88,11 @@ export default defineComponent({
|
|
|
95
88
|
}
|
|
96
89
|
};
|
|
97
90
|
|
|
98
|
-
const trackBg = computed(() => (
|
|
99
|
-
const thumbBg = computed(() => (
|
|
100
|
-
const thumbPosition = computed(() => (
|
|
91
|
+
const trackBg = computed(() => (chosenMode.value === MODE.LIGHT ? props.bgLight : props.bgDark));
|
|
92
|
+
const thumbBg = computed(() => (chosenMode.value === MODE.LIGHT ? props.bgDark : props.bgLight));
|
|
93
|
+
const thumbPosition = computed(() => (chosenMode.value === MODE.LIGHT ? 'translate-x-[100%]' : ''));
|
|
101
94
|
const iconFill = computed(() => {
|
|
102
|
-
return
|
|
95
|
+
return chosenMode.value === MODE.LIGHT ? props.textLight : props.textDark;
|
|
103
96
|
});
|
|
104
97
|
|
|
105
98
|
const classesTrack: ComputedRef<string> = computed(() => {
|
|
@@ -119,8 +112,9 @@ export default defineComponent({
|
|
|
119
112
|
svgPath,
|
|
120
113
|
onToggleHandler,
|
|
121
114
|
onKeyDown,
|
|
122
|
-
|
|
115
|
+
chosenMode,
|
|
123
116
|
iconFill,
|
|
117
|
+
MODE,
|
|
124
118
|
};
|
|
125
119
|
},
|
|
126
120
|
});
|
|
@@ -119,4 +119,25 @@ describe('VTab', () => {
|
|
|
119
119
|
'py-2',
|
|
120
120
|
]);
|
|
121
121
|
});
|
|
122
|
+
|
|
123
|
+
test('unstyled', async () => {
|
|
124
|
+
const wrapper = mount({
|
|
125
|
+
template: /*html*/ `
|
|
126
|
+
<v-tabs>
|
|
127
|
+
<template v-slot="tabs">
|
|
128
|
+
<v-tab unstyled data-test="vuetiful">Vuetiful</v-tab>
|
|
129
|
+
</template>
|
|
130
|
+
</v-tabs>
|
|
131
|
+
`,
|
|
132
|
+
components: {
|
|
133
|
+
'v-tabs': VTabs,
|
|
134
|
+
'v-tab': VTab,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const vuetiful = wrapper.find("[data-test='vuetiful']");
|
|
139
|
+
const slotContainer = vuetiful.find("[data-test='slot-container']");
|
|
140
|
+
expect(vuetiful.classes()).toEqual(['vuetiful-tab', 'flex', 'flex-col']);
|
|
141
|
+
expect(slotContainer.classes()).not.toContain('rounded-token');
|
|
142
|
+
});
|
|
122
143
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterEach, describe, expect,
|
|
1
|
+
import { afterEach, describe, expect, vi, test } from 'vitest';
|
|
2
2
|
import { DirectiveBinding } from 'vue';
|
|
3
3
|
import { vClipboard } from '.';
|
|
4
4
|
|
|
@@ -13,7 +13,7 @@ describe('clipboard', () => {
|
|
|
13
13
|
vi.resetAllMocks();
|
|
14
14
|
});
|
|
15
15
|
describe('given the v-clipboard directive is used', () => {
|
|
16
|
-
|
|
16
|
+
test('should copy text to the clipboard on click', () => {
|
|
17
17
|
window.navigator = navigatorMock;
|
|
18
18
|
|
|
19
19
|
const el = document.createElement('div');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterEach, describe, expect,
|
|
1
|
+
import { afterEach, describe, expect, vi, test } from 'vitest';
|
|
2
2
|
|
|
3
3
|
const localStorageMock = {
|
|
4
4
|
getItem: vi.fn(),
|
|
@@ -10,247 +10,95 @@ describe('useDarkMode', () => {
|
|
|
10
10
|
afterEach(() => {
|
|
11
11
|
vi.resetModules();
|
|
12
12
|
});
|
|
13
|
-
describe('getModeUserPrefers', () => {
|
|
14
|
-
describe('given not in browser', () => {
|
|
15
|
-
it('should return default modeUserPrefers', async () => {
|
|
16
|
-
const platform = await import('../utils/platform/platform.service');
|
|
17
|
-
vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
|
|
18
|
-
isBrowser: false,
|
|
19
|
-
});
|
|
20
13
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('given in browser', () => {
|
|
28
|
-
describe('given no modeUserPrefers in localStorage', () => {
|
|
29
|
-
it('should return default modeUserPrefers', async () => {
|
|
30
|
-
const platform = await import('../utils/platform/platform.service');
|
|
31
|
-
vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
|
|
32
|
-
isBrowser: true,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const { useDarkMode } = await import('./dark-mode.service');
|
|
36
|
-
const { getModeUserPrefers } = useDarkMode();
|
|
37
|
-
|
|
38
|
-
window.localStorage = localStorageMock as any;
|
|
39
|
-
vi.spyOn(window.localStorage, 'getItem').mockReturnValueOnce(null);
|
|
40
|
-
|
|
41
|
-
expect(getModeUserPrefers()).toBe(undefined);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
describe('given modeUserPrefers in localStorage', () => {
|
|
45
|
-
it('should return the value', async () => {
|
|
46
|
-
const platform = await import('../utils/platform/platform.service');
|
|
47
|
-
vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
|
|
48
|
-
isBrowser: true,
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const { useDarkMode } = await import('./dark-mode.service');
|
|
52
|
-
const { getModeUserPrefers } = useDarkMode();
|
|
53
|
-
|
|
54
|
-
window.localStorage = localStorageMock as any;
|
|
55
|
-
vi.spyOn(window.localStorage, 'getItem').mockReturnValueOnce('true');
|
|
56
|
-
|
|
57
|
-
expect(getModeUserPrefers()).toBe(true);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe('getModeOsPrefers', () => {
|
|
64
|
-
describe('given not in browser', () => {
|
|
65
|
-
it('should return default modeOsPrefers', async () => {
|
|
66
|
-
const platform = await import('../utils/platform/platform.service');
|
|
67
|
-
vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
|
|
68
|
-
isBrowser: false,
|
|
69
|
-
});
|
|
14
|
+
describe('applyMode', () => {
|
|
15
|
+
test('light mode set', async () => {
|
|
16
|
+
const { useDarkMode } = await import('./dark-mode.service');
|
|
17
|
+
const { applyMode, chosenMode, MODE } = useDarkMode();
|
|
70
18
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
});
|
|
19
|
+
applyMode(MODE.LIGHT);
|
|
20
|
+
expect(chosenMode.value).toBe(MODE.LIGHT);
|
|
21
|
+
expect(document.documentElement.classList.contains('dark')).toBe(false);
|
|
75
22
|
});
|
|
23
|
+
test('dark mode set', async () => {
|
|
24
|
+
const { useDarkMode } = await import('./dark-mode.service');
|
|
25
|
+
const { applyMode, chosenMode, MODE } = useDarkMode();
|
|
76
26
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const platform = await import('../utils/platform/platform.service');
|
|
81
|
-
vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
|
|
82
|
-
isBrowser: true,
|
|
83
|
-
});
|
|
84
|
-
const localStorageSpy = vi.spyOn(window.localStorage, 'setItem');
|
|
85
|
-
|
|
86
|
-
const { useDarkMode } = await import('./dark-mode.service');
|
|
87
|
-
const { getModeOsPrefers, MODE } = useDarkMode();
|
|
88
|
-
|
|
89
|
-
window.matchMedia = matchMediaMock(MODE.LIGHT) as any;
|
|
90
|
-
expect(getModeOsPrefers()).toBe(MODE.LIGHT);
|
|
91
|
-
expect(localStorageSpy).toHaveBeenCalledWith('modeOsPrefers', 'true');
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
describe('given prefers-color-scheme: dark', () => {
|
|
95
|
-
it('should return false', async () => {
|
|
96
|
-
const platform = await import('../utils/platform/platform.service');
|
|
97
|
-
vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
|
|
98
|
-
isBrowser: true,
|
|
99
|
-
});
|
|
100
|
-
const localStorageSpy = vi.spyOn(window.localStorage, 'setItem');
|
|
101
|
-
|
|
102
|
-
const { useDarkMode } = await import('./dark-mode.service');
|
|
103
|
-
const { getModeOsPrefers, MODE } = useDarkMode();
|
|
104
|
-
|
|
105
|
-
window.matchMedia = matchMediaMock(MODE.DARK) as any;
|
|
106
|
-
expect(getModeOsPrefers()).toBe(MODE.DARK);
|
|
107
|
-
expect(localStorageSpy).toHaveBeenCalledWith('modeOsPrefers', 'false');
|
|
108
|
-
});
|
|
109
|
-
});
|
|
27
|
+
applyMode(MODE.DARK);
|
|
28
|
+
expect(chosenMode.value).toBe(MODE.DARK);
|
|
29
|
+
expect(document.documentElement.classList.contains('dark')).toBe(true);
|
|
110
30
|
});
|
|
111
31
|
});
|
|
112
32
|
|
|
113
|
-
describe('
|
|
114
|
-
describe('given
|
|
115
|
-
|
|
116
|
-
const platform = await import('../utils/platform/platform.service');
|
|
117
|
-
vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
|
|
118
|
-
isBrowser: false,
|
|
119
|
-
});
|
|
120
|
-
|
|
33
|
+
describe('autoModeWatcher', () => {
|
|
34
|
+
describe('given mode changes', () => {
|
|
35
|
+
test('should set modeCurrent', async () => {
|
|
121
36
|
const { useDarkMode } = await import('./dark-mode.service');
|
|
122
|
-
const
|
|
123
|
-
expect(getModeAutoPrefers()).toBe(false);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
describe('given in browser', () => {
|
|
128
|
-
describe('given no modeUserPrefers in localStorage', () => {
|
|
129
|
-
it('should return default modeAutoPrefers', async () => {
|
|
130
|
-
const platform = await import('../utils/platform/platform.service');
|
|
131
|
-
vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
|
|
132
|
-
isBrowser: true,
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
const { useDarkMode } = await import('./dark-mode.service');
|
|
136
|
-
const { getModeAutoPrefers } = useDarkMode();
|
|
137
|
-
|
|
138
|
-
window.localStorage = localStorageMock as any;
|
|
139
|
-
vi.spyOn(window.localStorage, 'getItem').mockReturnValueOnce(null);
|
|
37
|
+
const darkMode = useDarkMode();
|
|
140
38
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
});
|
|
39
|
+
const mql = {
|
|
40
|
+
matches: true,
|
|
41
|
+
onchange: () => {
|
|
42
|
+
const mode = mql.matches ? darkMode.MODE.LIGHT : darkMode.MODE.DARK;
|
|
43
|
+
darkMode.applyMode(mode);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
vi.spyOn(window, 'matchMedia').mockReturnValueOnce(mql as any);
|
|
150
47
|
|
|
151
|
-
|
|
152
|
-
const { getModeAutoPrefers } = useDarkMode();
|
|
48
|
+
darkMode.autoModeWatcher();
|
|
153
49
|
|
|
154
|
-
|
|
155
|
-
|
|
50
|
+
mql.matches = false;
|
|
51
|
+
mql.onchange?.();
|
|
52
|
+
expect(darkMode.chosenMode.value).toBe('dark');
|
|
156
53
|
|
|
157
|
-
|
|
158
|
-
|
|
54
|
+
mql.matches = true;
|
|
55
|
+
mql.onchange?.();
|
|
56
|
+
expect(darkMode.chosenMode.value).toBe('light');
|
|
159
57
|
});
|
|
160
58
|
});
|
|
161
59
|
});
|
|
162
60
|
|
|
163
|
-
describe('
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
|
|
168
|
-
isBrowser: false,
|
|
169
|
-
});
|
|
170
|
-
vi.spyOn(window.localStorage, 'setItem');
|
|
171
|
-
|
|
172
|
-
const { useDarkMode } = await import('./dark-mode.service');
|
|
173
|
-
const { setModeUserPrefers, modeUserPrefers } = useDarkMode();
|
|
174
|
-
|
|
175
|
-
setModeUserPrefers(true);
|
|
176
|
-
expect(modeUserPrefers.value).toBe(true);
|
|
177
|
-
expect(window.localStorage.setItem).not.toHaveBeenCalled();
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
describe('given in browser', () => {
|
|
182
|
-
it('should set modeUserPrefers and localStorage', async () => {
|
|
183
|
-
const platform = await import('../utils/platform/platform.service');
|
|
184
|
-
vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
|
|
185
|
-
isBrowser: true,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const { useDarkMode } = await import('./dark-mode.service');
|
|
189
|
-
const { setModeUserPrefers, modeUserPrefers } = useDarkMode();
|
|
190
|
-
|
|
191
|
-
const localStorageSpy = vi.spyOn(window.localStorage, 'setItem');
|
|
61
|
+
describe('applyModeSSR', () => {
|
|
62
|
+
test('should add dark class to html tag', async () => {
|
|
63
|
+
const { useDarkMode } = await import('./dark-mode.service');
|
|
64
|
+
const darkMode = useDarkMode();
|
|
192
65
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
66
|
+
const html = '<html>';
|
|
67
|
+
const mode = darkMode.MODE.DARK;
|
|
68
|
+
const result = darkMode.applyModeSSR(html, mode);
|
|
69
|
+
expect(result).toBe('<html class="dark">');
|
|
197
70
|
});
|
|
198
71
|
});
|
|
199
72
|
|
|
200
|
-
describe('
|
|
201
|
-
|
|
73
|
+
describe('getModeFromCookie', () => {
|
|
74
|
+
test('should return default mode if cookie is not set', async () => {
|
|
202
75
|
const { useDarkMode } = await import('./dark-mode.service');
|
|
203
|
-
const
|
|
76
|
+
const darkMode = useDarkMode();
|
|
204
77
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
expect(
|
|
78
|
+
const cookies = '';
|
|
79
|
+
const result = darkMode.getModeFromCookie(cookies);
|
|
80
|
+
expect(result).toBe('dark');
|
|
208
81
|
});
|
|
209
|
-
|
|
82
|
+
test('should return mode from cookie', async () => {
|
|
210
83
|
const { useDarkMode } = await import('./dark-mode.service');
|
|
211
|
-
const
|
|
84
|
+
const darkMode = useDarkMode();
|
|
212
85
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
expect(
|
|
86
|
+
const cookies = 'vuetiful-mode=light';
|
|
87
|
+
const result = darkMode.getModeFromCookie(cookies);
|
|
88
|
+
expect(result).toBe('light');
|
|
216
89
|
});
|
|
217
90
|
});
|
|
218
91
|
|
|
219
92
|
describe('initializeMode', () => {
|
|
220
|
-
|
|
93
|
+
test('should set mode from cookie', async () => {
|
|
221
94
|
const { useDarkMode } = await import('./dark-mode.service');
|
|
222
|
-
const
|
|
95
|
+
const darkMode = useDarkMode();
|
|
223
96
|
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
expect(currentMode.value).toBe(mode);
|
|
227
|
-
});
|
|
228
|
-
});
|
|
97
|
+
const cookies = 'vuetiful-mode=dark';
|
|
98
|
+
vi.spyOn(document, 'cookie', 'get').mockReturnValueOnce(cookies);
|
|
229
99
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
it('should set modeCurrent', async () => {
|
|
233
|
-
const { useDarkMode } = await import('./dark-mode.service');
|
|
234
|
-
const darkMode = useDarkMode();
|
|
235
|
-
|
|
236
|
-
const mql = {
|
|
237
|
-
matches: true,
|
|
238
|
-
onchange: () => {
|
|
239
|
-
darkMode.setModeCurrent(mql.matches);
|
|
240
|
-
},
|
|
241
|
-
};
|
|
242
|
-
vi.spyOn(window, 'matchMedia').mockReturnValueOnce(mql as any);
|
|
243
|
-
|
|
244
|
-
darkMode.autoModeWatcher();
|
|
245
|
-
|
|
246
|
-
mql.matches = false;
|
|
247
|
-
mql.onchange?.();
|
|
248
|
-
expect(darkMode.currentMode.value).toBe(false);
|
|
249
|
-
|
|
250
|
-
mql.matches = true;
|
|
251
|
-
mql.onchange?.();
|
|
252
|
-
expect(darkMode.currentMode.value).toBe(true);
|
|
253
|
-
});
|
|
100
|
+
darkMode.initializeMode();
|
|
101
|
+
expect(darkMode.chosenMode.value).toBe('dark');
|
|
254
102
|
});
|
|
255
103
|
});
|
|
256
104
|
});
|
|
@@ -1,64 +1,48 @@
|
|
|
1
|
-
import { computed, readonly,
|
|
1
|
+
import { computed, readonly, ref } from 'vue';
|
|
2
2
|
import { usePlatform } from '../utils/platform/platform.service';
|
|
3
3
|
|
|
4
4
|
const { isBrowser } = usePlatform();
|
|
5
5
|
|
|
6
6
|
const MODE = {
|
|
7
|
-
LIGHT:
|
|
8
|
-
DARK:
|
|
7
|
+
LIGHT: 'light',
|
|
8
|
+
DARK: 'dark',
|
|
9
9
|
};
|
|
10
|
+
export type Mode = (typeof MODE)[keyof typeof MODE];
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const isDark = computed(() => currentMode.value === MODE.DARK);
|
|
12
|
+
const defaultMode = MODE.DARK;
|
|
13
|
+
const chosenMode = ref(defaultMode);
|
|
14
|
+
const isDark = computed(() => chosenMode.value === MODE.DARK);
|
|
15
15
|
|
|
16
16
|
const useDarkMode = () => {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const getModeUserPrefers = (): boolean | undefined => {
|
|
25
|
-
if (isBrowser) {
|
|
26
|
-
const mode = localStorage.getItem('modeUserPrefers');
|
|
27
|
-
if (mode !== null) modeUserPrefers.value = mode === 'true';
|
|
17
|
+
const getModeFromCookie = (cookies: string) => {
|
|
18
|
+
const cookie = cookies.split(';').find((c) => c.trim().startsWith('vuetiful-mode='));
|
|
19
|
+
if (cookie) {
|
|
20
|
+
const value = cookie.split('=')[1];
|
|
21
|
+
return value;
|
|
28
22
|
}
|
|
29
|
-
return
|
|
23
|
+
return defaultMode;
|
|
30
24
|
};
|
|
31
25
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (user === undefined) return os;
|
|
36
|
-
return user;
|
|
26
|
+
const applyModeSSR = (html: string, mode: Mode): string => {
|
|
27
|
+
if (mode === MODE.DARK) html = html.replace('<html', '<html class="dark"');
|
|
28
|
+
return html;
|
|
37
29
|
};
|
|
38
30
|
|
|
39
|
-
const
|
|
40
|
-
modeOsPrefers.value = value;
|
|
41
|
-
if (isBrowser) {
|
|
42
|
-
localStorage.setItem('modeOsPrefers', value.toString());
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
const setModeUserPrefers = (value: boolean): void => {
|
|
46
|
-
modeUserPrefers.value = value;
|
|
31
|
+
const initializeMode = () => {
|
|
47
32
|
if (isBrowser) {
|
|
48
|
-
|
|
33
|
+
const mode = getModeFromCookie(document.cookie);
|
|
34
|
+
applyMode(mode);
|
|
49
35
|
}
|
|
50
36
|
};
|
|
51
37
|
|
|
52
|
-
const
|
|
38
|
+
const applyMode = (value: Mode) => {
|
|
53
39
|
const elemHtmlClasses = document.documentElement.classList;
|
|
54
40
|
const classDark = 'dark';
|
|
55
41
|
value === MODE.LIGHT ? elemHtmlClasses.remove(classDark) : elemHtmlClasses.add(classDark);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const mode = getModeAutoPrefers();
|
|
61
|
-
setModeCurrent(mode);
|
|
42
|
+
if (isBrowser) {
|
|
43
|
+
document.cookie = `vuetiful-mode=${value};path=/;max-age=31536000;SameSite=Lax`;
|
|
44
|
+
}
|
|
45
|
+
chosenMode.value = value;
|
|
62
46
|
};
|
|
63
47
|
|
|
64
48
|
const autoModeWatcher = (): void => {
|
|
@@ -66,8 +50,9 @@ const useDarkMode = () => {
|
|
|
66
50
|
const setMode = (value: boolean) => {
|
|
67
51
|
const elemHtmlClasses = document.documentElement.classList;
|
|
68
52
|
const classDark = `dark`;
|
|
69
|
-
value
|
|
70
|
-
|
|
53
|
+
const mode = value ? MODE.LIGHT : MODE.DARK;
|
|
54
|
+
mode === MODE.LIGHT ? elemHtmlClasses.remove(classDark) : elemHtmlClasses.add(classDark);
|
|
55
|
+
applyMode(mode);
|
|
71
56
|
};
|
|
72
57
|
setMode(mql.matches);
|
|
73
58
|
mql.onchange = () => {
|
|
@@ -76,17 +61,13 @@ const useDarkMode = () => {
|
|
|
76
61
|
};
|
|
77
62
|
|
|
78
63
|
return {
|
|
79
|
-
|
|
80
|
-
modeUserPrefers: readonly(modeUserPrefers),
|
|
81
|
-
currentMode: readonly(currentMode),
|
|
64
|
+
chosenMode,
|
|
82
65
|
isDark: readonly(isDark),
|
|
83
|
-
getModeOsPrefers,
|
|
84
|
-
getModeUserPrefers,
|
|
85
|
-
getModeAutoPrefers,
|
|
86
|
-
setModeUserPrefers,
|
|
87
|
-
setModeCurrent,
|
|
88
|
-
autoModeWatcher,
|
|
89
66
|
initializeMode,
|
|
67
|
+
applyMode,
|
|
68
|
+
autoModeWatcher,
|
|
69
|
+
applyModeSSR,
|
|
70
|
+
getModeFromCookie,
|
|
90
71
|
MODE,
|
|
91
72
|
};
|
|
92
73
|
};
|