Skip to content

Commit

Permalink
add ThemeInit component that place script inside <head> tag (#176)
Browse files Browse the repository at this point in the history
* add ThemeInit component that place script inside <head> tag

* Fix bug

* proper sorting

* instantiate ThemeInit inside Settings component

* add comment about stringified function
  • Loading branch information
dimfeld authored and techniq committed Jan 6, 2024
1 parent e84e719 commit 4f929ff
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/friendly-pets-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte-ux": patch
---

Add ThemeInit component to prevent flash of unstyled content when SSR is enabled
6 changes: 6 additions & 0 deletions packages/svelte-ux/src/lib/components/Settings.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
<script lang="ts">
import ThemeInit from './ThemeInit.svelte';
import { settings as setSettings, type Settings } from './settings';
export let settings: Settings;
/** Include the ThemeInit component to improve SSR compatibility. */
export let themeInit = true;
setSettings(settings);
</script>

{#if themeInit}
<ThemeInit />
{/if}
<slot />
12 changes: 12 additions & 0 deletions packages/svelte-ux/src/lib/components/ThemeInit.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script>
import { createHeadSnippet } from '../styles/theme';
import { getSettings } from './settings';
const darkThemes = getSettings().themes?.dark ?? [];
let headSnippet = createHeadSnippet(darkThemes);
</script>
<svelte:head>
{@html headSnippet}
</svelte:head>
1 change: 1 addition & 0 deletions packages/svelte-ux/src/lib/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export { default as Tab } from './Tab.svelte';
export { default as Tabs } from './Tabs.svelte';
export { default as TextField } from './TextField.svelte';
export { default as ThemeButton } from './ThemeButton.svelte';
export { default as ThemeInit } from './ThemeInit.svelte';
export { default as Tilt } from './Tilt.svelte';
export { default as Toggle } from './Toggle.svelte';
export { default as ToggleButton } from './ToggleButton.svelte';
Expand Down
24 changes: 24 additions & 0 deletions packages/svelte-ux/src/lib/styles/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,27 @@ export const colorNames = [
'surface-300',
'surface-content',
];

/** Return a script tag that will set the initial theme from localStorage. This allows setting
* the theme before anything starts rendering, even when SSR is in use.
*
* This feels a bit weird compared to just placing the function directly in svelte:head,
* but it's the only way to inject the `darkThemes` array into the function.
**/
export function createHeadSnippet(darkThemes: string[]) {
function _applyInitialStyle(darkThemes) {
let theme = localStorage.getItem('theme');
if (theme) {
document.documentElement.dataset.theme = theme;
if (darkThemes.includes(theme)) {
document.documentElement.classList.add('dark');
}
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark');
}
}

let darkThemeList = darkThemes.map((theme) => `'${theme}'`).join(', ');

return `<script>(${_applyInitialStyle.toString()})([${darkThemeList}])</script>`;
}

0 comments on commit 4f929ff

Please sign in to comment.