UNPKG

@nex-ui/system

Version:

A lightweight and performant styling library based on Emotion, focusing on component architecture and developer experience.

184 lines (180 loc) 6.39 kB
"use client"; 'use strict'; var jsxRuntime = require('react/jsx-runtime'); var react = require('react'); var hooks = require('@nex-ui/hooks'); var ColorSchemeContex = require('./ColorSchemeContex.cjs'); function initializeValue(key, defaultValue) { // FIXME: mock ssr environment /* istanbul ignore if */ if (typeof window === 'undefined') { return undefined; } let value; try { value = localStorage.getItem(key) || undefined; if (!value) { localStorage.setItem(key, defaultValue); } } catch { // Unsupported } return value || defaultValue; } function getSystemColorScheme(mode) { if (typeof window !== 'undefined' && typeof window.matchMedia === 'function' && mode === 'system') { const mql = window.matchMedia('(prefers-color-scheme: dark)'); if (mql.matches) { return 'dark'; } return 'light'; } return undefined; } function getColorScheme(state) { if (state.mode === 'light' || state.mode === 'system' && state.systemColorScheme === 'light') { return 'light'; } if (state.mode === 'dark' || state.mode === 'system' && state.systemColorScheme === 'dark') { return 'dark'; } // FIXME: mock ssr environment /* istanbul ignore next */ return undefined; } function createGetColorSchemeSelector(selector) { return function getColorSchemeSelector(colorScheme) { if (selector) { if (selector.startsWith('data-') && !selector.includes('%s')) { return `[${selector}="${colorScheme}"] &`; } if (selector === 'class') { return `.${colorScheme} &`; } if (selector === 'data') { return `[data-color-scheme=${colorScheme}] &`; } return `${selector.replace('%s', colorScheme)}`; } /* istanbul ignore next */ return '&'; }; } const ColorSchemeProvider = ({ children, forcedMode, colorSchemeNode, modeStorageKey = 'color-scheme', defaultMode = 'system', colorSchemeSelector = 'data' })=>{ const [state, setState] = react.useState(()=>{ const initialMode = forcedMode ?? initializeValue(modeStorageKey, defaultMode); const systemColorScheme = getSystemColorScheme(initialMode); return { mode: initialMode, systemColorScheme }; }); const isMultiSchemes = state.mode === 'system'; const setMode = react.useCallback((mode)=>{ if (forcedMode) { return; } setState((currentState)=>{ if (mode === currentState.mode) { return currentState; } const newMode = mode ?? defaultMode; try { localStorage.setItem(modeStorageKey, newMode); } catch { // Unsupported } return { mode: newMode, systemColorScheme: getSystemColorScheme(newMode) }; }); }, [ defaultMode, forcedMode, modeStorageKey ]); const handleMediaQuery = hooks.useEvent((event)=>{ if (state.mode === 'system') { setState((currentState)=>{ const systemColorScheme = event.matches ? 'dark' : 'light'; if (currentState.systemColorScheme === systemColorScheme) { return currentState; } return { ...currentState, systemColorScheme }; }); } }); react.useEffect(()=>{ if (!isMultiSchemes || typeof window === 'undefined' || typeof window.matchMedia !== 'function') { return; } const media = window.matchMedia('(prefers-color-scheme: dark)'); media.addEventListener('change', handleMediaQuery); return ()=>media.removeEventListener('change', handleMediaQuery); }, [ handleMediaQuery, isMultiSchemes ]); const colorScheme = react.useMemo(()=>getColorScheme(state), [ state ]); react.useEffect(()=>{ if (colorScheme && colorSchemeSelector) { const selector = colorSchemeSelector; let rule = selector; if (selector === 'class') { rule = `.%s`; } if (selector === 'data') { rule = `[data-color-scheme=%s]`; } if (selector?.startsWith('data-') && !selector.includes('%s')) { // 'data-nui-color-scheme' -> '[data-nui-color-scheme="%s"]' rule = `[${selector}=%s]`; } const supportedColorSchemes = [ 'light', 'dark' ]; const node = colorSchemeNode ?? document.documentElement; if (rule.startsWith('.')) { node.classList.remove(...supportedColorSchemes.map((scheme)=>rule.substring(1).replace('%s', scheme))); node.classList.add(rule.substring(1).replace('%s', colorScheme)); } else { const matches = rule.replace('%s', colorScheme).match(/\[([^\]]+)\]/); if (matches) { const [attr, value] = matches[1].split('='); if (!value) { supportedColorSchemes.forEach((scheme)=>{ node.removeAttribute(attr.replace(colorScheme, scheme)); }); } node.setAttribute(attr, value ? value.replace(/"|'/g, '') : ''); } else { node.setAttribute(rule, colorScheme); } } } }, [ colorScheme, colorSchemeNode, colorSchemeSelector ]); const ctx = react.useMemo(()=>({ ...state, setMode, resolvedColorScheme: colorScheme }), [ state, setMode, colorScheme ]); return /*#__PURE__*/ jsxRuntime.jsx(ColorSchemeContex.InnerColorSchemeProvider, { value: ctx, children: children }); }; ColorSchemeProvider.displayName = 'ColorSchemeProvider'; exports.ColorSchemeProvider = ColorSchemeProvider; exports.createGetColorSchemeSelector = createGetColorSchemeSelector;