Skip to content

Commit

Permalink
feat: enable mode-watcher to manage the theme-color meta tag (#48)
Browse files Browse the repository at this point in the history
Co-authored-by: Hunter Johnston <[email protected]>
  • Loading branch information
ollema and huntabyte authored Jan 29, 2024
1 parent 1eb179a commit 733152f
Show file tree
Hide file tree
Showing 20 changed files with 285 additions and 103 deletions.
5 changes: 5 additions & 0 deletions .changeset/witty-mangos-sip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mode-watcher': minor
---

Allow `mode-watcher` to manage the theme-color meta tag
14 changes: 7 additions & 7 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,27 @@ module.exports = {
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:svelte/recommended',
'prettier'
'prettier',
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
extraFileExtensions: ['.svelte'],
},
env: {
browser: true,
es2017: true,
node: true
node: true,
},
overrides: [
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
}
}
]
parser: '@typescript-eslint/parser',
},
},
],
};
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"trailingComma": "es5",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ To set a default mode, use the `defaultMode` prop:
<ModeWatcher defaultMode={"dark"}>
```

`ModeWatcher` can manage the `theme-color` meta tag for you.

To enable this, set the `themeColor` prop to your preferred colors:

```svelte
<ModeWatcher themeColor={{ dark: "black", light: "white" }}>
```

## API

### toggleMode
Expand Down
4 changes: 2 additions & 2 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import type { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
webServer: {
command: 'npm run build && npm run preview',
port: 4173
port: 4173,
},
testDir: 'tests',
testMatch: /(.+\.)?(test|spec)\.[jt]s/
testMatch: /(.+\.)?(test|spec)\.[jt]s/,
};

export default config;
4 changes: 2 additions & 2 deletions postcss.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const config = {
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
tailwindcss(),
//But others, like autoprefixer, need to run after,
autoprefixer
]
autoprefixer,
],
};

module.exports = config;
26 changes: 13 additions & 13 deletions scripts/setupTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import type * as stores from '$app/stores';
import { configure } from '@testing-library/dom';

configure({
asyncUtilTimeout: 1500
asyncUtilTimeout: 1500,
});

// Mock SvelteKit runtime module $app/environment
vi.mock('$app/environment', (): typeof environment => ({
browser: false,
dev: true,
building: false,
version: 'any'
version: 'any',
}));

// Mock SvelteKit runtime module $app/navigation
Expand All @@ -29,7 +29,7 @@ vi.mock('$app/navigation', (): typeof navigation => ({
invalidate: () => Promise.resolve(),
invalidateAll: () => Promise.resolve(),
preloadData: () => Promise.resolve(),
preloadCode: () => Promise.resolve()
preloadCode: () => Promise.resolve(),
}));

// Mock SvelteKit runtime module $app/stores
Expand All @@ -40,12 +40,12 @@ vi.mock('$app/stores', (): typeof stores => {
url: new URL('http://localhost'),
params: {},
route: {
id: null
id: null,
},
status: 200,
error: null,
data: {},
form: undefined
form: undefined,
});
const updated = { subscribe: readable(false).subscribe, check: async () => false };

Expand All @@ -55,30 +55,30 @@ vi.mock('$app/stores', (): typeof stores => {
const page: typeof stores.page = {
subscribe(fn) {
return getStores().page.subscribe(fn);
}
},
};
const navigating: typeof stores.navigating = {
subscribe(fn) {
return getStores().navigating.subscribe(fn);
}
},
};
const updated: typeof stores.updated = {
subscribe(fn) {
return getStores().updated.subscribe(fn);
},
check: async () => false
check: async () => false,
};

return {
getStores,
navigating,
page,
updated
updated,
};
});

export const mediaQueryState = {
matches: false
matches: false,
};

const listeners: ((event: unknown) => void)[] = [];
Expand Down Expand Up @@ -107,10 +107,10 @@ Object.defineProperty(window, 'matchMedia', {
for (const callback of listeners) {
callback({
matches: mediaQueryState.matches,
media: '(prefers-color-scheme: light)'
media: '(prefers-color-scheme: light)',
});
}
}
})
}))
}),
})),
});
6 changes: 3 additions & 3 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
mode,
setMode,
toggleMode,
resetMode
} from './mode';
resetMode,
} from './mode.js';

export {
setMode,
Expand All @@ -15,7 +15,7 @@ export {
localStorageKey,
userPrefersMode,
systemPrefersMode,
mode
mode,
};

export { default as ModeWatcher } from './mode-watcher.svelte';
45 changes: 28 additions & 17 deletions src/lib/mode-watcher.svelte
Original file line number Diff line number Diff line change
@@ -1,37 +1,48 @@
<script lang="ts">
import { onMount } from 'svelte';
import { systemPrefersMode, setMode, localStorageKey, mode } from './mode';
import {
systemPrefersMode,
setMode,
localStorageKey,
mode,
themeColors as themeColorsStore,
setInitialMode,
} from './mode.js';
import type { Mode, ThemeColors } from './types.js';
import { isValidMode } from './stores.js';
export let track = true;
export let defaultMode: 'light' | 'dark' | 'system' = 'system';
export let defaultMode: Mode = 'system';
export let themeColors: ThemeColors = undefined;
themeColorsStore.set(themeColors);
onMount(() => {
const unsubscriber = mode.subscribe(() => {});
systemPrefersMode.tracking(track);
systemPrefersMode.query();
setMode((localStorage.getItem(localStorageKey) as 'dark' | 'light' | 'system') || defaultMode);
const localStorageMode = localStorage.getItem(localStorageKey);
setMode(isValidMode(localStorageMode) ? localStorageMode : defaultMode);
return () => {
unsubscriber();
};
});
function setInitialMode() {
const elem = document.documentElement,
mode = localStorage.getItem('mode') || '<DEFAULT_MODE>',
light =
mode === 'light' ||
(mode === 'system' && window.matchMedia('(prefers-color-scheme: light)').matches);
elem.classList[light ? 'remove' : 'add']('dark');
elem.style.colorScheme = light ? 'light' : 'dark';
localStorage.setItem('mode', mode);
}
const stringified = setInitialMode.toString().replace('<DEFAULT_MODE>', defaultMode);
const args = `"${defaultMode}"${themeColors ? `, ${JSON.stringify(themeColors)}` : ''}`;
</script>

<svelte:head>
{#if themeColors}
<!-- default to dark mode for to allow testing -->
<!-- this will be overwritten by FOUC prevention snippet below -->
<!-- but that snippet does not run in vitest -->
<meta name="theme-color" content={themeColors.dark} />
{/if}
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html `<script nonce="%sveltekit.nonce%">(` + stringified + `)();</script>`}
{@html `<script nonce="%sveltekit.nonce%">(` +
setInitialMode.toString() +
`)(` +
args +
`);</script>`}
</svelte:head>
33 changes: 30 additions & 3 deletions src/lib/mode.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { get } from 'svelte/store';
import { localStorageKey, userPrefersMode, systemPrefersMode, derivedMode } from './stores';
import {
localStorageKey,
userPrefersMode,
systemPrefersMode,
derivedMode,
themeColors,
} from './stores.js';
import type { Mode, ThemeColors } from './types.js';

/** Toggle between light and dark mode */
export function toggleMode(): void {
userPrefersMode.set(get(derivedMode) === 'dark' ? 'light' : 'dark');
}

/** Set the mode to light or dark */
export function setMode(mode: 'dark' | 'light' | 'system'): void {
export function setMode(mode: Mode): void {
userPrefersMode.set(mode);
}

Expand All @@ -16,4 +23,24 @@ export function resetMode(): void {
userPrefersMode.set('system');
}

export { localStorageKey, userPrefersMode, systemPrefersMode, derivedMode as mode };
export function setInitialMode(defaultMode: Mode, themeColors?: ThemeColors) {
const rootEl = document.documentElement;
const mode = localStorage.getItem('mode-watcher-mode') || defaultMode;
const light =
mode === 'light' ||
(mode === 'system' && window.matchMedia('(prefers-color-scheme: light)').matches);

rootEl.classList[light ? 'remove' : 'add']('dark');
rootEl.style.colorScheme = light ? 'light' : 'dark';

if (themeColors) {
const themeMetaEl = document.querySelector('meta[name="theme-color"]');
if (themeMetaEl) {
themeMetaEl.setAttribute('content', mode === 'light' ? themeColors.light : themeColors.dark);
}
}

localStorage.setItem('mode', mode);
}

export { localStorageKey, userPrefersMode, systemPrefersMode, derivedMode as mode, themeColors };
Loading

0 comments on commit 733152f

Please sign in to comment.