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

View File

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

View File

@ -21,7 +21,7 @@ export const ActionButton = ({
Element = 'button',
children,
}: 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'
} ${className}`

View File

@ -29,7 +29,7 @@ export const ActionIcon = ({
className={
'p-1 w-fit inline-grid place-content-center ' +
(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 || (
@ -39,7 +39,7 @@ export const ActionIcon = ({
height={iconSizes[size]}
className={
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 (
<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="/">
<img
src="/kitt-arcade-winking.svg"

View File

@ -1,6 +1,6 @@
export const PanelHeader = ({ title }: { title: string }) => {
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>
</div>
)

View File

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

View File

@ -53,9 +53,6 @@ export const ConvertToVariable = () => {
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}
>
ConvertToVariable

View File

@ -86,9 +86,6 @@ export const EqualAngle = () => {
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}
title="yo dawg"
>

View File

@ -86,9 +86,6 @@ export const EqualLength = () => {
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}
title="yo dawg"
>

View File

@ -65,9 +65,6 @@ export const HorzVert = ({
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}
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}
>
perpendicularDistance

View File

@ -69,9 +69,6 @@ export const RemoveConstrainingValues = () => {
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}
title="yo dawg"
>

View File

@ -131,9 +131,6 @@ export const SetAbsDistance = ({
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}
>
{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}
>
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}
>
{buttonType}

View File

@ -143,9 +143,6 @@ export const SetAngleLength = ({
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}
>
{angleOrLength}

View File

@ -12,6 +12,23 @@ body {
sans-serif;
-webkit-font-smoothing: antialiased;
-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 {

View File

@ -2,13 +2,31 @@ import ReactDOM from 'react-dom/client'
import './index.css'
import reportWebVitals from './reportWebVitals'
import { Toaster } from 'react-hot-toast'
import { useStore } from './useStore'
import { Router } from './Router'
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(
<>
<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 (
<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 }}
>
<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>
<p className="mt-6">
Moving the camera is easy. Just click and drag anywhere in the scene

View File

@ -8,7 +8,7 @@ const Introduction = () => {
return (
<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">
Welcome to the KittyCAD Modeling App
</h1>

View File

@ -7,7 +7,7 @@ const Sketching = () => {
return (
<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>
<p className="mt-6">
We still have to implement this step, and the rest of the tutorial!

View File

@ -32,7 +32,7 @@ const Units = () => {
return (
<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>
<SettingsSection
title="Unit System"

View File

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

View File

@ -189,6 +189,8 @@ export interface StoreState {
setHomeShowMenu: (showMenu: boolean) => void
onboardingStatus: string
setOnboardingStatus: (status: string) => void
theme: 'light' | 'dark'
setTheme: (theme: 'light' | 'dark') => void
homeMenuItems: {
name: string
path: string
@ -365,6 +367,13 @@ export const useStore = create<StoreState>()(
setDefaultBaseUnit: (defaultBaseUnit) => set({ defaultBaseUnit }),
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,
setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }),
homeMenuItems: [],
@ -390,6 +399,7 @@ export const useStore = create<StoreState>()(
'token',
'debugPanel',
'onboardingStatus',
'theme',
].includes(key)
)
),

View File

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