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:
13
src/App.tsx
13
src/App.tsx
@ -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' }}
|
||||||
|
@ -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,
|
||||||
}))
|
}))
|
||||||
|
@ -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) => ({
|
||||||
|
@ -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')
|
||||||
|
9
src/lib/getSystemTheme.ts
Normal file
9
src/lib/getSystemTheme.ts
Normal 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
|
||||||
|
}
|
@ -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"
|
||||||
|
@ -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 }),
|
||||||
|
Reference in New Issue
Block a user