From 6b189db615b653e8ac2116291a7b5fa76a90ed73 Mon Sep 17 00:00:00 2001 From: Mateus Lage Date: Mon, 1 Dec 2025 13:33:46 -0300 Subject: [PATCH 1/2] feat(usePrefersColorScheme): add utility export --- packages/runed/src/lib/utilities/index.ts | 1 + .../lib/utilities/use-prefers-dark/index.ts | 1 + .../use-prefers-dark.svelte.ts | 56 +++++++++++++++ .../use-prefers-dark.test.svelte.ts | 32 +++++++++ .../src/content/utilities/use-prefers-dark.md | 70 +++++++++++++++++++ .../components/demos/use-prefers-dark.svelte | 32 +++++++++ 6 files changed, 192 insertions(+) create mode 100644 packages/runed/src/lib/utilities/use-prefers-dark/index.ts create mode 100644 packages/runed/src/lib/utilities/use-prefers-dark/use-prefers-dark.svelte.ts create mode 100644 packages/runed/src/lib/utilities/use-prefers-dark/use-prefers-dark.test.svelte.ts create mode 100644 sites/docs/src/content/utilities/use-prefers-dark.md create mode 100644 sites/docs/src/lib/components/demos/use-prefers-dark.svelte diff --git a/packages/runed/src/lib/utilities/index.ts b/packages/runed/src/lib/utilities/index.ts index 55f528b8..69ff6fea 100644 --- a/packages/runed/src/lib/utilities/index.ts +++ b/packages/runed/src/lib/utilities/index.ts @@ -25,6 +25,7 @@ export * from "./use-event-listener/index.js"; export * from "./use-geolocation/index.js"; export * from "./use-intersection-observer/index.js"; export * from "./use-mutation-observer/index.js"; +export * from "./use-prefers-dark/index.js"; export * from "./use-resize-observer/index.js"; export * from "./use-interval/index.js"; export * from "./use-throttle/index.js"; diff --git a/packages/runed/src/lib/utilities/use-prefers-dark/index.ts b/packages/runed/src/lib/utilities/use-prefers-dark/index.ts new file mode 100644 index 00000000..53f7efba --- /dev/null +++ b/packages/runed/src/lib/utilities/use-prefers-dark/index.ts @@ -0,0 +1 @@ +export * from "./use-prefers-dark.svelte.js"; diff --git a/packages/runed/src/lib/utilities/use-prefers-dark/use-prefers-dark.svelte.ts b/packages/runed/src/lib/utilities/use-prefers-dark/use-prefers-dark.svelte.ts new file mode 100644 index 00000000..9e56b5b8 --- /dev/null +++ b/packages/runed/src/lib/utilities/use-prefers-dark/use-prefers-dark.svelte.ts @@ -0,0 +1,56 @@ +import { MediaQuery } from "svelte/reactivity"; + +export type UsePrefersDarkOptions = { + /** + * Fallback value for server-side rendering + * + * @defaultValue false + */ + fallback?: boolean; +}; + +/** + * Reactive dark mode preference detection using the browser's + * [prefers-color-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) media query. + * + * @see https://runed.dev/docs/utilities/use-prefers-dark + */ +export class UsePrefersDark { + #mediaQuery: MediaQuery; + #fallback: boolean; + + constructor(options: UsePrefersDarkOptions = {}) { + this.#fallback = options.fallback ?? false; + this.#mediaQuery = new MediaQuery("(prefers-color-scheme: dark)", this.#fallback); + } + + /** + * Whether the user prefers dark mode + */ + get current(): boolean { + return this.#mediaQuery.current; + } +} + +/** + * Convenience function for creating a dark mode preference detector + * + * @param options - Configuration options + * @returns A reactive dark mode preference detector + * + * @example + * ```svelte + * + * + *
+ * {darkMode.current ? 'Dark mode' : 'Light mode'} + *
+ * ``` + */ +export function usePrefersDark(options: UsePrefersDarkOptions = {}): UsePrefersDark { + return new UsePrefersDark(options); +} diff --git a/packages/runed/src/lib/utilities/use-prefers-dark/use-prefers-dark.test.svelte.ts b/packages/runed/src/lib/utilities/use-prefers-dark/use-prefers-dark.test.svelte.ts new file mode 100644 index 00000000..81e3dd94 --- /dev/null +++ b/packages/runed/src/lib/utilities/use-prefers-dark/use-prefers-dark.test.svelte.ts @@ -0,0 +1,32 @@ +import { describe, expect } from "vitest"; +import { flushSync } from "svelte"; +import { UsePrefersDark, usePrefersDark } from "./use-prefers-dark.svelte.js"; +import { testWithEffect } from "$lib/test/util.svelte.js"; + +describe("UsePrefersDark", () => { + testWithEffect("initializes with fallback value", () => { + const util = new UsePrefersDark({ fallback: true }); + flushSync(); + expect(typeof util.current).toBe("boolean"); + }); + + testWithEffect("initializes with default fallback", () => { + const util = new UsePrefersDark(); + flushSync(); + expect(typeof util.current).toBe("boolean"); + }); + + testWithEffect("usePrefersDark function returns UsePrefersDark instance", () => { + const util = usePrefersDark({ fallback: false }); + flushSync(); + expect(util).toBeInstanceOf(UsePrefersDark); + expect(typeof util.current).toBe("boolean"); + }); + + testWithEffect("usePrefersDark function with default options", () => { + const util = usePrefersDark(); + flushSync(); + expect(util).toBeInstanceOf(UsePrefersDark); + expect(typeof util.current).toBe("boolean"); + }); +}); diff --git a/sites/docs/src/content/utilities/use-prefers-dark.md b/sites/docs/src/content/utilities/use-prefers-dark.md new file mode 100644 index 00000000..b70346a7 --- /dev/null +++ b/sites/docs/src/content/utilities/use-prefers-dark.md @@ -0,0 +1,70 @@ +--- +title: usePrefersDark +description: Detect dark mode preference using the browser's prefers-color-scheme media query. +category: Sensors +--- + + + +`usePrefersDark` provides a reactive boolean that reflects whether the user prefers dark mode based +on their browser or OS settings. It uses the +[prefers-color-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) +media query and updates automatically when the preference changes. + +## Demo + + + +## Usage + +```svelte + + +
+ {darkMode.current ? "🌙 Dark mode" : "☀️ Light mode"} +
+``` + +You can also use the class directly: + +```svelte + + +

Dark mode preferred: {darkMode.current ? "Yes" : "No"}

+``` + +## Type Definition + +```ts +type UsePrefersDarkOptions = { + /** + * Fallback value for server-side rendering + * @defaultValue false + */ + fallback?: boolean; +}; + +class UsePrefersDark { + constructor(options?: UsePrefersDarkOptions); + readonly current: boolean; // true when dark mode is preferred +} + +function usePrefersDark(options?: UsePrefersDarkOptions): UsePrefersDark; +``` + +## Notes + +- Uses the `prefers-color-scheme: dark` media query. +- During server-side rendering, returns the fallback value (defaults to `false`). +- Automatically updates when user changes their system dark mode preference. +- Works with both light and dark theme preferences. diff --git a/sites/docs/src/lib/components/demos/use-prefers-dark.svelte b/sites/docs/src/lib/components/demos/use-prefers-dark.svelte new file mode 100644 index 00000000..ac77a2c4 --- /dev/null +++ b/sites/docs/src/lib/components/demos/use-prefers-dark.svelte @@ -0,0 +1,32 @@ + + + +

Dark mode preferred: {darkMode.current ? "true" : "false"}

+

Preference changes: {changeCount}

+

Try changing your system dark mode preference to see this update!

+
+ + From 1dbc6a09c6fd2a52f6d42d920ea95e2f78701162 Mon Sep 17 00:00:00 2001 From: Mateus Lage Date: Mon, 22 Dec 2025 20:43:33 -0300 Subject: [PATCH 2/2] refactor(usePrefersDark): rename to usePrefersColorScheme --- packages/runed/src/lib/utilities/index.ts | 2 +- .../use-preferred-color-scheme/index.ts | 1 + .../use-preferred-color-scheme.svelte.ts | 37 ++++++++++ .../use-preferred-color-scheme.test.svelte.ts | 35 +++++++++ .../lib/utilities/use-prefers-dark/index.ts | 1 - .../use-prefers-dark.svelte.ts | 56 -------------- .../use-prefers-dark.test.svelte.ts | 32 -------- .../utilities/use-preferred-color-scheme.md | 73 +++++++++++++++++++ .../src/content/utilities/use-prefers-dark.md | 70 ------------------ .../demos/use-preferred-color-scheme.svelte | 11 +++ .../components/demos/use-prefers-dark.svelte | 32 -------- 11 files changed, 158 insertions(+), 192 deletions(-) create mode 100644 packages/runed/src/lib/utilities/use-preferred-color-scheme/index.ts create mode 100644 packages/runed/src/lib/utilities/use-preferred-color-scheme/use-preferred-color-scheme.svelte.ts create mode 100644 packages/runed/src/lib/utilities/use-preferred-color-scheme/use-preferred-color-scheme.test.svelte.ts delete mode 100644 packages/runed/src/lib/utilities/use-prefers-dark/index.ts delete mode 100644 packages/runed/src/lib/utilities/use-prefers-dark/use-prefers-dark.svelte.ts delete mode 100644 packages/runed/src/lib/utilities/use-prefers-dark/use-prefers-dark.test.svelte.ts create mode 100644 sites/docs/src/content/utilities/use-preferred-color-scheme.md delete mode 100644 sites/docs/src/content/utilities/use-prefers-dark.md create mode 100644 sites/docs/src/lib/components/demos/use-preferred-color-scheme.svelte delete mode 100644 sites/docs/src/lib/components/demos/use-prefers-dark.svelte diff --git a/packages/runed/src/lib/utilities/index.ts b/packages/runed/src/lib/utilities/index.ts index 69ff6fea..c2ef8e13 100644 --- a/packages/runed/src/lib/utilities/index.ts +++ b/packages/runed/src/lib/utilities/index.ts @@ -25,7 +25,7 @@ export * from "./use-event-listener/index.js"; export * from "./use-geolocation/index.js"; export * from "./use-intersection-observer/index.js"; export * from "./use-mutation-observer/index.js"; -export * from "./use-prefers-dark/index.js"; +export * from "./use-preferred-color-scheme/index.js"; export * from "./use-resize-observer/index.js"; export * from "./use-interval/index.js"; export * from "./use-throttle/index.js"; diff --git a/packages/runed/src/lib/utilities/use-preferred-color-scheme/index.ts b/packages/runed/src/lib/utilities/use-preferred-color-scheme/index.ts new file mode 100644 index 00000000..693c395e --- /dev/null +++ b/packages/runed/src/lib/utilities/use-preferred-color-scheme/index.ts @@ -0,0 +1 @@ +export * from "./use-preferred-color-scheme.svelte.js"; diff --git a/packages/runed/src/lib/utilities/use-preferred-color-scheme/use-preferred-color-scheme.svelte.ts b/packages/runed/src/lib/utilities/use-preferred-color-scheme/use-preferred-color-scheme.svelte.ts new file mode 100644 index 00000000..757622c4 --- /dev/null +++ b/packages/runed/src/lib/utilities/use-preferred-color-scheme/use-preferred-color-scheme.svelte.ts @@ -0,0 +1,37 @@ +import { MediaQuery } from "svelte/reactivity"; + +export type ColorSchemeType = "dark" | "light" | "no-preference"; + +export type UsePreferredColorSchemeOptions = { + /** + * Fallback value for server-side rendering + * @defaultValue "no-preference" + */ + fallback?: ColorSchemeType; +}; + +/** + * Reactive prefers-color-scheme media query. + * + * @see https://runed.dev/docs/utilities/use-preferred-color-scheme + */ +export function usePreferredColorScheme(options?: UsePreferredColorSchemeOptions) { + const { fallback = "no-preference" } = options ?? {}; + + // Map ColorSchemeType fallback to boolean values for MediaQuery + const isLightFallback = fallback === "light"; + const isDarkFallback = fallback === "dark"; + + const isLight = new MediaQuery("(prefers-color-scheme: light)", isLightFallback); + const isDark = new MediaQuery("(prefers-color-scheme: dark)", isDarkFallback); + const current = $derived.by(() => { + if (isDark.current) return "dark"; + if (isLight.current) return "light"; + return "no-preference"; + }); + return { + get current() { + return current; + }, + }; +} diff --git a/packages/runed/src/lib/utilities/use-preferred-color-scheme/use-preferred-color-scheme.test.svelte.ts b/packages/runed/src/lib/utilities/use-preferred-color-scheme/use-preferred-color-scheme.test.svelte.ts new file mode 100644 index 00000000..fa2e91c2 --- /dev/null +++ b/packages/runed/src/lib/utilities/use-preferred-color-scheme/use-preferred-color-scheme.test.svelte.ts @@ -0,0 +1,35 @@ +import { describe, expect } from "vitest"; +import { flushSync } from "svelte"; +import { usePreferredColorScheme } from "./use-preferred-color-scheme.svelte.js"; +import { testWithEffect } from "$lib/test/util.svelte.js"; + +describe("usePreferredColorScheme", () => { + testWithEffect("initializes with dark fallback", () => { + const util = usePreferredColorScheme({ fallback: "dark" }); + flushSync(); + expect(typeof util.current).toBe("string"); + expect(["dark", "light", "no-preference"].includes(util.current)).toBe(true); + }); + + testWithEffect("initializes with light fallback", () => { + const util = usePreferredColorScheme({ fallback: "light" }); + flushSync(); + expect(typeof util.current).toBe("string"); + expect(["dark", "light", "no-preference"].includes(util.current)).toBe(true); + }); + + testWithEffect("initializes with no-preference fallback (default)", () => { + const util = usePreferredColorScheme(); + flushSync(); + expect(typeof util.current).toBe("string"); + expect(["dark", "light", "no-preference"].includes(util.current)).toBe(true); + }); + + testWithEffect("returns object with current getter", () => { + const util = usePreferredColorScheme({ fallback: "no-preference" }); + flushSync(); + expect(typeof util).toBe("object"); + expect(typeof util.current).toBe("string"); + expect(["dark", "light", "no-preference"].includes(util.current)).toBe(true); + }); +}); diff --git a/packages/runed/src/lib/utilities/use-prefers-dark/index.ts b/packages/runed/src/lib/utilities/use-prefers-dark/index.ts deleted file mode 100644 index 53f7efba..00000000 --- a/packages/runed/src/lib/utilities/use-prefers-dark/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./use-prefers-dark.svelte.js"; diff --git a/packages/runed/src/lib/utilities/use-prefers-dark/use-prefers-dark.svelte.ts b/packages/runed/src/lib/utilities/use-prefers-dark/use-prefers-dark.svelte.ts deleted file mode 100644 index 9e56b5b8..00000000 --- a/packages/runed/src/lib/utilities/use-prefers-dark/use-prefers-dark.svelte.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { MediaQuery } from "svelte/reactivity"; - -export type UsePrefersDarkOptions = { - /** - * Fallback value for server-side rendering - * - * @defaultValue false - */ - fallback?: boolean; -}; - -/** - * Reactive dark mode preference detection using the browser's - * [prefers-color-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) media query. - * - * @see https://runed.dev/docs/utilities/use-prefers-dark - */ -export class UsePrefersDark { - #mediaQuery: MediaQuery; - #fallback: boolean; - - constructor(options: UsePrefersDarkOptions = {}) { - this.#fallback = options.fallback ?? false; - this.#mediaQuery = new MediaQuery("(prefers-color-scheme: dark)", this.#fallback); - } - - /** - * Whether the user prefers dark mode - */ - get current(): boolean { - return this.#mediaQuery.current; - } -} - -/** - * Convenience function for creating a dark mode preference detector - * - * @param options - Configuration options - * @returns A reactive dark mode preference detector - * - * @example - * ```svelte - * - * - *
- * {darkMode.current ? 'Dark mode' : 'Light mode'} - *
- * ``` - */ -export function usePrefersDark(options: UsePrefersDarkOptions = {}): UsePrefersDark { - return new UsePrefersDark(options); -} diff --git a/packages/runed/src/lib/utilities/use-prefers-dark/use-prefers-dark.test.svelte.ts b/packages/runed/src/lib/utilities/use-prefers-dark/use-prefers-dark.test.svelte.ts deleted file mode 100644 index 81e3dd94..00000000 --- a/packages/runed/src/lib/utilities/use-prefers-dark/use-prefers-dark.test.svelte.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { describe, expect } from "vitest"; -import { flushSync } from "svelte"; -import { UsePrefersDark, usePrefersDark } from "./use-prefers-dark.svelte.js"; -import { testWithEffect } from "$lib/test/util.svelte.js"; - -describe("UsePrefersDark", () => { - testWithEffect("initializes with fallback value", () => { - const util = new UsePrefersDark({ fallback: true }); - flushSync(); - expect(typeof util.current).toBe("boolean"); - }); - - testWithEffect("initializes with default fallback", () => { - const util = new UsePrefersDark(); - flushSync(); - expect(typeof util.current).toBe("boolean"); - }); - - testWithEffect("usePrefersDark function returns UsePrefersDark instance", () => { - const util = usePrefersDark({ fallback: false }); - flushSync(); - expect(util).toBeInstanceOf(UsePrefersDark); - expect(typeof util.current).toBe("boolean"); - }); - - testWithEffect("usePrefersDark function with default options", () => { - const util = usePrefersDark(); - flushSync(); - expect(util).toBeInstanceOf(UsePrefersDark); - expect(typeof util.current).toBe("boolean"); - }); -}); diff --git a/sites/docs/src/content/utilities/use-preferred-color-scheme.md b/sites/docs/src/content/utilities/use-preferred-color-scheme.md new file mode 100644 index 00000000..aa7557ce --- /dev/null +++ b/sites/docs/src/content/utilities/use-preferred-color-scheme.md @@ -0,0 +1,73 @@ +--- +title: usePreferredColorScheme +description: Detect color scheme preference using the browser's prefers-color-scheme media query. +category: Sensors +--- + + + +`usePreferredColorScheme` provides a reactive string that reflects the user's color scheme +preference based on their browser or OS settings. It uses the +[prefers-color-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) +media query and updates automatically when the preference changes. + +## Demo + + + +## Usage + +```svelte + + +
+ {colorScheme.current === "dark" + ? "🌙 Dark mode" + : colorScheme.current === "light" + ? "☀️ Light mode" + : "🎨 No preference"} +
+ + +
+ {colorSchemeWithDarkFallback.current === "dark" ? "🌙 Dark mode (fallback)" : "☀️ Light mode"} +
+ +
+ {colorSchemeWithLightFallback.current === "light" ? "☀️ Light mode (fallback)" : "🌙 Dark mode"} +
+``` + +## Type Definition + +```ts +type ColorSchemeType = "dark" | "light" | "no-preference"; + +type UsePreferredColorSchemeOptions = { + /** + * Fallback value for server-side rendering + * @defaultValue "no-preference" + */ + fallback?: ColorSchemeType; +}; + +function usePreferredColorScheme(options?: UsePreferredColorSchemeOptions): { + readonly current: ColorSchemeType; +}; +``` + +## Notes + +- Uses the `prefers-color-scheme: dark` and `prefers-color-scheme: light` media queries. +- Returns "dark", "light", or "no-preference" based on user's system preference. +- During server-side rendering, returns the specified fallback value (defaults to "no-preference"). +- Automatically updates when user changes their system color scheme preference. +- Works with both light and dark theme preferences and detects when no preference is set. diff --git a/sites/docs/src/content/utilities/use-prefers-dark.md b/sites/docs/src/content/utilities/use-prefers-dark.md deleted file mode 100644 index b70346a7..00000000 --- a/sites/docs/src/content/utilities/use-prefers-dark.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: usePrefersDark -description: Detect dark mode preference using the browser's prefers-color-scheme media query. -category: Sensors ---- - - - -`usePrefersDark` provides a reactive boolean that reflects whether the user prefers dark mode based -on their browser or OS settings. It uses the -[prefers-color-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) -media query and updates automatically when the preference changes. - -## Demo - - - -## Usage - -```svelte - - -
- {darkMode.current ? "🌙 Dark mode" : "☀️ Light mode"} -
-``` - -You can also use the class directly: - -```svelte - - -

Dark mode preferred: {darkMode.current ? "Yes" : "No"}

-``` - -## Type Definition - -```ts -type UsePrefersDarkOptions = { - /** - * Fallback value for server-side rendering - * @defaultValue false - */ - fallback?: boolean; -}; - -class UsePrefersDark { - constructor(options?: UsePrefersDarkOptions); - readonly current: boolean; // true when dark mode is preferred -} - -function usePrefersDark(options?: UsePrefersDarkOptions): UsePrefersDark; -``` - -## Notes - -- Uses the `prefers-color-scheme: dark` media query. -- During server-side rendering, returns the fallback value (defaults to `false`). -- Automatically updates when user changes their system dark mode preference. -- Works with both light and dark theme preferences. diff --git a/sites/docs/src/lib/components/demos/use-preferred-color-scheme.svelte b/sites/docs/src/lib/components/demos/use-preferred-color-scheme.svelte new file mode 100644 index 00000000..9b340282 --- /dev/null +++ b/sites/docs/src/lib/components/demos/use-preferred-color-scheme.svelte @@ -0,0 +1,11 @@ + + + + Preferred Color Scheme: + {{ colorScheme: colorScheme.current }} + diff --git a/sites/docs/src/lib/components/demos/use-prefers-dark.svelte b/sites/docs/src/lib/components/demos/use-prefers-dark.svelte deleted file mode 100644 index ac77a2c4..00000000 --- a/sites/docs/src/lib/components/demos/use-prefers-dark.svelte +++ /dev/null @@ -1,32 +0,0 @@ - - - -

Dark mode preferred: {darkMode.current ? "true" : "false"}

-

Preference changes: {changeCount}

-

Try changing your system dark mode preference to see this update!

-
- -