Skip to content

Commit

Permalink
feat(useColorMode): expose system and store ref, close #2023
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Apr 13, 2023
1 parent d23ca30 commit d150ca2
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 12 deletions.
2 changes: 2 additions & 0 deletions packages/core/useColorMode/component.ts
Expand Up @@ -9,6 +9,8 @@ export const UseColorMode = /* #__PURE__ */ defineComponent<UseColorModeOptions>
const mode = useColorMode(props)
const data = reactive({
mode,
system: mode.system,
store: mode.store,
})

return () => {
Expand Down
15 changes: 15 additions & 0 deletions packages/core/useColorMode/index.md
Expand Up @@ -43,6 +43,21 @@ const mode = useColorMode({
}) // Ref<'dark' | 'light' | 'dim' | 'cafe'>
```

## Advanced Usage

You can also explicit access to the system preference and storaged user override mode.

```js
import { useColorMode } from '@vueuse/core'

const { system, store } = useColorMode()

system.value // 'dark' | 'light'
store.value // 'dark' | 'light' | 'auto'

const myColorMode = computed(() => store.value === 'auto' ? system.value : store.value)
```

## Component Usage

```html
Expand Down
19 changes: 13 additions & 6 deletions packages/core/useColorMode/index.test.ts
Expand Up @@ -11,14 +11,12 @@ describe('useColorMode', () => {
vi.mock('../usePreferredDark', () => {
const mockPreferredDark = ref(false)
return {
usePreferredDark: [true, ...new Array(8).fill(false), true].reduce((fn, v) => fn.mockImplementationOnce(() => {
mockPreferredDark.value = v
return mockPreferredDark
}), vi.fn()),
usePreferredDark: () => mockPreferredDark,
}
})

beforeEach(() => {
usePreferredDark().value = false
localStorage.clear()
htmlEl!.className = ''
})
Expand All @@ -28,9 +26,11 @@ describe('useColorMode', () => {
vi.resetModules()
})

it('should translate auto mode when prefer dark', () => {
it('should translate auto mode when prefer dark', async () => {
const mode = useColorMode()
mode.value = 'auto'
usePreferredDark().value = true
await nextTwoTick()
expect(mode.value).toBe('dark')
expect(localStorage.getItem(storageKey)).toBe('auto')
expect(htmlEl?.className).toMatch(/dark/)
Expand Down Expand Up @@ -109,11 +109,18 @@ describe('useColorMode', () => {

it('should only change html class when preferred dark changed', async () => {
const mode = useColorMode({ emitAuto: true })
usePreferredDark()
usePreferredDark().value = true

await nextTwoTick()
expect(mode.value).toBe('auto')
expect(localStorage.getItem(storageKey)).toBe('auto')
expect(htmlEl?.className).toMatch(/dark/)
})

it('should use state to access mode & preference', () => {
const state = useColorMode()
expect(state.store.value).toBe('auto')
expect(state.system.value).toBe('light')
expect(state.value).toBe('light')
})
})
27 changes: 21 additions & 6 deletions packages/core/useColorMode/index.ts
Expand Up @@ -88,13 +88,21 @@ export interface UseColorModeOptions<T extends string = BasicColorSchema> extend
disableTransition?: boolean
}

export type UseColorModeReturn<T extends string = BasicColorSchema> =
Ref<T> & {
store: Ref<T>
system: Ref<'light' | 'dark'>
}

/**
* Reactive color mode with auto data persistence.
*
* @see https://vueuse.org/useColorMode
* @param options
*/
export function useColorMode<T extends string = BasicColorSchema>(options: UseColorModeOptions<T> = {}) {
export function useColorMode<T extends string = BasicColorSchema>(
options: UseColorModeOptions<T> = {},
): UseColorModeReturn<T> {
const {
selector = 'html',
attribute = 'class',
Expand All @@ -117,7 +125,7 @@ export function useColorMode<T extends string = BasicColorSchema>(options: UseCo
} as Record<BasicColorSchema | T, string>

const preferredDark = usePreferredDark({ window })
const preferredMode = computed(() => preferredDark.value ? 'dark' : 'light')
const system = computed(() => preferredDark.value ? 'dark' : 'light')

const store = storageRef || (storageKey == null
? ref(initialValue) as Ref<T | BasicColorSchema>
Expand All @@ -126,7 +134,7 @@ export function useColorMode<T extends string = BasicColorSchema>(options: UseCo
const state = computed<T | BasicColorSchema>({
get() {
return (store.value === 'auto' && !emitAuto)
? preferredMode.value
? system.value
: store.value
},
set(v) {
Expand Down Expand Up @@ -175,7 +183,7 @@ export function useColorMode<T extends string = BasicColorSchema>(options: UseCo
})

function defaultOnChanged(mode: T | BasicColorSchema) {
const resolvedMode = mode === 'auto' ? preferredMode.value : mode
const resolvedMode = mode === 'auto' ? system.value : mode
updateHTMLAttrs(selector, attribute, modes[resolvedMode] ?? resolvedMode)
}

Expand All @@ -187,10 +195,17 @@ export function useColorMode<T extends string = BasicColorSchema>(options: UseCo
}

watch(state, onChanged, { flush: 'post', immediate: true })

if (emitAuto)
watch(preferredMode, () => onChanged(state.value), { flush: 'post' })
watch(system, () => onChanged(state.value), { flush: 'post' })

tryOnMounted(() => onChanged(state.value))

return state
try {
return Object.assign(state, { store, system }) as UseColorModeReturn<T>
}
catch (e) {
// In Vue 2.6, ref might not be extensible
return state as any as UseColorModeReturn<T>
}
}

0 comments on commit d150ca2

Please sign in to comment.