chatwoot/chatwoot

View on GitHub
app/javascript/portal/portalThemeHelper.js

Summary

Maintainability
A
0 mins
Test Coverage
B
85%
import { adjustColorForContrast } from '../shared/helpers/colorHelper.js';

export const setPortalHoverColor = theme => {
  // This function is to set the hover color for the portal
  if (theme === 'system') {
    const prefersDarkMode = window.matchMedia(
      '(prefers-color-scheme: dark)'
    ).matches;
    theme = prefersDarkMode ? 'dark' : 'light';
  }

  const portalColor = window.portalConfig.portalColor;
  const bgColor = theme === 'dark' ? '#151718' : 'white';
  const hoverColor = adjustColorForContrast(portalColor, bgColor);

  // Set hover color for border and text dynamically
  document.documentElement.style.setProperty(
    '--dynamic-hover-color',
    hoverColor
  );
};

export const removeQueryParamsFromUrl = (queryParam = 'theme') => {
  // This function is to remove the theme query param from the URL
  // This is done so that the theme is not persisted in the URL
  // This is called when the theme is switched from the dropdown
  const url = new URL(window.location.href);
  const param = url.searchParams.get(queryParam);

  if (param) {
    url.searchParams.delete(queryParam);
    window.history.replaceState({}, '', url.toString()); // Convert URL to string
  }
};

export const updateThemeInHeader = theme => {
  // This function is to update the theme selection in the header in real time
  const themeToggleButton = document.getElementById('toggle-appearance');

  if (!themeToggleButton) return;
  const allElementInButton =
    themeToggleButton.querySelectorAll('.theme-button');

  if (!allElementInButton) return;
  allElementInButton.forEach(button => {
    button.classList.toggle('hidden', button.dataset.theme !== theme);
    button.classList.toggle('flex', button.dataset.theme === theme);
  });
};

export const switchTheme = theme => {
  if (theme === 'system') {
    localStorage.removeItem('theme');
    const prefersDarkMode = window.matchMedia(
      '(prefers-color-scheme: dark)'
    ).matches;
    // remove this so that the system theme is used

    document.documentElement.classList.remove('dark', 'light');
    document.documentElement.classList.add(prefersDarkMode ? 'dark' : 'light');
  } else {
    localStorage.theme = theme;

    document.documentElement.classList.remove('dark', 'light');
    document.documentElement.classList.add(theme);
  }

  setPortalHoverColor(theme);
  updateThemeInHeader(theme);
  removeQueryParamsFromUrl();
};

export const initializeThemeSwitchButtons = () => {
  const appearanceDropdown = document.getElementById('appearance-dropdown');
  appearanceDropdown.dataset.currentTheme = localStorage.theme || 'system';

  appearanceDropdown.addEventListener('click', event => {
    const target = event.target.closest('button[data-theme]');

    if (target) {
      const { theme } = target.dataset;
      // setting this data property will automatically toggle the checkmark using CSS
      appearanceDropdown.dataset.currentTheme = theme;
      switchTheme(theme);
      // wait for a bit before hiding the dropdown
      appearanceDropdown.style.display = 'none';
    }
  });
};

export const initializeToggleButton = () => {
  const themeToggleButton = document.getElementById('toggle-appearance');

  themeToggleButton?.addEventListener('click', () => {
    const appearanceDropdown = document.getElementById('appearance-dropdown');

    const isCurrentlyHidden = appearanceDropdown.style.display === 'none';
    // Toggle the appearanceDropdown
    appearanceDropdown.style.display = isCurrentlyHidden ? 'flex' : 'none';
  });
};

export const initializeMediaQueryListener = () => {
  const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

  mediaQuery.addEventListener('change', () => {
    if (['light', 'dark'].includes(localStorage.theme)) return;

    switchTheme('system');
  });
};

export const initializeTheme = () => {
  if (window.portalConfig.isPlainLayoutEnabled === 'true') return;
  // start with updating the theme in the header, this will set the current theme on the button
  // and set the hover color at the start of init, this is set again when the theme is switched
  setPortalHoverColor(localStorage.theme || 'system');
  window.updateThemeInHeader = updateThemeInHeader;
  updateThemeInHeader(localStorage.theme || 'system');

  // add the event listeners for the dropdown toggle and theme buttons
  initializeToggleButton();
  initializeThemeSwitchButtons();

  // add the media query listener to update the theme when the system theme changes
  initializeMediaQueryListener();
};