Add dark mode (#199)

* Add passive dark mode to everything but codemirror

* Add dark theme support for Codemirror

* Make theme a user setting

* Fix button text size

* guard against undefined window

* Formatting and test fix
This commit is contained in:
Frank Noirot
2023-07-31 06:33:10 -04:00
committed by GitHub
parent 894bddb369
commit c31f1ad98b
26 changed files with 90 additions and 59 deletions

View File

@ -55,6 +55,7 @@ export function App() {
token, token,
formatCode, formatCode,
debugPanel, debugPanel,
theme,
} = useStore((s) => ({ } = useStore((s) => ({
editorView: s.editorView, editorView: s.editorView,
setEditorView: s.setEditorView, setEditorView: s.setEditorView,
@ -85,6 +86,7 @@ export function App() {
formatCode: s.formatCode, formatCode: s.formatCode,
debugPanel: s.debugPanel, debugPanel: s.debugPanel,
addKCLError: s.addKCLError, addKCLError: s.addKCLError,
theme: s.theme,
})) }))
const showTauriTokenInput = isTauri() && !token const showTauriTokenInput = isTauri() && !token
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => { // const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
@ -268,6 +270,7 @@ export function App() {
} }
asyncWrap() asyncWrap()
}, [code, isStreamReady]) }, [code, isStreamReady])
return ( return (
<div className="h-screen"> <div className="h-screen">
<AppHeader /> <AppHeader />
@ -293,6 +296,7 @@ export function App() {
extensions={[javascript({ jsx: true }), lineHighlightField]} extensions={[javascript({ jsx: true }), lineHighlightField]}
onChange={onChange} onChange={onChange}
onUpdate={onUpdate} onUpdate={onUpdate}
theme={theme}
onCreateEditor={(_editorView) => setEditorView(_editorView)} onCreateEditor={(_editorView) => setEditorView(_editorView)}
/> />
</div> </div>

View File

@ -39,7 +39,6 @@ export const Toolbar = () => {
sketchMode: 'selectFace', sketchMode: 'selectFace',
}) })
}} }}
className="border m-1 px-1 rounded text-xs"
> >
Start Sketch Start Sketch
</button> </button>
@ -59,7 +58,6 @@ export const Toolbar = () => {
) )
updateAst(modifiedAst) updateAst(modifiedAst)
}} }}
className="border m-1 px-1 rounded text-xs"
> >
SketchOnFace SketchOnFace
</button> </button>
@ -75,7 +73,6 @@ export const Toolbar = () => {
position: guiMode.position, position: guiMode.position,
}) })
}} }}
className="border m-1 px-1 rounded text-xs"
> >
Edit Sketch Edit Sketch
</button> </button>
@ -95,7 +92,6 @@ export const Toolbar = () => {
) )
updateAst(modifiedAst, { focusPath: pathToExtrudeArg }) updateAst(modifiedAst, { focusPath: pathToExtrudeArg })
}} }}
className="border m-1 px-1 rounded text-xs"
> >
ExtrudeSketch ExtrudeSketch
</button> </button>
@ -113,7 +109,6 @@ export const Toolbar = () => {
) )
updateAst(modifiedAst, { focusPath: pathToExtrudeArg }) updateAst(modifiedAst, { focusPath: pathToExtrudeArg })
}} }}
className="border m-1 px-1 rounded text-xs"
> >
ExtrudeSketch (w/o pipe) ExtrudeSketch (w/o pipe)
</button> </button>
@ -121,10 +116,7 @@ export const Toolbar = () => {
)} )}
{guiMode.mode === 'sketch' && ( {guiMode.mode === 'sketch' && (
<button <button onClick={() => setGuiMode({ mode: 'default' })}>
onClick={() => setGuiMode({ mode: 'default' })}
className="border m-1 px-1 rounded text-xs"
>
Exit sketch Exit sketch
</button> </button>
)} )}
@ -142,9 +134,6 @@ export const Toolbar = () => {
return ( return (
<button <button
key={sketchFnName} key={sketchFnName}
className={`border m-0.5 px-0.5 rounded text-xs ${
guiMode.sketchMode === sketchFnName && 'bg-gray-400'
}`}
onClick={() => onClick={() =>
setGuiMode({ setGuiMode({
...guiMode, ...guiMode,

View File

@ -21,7 +21,7 @@ export const ActionButton = ({
Element = 'button', Element = 'button',
children, children,
}: ActionButtonProps) => { }: ActionButtonProps) => {
const classNames = `group mono flex items-center gap-2 text-chalkboard-110 rounded-sm border border-chalkboard-40 hover:border-liquid-40 p-[3px] ${ const classNames = `group mono text-base flex items-center gap-2 rounded-sm border border-chalkboard-40 dark:border-chalkboard-60 hover:border-liquid-40 p-[3px] ${
icon ? 'pr-2' : 'px-2' icon ? 'pr-2' : 'px-2'
} ${className}` } ${className}`

View File

@ -29,7 +29,7 @@ export const ActionIcon = ({
className={ className={
'p-1 w-fit inline-grid place-content-center ' + 'p-1 w-fit inline-grid place-content-center ' +
(bgClassName || (bgClassName ||
'bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90') 'bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-liquid-20 dark:group-hover:bg-liquid-10 dark:hover:bg-liquid-10')
} }
> >
{children || ( {children || (
@ -39,7 +39,7 @@ export const ActionIcon = ({
height={iconSizes[size]} height={iconSizes[size]}
className={ className={
iconClassName || iconClassName ||
'text-liquid-20 group-hover:text-liquid-10 hover:text-liquid-10' 'text-liquid-20 group-hover:text-liquid-10 hover:text-liquid-10 dark:text-liquid-100 dark:group-hover:text-liquid-100 dark:hover:text-liquid-100'
} }
/> />
)} )}

View File

@ -13,7 +13,7 @@ export const AppHeader = ({ showToolbar = true, children }: AppHeaderProps) => {
})) }))
return ( return (
<header className="py-1 px-5 bg-chalkboard-10 border-b border-chalkboard-30 flex justify-between items-center"> <header className="py-1 px-5 bg-chalkboard-10 dark:bg-chalkboard-100 border-b dark:border-b-2 border-chalkboard-30 dark:border-chalkboard-70 flex justify-between items-center">
<Link to="/"> <Link to="/">
<img <img
src="/kitt-arcade-winking.svg" src="/kitt-arcade-winking.svg"

View File

@ -1,6 +1,6 @@
export const PanelHeader = ({ title }: { title: string }) => { export const PanelHeader = ({ title }: { title: string }) => {
return ( return (
<div className="font-mono text-[11px] bg-stone-100 w-full pl-4 h-[20px] text-stone-700 flex items-center"> <div className="font-mono text-[11px] bg-chalkboard-20 dark:bg-chalkboard-110 dark:border-b-2 dark:border-b-chalkboard-90 w-full pl-4 h-[20px] flex items-center">
<span className="pt-1">{title}</span> <span className="pt-1">{title}</span>
</div> </div>
) )

View File

@ -18,6 +18,10 @@
height: calc(var(--toggle-size) + var(--padding)); height: calc(var(--toggle-size) + var(--padding));
} }
:global(.dark) .toggle > span {
@apply border-chalkboard-40;
}
.toggle > span::after { .toggle > span::after {
content: ''; content: '';
@apply absolute w-4 h-4 rounded-sm bg-chalkboard-110; @apply absolute w-4 h-4 rounded-sm bg-chalkboard-110;
@ -27,6 +31,10 @@
transition: translate 0.08s ease-out; transition: translate 0.08s ease-out;
} }
:global(.dark) .toggle > span::after {
@apply bg-chalkboard-10;
}
.toggle input:checked + span::after { .toggle input:checked + span::after {
translate: calc(50% - var(--padding)) -50%; translate: calc(50% - var(--padding)) -50%;
} }

View File

@ -53,9 +53,6 @@ export const ConvertToVariable = () => {
console.log('e', e) console.log('e', e)
} }
}} }}
className={`border m-1 px-1 rounded text-xs ${
enableAngLen ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
}`}
disabled={!enableAngLen} disabled={!enableAngLen}
> >
ConvertToVariable ConvertToVariable

View File

@ -86,9 +86,6 @@ export const EqualAngle = () => {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
}} }}
className={`border m-1 px-1 rounded text-xs ${
enableEqual ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
}`}
disabled={!enableEqual} disabled={!enableEqual}
title="yo dawg" title="yo dawg"
> >

View File

@ -86,9 +86,6 @@ export const EqualLength = () => {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
}} }}
className={`border m-1 px-1 rounded text-xs ${
enableEqual ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
}`}
disabled={!enableEqual} disabled={!enableEqual}
title="yo dawg" title="yo dawg"
> >

View File

@ -65,9 +65,6 @@ export const HorzVert = ({
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
}} }}
className={`border m-1 px-1 rounded text-xs ${
enableHorz ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
}`}
disabled={!enableHorz} disabled={!enableHorz}
title="yo dawg" title="yo dawg"
> >

View File

@ -187,9 +187,6 @@ export const Intersect = () => {
}) })
} }
}} }}
className={`border m-1 px-1 rounded text-xs ${
enable ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
}`}
disabled={!enable} disabled={!enable}
> >
perpendicularDistance perpendicularDistance

View File

@ -69,9 +69,6 @@ export const RemoveConstrainingValues = () => {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
}} }}
className={`border m-1 px-1 rounded text-xs ${
enableHorz ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
}`}
disabled={!enableHorz} disabled={!enableHorz}
title="yo dawg" title="yo dawg"
> >

View File

@ -131,9 +131,6 @@ export const SetAbsDistance = ({
console.log('e', e) console.log('e', e)
} }
}} }}
className={`border m-1 px-1 rounded text-xs ${
enableAngLen ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
}`}
disabled={!enableAngLen} disabled={!enableAngLen}
> >
{buttonType} {buttonType}

View File

@ -146,9 +146,6 @@ export const SetAngleBetween = () => {
}) })
} }
}} }}
className={`border m-1 px-1 rounded text-xs ${
enable ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
}`}
disabled={!enable} disabled={!enable}
> >
angleBetween angleBetween

View File

@ -168,9 +168,6 @@ export const SetHorzVertDistance = ({
}) })
} }
}} }}
className={`border m-1 px-1 rounded text-xs ${
enable ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
}`}
disabled={!enable} disabled={!enable}
> >
{buttonType} {buttonType}

View File

@ -143,9 +143,6 @@ export const SetAngleLength = ({
console.log('e', e) console.log('e', e)
} }
}} }}
className={`border m-1 px-1 rounded text-xs ${
enableAngLen ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
}`}
disabled={!enableAngLen} disabled={!enableAngLen}
> >
{angleOrLength} {angleOrLength}

View File

@ -12,6 +12,23 @@ body {
sans-serif; sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
@apply text-chalkboard-110 bg-chalkboard-10;
}
body.dark {
@apply bg-chalkboard-100 text-chalkboard-10;
}
button {
@apply border m-0.5 px-3 rounded text-xs;
}
.dark button {
@apply border-chalkboard-20 hover:border-chalkboard-10 hover:bg-chalkboard-90;
}
.dark button:disabled {
@apply bg-chalkboard-90 text-chalkboard-40 border-chalkboard-70;
} }
.mono { .mono {

View File

@ -2,13 +2,31 @@ 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 { Router } from './Router' import { Router } from './Router'
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement) const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
function setThemeClass(state: Partial<{ theme: string }>) {
if (state.theme === 'dark') {
document.body.classList.add('dark')
} else {
document.body.classList.remove('dark')
}
}
const { theme } = useStore.getState()
setThemeClass({ theme })
useStore.subscribe(setThemeClass)
root.render( root.render(
<> <>
<Router /> <Router />
<Toaster position="bottom-center" /> <Toaster
position="bottom-center"
toastOptions={{
className:
'bg-chalkboard-10 dark:bg-chalkboard-90 text-chalkboard-110 dark:text-chalkboard-10',
}}
/>
</> </>
) )

View File

@ -10,10 +10,10 @@ const Units = () => {
return ( return (
<div <div
className="fixed grid justify-center items-end inset-0 bg-chalkboard-110/50 z-50" className="fixed grid justify-center items-end inset-0 bg-chalkboard-110/50 dark:bg-chalkboard-110/80 z-50"
style={{ clipPath }} style={{ clipPath }}
> >
<div className="max-w-2xl flex flex-col justify-center bg-white p-8 rounded"> <div className="max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
<h1 className="text-2xl font-bold">Camera</h1> <h1 className="text-2xl font-bold">Camera</h1>
<p className="mt-6"> <p className="mt-6">
Moving the camera is easy. Just click and drag anywhere in the scene Moving the camera is easy. Just click and drag anywhere in the scene

View File

@ -8,7 +8,7 @@ const Introduction = () => {
return ( return (
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50"> <div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
<div className="max-w-3xl bg-white p-8 rounded"> <div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
<h1 className="text-2xl font-bold"> <h1 className="text-2xl font-bold">
Welcome to the KittyCAD Modeling App Welcome to the KittyCAD Modeling App
</h1> </h1>

View File

@ -7,7 +7,7 @@ const Sketching = () => {
return ( return (
<div className="fixed grid justify-center items-end inset-0 bg-chalkboard-110/50 z-50"> <div className="fixed grid justify-center items-end inset-0 bg-chalkboard-110/50 z-50">
<div className="max-w-2xl flex flex-col justify-center bg-white p-8 rounded"> <div className="max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
<h1 className="text-2xl font-bold">Sketching</h1> <h1 className="text-2xl font-bold">Sketching</h1>
<p className="mt-6"> <p className="mt-6">
We still have to implement this step, and the rest of the tutorial! We still have to implement this step, and the rest of the tutorial!

View File

@ -32,7 +32,7 @@ const Units = () => {
return ( return (
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50"> <div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
<div className="max-w-3xl bg-white p-8 rounded"> <div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
<h1 className="text-2xl font-bold">Set your units</h1> <h1 className="text-2xl font-bold">Set your units</h1>
<SettingsSection <SettingsSection
title="Unit System" title="Unit System"

View File

@ -26,8 +26,9 @@ export const Settings = () => {
setDefaultBaseUnit: saveDefaultBaseUnit, setDefaultBaseUnit: saveDefaultBaseUnit,
saveDebugPanel, saveDebugPanel,
originalDebugPanel, originalDebugPanel,
onboardingStatus: ogOnboardingStatus, setOnboardingStatus,
setOnboardingStatus: saveOnboardingStatus, theme: ogTheme,
setTheme: saveTheme,
} = useStore((s) => ({ } = useStore((s) => ({
defaultDir: s.defaultDir, defaultDir: s.defaultDir,
setDefaultDir: s.setDefaultDir, setDefaultDir: s.setDefaultDir,
@ -39,8 +40,9 @@ export const Settings = () => {
setDefaultBaseUnit: s.setDefaultBaseUnit, setDefaultBaseUnit: s.setDefaultBaseUnit,
saveDebugPanel: s.setDebugPanel, saveDebugPanel: s.setDebugPanel,
originalDebugPanel: s.debugPanel, originalDebugPanel: s.debugPanel,
onboardingStatus: s.onboardingStatus,
setOnboardingStatus: s.setOnboardingStatus, setOnboardingStatus: s.setOnboardingStatus,
theme: s.theme,
setTheme: s.setTheme,
})) }))
const [defaultDir, setDefaultDir] = useState(ogDefaultDir) const [defaultDir, setDefaultDir] = useState(ogDefaultDir)
const [defaultProjectName, setDefaultProjectName] = const [defaultProjectName, setDefaultProjectName] =
@ -49,7 +51,7 @@ export const Settings = () => {
useState(ogDefaultUnitSystem) useState(ogDefaultUnitSystem)
const [defaultBaseUnit, setDefaultBaseUnit] = useState(ogDefaultBaseUnit) const [defaultBaseUnit, setDefaultBaseUnit] = useState(ogDefaultBaseUnit)
const [debugPanel, setDebugPanel] = useState(originalDebugPanel) const [debugPanel, setDebugPanel] = useState(originalDebugPanel)
const [onboardingStatus, setOnboardingStatus] = useState(ogOnboardingStatus) const [theme, setTheme] = useState(ogTheme)
async function handleDirectorySelection() { async function handleDirectorySelection() {
const newDirectory = await open({ const newDirectory = await open({
@ -69,7 +71,7 @@ export const Settings = () => {
saveDefaultUnitSystem(defaultUnitSystem) saveDefaultUnitSystem(defaultUnitSystem)
saveDefaultBaseUnit(defaultBaseUnit) saveDefaultBaseUnit(defaultBaseUnit)
saveDebugPanel(debugPanel) saveDebugPanel(debugPanel)
saveOnboardingStatus(onboardingStatus) saveTheme(theme)
toast.success('Settings saved!') toast.success('Settings saved!')
} }
@ -176,13 +178,25 @@ export const Settings = () => {
onChange={(e) => setDebugPanel(e.target.checked)} onChange={(e) => setDebugPanel(e.target.checked)}
/> />
</SettingsSection> </SettingsSection>
<SettingsSection
title="Editor Theme"
description="Apply a light or dark theme to the editor"
>
<Toggle
name="settings-theme"
offLabel="Dark"
onLabel="Light"
checked={theme === 'light'}
onChange={(e) => setTheme(e.target.checked ? 'light' : 'dark')}
/>
</SettingsSection>
<SettingsSection <SettingsSection
title="Onboarding" title="Onboarding"
description="Replay the onboarding process" description="Replay the onboarding process"
> >
<ActionButton <ActionButton
onClick={() => { onClick={() => {
saveOnboardingStatus('') setOnboardingStatus('')
navigate('/') navigate('/')
}} }}
icon={{ icon: faArrowRotateBack }} icon={{ icon: faArrowRotateBack }}

View File

@ -189,6 +189,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'
setTheme: (theme: 'light' | 'dark') => void
homeMenuItems: { homeMenuItems: {
name: string name: string
path: string path: string
@ -365,6 +367,13 @@ export const useStore = create<StoreState>()(
setDefaultBaseUnit: (defaultBaseUnit) => set({ defaultBaseUnit }), setDefaultBaseUnit: (defaultBaseUnit) => set({ defaultBaseUnit }),
onboardingStatus: '', onboardingStatus: '',
setOnboardingStatus: (onboardingStatus) => set({ onboardingStatus }), setOnboardingStatus: (onboardingStatus) => set({ onboardingStatus }),
theme:
typeof window !== 'undefined' &&
'matchMedia' in window &&
window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light',
setTheme: (theme) => set({ theme }),
showHomeMenu: true, showHomeMenu: true,
setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }), setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }),
homeMenuItems: [], homeMenuItems: [],
@ -390,6 +399,7 @@ export const useStore = create<StoreState>()(
'token', 'token',
'debugPanel', 'debugPanel',
'onboardingStatus', 'onboardingStatus',
'theme',
].includes(key) ].includes(key)
) )
), ),

View File

@ -38,5 +38,6 @@ module.exports = {
}, },
}, },
}, },
darkMode: 'class',
plugins: [], plugins: [],
} }