Add support for system theme (#245)

* Add support for 'system' theme value

* Add ability to set theme to 'system' in settings

* Fix tsc errors for Theme
This commit is contained in:
Frank Noirot
2023-08-09 13:57:07 -04:00
committed by GitHub
parent 8ebb8b8b94
commit 968a67e654
7 changed files with 56 additions and 35 deletions

View File

@ -19,7 +19,7 @@ import {
lineHighlightField, lineHighlightField,
addLineHighlight, addLineHighlight,
} from './editor/highlightextension' } from './editor/highlightextension'
import { PaneType, Selections, useStore } from './useStore' import { PaneType, Selections, Themes, useStore } from './useStore'
import { Logs, KCLErrors } from './components/Logs' import { Logs, KCLErrors } from './components/Logs'
import { CollapsiblePanel } from './components/CollapsiblePanel' import { CollapsiblePanel } from './components/CollapsiblePanel'
import { MemoryPanel } from './components/MemoryPanel' import { MemoryPanel } from './components/MemoryPanel'
@ -42,6 +42,7 @@ import {
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import { TEST } from './env' import { TEST } from './env'
import { getNormalisedCoordinates } from './lib/utils' import { getNormalisedCoordinates } from './lib/utils'
import { getSystemTheme } from './lib/getSystemTheme'
export function App() { export function App() {
const streamRef = useRef<HTMLDivElement>(null) const streamRef = useRef<HTMLDivElement>(null)
@ -127,6 +128,8 @@ export function App() {
streamDimensions: s.streamDimensions, streamDimensions: s.streamDimensions,
})) }))
const editorTheme = theme === Themes.System ? getSystemTheme() : theme
// Pane toggling keyboard shortcuts // Pane toggling keyboard shortcuts
const togglePane = useCallback( const togglePane = useCallback(
(newPane: PaneType) => (newPane: PaneType) =>
@ -460,26 +463,26 @@ export function App() {
]} ]}
onChange={onChange} onChange={onChange}
onUpdate={onUpdate} onUpdate={onUpdate}
theme={theme} theme={editorTheme}
onCreateEditor={(_editorView) => setEditorView(_editorView)} onCreateEditor={(_editorView) => setEditorView(_editorView)}
/> />
</div> </div>
</CollapsiblePanel> </CollapsiblePanel>
<section className="flex flex-col"> <section className="flex flex-col">
<MemoryPanel <MemoryPanel
theme={theme} theme={editorTheme}
open={openPanes.includes('variables')} open={openPanes.includes('variables')}
title="Variables" title="Variables"
icon={faSquareRootVariable} icon={faSquareRootVariable}
/> />
<Logs <Logs
theme={theme} theme={editorTheme}
open={openPanes.includes('logs')} open={openPanes.includes('logs')}
title="Logs" title="Logs"
icon={faCodeCommit} icon={faCodeCommit}
/> />
<KCLErrors <KCLErrors
theme={theme} theme={editorTheme}
open={openPanes.includes('kclErrors')} open={openPanes.includes('kclErrors')}
title="KCL Errors" title="KCL Errors"
iconClassNames={{ icon: 'group-open:text-destroy-30' }} iconClassNames={{ icon: 'group-open:text-destroy-30' }}

View File

@ -1,15 +1,15 @@
import ReactJson from 'react-json-view' import ReactJson from 'react-json-view'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useStore } from '../useStore' import { Themes, useStore } from '../useStore'
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel' import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
const ReactJsonTypeHack = ReactJson as any const ReactJsonTypeHack = ReactJson as any
interface LogPanelProps extends CollapsiblePanelProps { interface LogPanelProps extends CollapsiblePanelProps {
theme?: 'light' | 'dark' theme?: Exclude<Themes, Themes.System>
} }
export const Logs = ({ theme = 'light', ...props }: LogPanelProps) => { export const Logs = ({ theme = Themes.Light, ...props }: LogPanelProps) => {
const { logs } = useStore(({ logs }) => ({ const { logs } = useStore(({ logs }) => ({
logs, logs,
})) }))
@ -42,7 +42,10 @@ export const Logs = ({ theme = 'light', ...props }: LogPanelProps) => {
) )
} }
export const KCLErrors = ({ theme = 'light', ...props }: LogPanelProps) => { export const KCLErrors = ({
theme = Themes.Light,
...props
}: LogPanelProps) => {
const { kclErrors } = useStore(({ kclErrors }) => ({ const { kclErrors } = useStore(({ kclErrors }) => ({
kclErrors, kclErrors,
})) }))

View File

@ -1,15 +1,15 @@
import ReactJson from 'react-json-view' import ReactJson from 'react-json-view'
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel' import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
import { useStore } from '../useStore' import { Themes, useStore } from '../useStore'
import { useMemo } from 'react' import { useMemo } from 'react'
import { ProgramMemory } from '../lang/executor' import { ProgramMemory } from '../lang/executor'
interface MemoryPanelProps extends CollapsiblePanelProps { interface MemoryPanelProps extends CollapsiblePanelProps {
theme?: 'light' | 'dark' theme?: Exclude<Themes, Themes.System>
} }
export const MemoryPanel = ({ export const MemoryPanel = ({
theme = 'light', theme = Themes.Light,
...props ...props
}: MemoryPanelProps) => { }: MemoryPanelProps) => {
const { programMemory } = useStore((s) => ({ const { programMemory } = useStore((s) => ({

View File

@ -2,13 +2,15 @@ import ReactDOM from 'react-dom/client'
import './index.css' import './index.css'
import reportWebVitals from './reportWebVitals' import reportWebVitals from './reportWebVitals'
import { Toaster } from 'react-hot-toast' import { Toaster } from 'react-hot-toast'
import { useStore } from './useStore' import { Themes, useStore } from './useStore'
import { Router } from './Router' import { Router } from './Router'
import { HotkeysProvider } from 'react-hotkeys-hook' import { HotkeysProvider } from 'react-hotkeys-hook'
import { getSystemTheme } from './lib/getSystemTheme'
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement) const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
function setThemeClass(state: Partial<{ theme: string }>) { function setThemeClass(state: Partial<{ theme: Themes }>) {
if (state.theme === 'dark') { const systemTheme = state.theme === Themes.System && getSystemTheme()
if (state.theme === Themes.Dark || systemTheme === Themes.Dark) {
document.body.classList.add('dark') document.body.classList.add('dark')
} else { } else {
document.body.classList.remove('dark') document.body.classList.remove('dark')

View File

@ -0,0 +1,9 @@
import { Themes } from '../useStore'
export function getSystemTheme(): Exclude<Themes, 'system'> {
return typeof window !== 'undefined' &&
'matchMedia' in window &&
window.matchMedia('(prefers-color-scheme: dark)').matches
? Themes.Dark
: Themes.Light
}

View File

@ -6,7 +6,7 @@ import {
import { ActionButton } from '../components/ActionButton' import { ActionButton } from '../components/ActionButton'
import { AppHeader } from '../components/AppHeader' import { AppHeader } from '../components/AppHeader'
import { open } from '@tauri-apps/api/dialog' import { open } from '@tauri-apps/api/dialog'
import { baseUnits, useStore } from '../useStore' import { Themes, baseUnits, useStore } from '../useStore'
import { useRef } from 'react' import { useRef } from 'react'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import { Toggle } from '../components/Toggle/Toggle' import { Toggle } from '../components/Toggle/Toggle'
@ -184,21 +184,25 @@ export const Settings = () => {
title="Editor Theme" title="Editor Theme"
description="Apply a light or dark theme to the editor" description="Apply a light or dark theme to the editor"
> >
<Toggle <select
name="settings-theme" id="settings-theme"
offLabel="Dark" className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
onLabel="Light" value={theme}
checked={theme === 'light'}
onChange={(e) => { onChange={(e) => {
const newTheme = e.target.checked ? 'light' : 'dark' setTheme(e.target.value as Themes)
setTheme(newTheme)
toast.success( toast.success(
newTheme.slice(0, 1).toLocaleUpperCase() + 'Theme changed to ' +
newTheme.slice(1) + e.target.value.slice(0, 1).toLocaleUpperCase() +
' mode activated' e.target.value.slice(1)
) )
}} }}
/> >
{Object.entries(Themes).map(([label, value]) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
</SettingsSection> </SettingsSection>
<SettingsSection <SettingsSection
title="Onboarding" title="Onboarding"

View File

@ -97,6 +97,11 @@ export type GuiModes =
} }
type UnitSystem = 'imperial' | 'metric' type UnitSystem = 'imperial' | 'metric'
export enum Themes {
Light = 'light',
Dark = 'dark',
System = 'system',
}
export const baseUnits: Record<UnitSystem, string[]> = { export const baseUnits: Record<UnitSystem, string[]> = {
imperial: ['in', 'ft'], imperial: ['in', 'ft'],
@ -204,8 +209,8 @@ export interface StoreState {
setHomeShowMenu: (showMenu: boolean) => void setHomeShowMenu: (showMenu: boolean) => void
onboardingStatus: string onboardingStatus: string
setOnboardingStatus: (status: string) => void setOnboardingStatus: (status: string) => void
theme: 'light' | 'dark' theme: Themes
setTheme: (theme: 'light' | 'dark') => void setTheme: (theme: Themes) => void
openPanes: PaneType[] openPanes: PaneType[]
setOpenPanes: (panes: PaneType[]) => void setOpenPanes: (panes: PaneType[]) => void
homeMenuItems: { homeMenuItems: {
@ -409,12 +414,7 @@ export const useStore = create<StoreState>()(
setDefaultBaseUnit: (defaultBaseUnit) => set({ defaultBaseUnit }), setDefaultBaseUnit: (defaultBaseUnit) => set({ defaultBaseUnit }),
onboardingStatus: '', onboardingStatus: '',
setOnboardingStatus: (onboardingStatus) => set({ onboardingStatus }), setOnboardingStatus: (onboardingStatus) => set({ onboardingStatus }),
theme: theme: Themes.System,
typeof window !== 'undefined' &&
'matchMedia' in window &&
window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light',
setTheme: (theme) => set({ theme }), setTheme: (theme) => set({ theme }),
openPanes: ['code'], openPanes: ['code'],
setOpenPanes: (openPanes) => set({ openPanes }), setOpenPanes: (openPanes) => set({ openPanes }),