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:
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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}`
|
||||
|
||||
|
@ -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'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -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%;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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 {
|
||||
|
@ -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',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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!
|
||||
|
@ -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"
|
||||
|
@ -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 }}
|
||||
|
@ -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)
|
||||
)
|
||||
),
|
||||
|
@ -38,5 +38,6 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
darkMode: 'class',
|
||||
plugins: [],
|
||||
}
|
||||
|
Reference in New Issue
Block a user