@witchcraft/ui 0.3.4 → 0.3.5

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/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "witchcraftUi",
3
3
  "configKey": "witchcraftUi",
4
- "version": "0.3.4",
4
+ "version": "0.3.5",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -10,13 +10,56 @@ import { type Ref } from "vue";
10
10
  *
11
11
  * Note that this should only be called once at the root of the app.
12
12
  */
13
- export declare const useDarkMode: ({ useLocalStorage, darkModeOrder, isClientSide }?: DarkModeOptions) => DarkModeState & DarkModeCommands;
13
+ export declare const useDarkMode: ({ useLocalStorage, darkModeOrder, isClientSide, useViewTransition }?: DarkModeOptions) => DarkModeState & DarkModeCommands;
14
14
  export declare const defaultDarkModeOrder: readonly ["system", "dark", "light"];
15
15
  export type DarkModeOptions = {
16
16
  useLocalStorage?: boolean | string;
17
17
  darkModeOrder?: readonly ("system" | "dark" | "light")[];
18
18
  /** True by default, should be passed import.meta.client if using nuxt, or false when running server side. */
19
19
  isClientSide?: boolean;
20
+ /**
21
+ * Whether to use the view transition to animate the dark mode switch (you just need to add the css).
22
+ *
23
+ * Note that the transitition is NOT triggered if visually the mode does not change (e.g. system mode is dark and the user switches from system to dark, visually nothing changes so transitioning is skipped).
24
+ *
25
+ * There is an example in storybook. But basically:
26
+ *
27
+ * ```css
28
+ *
29
+ * #root { // the dark mode switcher works on the WRoot component not the html root
30
+ * view-transition-name: wroot;
31
+ * height: 100dvh;
32
+ * padding: 0;
33
+ * }
34
+ *
35
+ * ::view-transition-new(wroot) {
36
+ * animation: grow var(--story-anim-length) ease-in-out;
37
+ * animation-fill-mode: both;
38
+ * z-index: 2;
39
+ * mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><circle cx="20" cy="20" r="20" fill="white"/></svg>') center / 0 no-repeat;
40
+ * }
41
+ *
42
+ * ::view-transition-old(wroot) {
43
+ * animation: none;
44
+ * animation-fill-mode: both;
45
+ * z-index: 1;
46
+ * }
47
+ *
48
+ * @keyframes grow {
49
+ * from {
50
+ * mask-size: 0dvw;
51
+ * }
52
+ * to {
53
+ * mask-size: 300dvw;
54
+ * }
55
+ * }
56
+ * ```
57
+ *
58
+ * See https://theme-toggle.rdsx.dev/ for more ideas.
59
+ *
60
+ * @default true
61
+ */
62
+ useViewTransition?: boolean;
20
63
  };
21
64
  export interface DarkModeCommands {
22
65
  setDarkMode: (value: "dark" | "light" | "system") => void;
@@ -1,12 +1,12 @@
1
- import { computed, onMounted, provide, ref, watch } from "vue";
1
+ import { computed, nextTick, onMounted, provide, ref, watch } from "vue";
2
2
  import { darkModeCommandsInjectionKey, darkModeStateInjectionKey, isDarkModeInjectionKey, manualDarkModeInjectionKey } from "../injectionKeys.js";
3
3
  const defaultLocalStorageKey = "prefersColorSchemeDark";
4
4
  const defaultOrder = ["system", "dark", "light"];
5
5
  export const useDarkMode = ({
6
6
  useLocalStorage = true,
7
7
  darkModeOrder = defaultOrder,
8
- /** True by default, should be passed import.meta.client if using nuxt, or false when running server side. */
9
- isClientSide = true
8
+ isClientSide = true,
9
+ useViewTransition = true
10
10
  } = {}) => {
11
11
  const systemDarkMode = ref(false);
12
12
  const manualDarkMode = ref(void 0);
@@ -22,13 +22,30 @@ export const useDarkMode = ({
22
22
  function setDarkMode(value) {
23
23
  manualDarkMode.value = value === "dark" ? true : value === "light" ? false : void 0;
24
24
  }
25
+ function getNextValue() {
26
+ const index = darkModeOrder.indexOf(darkModeState.value);
27
+ return index === 2 ? darkModeOrder[0] : darkModeOrder[index + 1];
28
+ }
29
+ function _cycleDarkMode() {
30
+ setDarkMode(getNextValue());
31
+ }
25
32
  function cycleDarkMode() {
33
+ if (!useViewTransition) {
34
+ _cycleDarkMode();
35
+ return;
36
+ }
37
+ const nextValue = getNextValue();
26
38
  const index = darkModeOrder.indexOf(darkModeState.value);
27
- if (index === 2) {
28
- setDarkMode(darkModeOrder[0]);
29
- } else {
30
- setDarkMode(darkModeOrder[index + 1]);
39
+ const systemDarkModeName = systemDarkMode.value ? "dark" : "light";
40
+ if (nextValue === "system" && systemDarkModeName === darkModeOrder[index]) {
41
+ _cycleDarkMode();
42
+ return;
31
43
  }
44
+ if (!document.startViewTransition) _cycleDarkMode();
45
+ document.startViewTransition(async () => {
46
+ _cycleDarkMode();
47
+ await nextTick();
48
+ });
32
49
  }
33
50
  onMounted(() => {
34
51
  window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", ({ matches }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@witchcraft/ui",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Vue component library.",
5
5
  "type": "module",
6
6
  "main": "./dist/runtime/main.lib.js",
@@ -19,6 +19,7 @@ const meta: Meta<typeof LibDarkModeSwitcher> = {
19
19
  export default meta
20
20
  type Story = StoryObj<typeof LibDarkModeSwitcher>
21
21
 
22
+
22
23
  export const Primary: Story = {
23
24
  render: args => ({
24
25
  components,
@@ -35,10 +36,15 @@ export const Primary: Story = {
35
36
  @update:darkMode="darkMode = $event"
36
37
  v-bind="{...args}"
37
38
  ></LibDarkModeSwitcher>
39
+ <!-- workaround for style tag not being allowed -->
40
+ <component is="style">
41
+ {{args.css}}
42
+ </component>
38
43
 
39
44
  `
40
45
  })
41
46
  }
47
+
42
48
  export const WithoutLabel: Story = {
43
49
  ...Primary,
44
50
  args: {
@@ -46,3 +52,46 @@ export const WithoutLabel: Story = {
46
52
  showLabel: false
47
53
  }
48
54
  }
55
+
56
+ export const WithViewTransitionApi: Story = {
57
+ ...Primary,
58
+ args: {
59
+ ...Primary.args,
60
+ css: `
61
+ :root {
62
+ --story-anim-length: 1s; /* Shorter for testing */
63
+ }
64
+
65
+ #root {
66
+ view-transition-name: wroot;
67
+ height: 100dvh;
68
+ padding: 0;
69
+
70
+ }
71
+
72
+ ::view-transition-new(wroot) {
73
+ animation: grow var(--story-anim-length) ease-in-out;
74
+ animation-fill-mode: both;
75
+ z-index: 2;
76
+ mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><circle cx="20" cy="20" r="20" fill="white"/></svg>') center / 0 no-repeat;
77
+ mask-size: 50dvw;
78
+ }
79
+
80
+ ::view-transition-old(wroot) {
81
+ animation: none;
82
+ animation-fill-mode: both;
83
+ z-index: 1;
84
+ }
85
+
86
+ @keyframes grow {
87
+ from {
88
+ mask-size: 0dvw;
89
+ }
90
+ to {
91
+ mask-size: 300dvw;
92
+ }
93
+ }
94
+ `
95
+ }
96
+ }
97
+
@@ -1,4 +1,4 @@
1
- import { computed, onMounted, provide, type Ref, ref, watch } from "vue"
1
+ import { computed, nextTick, onMounted, provide, type Ref, ref, watch } from "vue"
2
2
 
3
3
  import { darkModeCommandsInjectionKey, darkModeStateInjectionKey, isDarkModeInjectionKey, manualDarkModeInjectionKey } from "../injectionKeys.js"
4
4
 
@@ -19,8 +19,9 @@ const defaultOrder = ["system", "dark", "light"] as const
19
19
  export const useDarkMode = ({
20
20
  useLocalStorage = true,
21
21
  darkModeOrder = defaultOrder,
22
- /** True by default, should be passed import.meta.client if using nuxt, or false when running server side. */
23
- isClientSide = true
22
+
23
+ isClientSide = true,
24
+ useViewTransition = true
24
25
  }: DarkModeOptions = {}): DarkModeState & DarkModeCommands => {
25
26
  const systemDarkMode = ref(false)
26
27
  const manualDarkMode = ref<boolean | undefined>(undefined)
@@ -48,14 +49,38 @@ export const useDarkMode = ({
48
49
  ? false
49
50
  : undefined
50
51
  }
52
+
53
+ function getNextValue(): "dark" | "light" | "system" {
54
+ const index = darkModeOrder.indexOf(darkModeState.value)
55
+
56
+ return index === 2
57
+ ? darkModeOrder[0]!
58
+ : darkModeOrder[index + 1]!
59
+ }
60
+
61
+ function _cycleDarkMode(): void {
62
+ setDarkMode(getNextValue())
63
+ }
64
+
51
65
  function cycleDarkMode(): void {
66
+ if (!useViewTransition) {
67
+ _cycleDarkMode()
68
+ return
69
+ }
70
+ const nextValue = getNextValue()
52
71
  const index = darkModeOrder.indexOf(darkModeState.value)
72
+ const systemDarkModeName = systemDarkMode.value ? "dark" : "light"
53
73
 
54
- if (index === 2) {
55
- setDarkMode(darkModeOrder[0]!)
56
- } else {
57
- setDarkMode(darkModeOrder[index + 1]!)
74
+ if (nextValue === "system" && systemDarkModeName === darkModeOrder[index]) {
75
+ // don't do view transitions if we're not really transitioning
76
+ _cycleDarkMode()
77
+ return
58
78
  }
79
+ if (!document.startViewTransition) _cycleDarkMode()
80
+ document.startViewTransition(async () => {
81
+ _cycleDarkMode()
82
+ await nextTick()
83
+ })
59
84
  }
60
85
 
61
86
  onMounted(() => {
@@ -111,6 +136,49 @@ export type DarkModeOptions = {
111
136
  darkModeOrder?: readonly ("system" | "dark" | "light")[]
112
137
  /** True by default, should be passed import.meta.client if using nuxt, or false when running server side. */
113
138
  isClientSide?: boolean
139
+ /**
140
+ * Whether to use the view transition to animate the dark mode switch (you just need to add the css).
141
+ *
142
+ * Note that the transitition is NOT triggered if visually the mode does not change (e.g. system mode is dark and the user switches from system to dark, visually nothing changes so transitioning is skipped).
143
+ *
144
+ * There is an example in storybook. But basically:
145
+ *
146
+ * ```css
147
+ *
148
+ * #root { // the dark mode switcher works on the WRoot component not the html root
149
+ * view-transition-name: wroot;
150
+ * height: 100dvh;
151
+ * padding: 0;
152
+ * }
153
+ *
154
+ * ::view-transition-new(wroot) {
155
+ * animation: grow var(--story-anim-length) ease-in-out;
156
+ * animation-fill-mode: both;
157
+ * z-index: 2;
158
+ * mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><circle cx="20" cy="20" r="20" fill="white"/></svg>') center / 0 no-repeat;
159
+ * }
160
+ *
161
+ * ::view-transition-old(wroot) {
162
+ * animation: none;
163
+ * animation-fill-mode: both;
164
+ * z-index: 1;
165
+ * }
166
+ *
167
+ * @keyframes grow {
168
+ * from {
169
+ * mask-size: 0dvw;
170
+ * }
171
+ * to {
172
+ * mask-size: 300dvw;
173
+ * }
174
+ * }
175
+ * ```
176
+ *
177
+ * See https://theme-toggle.rdsx.dev/ for more ideas.
178
+ *
179
+ * @default true
180
+ */
181
+ useViewTransition?: boolean
114
182
  }
115
183
 
116
184
  export interface DarkModeCommands {