@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.
Files changed (35) hide show
  1. package/dist/types/components/atoms/VLightSwitch.vue.d.ts +5 -1
  2. package/dist/types/services/dark-mode.service.d.ts +13 -13
  3. package/dist/types/services/index.d.ts +2 -2
  4. package/dist/types/utils/colors/colors.service.d.ts +69 -0
  5. package/dist/types/utils/index.d.ts +5 -1
  6. package/dist/types/utils/theme/theme.service.d.ts +8 -23
  7. package/dist/types/utils/theme/themes.d.ts +35 -0
  8. package/dist/vuetiful.es.mjs +447 -146
  9. package/dist/vuetiful.umd.js +71 -16
  10. package/package.json +1 -1
  11. package/src/components/atoms/VLightSwitch.test.ts +61 -12
  12. package/src/components/atoms/VLightSwitch.vue +13 -19
  13. package/src/components/molecules/VTabs/VTab.test.ts +21 -0
  14. package/src/directives/clipboard.test.ts +2 -2
  15. package/src/services/dark-mode.service.test.ts +58 -210
  16. package/src/services/dark-mode.service.ts +32 -51
  17. package/src/services/drawer.service.test.ts +4 -4
  18. package/src/services/highlight.service.test.ts +3 -3
  19. package/src/services/index.ts +2 -2
  20. package/src/services/rail.service.test.ts +2 -2
  21. package/src/utils/colors/colors.service.ts +293 -0
  22. package/src/utils/index.ts +5 -1
  23. package/src/utils/platform/platform.service.test.ts +3 -3
  24. package/src/utils/theme/callback.test.ts +9 -5
  25. package/src/utils/theme/remove.test.ts +7 -5
  26. package/src/utils/theme/theme-switcher.vue +34 -37
  27. package/src/utils/theme/theme.service.test.ts +160 -58
  28. package/src/utils/theme/theme.service.ts +139 -78
  29. package/src/utils/theme/themes.ts +122 -0
  30. package/dist/types/components/index.test.d.ts +0 -1
  31. package/dist/types/index.test.d.ts +0 -1
  32. package/dist/types/utils/index.test.d.ts +0 -1
  33. package/src/components/index.test.ts +0 -10
  34. package/src/index.test.ts +0 -26
  35. package/src/utils/index.test.ts +0 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-coaching/vuetiful",
3
- "version": "0.23.2",
3
+ "version": "0.24.0",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "dev": "onchange 'src/**/*.vue' 'src/**/*.ts' 'src/**/*.css' -- npm run build",
@@ -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 { MODE } = useDarkMode();
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
- const matchMediaMock = (matches: boolean) => vi.fn(() => ({ matches, onchange: vi.fn() }));
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('VLightSwitch', () => {
11
- expect(VLightSwitch).toBeTruthy();
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
- // TODO: add tests
15
- test('VLightSwitch using slot', () => {
16
- window.matchMedia = matchMediaMock(MODE.LIGHT) as any;
17
- const wrapper = mount(VLightSwitch);
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
- expect(wrapper).toBeTruthy();
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="currentMode"
11
- :title="`Toggle ${currentMode === false ? 'Dark' : 'Light'} Mode`"
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="currentMode ? svgPath.sun : svgPath.moon" />
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 { computed, ComputedRef, defineComponent, onMounted } from 'vue';
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 { initializeMode, setModeCurrent, setModeUserPrefers, currentMode, MODE } = useDarkMode();
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 = !currentMode.value;
84
- setModeUserPrefers(toggle);
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(() => (currentMode.value === MODE.LIGHT ? props.bgLight : props.bgDark));
99
- const thumbBg = computed(() => (currentMode.value === MODE.LIGHT ? props.bgDark : props.bgLight));
100
- const thumbPosition = computed(() => (currentMode.value === MODE.LIGHT ? 'translate-x-[100%]' : ''));
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 currentMode.value === MODE.LIGHT ? props.textLight : props.textDark;
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
- currentMode,
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, it, vi } from 'vitest';
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
- it('should copy text to the clipboard on click', () => {
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, it, vi } from 'vitest';
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
- const { useDarkMode } = await import('./dark-mode.service');
22
- const { getModeUserPrefers } = useDarkMode();
23
- expect(getModeUserPrefers()).toBe(undefined);
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
- const { useDarkMode } = await import('./dark-mode.service');
72
- const { getModeOsPrefers } = useDarkMode();
73
- expect(getModeOsPrefers()).toBe(false);
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
- describe('given in browser', () => {
78
- describe('given prefers-color-scheme: light', () => {
79
- it('should return true', async () => {
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('getModeAutoPrefers', () => {
114
- describe('given not in browser', () => {
115
- it('should return default modeAutoPrefers', async () => {
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 { getModeAutoPrefers } = useDarkMode();
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
- expect(getModeAutoPrefers()).toBe(false);
142
- });
143
- });
144
- describe('given modeUserPrefers in localStorage', () => {
145
- it('should return the value', async () => {
146
- const platform = await import('../utils/platform/platform.service');
147
- vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
148
- isBrowser: true,
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
- const { useDarkMode } = await import('./dark-mode.service');
152
- const { getModeAutoPrefers } = useDarkMode();
48
+ darkMode.autoModeWatcher();
153
49
 
154
- window.localStorage = localStorageMock as any;
155
- vi.spyOn(window.localStorage, 'getItem').mockReturnValueOnce('true');
50
+ mql.matches = false;
51
+ mql.onchange?.();
52
+ expect(darkMode.chosenMode.value).toBe('dark');
156
53
 
157
- expect(getModeAutoPrefers()).toBe(true);
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('setModeUserPrefers', () => {
164
- describe('given not in browser', () => {
165
- it('should set modeUserPrefers', async () => {
166
- const platform = await import('../utils/platform/platform.service');
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
- setModeUserPrefers(true);
194
- expect(modeUserPrefers.value).toBe(true);
195
- expect(localStorageSpy).toHaveBeenCalledWith('modeUserPrefers', 'true');
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('setModeCurrent', () => {
201
- it('light mode set', async () => {
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 { setModeCurrent, currentMode, MODE } = useDarkMode();
76
+ const darkMode = useDarkMode();
204
77
 
205
- setModeCurrent(MODE.LIGHT);
206
- expect(currentMode.value).toBe(MODE.LIGHT);
207
- expect(document.documentElement.classList.contains('dark')).toBe(false);
78
+ const cookies = '';
79
+ const result = darkMode.getModeFromCookie(cookies);
80
+ expect(result).toBe('dark');
208
81
  });
209
- it('dark mode set', async () => {
82
+ test('should return mode from cookie', async () => {
210
83
  const { useDarkMode } = await import('./dark-mode.service');
211
- const { setModeCurrent, currentMode, MODE } = useDarkMode();
84
+ const darkMode = useDarkMode();
212
85
 
213
- setModeCurrent(MODE.DARK);
214
- expect(currentMode.value).toBe(MODE.DARK);
215
- expect(document.documentElement.classList.contains('dark')).toBe(true);
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
- it('should set currentMode', async () => {
93
+ test('should set mode from cookie', async () => {
221
94
  const { useDarkMode } = await import('./dark-mode.service');
222
- const { initializeMode, currentMode, getModeAutoPrefers } = useDarkMode();
95
+ const darkMode = useDarkMode();
223
96
 
224
- const mode = getModeAutoPrefers();
225
- initializeMode();
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
- describe('autoModeWatcher', () => {
231
- describe('given mode changes', () => {
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, Ref, ref } from 'vue';
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: true,
8
- DARK: false,
7
+ LIGHT: 'light',
8
+ DARK: 'dark',
9
9
  };
10
+ export type Mode = (typeof MODE)[keyof typeof MODE];
10
11
 
11
- const modeOsPrefers = ref(MODE.DARK);
12
- const currentMode = ref(MODE.DARK);
13
- const modeUserPrefers: Ref<boolean | undefined> = ref(undefined);
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 getModeOsPrefers = (): boolean => {
18
- let prefersLightMode = false;
19
- if (isBrowser) prefersLightMode = window.matchMedia('(prefers-color-scheme: light)').matches;
20
- setModeOsPrefers(prefersLightMode);
21
- return prefersLightMode;
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 modeUserPrefers.value;
23
+ return defaultMode;
30
24
  };
31
25
 
32
- const getModeAutoPrefers = (): boolean => {
33
- const os = getModeOsPrefers();
34
- const user = getModeUserPrefers();
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 setModeOsPrefers = (value: boolean) => {
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
- localStorage.setItem('modeUserPrefers', value.toString());
33
+ const mode = getModeFromCookie(document.cookie);
34
+ applyMode(mode);
49
35
  }
50
36
  };
51
37
 
52
- const setModeCurrent = (value: boolean) => {
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
- currentMode.value = value;
57
- };
58
-
59
- const initializeMode = (): void => {
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 === MODE.LIGHT ? elemHtmlClasses.remove(classDark) : elemHtmlClasses.add(classDark);
70
- setModeCurrent(value);
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
- modeOsPrefers: readonly(modeOsPrefers),
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
  };