Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add theme color support #206

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

- Nuxt 3 and Nuxt Bridge support
- Add `.${color}-mode` class to `<html>` for easy CSS theming
- Automatically set [theme color](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/theme-color) of the page
- Force a page to a specific color mode (perfect for incremental development)
- Works with client-side and universal rendering
- Auto detect system [color-mode](https://drafts.csswg.org/mediaqueries-5/#descdef-media-prefers-color-mode)
Expand Down
4 changes: 3 additions & 1 deletion docs/content/1.index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Dark and Light mode with auto detection made easy with Nuxt 🌗

- Nuxt 3 and Nuxt Bridge support
- Add `.${color}-mode` class to `<html>` for easy CSS theming
- Automatically set [theme color](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/theme-color) of the page
- Force a page to a specific color mode (perfect for incremental development)
- Works with client-side and universal rendering
- Auto detect system [color-mode](https://drafts.csswg.org/mediaqueries-5/#descdef-media-prefers-color-mode)
Expand Down Expand Up @@ -142,7 +143,8 @@ export default defineNuxtConfig({
componentName: 'ColorScheme',
classPrefix: '',
classSuffix: '-mode',
storageKey: 'nuxt-color-mode'
storageKey: 'nuxt-color-mode',
themeColors: null
}
})
```
Expand Down
9 changes: 8 additions & 1 deletion playground/nuxt.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,12 @@ import colorModeModule from '../src/module'
export default defineNuxtConfig({
components: { global: true, dirs: ['~/components'] },
css: ['~/assets/main.css'],
modules: [colorModeModule]
modules: [colorModeModule],
colorMode: {
themeColors: {
dark: '#091a28',
light: '#f3f5f4',
sepia: '#f1e7d0'
}
}
})
30 changes: 17 additions & 13 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 12 additions & 2 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const DEFAULTS: ModuleOptions = {
classPrefix: '',
classSuffix: '-mode',
dataValue: '',
storageKey: 'nuxt-color-mode'
storageKey: 'nuxt-color-mode',
themeColors: null
}

export default defineNuxtModule({
Expand All @@ -33,7 +34,12 @@ export default defineNuxtModule({
// Read script from disk and add to options
const scriptPath = await resolver.resolve('./script.min.js')
const scriptT = await fsp.readFile(scriptPath, 'utf-8')
options.script = template(scriptT)({ options })
options.script = template(scriptT)({
options: {
...options,
themeColors: JSON.stringify(options.themeColors)
}
})

// Inject options via virtual template
nuxt.options.alias['#color-mode-options'] = addTemplate({
Expand Down Expand Up @@ -162,4 +168,8 @@ export interface ModuleOptions {
* The script that will be injected into the head of the page
*/
script?: string
/**
* Default: null
*/
themeColors: Record<string, string> | null
}
35 changes: 29 additions & 6 deletions src/script.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
// @ts-nocheck
// Add dark / light detection that runs before loading Nuxt
(() => {
// Global variable minimizers
const w = window
const de = document.documentElement
const d = document
const de = d.documentElement

const knownColorSchemes = ['dark', 'light']

const preference = (window && window.localStorage && window.localStorage.getItem && window.localStorage.getItem('<%= options.storageKey %>')) || '<%= options.preference %>'

/* Backticks are needed to proper escape quotes in options.themeColors.
Using ES template literals instead of "interpolate" delimiter (lodash)
is needed to preserve the backticks in the minify process
*/
const metaThemeColors = JSON.parse(`${options.themeColors}`)

// Get previous meta element if the script is run the second time (e.g. in dev mode)
let metaElementThemeColor = d.head.querySelector('meta[name=theme-color]')
let value = preference === 'system' ? getColorScheme() : preference
// Applied forced color mode
const forcedColorMode = de.getAttribute('data-color-mode-forced')
Expand All @@ -16,7 +27,6 @@

addColorScheme(value)

// @ts-ignore
w['<%= options.globalName %>'] = {
preference,
value,
Expand All @@ -25,7 +35,6 @@
removeColorScheme
}

// @ts-ignore
function addColorScheme (value) {
const className = '<%= options.classPrefix %>' + value + '<%= options.classSuffix %>'
const dataValue = '<%= options.dataValue %>'
Expand All @@ -37,9 +46,25 @@
if (dataValue) {
de.setAttribute('data-' + dataValue, value)
}

// theme-color refers to a meta attribute which indicates a
// suggested color user agents can use to customize the interface
// more info: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/theme-color
const currentMetaThemeColor = metaThemeColors?.[value]
if (currentMetaThemeColor) {
if (!metaElementThemeColor) {
metaElementThemeColor = d.createElement('meta')
metaElementThemeColor.name = 'theme-color'
}
metaElementThemeColor.content = currentMetaThemeColor
if (metaElementThemeColor.parentNode !== d.head) {
d.head.appendChild(metaElementThemeColor)
}
} else if (metaElementThemeColor?.parentNode) {
metaElementThemeColor.parentNode.removeChild(metaElementThemeColor)
}
}

// @ts-ignore
function removeColorScheme (value) {
const className = '<%= options.classPrefix %>' + value + '<%= options.classSuffix %>'
const dataValue = '<%= options.dataValue %>'
Expand All @@ -53,13 +78,11 @@
}
}

// @ts-ignore
function prefersColorScheme (suffix) {
return w.matchMedia('(prefers-color-scheme' + suffix + ')')
}

function getColorScheme () {
// @ts-ignore
if (w.matchMedia && prefersColorScheme('').media !== 'not all') {
for (const colorScheme of knownColorSchemes) {
if (prefersColorScheme(':' + colorScheme).matches) {
Expand Down