Skip to content

Commit 18d2bc0

Browse files
authored
fix: address animated theme toggler review feedback
1 parent 0d28194 commit 18d2bc0

3 files changed

Lines changed: 40 additions & 20 deletions

File tree

docs/content/components/animated-theme-toggler.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,10 @@ Add the following CSS to your global stylesheet. The component sets `--spark-the
310310
html[data-spark-theme-vt="active"]::view-transition-group(root) {
311311
animation-duration: var(--spark-theme-toggle-vt-duration);
312312
}
313+
314+
html[data-spark-theme-vt="active"]::view-transition-new(root) {
315+
clip-path: var(--spark-theme-vt-clip-from);
316+
}
313317
```
314318

315319
## Usage

docs/src/components/spark-ui/animated-theme-toggler/animated-theme-toggler.vue

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { computed, onMounted, onUnmounted, ref } from "vue";
2+
import { computed, nextTick, onMounted, onUnmounted, ref } from "vue";
33
import { cn } from "../../../lib/utils";
44
55
export type TransitionVariant =
@@ -37,7 +37,7 @@ const emit = defineEmits<{
3737
}>();
3838
3939
type ViewTransitionDocument = Document & {
40-
startViewTransition?: (callback: () => void) => {
40+
startViewTransition?: (callback: () => void | Promise<void>) => {
4141
ready: Promise<void>;
4242
finished: Promise<void>;
4343
};
@@ -161,15 +161,16 @@ function getThemeTransitionClipPaths(
161161
162162
const applyTheme = () => {
163163
const newIsDark = !isDark.value;
164-
// Always toggle the class synchronously so the View Transitions API
165-
// snapshots the new theme inside the startViewTransition callback.
166-
document.documentElement.classList.toggle("dark");
164+
167165
if (isControlled.value) {
168166
emit("themeChange", newIsDark ? "dark" : "light");
169-
} else {
170-
internalIsDark.value = newIsDark;
171-
localStorage.setItem("theme", newIsDark ? "dark" : "light");
167+
return;
172168
}
169+
170+
// Keep DOM class in sync before view-transition snapshots in uncontrolled mode.
171+
document.documentElement.classList.toggle("dark", newIsDark);
172+
internalIsDark.value = newIsDark;
173+
localStorage.setItem("theme", newIsDark ? "dark" : "light");
173174
};
174175
175176
const toggleTheme = () => {
@@ -178,7 +179,11 @@ const toggleTheme = () => {
178179
179180
const doc = document as ViewTransitionDocument;
180181
181-
if (typeof doc.startViewTransition !== "function") {
182+
const prefersReducedMotion =
183+
typeof window.matchMedia === "function" &&
184+
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
185+
186+
if (prefersReducedMotion || typeof doc.startViewTransition !== "function") {
182187
applyTheme();
183188
return;
184189
}
@@ -220,7 +225,10 @@ const toggleTheme = () => {
220225
root.style.removeProperty("--spark-theme-vt-clip-from");
221226
};
222227
223-
const transition = doc.startViewTransition(applyTheme);
228+
const transition = doc.startViewTransition(async () => {
229+
applyTheme();
230+
await nextTick();
231+
});
224232
transition.finished.finally(cleanup);
225233
226234
transition.ready.then(() => {

docs/src/example/animated-theme-toggler/animated-theme-toggler.vue

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { computed, onMounted, onUnmounted, ref } from "vue";
2+
import { computed, nextTick, onMounted, onUnmounted, ref } from "vue";
33
import { cn } from "../../lib/utils";
44
55
export type TransitionVariant =
@@ -37,7 +37,7 @@ const emit = defineEmits<{
3737
}>();
3838
3939
type ViewTransitionDocument = Document & {
40-
startViewTransition?: (callback: () => void) => {
40+
startViewTransition?: (callback: () => void | Promise<void>) => {
4141
ready: Promise<void>;
4242
finished: Promise<void>;
4343
};
@@ -161,15 +161,16 @@ function getThemeTransitionClipPaths(
161161
162162
const applyTheme = () => {
163163
const newIsDark = !isDark.value;
164-
// Always toggle the class synchronously so the View Transitions API
165-
// snapshots the new theme inside the startViewTransition callback.
166-
document.documentElement.classList.toggle("dark");
164+
167165
if (isControlled.value) {
168166
emit("themeChange", newIsDark ? "dark" : "light");
169-
} else {
170-
internalIsDark.value = newIsDark;
171-
localStorage.setItem("theme", newIsDark ? "dark" : "light");
167+
return;
172168
}
169+
170+
// Keep DOM class in sync before view-transition snapshots in uncontrolled mode.
171+
document.documentElement.classList.toggle("dark", newIsDark);
172+
internalIsDark.value = newIsDark;
173+
localStorage.setItem("theme", newIsDark ? "dark" : "light");
173174
};
174175
175176
const toggleTheme = () => {
@@ -178,7 +179,11 @@ const toggleTheme = () => {
178179
179180
const doc = document as ViewTransitionDocument;
180181
181-
if (typeof doc.startViewTransition !== "function") {
182+
const prefersReducedMotion =
183+
typeof window.matchMedia === "function" &&
184+
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
185+
186+
if (prefersReducedMotion || typeof doc.startViewTransition !== "function") {
182187
applyTheme();
183188
return;
184189
}
@@ -220,7 +225,10 @@ const toggleTheme = () => {
220225
root.style.removeProperty("--spark-theme-vt-clip-from");
221226
};
222227
223-
const transition = doc.startViewTransition(applyTheme);
228+
const transition = doc.startViewTransition(async () => {
229+
applyTheme();
230+
await nextTick();
231+
});
224232
transition.finished.finally(cleanup);
225233
226234
transition.ready.then(() => {

0 commit comments

Comments
 (0)