AppearanceModeDropdown.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import React, {
  2. FC, useCallback, useRef,
  3. } from 'react';
  4. import { useTranslation } from 'next-i18next';
  5. import { useRipple } from 'react-use-ripple';
  6. import { UncontrolledTooltip } from 'reactstrap';
  7. import { useUserUISettings } from '~/client/services/user-ui-settings';
  8. import { usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser } from '~/stores/ui';
  9. import { Themes, useNextThemes } from '~/stores/use-next-themes';
  10. import SidebarDockIcon from '../Icons/SidebarDockIcon';
  11. import SidebarDrawerIcon from '../Icons/SidebarDrawerIcon';
  12. type AppearanceModeDropdownProps = {
  13. isAuthenticated: boolean,
  14. }
  15. export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: AppearanceModeDropdownProps) => {
  16. const { t } = useTranslation('commons');
  17. const { isAuthenticated } = props;
  18. const {
  19. setTheme, resolvedTheme, useOsSettings, isDarkMode, isForcedByGrowiTheme,
  20. } = useNextThemes();
  21. const { data: isPreferDrawerMode, update: updatePreferDrawerMode } = usePreferDrawerModeByUser();
  22. const { data: isPreferDrawerModeOnEdit, mutate: mutatePreferDrawerModeOnEdit } = usePreferDrawerModeOnEditByUser();
  23. const { scheduleToPut } = useUserUISettings();
  24. // ripple
  25. const buttonRef = useRef(null);
  26. useRipple(buttonRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
  27. const preferDrawerModeSwitchModifiedHandler = useCallback((preferDrawerMode: boolean, isEditMode: boolean) => {
  28. if (isEditMode) {
  29. mutatePreferDrawerModeOnEdit(preferDrawerMode);
  30. scheduleToPut({ preferDrawerModeOnEditByUser: preferDrawerMode });
  31. }
  32. else {
  33. updatePreferDrawerMode(preferDrawerMode);
  34. }
  35. }, [updatePreferDrawerMode, mutatePreferDrawerModeOnEdit, scheduleToPut]);
  36. const followOsCheckboxModifiedHandler = useCallback((isChecked: boolean) => {
  37. if (isChecked) {
  38. setTheme(Themes.SYSTEM);
  39. }
  40. else {
  41. setTheme(resolvedTheme ?? Themes.LIGHT);
  42. }
  43. }, [resolvedTheme, setTheme]);
  44. const userPreferenceSwitchModifiedHandler = useCallback((isDarkMode: boolean) => {
  45. setTheme(isDarkMode ? Themes.DARK : Themes.LIGHT);
  46. }, [setTheme]);
  47. /* eslint-disable react/prop-types */
  48. const IconWithTooltip = ({
  49. id, label, children, additionalClasses,
  50. }) => (
  51. <>
  52. <div id={id} className={`px-2 grw-icon-container ${additionalClasses != null ? additionalClasses : ''}`}>{children}</div>
  53. <UncontrolledTooltip placement="bottom" fade={false} target={id}>{label}</UncontrolledTooltip>
  54. </>
  55. );
  56. const dropdownDivider = <div className="dropdown-divider"></div>;
  57. const renderSidebarModeSwitch = useCallback((isEditMode: boolean) => {
  58. return (
  59. <>
  60. <h6 className="dropdown-header">{t(isEditMode ? 'personal_dropdown.sidebar_mode_editor' : 'personal_dropdown.sidebar_mode')}</h6>
  61. <form className="px-4">
  62. <div className="justify-content-center">
  63. <div className="col-auto mb-0 d-flex align-items-center">
  64. <IconWithTooltip id={isEditMode ? 'iwt-sidebar-editor-drawer' : 'iwt-sidebar-drawer'} label="Drawer" additionalClasses="grw-sidebar-mode-icon">
  65. <SidebarDrawerIcon />
  66. </IconWithTooltip>
  67. <div className="form-check form-switch form-check-secondary ms-2">
  68. <input
  69. id={isEditMode ? 'swSidebarModeOnEditor' : 'swSidebarMode'}
  70. className="form-check-input"
  71. type="checkbox"
  72. checked={isEditMode ? !isPreferDrawerModeOnEdit : !isPreferDrawerMode}
  73. onChange={e => preferDrawerModeSwitchModifiedHandler(!e.target.checked, isEditMode)}
  74. />
  75. <label className="form-label form-check-label" htmlFor={isEditMode ? 'swSidebarModeOnEditor' : 'swSidebarMode'}></label>
  76. </div>
  77. <IconWithTooltip id={isEditMode ? 'iwt-sidebar-editor-dock' : 'iwt-sidebar-dock'} label="Dock" additionalClasses="grw-sidebar-mode-icon">
  78. <SidebarDockIcon />
  79. </IconWithTooltip>
  80. </div>
  81. </div>
  82. </form>
  83. </>
  84. );
  85. }, [isPreferDrawerMode, isPreferDrawerModeOnEdit, preferDrawerModeSwitchModifiedHandler, t]);
  86. return (
  87. <div className="dropend">
  88. {/* setting button */}
  89. {/* remove .dropdown-toggle for hide caret */}
  90. {/* See https://stackoverflow.com/a/44577512/13183572 */}
  91. <button className="btn btn-primary" type="button" data-bs-toggle="dropdown" ref={buttonRef} aria-haspopup="true">
  92. <span className="material-symbols-outlined">settings</span>
  93. </button>
  94. {/* dropdown */}
  95. <div className="dropdown-menu">
  96. {/* sidebar mode */}
  97. {renderSidebarModeSwitch(false)}
  98. {/* side bar mode on editor */}
  99. {isAuthenticated && (
  100. <>
  101. {dropdownDivider}
  102. {renderSidebarModeSwitch(true)}
  103. </>
  104. )}
  105. {/* color mode */}
  106. { !isForcedByGrowiTheme && (
  107. <>
  108. {dropdownDivider}
  109. <h6 className="dropdown-header">{t('personal_dropdown.color_mode')}</h6>
  110. <form className="px-4">
  111. <div className="justify-content-center">
  112. <div className="col-auto d-flex align-items-center">
  113. <IconWithTooltip id="iwt-light" label="Light" additionalClasses={useOsSettings ? 'grw-color-mode-icon-muted' : 'grw-color-mode-icon'}>
  114. <span className="material-symbols-outlined">light_mode</span>
  115. </IconWithTooltip>
  116. <div className="form-check form-switch form-check-secondary ms-2">
  117. <input
  118. id="swUserPreference"
  119. className="form-check-input"
  120. type="checkbox"
  121. checked={isDarkMode}
  122. disabled={useOsSettings}
  123. onChange={e => userPreferenceSwitchModifiedHandler(e.target.checked)}
  124. />
  125. <label className="form-label form-check-label" htmlFor="swUserPreference"></label>
  126. </div>
  127. <IconWithTooltip id="iwt-dark" label="Dark" additionalClasses={useOsSettings ? 'grw-color-mode-icon-muted' : 'grw-color-mode-icon'}>
  128. <span className="material-symbols-outlined">dark_mode</span>
  129. </IconWithTooltip>
  130. </div>
  131. </div>
  132. <div>
  133. <div className="col-auto">
  134. <div className="form-check">
  135. <input
  136. id="cbFollowOs"
  137. className="form-check-input"
  138. type="checkbox"
  139. checked={useOsSettings}
  140. onChange={e => followOsCheckboxModifiedHandler(e.target.checked)}
  141. />
  142. <label className="form-label form-check-label text-nowrap" htmlFor="cbFollowOs">{t('personal_dropdown.use_os_settings')}</label>
  143. </div>
  144. </div>
  145. </div>
  146. </form>
  147. </>
  148. ) }
  149. </div>
  150. </div>
  151. );
  152. };