Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions packages/docs/src/components/code-sandbox/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,26 @@
background-color: #161616;
}

@media (prefers-color-scheme: dark) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all these dark mode changes should only look at data-theme.

:root:not([data-theme]) {
.browser iframe {
border: 1px solid #333;
border-top: none;
}

.browser .bar {
background-color: #1f1f1f;
}

.browser .url {
background-color: #161616;
}
.browser .bar > ul > li.edit > a:hover {
color: #aaa;
}
}
}

.browser .bar > ul {
display: flex;
flex-direction: row;
Expand Down
9 changes: 4 additions & 5 deletions packages/docs/src/components/router-head/theme-script.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ export const themeStorageKey = 'theme-preference';
export const ThemeScript = () => {
const themeScript = `
try {
document.firstElementChild
.setAttribute('data-theme',
localStorage.getItem('${themeStorageKey}') ??
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
);
const getItem = localStorage.getItem('${themeStorageKey}')
if(getItem === 'light' || getItem === 'dark'){
document.firstElementChild.setAttribute('data-theme', getItem);
}
} catch (err) { }`;
return <script dangerouslySetInnerHTML={themeScript} />;
};
19 changes: 19 additions & 0 deletions packages/docs/src/components/theme-toggle/Brilliance.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { component$ } from '@builder.io/qwik';

interface BrillianceIconProps {
class?: string;
}

export const BrillianceIcon = component$<BrillianceIconProps>(({ class: className, ...props }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
class={className}
viewBox="0 0 16 16"
{...props}
>
<path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16M1 8a7 7 0 0 0 7 7 3.5 3.5 0 1 0 0-7 3.5 3.5 0 1 1 0-7 7 7 0 0 0-7 7" />
</svg>
);
});
25 changes: 25 additions & 0 deletions packages/docs/src/components/theme-toggle/Moon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { component$ } from '@builder.io/qwik';

interface MoonIconProps {
class?: string;
}

export const MoonIcon = component$<MoonIconProps>(({ class: className, ...props }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class={className}
{...props}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z"
/>
</svg>
);
});
25 changes: 25 additions & 0 deletions packages/docs/src/components/theme-toggle/Sun.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { component$ } from '@builder.io/qwik';

interface SunIconProps {
class?: string;
}

export const SunIcon = component$<SunIconProps>(({ class: className, ...props }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class={className}
{...props}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z"
/>
</svg>
);
});
84 changes: 0 additions & 84 deletions packages/docs/src/components/theme-toggle/sun-and-moon.css

This file was deleted.

25 changes: 0 additions & 25 deletions packages/docs/src/components/theme-toggle/sun-and-moon.tsx

This file was deleted.

40 changes: 12 additions & 28 deletions packages/docs/src/components/theme-toggle/theme-toggle.css
Original file line number Diff line number Diff line change
@@ -1,33 +1,17 @@
.theme-toggle {
--size: 22px;
--icon-fill: hsl(210 10% 15%);
--icon-fill-hover: hsl(210 10% 30%);

display: block;

background: none;
border: none;
padding: 0;

inline-size: var(--size);
block-size: var(--size);
aspect-ratio: 1;
border-radius: 50%;

cursor: pointer;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;

outline-offset: 5px;
.themeIcon {
opacity: 0;
transition: opacity 400ms ease-in-out;
width: 25px;
height: 25px;
}

[data-theme='dark'] .theme-toggle {
--icon-fill: hsl(210 10% 100%);
--icon-fill-hover: hsl(210 15% 70%);
.themeIcon.auto {
width: 21px;
height: 21px;
}

.theme-toggle > svg {
inline-size: 100%;
block-size: 100%;
stroke-linecap: round;
html[data-theme='light'] .themeIcon.light,
html[data-theme='dark'] .themeIcon.dark,
html:not([data-theme]) .themeIcon.auto {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Data-theme should always hold the current theme even if it's auto. That way the css is much simpler.

So for auto mode we need a different prop. Maybe data-theme-name? Then all three icons can check that. And data-theme basically only ever contains "dark" (unless we add more themes).

opacity: 1;
}
92 changes: 45 additions & 47 deletions packages/docs/src/components/theme-toggle/theme-toggle.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
import { component$, event$, useContext, useStyles$ } from '@builder.io/qwik';
import { SunAndMoon } from './sun-and-moon';
import { component$, event$, isServer, useStyles$ } from '@builder.io/qwik';
import { themeStorageKey } from '../router-head/theme-script';
import themeToggle from './theme-toggle.css?inline';
import { GlobalStore } from '../../context';
import { SunIcon } from './Sun';
import { MoonIcon } from './Moon';
import { BrillianceIcon } from './Brilliance';
export type ThemePreference = 'dark' | 'light' | 'auto';
export const setPreference = (theme: ThemePreference) => {
if (theme === 'auto') {
document.firstElementChild?.removeAttribute('data-theme');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it should pick the media query answer

} else {
document.firstElementChild?.setAttribute('data-theme', theme!);
}

localStorage.setItem(themeStorageKey, theme);
};

export type ThemePreference = 'dark' | 'light';
export const getColorPreference = (): ThemePreference => {
if (isServer) {
return 'auto';
}
let theme;
try {
theme = localStorage.getItem(themeStorageKey);
} catch {
//
}
return (theme as ThemePreference) || 'auto';
};

export const colorSchemeChangeListener = (onColorSchemeChange: (isDark: boolean) => void) => {
const listener = ({ matches: isDark }: MediaQueryListEvent) => {
Expand All @@ -18,56 +40,32 @@ export const colorSchemeChangeListener = (onColorSchemeChange: (isDark: boolean)
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', listener);
};

export const setPreference = (theme: ThemePreference) => {
localStorage.setItem(themeStorageKey, theme);
reflectPreference(theme);
};

export const reflectPreference = (theme: ThemePreference) => {
document.firstElementChild?.setAttribute('data-theme', theme);
};

export const getColorPreference = (): ThemePreference => {
let theme;
try {
theme = localStorage.getItem(themeStorageKey);
} catch (err) {
//
}
if (theme) {
return theme as ThemePreference;
} else {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
};

export const ThemeToggle = component$(() => {
useStyles$(themeToggle);
const state = useContext(GlobalStore);

const onClick$ = event$(() => {
state.theme = state.theme === 'light' ? 'dark' : 'light';
setPreference(state.theme);
let currentTheme = getColorPreference();
if (currentTheme === 'dark') {
currentTheme = 'light';
} else if (currentTheme === 'light') {
currentTheme = 'auto';
} else {
currentTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'light' : 'dark';
}
setPreference(currentTheme);
});

return (
<>
<span class="lg:hidden">
<button onClick$={onClick$}>{state.theme === 'light' ? 'Dark' : 'Light'} theme</button>
</span>
<span class="hidden lg:block">
<button
type="button"
class="theme-toggle"
id="theme-toggle"
title="Toggles light & dark"
aria-label={state.theme}
aria-live="polite"
onClick$={onClick$}
>
<SunAndMoon />
</button>
</span>
<button
onClick$={onClick$}
class="group relative flex h-8 w-8 items-center justify-center rounded-md bg-background text-foreground hover:opacity-60"
>
<div class="absolute inset-0 grid place-items-center transition-transform duration-200 ease-out group-hover:scale-110 group-active:scale-75">
<SunIcon class="themeIcon light col-start-1 row-start-1" />
<MoonIcon class="themeIcon dark col-start-1 row-start-1" />
<BrillianceIcon class="themeIcon auto col-start-1 row-start-1" />
</div>
</button>
</>
);
});
Loading
Loading