feat: add free dark mode (#1748)
This commit is contained in:
@@ -37,6 +37,7 @@ export enum PrefKey {
|
|||||||
NewNoteTitleFormat = 'newNoteTitleFormat',
|
NewNoteTitleFormat = 'newNoteTitleFormat',
|
||||||
CustomNoteTitleFormat = 'customNoteTitleFormat',
|
CustomNoteTitleFormat = 'customNoteTitleFormat',
|
||||||
UpdateSavingStatusIndicator = 'updateSavingStatusIndicator',
|
UpdateSavingStatusIndicator = 'updateSavingStatusIndicator',
|
||||||
|
DarkMode = 'darkMode',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NewNoteTitleFormat {
|
export enum NewNoteTitleFormat {
|
||||||
@@ -82,8 +83,8 @@ export type PrefValue = {
|
|||||||
[PrefKey.NotesHideTags]: boolean
|
[PrefKey.NotesHideTags]: boolean
|
||||||
[PrefKey.NotesHideEditorIcon]: boolean
|
[PrefKey.NotesHideEditorIcon]: boolean
|
||||||
[PrefKey.UseSystemColorScheme]: boolean
|
[PrefKey.UseSystemColorScheme]: boolean
|
||||||
[PrefKey.AutoLightThemeIdentifier]: FeatureIdentifier | 'Default'
|
[PrefKey.AutoLightThemeIdentifier]: FeatureIdentifier | 'Default' | 'Dark'
|
||||||
[PrefKey.AutoDarkThemeIdentifier]: FeatureIdentifier | 'Default'
|
[PrefKey.AutoDarkThemeIdentifier]: FeatureIdentifier | 'Default' | 'Dark'
|
||||||
[PrefKey.NoteAddToParentFolders]: boolean
|
[PrefKey.NoteAddToParentFolders]: boolean
|
||||||
[PrefKey.MobileSortNotesBy]: CollectionSortProperty
|
[PrefKey.MobileSortNotesBy]: CollectionSortProperty
|
||||||
[PrefKey.MobileSortNotesReverse]: boolean
|
[PrefKey.MobileSortNotesReverse]: boolean
|
||||||
@@ -99,4 +100,5 @@ export type PrefValue = {
|
|||||||
[PrefKey.EditorLineHeight]: EditorLineHeight
|
[PrefKey.EditorLineHeight]: EditorLineHeight
|
||||||
[PrefKey.EditorFontSize]: EditorFontSize
|
[PrefKey.EditorFontSize]: EditorFontSize
|
||||||
[PrefKey.UpdateSavingStatusIndicator]: boolean
|
[PrefKey.UpdateSavingStatusIndicator]: boolean
|
||||||
|
[PrefKey.DarkMode]: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,14 +20,14 @@
|
|||||||
--sn-stylekit-background-color: #ffffff;
|
--sn-stylekit-background-color: #ffffff;
|
||||||
// For borders inside background-color
|
// For borders inside background-color
|
||||||
--sn-stylekit-border-color: #dfe1e4;
|
--sn-stylekit-border-color: #dfe1e4;
|
||||||
--sn-stylekit-foreground-color: #19191C;
|
--sn-stylekit-foreground-color: #19191c;
|
||||||
// Colors for layers placed on top of non-prefixed background, border, and foreground
|
// Colors for layers placed on top of non-prefixed background, border, and foreground
|
||||||
--sn-stylekit-contrast-background-color: rgba(244, 245, 247, 1.0);
|
--sn-stylekit-contrast-background-color: rgba(244, 245, 247, 1);
|
||||||
--sn-stylekit-contrast-foreground-color: #2e2e2e;
|
--sn-stylekit-contrast-foreground-color: #2e2e2e;
|
||||||
--sn-stylekit-contrast-border-color: #e3e3e3; // For borders inside contrast-background-color
|
--sn-stylekit-contrast-border-color: #e3e3e3; // For borders inside contrast-background-color
|
||||||
|
|
||||||
// Alternative set of background and contrast options
|
// Alternative set of background and contrast options
|
||||||
--sn-stylekit-secondary-background-color: #EEEFF1;
|
--sn-stylekit-secondary-background-color: #eeeff1;
|
||||||
--sn-stylekit-secondary-foreground-color: #2e2e2e;
|
--sn-stylekit-secondary-foreground-color: #2e2e2e;
|
||||||
--sn-stylekit-secondary-border-color: #e3e3e3;
|
--sn-stylekit-secondary-border-color: #e3e3e3;
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
const CachedThemesKey = 'cachedThemes'
|
const CachedThemesKey = 'cachedThemes'
|
||||||
const TimeBeforeApplyingColorScheme = 5
|
const TimeBeforeApplyingColorScheme = 5
|
||||||
const DefaultThemeIdentifier = 'Default'
|
const DefaultThemeIdentifier = 'Default'
|
||||||
|
const DarkThemeIdentifier = 'Dark'
|
||||||
|
|
||||||
export class ThemeManager extends AbstractService {
|
export class ThemeManager extends AbstractService {
|
||||||
private activeThemes: Uuid[] = []
|
private activeThemes: Uuid[] = []
|
||||||
@@ -90,11 +91,13 @@ export class ThemeManager extends AbstractService {
|
|||||||
private handlePreferencesChangeEvent(): void {
|
private handlePreferencesChangeEvent(): void {
|
||||||
const useDeviceThemeSettings = this.application.getPreference(PrefKey.UseSystemColorScheme, false)
|
const useDeviceThemeSettings = this.application.getPreference(PrefKey.UseSystemColorScheme, false)
|
||||||
|
|
||||||
if (useDeviceThemeSettings !== this.lastUseDeviceThemeSettings) {
|
const hasPreferenceChanged = useDeviceThemeSettings !== this.lastUseDeviceThemeSettings
|
||||||
|
|
||||||
|
if (hasPreferenceChanged) {
|
||||||
this.lastUseDeviceThemeSettings = useDeviceThemeSettings
|
this.lastUseDeviceThemeSettings = useDeviceThemeSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useDeviceThemeSettings) {
|
if (hasPreferenceChanged && useDeviceThemeSettings) {
|
||||||
const prefersDarkColorScheme = window.matchMedia('(prefers-color-scheme: dark)')
|
const prefersDarkColorScheme = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
|
|
||||||
this.setThemeAsPerColorScheme(prefersDarkColorScheme.matches)
|
this.setThemeAsPerColorScheme(prefersDarkColorScheme.matches)
|
||||||
@@ -159,7 +162,11 @@ export class ThemeManager extends AbstractService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private colorSchemeEventHandler(event: MediaQueryListEvent) {
|
private colorSchemeEventHandler(event: MediaQueryListEvent) {
|
||||||
this.setThemeAsPerColorScheme(event.matches)
|
const shouldChangeTheme = this.application.getPreference(PrefKey.UseSystemColorScheme, false)
|
||||||
|
|
||||||
|
if (shouldChangeTheme) {
|
||||||
|
this.setThemeAsPerColorScheme(event.matches)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private showColorSchemeToast(setThemeCallback: () => void) {
|
private showColorSchemeToast(setThemeCallback: () => void) {
|
||||||
@@ -192,24 +199,41 @@ export class ThemeManager extends AbstractService {
|
|||||||
|
|
||||||
private setThemeAsPerColorScheme(prefersDarkColorScheme: boolean) {
|
private setThemeAsPerColorScheme(prefersDarkColorScheme: boolean) {
|
||||||
const preference = prefersDarkColorScheme ? PrefKey.AutoDarkThemeIdentifier : PrefKey.AutoLightThemeIdentifier
|
const preference = prefersDarkColorScheme ? PrefKey.AutoDarkThemeIdentifier : PrefKey.AutoLightThemeIdentifier
|
||||||
|
const preferenceDefault =
|
||||||
|
preference === PrefKey.AutoDarkThemeIdentifier ? DarkThemeIdentifier : DefaultThemeIdentifier
|
||||||
|
|
||||||
const themes = this.application.items
|
const themes = this.application.items
|
||||||
.getDisplayableComponents()
|
.getDisplayableComponents()
|
||||||
.filter((component) => component.isTheme()) as SNTheme[]
|
.filter((component) => component.isTheme()) as SNTheme[]
|
||||||
|
|
||||||
const activeTheme = themes.find((theme) => theme.active && !theme.isLayerable())
|
const activeTheme = themes.find((theme) => theme.active && !theme.isLayerable())
|
||||||
const activeThemeIdentifier = activeTheme ? activeTheme.identifier : DefaultThemeIdentifier
|
const activeThemeIdentifier = activeTheme
|
||||||
|
? activeTheme.identifier
|
||||||
|
: this.application.getPreference(PrefKey.DarkMode, false)
|
||||||
|
? DarkThemeIdentifier
|
||||||
|
: DefaultThemeIdentifier
|
||||||
|
|
||||||
const themeIdentifier = this.application.getPreference(preference, DefaultThemeIdentifier) as string
|
const themeIdentifier = this.application.getPreference(preference, preferenceDefault) as string
|
||||||
|
|
||||||
|
const toggleActiveTheme = () => {
|
||||||
|
if (activeTheme) {
|
||||||
|
void this.application.mutator.toggleTheme(activeTheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const setTheme = () => {
|
const setTheme = () => {
|
||||||
if (themeIdentifier === DefaultThemeIdentifier && activeTheme) {
|
if (themeIdentifier === DefaultThemeIdentifier) {
|
||||||
this.application.mutator.toggleTheme(activeTheme).catch(console.error)
|
toggleActiveTheme()
|
||||||
|
void this.application.setPreference(PrefKey.DarkMode, false)
|
||||||
|
} else if (themeIdentifier === DarkThemeIdentifier) {
|
||||||
|
toggleActiveTheme()
|
||||||
|
void this.application.setPreference(PrefKey.DarkMode, true)
|
||||||
} else {
|
} else {
|
||||||
const theme = themes.find((theme) => theme.package_info.identifier === themeIdentifier)
|
const theme = themes.find((theme) => theme.package_info.identifier === themeIdentifier)
|
||||||
if (theme && !theme.active) {
|
if (theme && !theme.active) {
|
||||||
this.application.mutator.toggleTheme(theme).catch(console.error)
|
this.application.mutator.toggleTheme(theme).catch(console.error)
|
||||||
}
|
}
|
||||||
|
void this.application.setPreference(PrefKey.DarkMode, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import FileDragNDropProvider from '../FileDragNDropProvider/FileDragNDropProvide
|
|||||||
import ResponsivePaneProvider from '../ResponsivePane/ResponsivePaneProvider'
|
import ResponsivePaneProvider from '../ResponsivePane/ResponsivePaneProvider'
|
||||||
import AndroidBackHandlerProvider from '@/NativeMobileWeb/useAndroidBackHandler'
|
import AndroidBackHandlerProvider from '@/NativeMobileWeb/useAndroidBackHandler'
|
||||||
import ConfirmDeleteAccountContainer from '@/Components/ConfirmDeleteAccountModal/ConfirmDeleteAccountModal'
|
import ConfirmDeleteAccountContainer from '@/Components/ConfirmDeleteAccountModal/ConfirmDeleteAccountModal'
|
||||||
|
import DarkModeHandler from '../DarkModeHandler/DarkModeHandler'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -190,6 +191,7 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AndroidBackHandlerProvider application={application}>
|
<AndroidBackHandlerProvider application={application}>
|
||||||
|
<DarkModeHandler application={application} />
|
||||||
<ResponsivePaneProvider>
|
<ResponsivePaneProvider>
|
||||||
<PremiumModalProvider application={application} viewControllerManager={viewControllerManager}>
|
<PremiumModalProvider application={application} viewControllerManager={viewControllerManager}>
|
||||||
<div className={platformString + ' main-ui-view sn-component'}>
|
<div className={platformString + ' main-ui-view sn-component'}>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { WebApplication } from '@/Application/Application'
|
||||||
|
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
||||||
|
import { ApplicationEvent, PrefKey } from '@standardnotes/snjs'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
application: WebApplication
|
||||||
|
}
|
||||||
|
|
||||||
|
const DarkModeHandler = ({ application }: Props) => {
|
||||||
|
useEffect(() => {
|
||||||
|
application.addSingleEventObserver(ApplicationEvent.PreferencesChanged, async () => {
|
||||||
|
const isDarkModeOn = application.getPreference(PrefKey.DarkMode, PrefDefaults[PrefKey.DarkMode])
|
||||||
|
|
||||||
|
if (isDarkModeOn) {
|
||||||
|
document.documentElement.classList.add('dark-mode')
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark-mode')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [application])
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DarkModeHandler
|
||||||
@@ -4,7 +4,7 @@ import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
|||||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||||
import Switch from '@/Components/Switch/Switch'
|
import Switch from '@/Components/Switch/Switch'
|
||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { ContentType, FeatureIdentifier, FeatureStatus, PrefKey, GetFeatures, SNTheme } from '@standardnotes/snjs'
|
import { ContentType, FeatureIdentifier, PrefKey, GetFeatures, SNTheme } from '@standardnotes/snjs'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent, useEffect, useState } from 'react'
|
import { FunctionComponent, useEffect, useState } from 'react'
|
||||||
import { Subtitle, Title, Text } from '@/Components/Preferences/PreferencesComponents/Content'
|
import { Subtitle, Title, Text } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||||
@@ -21,18 +21,13 @@ type Props = {
|
|||||||
|
|
||||||
const Appearance: FunctionComponent<Props> = ({ application }) => {
|
const Appearance: FunctionComponent<Props> = ({ application }) => {
|
||||||
const premiumModal = usePremiumModal()
|
const premiumModal = usePremiumModal()
|
||||||
const isEntitledToMidnightTheme =
|
|
||||||
application.features.getFeatureStatus(FeatureIdentifier.MidnightTheme) === FeatureStatus.Entitled
|
|
||||||
|
|
||||||
const [themeItems, setThemeItems] = useState<DropdownItem[]>([])
|
const [themeItems, setThemeItems] = useState<DropdownItem[]>([])
|
||||||
const [autoLightTheme, setAutoLightTheme] = useState<string>(() =>
|
const [autoLightTheme, setAutoLightTheme] = useState<string>(() =>
|
||||||
application.getPreference(PrefKey.AutoLightThemeIdentifier, PrefDefaults[PrefKey.AutoLightThemeIdentifier]),
|
application.getPreference(PrefKey.AutoLightThemeIdentifier, PrefDefaults[PrefKey.AutoLightThemeIdentifier]),
|
||||||
)
|
)
|
||||||
const [autoDarkTheme, setAutoDarkTheme] = useState<string>(() =>
|
const [autoDarkTheme, setAutoDarkTheme] = useState<string>(() =>
|
||||||
application.getPreference(
|
application.getPreference(PrefKey.AutoDarkThemeIdentifier, PrefDefaults[PrefKey.AutoDarkThemeIdentifier]),
|
||||||
PrefKey.AutoDarkThemeIdentifier,
|
|
||||||
isEntitledToMidnightTheme ? FeatureIdentifier.MidnightTheme : PrefDefaults[PrefKey.AutoDarkThemeIdentifier],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
const [useDeviceSettings, setUseDeviceSettings] = useState(() =>
|
const [useDeviceSettings, setUseDeviceSettings] = useState(() =>
|
||||||
application.getPreference(PrefKey.UseSystemColorScheme, PrefDefaults[PrefKey.UseSystemColorScheme]),
|
application.getPreference(PrefKey.UseSystemColorScheme, PrefDefaults[PrefKey.UseSystemColorScheme]),
|
||||||
@@ -63,6 +58,11 @@ const Appearance: FunctionComponent<Props> = ({ application }) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
themesAsItems.unshift({
|
||||||
|
label: 'Dark',
|
||||||
|
value: 'Dark',
|
||||||
|
})
|
||||||
|
|
||||||
themesAsItems.unshift({
|
themesAsItems.unshift({
|
||||||
label: 'Default',
|
label: 'Default',
|
||||||
value: 'Default',
|
value: 'Default',
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { ComponentArea, ContentType, FeatureIdentifier, GetFeatures, SNComponent } from '@standardnotes/snjs'
|
import {
|
||||||
|
ApplicationEvent,
|
||||||
|
ComponentArea,
|
||||||
|
ContentType,
|
||||||
|
FeatureIdentifier,
|
||||||
|
GetFeatures,
|
||||||
|
PrefKey,
|
||||||
|
SNComponent,
|
||||||
|
} from '@standardnotes/snjs'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
|
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import Icon from '@/Components/Icon/Icon'
|
import Icon from '@/Components/Icon/Icon'
|
||||||
@@ -12,6 +20,7 @@ import RadioIndicator from '../RadioIndicator/RadioIndicator'
|
|||||||
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
||||||
import { QuickSettingsController } from '@/Controllers/QuickSettingsController'
|
import { QuickSettingsController } from '@/Controllers/QuickSettingsController'
|
||||||
import PanelSettingsSection from './PanelSettingsSection'
|
import PanelSettingsSection from './PanelSettingsSection'
|
||||||
|
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
||||||
|
|
||||||
const focusModeAnimationDuration = 1255
|
const focusModeAnimationDuration = 1255
|
||||||
|
|
||||||
@@ -38,7 +47,19 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ application, quickSet
|
|||||||
const { closeQuickSettingsMenu, focusModeEnabled, setFocusModeEnabled } = quickSettingsMenuController
|
const { closeQuickSettingsMenu, focusModeEnabled, setFocusModeEnabled } = quickSettingsMenuController
|
||||||
const [themes, setThemes] = useState<ThemeItem[]>([])
|
const [themes, setThemes] = useState<ThemeItem[]>([])
|
||||||
const [toggleableComponents, setToggleableComponents] = useState<SNComponent[]>([])
|
const [toggleableComponents, setToggleableComponents] = useState<SNComponent[]>([])
|
||||||
const [defaultThemeOn, setDefaultThemeOn] = useState(false)
|
|
||||||
|
const [isDarkModeOn, setDarkModeOn] = useState(
|
||||||
|
application.getPreference(PrefKey.DarkMode, PrefDefaults[PrefKey.DarkMode]),
|
||||||
|
)
|
||||||
|
const defaultThemeOn =
|
||||||
|
!themes.map((item) => item?.component).find((theme) => theme?.active && !theme.isLayerable()) && !isDarkModeOn
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
application.addSingleEventObserver(ApplicationEvent.PreferencesChanged, async () => {
|
||||||
|
const isDarkModeOn = application.getPreference(PrefKey.DarkMode, PrefDefaults[PrefKey.DarkMode])
|
||||||
|
setDarkModeOn(isDarkModeOn)
|
||||||
|
})
|
||||||
|
}, [application])
|
||||||
|
|
||||||
const prefsButtonRef = useRef<HTMLButtonElement>(null)
|
const prefsButtonRef = useRef<HTMLButtonElement>(null)
|
||||||
const defaultThemeButtonRef = useRef<HTMLButtonElement>(null)
|
const defaultThemeButtonRef = useRef<HTMLButtonElement>(null)
|
||||||
@@ -73,8 +94,6 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ application, quickSet
|
|||||||
})
|
})
|
||||||
|
|
||||||
setThemes(themes.sort(sortThemes))
|
setThemes(themes.sort(sortThemes))
|
||||||
|
|
||||||
setDefaultThemeOn(!themes.map((item) => item?.component).find((theme) => theme?.active && !theme.isLayerable()))
|
|
||||||
}, [application])
|
}, [application])
|
||||||
|
|
||||||
const reloadToggleableComponents = useCallback(() => {
|
const reloadToggleableComponents = useCallback(() => {
|
||||||
@@ -131,13 +150,25 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ application, quickSet
|
|||||||
[application],
|
[application],
|
||||||
)
|
)
|
||||||
|
|
||||||
const toggleDefaultTheme = useCallback(() => {
|
const deactivateAnyNonLayerableTheme = useCallback(() => {
|
||||||
const activeTheme = themes.map((item) => item.component).find((theme) => theme?.active && !theme.isLayerable())
|
const activeTheme = themes.map((item) => item.component).find((theme) => theme?.active && !theme.isLayerable())
|
||||||
if (activeTheme) {
|
if (activeTheme) {
|
||||||
application.mutator.toggleTheme(activeTheme).catch(console.error)
|
application.mutator.toggleTheme(activeTheme).catch(console.error)
|
||||||
}
|
}
|
||||||
}, [application, themes])
|
}, [application, themes])
|
||||||
|
|
||||||
|
const toggleDefaultTheme = useCallback(() => {
|
||||||
|
deactivateAnyNonLayerableTheme()
|
||||||
|
application.setPreference(PrefKey.DarkMode, false)
|
||||||
|
}, [application, deactivateAnyNonLayerableTheme])
|
||||||
|
|
||||||
|
const toggleDarkMode = useCallback(() => {
|
||||||
|
if (!isDarkModeOn) {
|
||||||
|
deactivateAnyNonLayerableTheme()
|
||||||
|
application.setPreference(PrefKey.DarkMode, true)
|
||||||
|
}
|
||||||
|
}, [application, isDarkModeOn, deactivateAnyNonLayerableTheme])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={mainRef}>
|
<div ref={mainRef}>
|
||||||
<div className="my-1 px-3 text-sm font-semibold uppercase text-text">Themes</div>
|
<div className="my-1 px-3 text-sm font-semibold uppercase text-text">Themes</div>
|
||||||
@@ -149,6 +180,13 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ application, quickSet
|
|||||||
<RadioIndicator checked={defaultThemeOn} className="mr-2" />
|
<RadioIndicator checked={defaultThemeOn} className="mr-2" />
|
||||||
Default
|
Default
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-sm"
|
||||||
|
onClick={toggleDarkMode}
|
||||||
|
>
|
||||||
|
<RadioIndicator checked={isDarkModeOn} className="mr-2" />
|
||||||
|
Dark
|
||||||
|
</button>
|
||||||
{themes.map((theme) => (
|
{themes.map((theme) => (
|
||||||
<ThemesMenuButton item={theme} application={application} key={theme.component?.uuid ?? theme.identifier} />
|
<ThemesMenuButton item={theme} application={application} key={theme.component?.uuid ?? theme.identifier} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { FeatureIdentifier, FeatureStatus } from '@standardnotes/snjs'
|
import { FeatureIdentifier, FeatureStatus, PrefKey } from '@standardnotes/snjs'
|
||||||
import { FunctionComponent, MouseEventHandler, useCallback, useMemo } from 'react'
|
import { FunctionComponent, MouseEventHandler, useCallback, useMemo } from 'react'
|
||||||
import Icon from '@/Components/Icon/Icon'
|
import Icon from '@/Components/Icon/Icon'
|
||||||
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
||||||
@@ -32,10 +32,15 @@ const ThemesMenuButton: FunctionComponent<Props> = ({ application, item }) => {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
if (item.component && canActivateTheme) {
|
if (item.component && canActivateTheme) {
|
||||||
const themeIsLayerableOrNotActive = item.component.isLayerable() || !item.component.active
|
const isThemeLayerable = item.component.isLayerable()
|
||||||
|
const themeIsLayerableOrNotActive = isThemeLayerable || !item.component.active
|
||||||
|
|
||||||
if (themeIsLayerableOrNotActive) {
|
if (themeIsLayerableOrNotActive) {
|
||||||
application.mutator.toggleTheme(item.component).catch(console.error)
|
application.mutator.toggleTheme(item.component).catch(console.error)
|
||||||
|
|
||||||
|
if (!isThemeLayerable) {
|
||||||
|
application.setPreference(PrefKey.DarkMode, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
premiumModal.activate(`${item.name} theme`)
|
premiumModal.activate(`${item.name} theme`)
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ export const PrefDefaults = {
|
|||||||
[PrefKey.NotesHideEditorIcon]: false,
|
[PrefKey.NotesHideEditorIcon]: false,
|
||||||
[PrefKey.UseSystemColorScheme]: false,
|
[PrefKey.UseSystemColorScheme]: false,
|
||||||
[PrefKey.AutoLightThemeIdentifier]: 'Default',
|
[PrefKey.AutoLightThemeIdentifier]: 'Default',
|
||||||
[PrefKey.AutoDarkThemeIdentifier]: 'Default',
|
[PrefKey.AutoDarkThemeIdentifier]: 'Dark',
|
||||||
[PrefKey.NoteAddToParentFolders]: true,
|
[PrefKey.NoteAddToParentFolders]: true,
|
||||||
[PrefKey.NewNoteTitleFormat]: NewNoteTitleFormat.CurrentDateAndTime,
|
[PrefKey.NewNoteTitleFormat]: NewNoteTitleFormat.CurrentDateAndTime,
|
||||||
[PrefKey.CustomNoteTitleFormat]: 'YYYY-MM-DD [at] hh:mm A',
|
[PrefKey.CustomNoteTitleFormat]: 'YYYY-MM-DD [at] hh:mm A',
|
||||||
[PrefKey.UpdateSavingStatusIndicator]: true,
|
[PrefKey.UpdateSavingStatusIndicator]: true,
|
||||||
|
[PrefKey.DarkMode]: false,
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
71
packages/web/src/stylesheets/_dark-mode.scss
Normal file
71
packages/web/src/stylesheets/_dark-mode.scss
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
.dark-mode {
|
||||||
|
--foreground-color: #eeeeee;
|
||||||
|
--background-color: #0f1011;
|
||||||
|
--highlight-color: #a464c2;
|
||||||
|
--border-color: #0f1011;
|
||||||
|
|
||||||
|
--sn-component-foreground-color: var(--foreground-color);
|
||||||
|
--sn-component-background-color: transparent;
|
||||||
|
--sn-component-foreground-highlight-color: var(--highlight-color);
|
||||||
|
--sn-component-outer-border-color: transparent;
|
||||||
|
--sn-component-inner-border-color: var(--foreground-color);
|
||||||
|
|
||||||
|
// StyleKit Vars
|
||||||
|
|
||||||
|
--sn-stylekit-passive-color-0: #999999;
|
||||||
|
--sn-stylekit-passive-color-3: #28292b;
|
||||||
|
--sn-stylekit-passive-color-4: #1c1d1e;
|
||||||
|
--sn-stylekit-passive-color-5: #1d1f20;
|
||||||
|
|
||||||
|
--sn-stylekit-shadow-color: #000000;
|
||||||
|
|
||||||
|
--sn-stylekit-info-color: var(--highlight-color);
|
||||||
|
--sn-stylekit-info-contrast-color: var(--foreground-color);
|
||||||
|
|
||||||
|
--sn-stylekit-neutral-color: #7c7c7c;
|
||||||
|
--sn-stylekit-neutral-contrast-color: #ffffff;
|
||||||
|
|
||||||
|
--sn-stylekit-success-color: #2b9612;
|
||||||
|
--sn-stylekit-success-contrast-color: #ffffff;
|
||||||
|
|
||||||
|
--sn-stylekit-warning-color: #f6a200;
|
||||||
|
--sn-stylekit-warning-contrast-color: #ffffff;
|
||||||
|
|
||||||
|
--sn-stylekit-danger-color: #f80324;
|
||||||
|
--sn-stylekit-danger-contrast-color: #ffffff;
|
||||||
|
|
||||||
|
--sn-stylekit-editor-background-color: var(--sn-stylekit-background-color);
|
||||||
|
--sn-stylekit-editor-foreground-color: var(--sn-stylekit-foreground-color);
|
||||||
|
|
||||||
|
--sn-stylekit-background-color: var(--background-color);
|
||||||
|
--sn-stylekit-foreground-color: var(--foreground-color);
|
||||||
|
--sn-stylekit-border-color: #000000;
|
||||||
|
|
||||||
|
--sn-stylekit-contrast-background-color: #000000;
|
||||||
|
--sn-stylekit-contrast-foreground-color: #ffffff;
|
||||||
|
--sn-stylekit-contrast-border-color: #000000;
|
||||||
|
|
||||||
|
--sn-stylekit-secondary-background-color: var(--sn-stylekit-passive-color-4);
|
||||||
|
--sn-stylekit-secondary-foreground-color: #ffffff;
|
||||||
|
--sn-stylekit-secondary-border-color: #000000;
|
||||||
|
|
||||||
|
--sn-stylekit-secondary-contrast-background-color: #000000;
|
||||||
|
--sn-stylekit-secondary-contrast-foreground-color: #ffffff;
|
||||||
|
--sn-stylekit-secondary-contrast-border-color: #ffffff;
|
||||||
|
|
||||||
|
--sn-stylekit-paragraph-text-color: #ffffff;
|
||||||
|
|
||||||
|
--sn-desktop-titlebar-bg-color: var(--background-color);
|
||||||
|
--sn-desktop-titlebar-border-color: var(--border-color);
|
||||||
|
--sn-desktop-titlebar-ui-color: var(--foreground-color);
|
||||||
|
--sn-desktop-titlebar-ui-hover-color: var(--highlight-color);
|
||||||
|
|
||||||
|
--sn-stylekit-scrollbar-track-border-color: var(--border-color);
|
||||||
|
--sn-stylekit-scrollbar-thumb-color: var(--sn-stylekit-info-color);
|
||||||
|
|
||||||
|
--sn-stylekit-menu-border: 1px solid #424242;
|
||||||
|
|
||||||
|
// Theme
|
||||||
|
|
||||||
|
--navigation-item-selected-background-color: var(--background-color);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
@import '../../../styles/src/Styles/_scrollbar.scss';
|
@import '../../../styles/src/Styles/_scrollbar.scss';
|
||||||
@import '../../../styles/src/Styles/utils/_animation.scss';
|
@import '../../../styles/src/Styles/utils/_animation.scss';
|
||||||
@import 'theme';
|
@import 'theme';
|
||||||
|
@import 'dark-mode';
|
||||||
@import 'main';
|
@import 'main';
|
||||||
@import 'ui';
|
@import 'ui';
|
||||||
@import 'footer';
|
@import 'footer';
|
||||||
|
|||||||
Reference in New Issue
Block a user