Skip to content

Commit

Permalink
Merge pull request #5698 from nextcloud-libraries/feat/is-dark-theme
Browse files Browse the repository at this point in the history
feat: add isDarkTheme functions and composables
  • Loading branch information
susnux authored Nov 6, 2024
2 parents e8d9989 + 5eba1f1 commit b7eeee8
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 13 deletions.
2 changes: 1 addition & 1 deletion REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ SPDX-FileCopyrightText = "2020-2024 Nextcloud translators"
SPDX-License-Identifier = "AGPL-3.0-or-later"

[[annotations]]
path = ["tsconfig.json", "tsconfig.webpack.json", "cypress/tsconfig.json", "tests/tsconfig.json"]
path = ["tsconfig.json", "cypress/tsconfig.json", "tests/tsconfig.json"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2022-2024 Nextcloud GmbH and Nextcloud contributors"
SPDX-License-Identifier = "CC0-1.0"
Expand Down
72 changes: 72 additions & 0 deletions docs/composables/useIsDarkTheme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<!--
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

```ts static
import {
useIsDarkTheme,
useIsDarkThemeElement,
} from '@nextcloud/vue/dist/Composables/useIsDarkTheme.js'
```

Same as `isDarkTheme` functions, but with reactivity.

## Definition

```ts static
/**
* Check whether the dark theme is enabled on a specific element.
* If you need to check an entire page, use `useIsDarkTheme` instead for better performance.
* Reacts on element attributes change and system theme change.
* @param el - The element to check for the dark theme enabled on, default is document.body
* @return - computed boolean whether the dark theme is enabled
*/
declare function useIsDarkThemeElement(el: MaybeRef<HTMLElement> = document.body): DeepReadonly<Ref<boolean>>

/**
* Shared composable to check whether the dark theme is enabled on the page.
* Reacts on body data-theme-* attributes change and system theme change.
* @return - computed boolean whether the dark theme is enabled
*/
declare function useIsDarkTheme(): DeepReadonly<Ref<boolean>>
```

## Example

```vue
<template>
<div>
<div :style="{ backgroundColor: isDarkTheme ? 'black' : 'white' }">
Is dark theme enabled? {{ isDarkTheme }}
</div>
<NcButton @click="switchTheme">Switch theme</NcButton>
</div>
</template>
<script>
export default {
setup() {
const isDarkTheme = useIsDarkTheme()
// For documentation only. Do not use in production.
function switchTheme() {
if (isDarkTheme.value) {
document.body.setAttribute('data-theme-light', '')
document.body.removeAttribute('data-theme-dark')
document.body.setAttribute('data-themes', 'light')
} else {
document.body.setAttribute('data-theme-dark', '')
document.body.removeAttribute('data-theme-light')
document.body.setAttribute('data-themes', 'dark')
}
}
return {
isDarkTheme,
switchTheme,
}
},
}
</script>
```
36 changes: 36 additions & 0 deletions docs/functions/isDarkTheme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!--
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

```ts static
import {
isDarkTheme,
checkIfDarkTheme,
} from '@nextcloud/vue/dist/Functions/isDarkTheme.js'
```

Check whether the dark theme is enabled in Nextcloud.

You should not use `window.matchMedia.('(prefers-color-scheme: dark)')`. It checks for the user's system theme, but Nextcloud Dark theme could be enabled even on the light system theme.

You should not use `[data-themes*=dark]` or `[data-theme-dark]` attributes on the body. It checks for explicitly set dark theme, but a user may use the system or custom theme.

## Definitions

```ts static
/**
* Check whether the dark theme is used on a specific element
* @param el - Element to check for dark theme, which is used for `data-theme-*` checking (default is `document.body`)
* @return - Whether the dark theme is enabled via Nextcloud theme
*/
declare function checkIfDarkTheme(el: HTMLElement = document.body): boolean;

/**
* Whether the dark theme is enabled in Nextcloud.
* The variable is defined on page load and not reactive.
* Use `checkIfDarkTheme` if you need to check it at a specific moment.
* Use `useDarkTheme` if you need a reactive variable in a Vue component.
*/
declare var isDarkTheme
```
1 change: 1 addition & 0 deletions src/composables/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './useIsFullscreen/index.js'
export * from './useIsMobile/index.js'
export * from './useFormatDateTime.ts'
export * from './useHotKey/index.js'
export * from './useIsDarkTheme/index.ts'
40 changes: 40 additions & 0 deletions src/composables/useIsDarkTheme/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { DeepReadonly, Ref } from 'vue'
import { ref, readonly, watch } from 'vue'
import { createSharedComposable, usePreferredDark, useMutationObserver } from '@vueuse/core'
import { checkIfDarkTheme } from '../../functions/isDarkTheme/index.ts'

/**
* Check whether the dark theme is enabled on a specific element.
* If you need to check an entire page, use `useIsDarkTheme` instead for better performance.
* Reacts on element attributes change and system theme change.
* @param el - The element to check for the dark theme enabled on (default is `document.body`)
* @return {DeepReadonly<Ref<boolean>>} - computed boolean whether the dark theme is enabled
*/
export function useIsDarkThemeElement(el: HTMLElement = document.body): DeepReadonly<Ref<boolean>> {
const isDarkTheme = ref(checkIfDarkTheme(el))
const isDarkSystemTheme = usePreferredDark()

/** Update the isDarkTheme */
function updateIsDarkTheme() {
isDarkTheme.value = checkIfDarkTheme(el)
}

// Watch for element change to handle data-theme* attributes change
useMutationObserver(el, updateIsDarkTheme, { attributes: true })
// Watch for system theme change for the default theme
watch(isDarkSystemTheme, updateIsDarkTheme, { immediate: true })

return readonly(isDarkTheme)
}

/**
* Shared composable to check whether the dark theme is enabled on the page.
* Reacts on body data-theme-* attributes change and system theme change.
* @return {DeepReadonly<Ref<boolean>>} - computed boolean whether the dark theme is enabled
*/
export const useIsDarkTheme = createSharedComposable(() => useIsDarkThemeElement())
1 change: 1 addition & 0 deletions src/functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
export * from './a11y/index.ts'
export * from './emoji/index.ts'
export * from './reference/index.js'
export * from './isDarkTheme/index.ts'
export { default as usernameToColor } from './usernameToColor/index.js'
32 changes: 32 additions & 0 deletions src/functions/isDarkTheme/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

/**
* Check whether the dark theme is used on a specific element
* @param el - Element to check for dark theme, which is used for `data-theme-*` checking (default is `document.body`)
* @return {boolean} - Whether the dark theme is enabled via Nextcloud theme
*/
export function checkIfDarkTheme(el: HTMLElement = document.body): boolean {
// Nextcloud uses --background-invert-if-dark for dark theme filters in CSS
// Values:
// - 'invert(100%)' for dark theme
// - 'no' for light theme
// This is the most reliable way to check for dark theme, including custom themes
const backgroundInvertIfDark = window.getComputedStyle(el).getPropertyValue('--background-invert-if-dark')
if (backgroundInvertIfDark !== undefined) {
return backgroundInvertIfDark === 'invert(100%)'
}

// There is no theme? Fallback to the light theme
return false
}

/**
* Whether the dark theme is enabled in Nextcloud.
* The variable is defined on page load and not reactive.
* Use `checkIfDarkTheme` if you need to check it at a specific moment.
* Use `useDarkTheme` if you need a reactive variable in a Vue component.
*/
export const isDarkTheme = checkIfDarkTheme()
9 changes: 9 additions & 0 deletions styleguide.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ module.exports = async () => {
name: 'emoji',
content: 'docs/functions/emoji.md',
},
{
name: 'isDarkTheme',
content: 'docs/functions/isDarkTheme.md',
},
{
name: 'usernameToColor',
content: 'docs/functions/usernameToColor.md',
Expand All @@ -151,6 +155,11 @@ module.exports = async () => {
{
name: 'useHotKey',
content: 'docs/composables/useHotKey.md',

},
{
name: 'useIsDarkTheme',
content: 'docs/composables/useIsDarkTheme.md',
},
],
},
Expand Down
3 changes: 3 additions & 0 deletions styleguide/global.requires.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import usernameToColor from '../src/functions/usernameToColor/index.js'
import Tooltip from './../src/directives/Tooltip/index.js'
import Focus from './../src/directives/Focus/index.js'
import Linkify from './../src/directives/Linkify/index.js'
import { useIsDarkTheme } from '../src/composables/index.js'

import axios from '@nextcloud/axios'

Expand Down Expand Up @@ -166,6 +167,8 @@ window.emojiAddRecent = emojiAddRecent
window.getCurrentSkinTone = getCurrentSkinTone
window.setCurrentSkinTone = setCurrentSkinTone
window.usernameToColor = usernameToColor
// Exported composables
window.useIsDarkTheme = useIsDarkTheme

// Directives
Vue.directive('Tooltip', Tooltip)
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"include": ["./src/**/*.ts"],
"exclude": ["./src/**/*.cy.ts"],
"compilerOptions": {
"allowImportingTsExtensions": true,
"allowSyntheticDefaultImports": true,
"moduleResolution": "Bundler",
"target": "ESNext",
Expand Down
6 changes: 0 additions & 6 deletions tsconfig.webpack.json

This file was deleted.

6 changes: 0 additions & 6 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,6 @@ webpackRules.RULE_TS = {
test: /\.tsx?$/,
use: [
'babel-loader',
{
loader: 'ts-loader',
options: {
configFile: 'tsconfig.webpack.json',
},
},
],
}

Expand Down

0 comments on commit b7eeee8

Please sign in to comment.