From 71a14da5cc232d872bc21a5bdfb5ef0d60ca5e1d Mon Sep 17 00:00:00 2001 From: Adel Khamatov Date: Fri, 18 Jun 2021 10:38:04 +0300 Subject: [PATCH 01/20] IsNotAny checks any and any[] --- src/exercises/node_modules/type-assertions/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/exercises/node_modules/type-assertions/index.ts b/src/exercises/node_modules/type-assertions/index.ts index 08616f37..63b153a6 100644 --- a/src/exercises/node_modules/type-assertions/index.ts +++ b/src/exercises/node_modules/type-assertions/index.ts @@ -17,10 +17,11 @@ export type IsTypeAssignable = IsNotAny extends false ? false : ( ); /** - * Returns `false` if `any` is specified, otherwise returns `true`. + * Returns `false` if `any` or `any[]` is specified, otherwise returns `true`. * @see https://stackoverflow.com/a/49928360/3406963 */ -export type IsNotAny = 0 extends (1 & T) ? false : true; + +export type IsNotAny = 0 extends (1 & T) ? false : ( 0 extends (1 & ArrayElement) ? false : true); /** * Returns true for false and vice versa. From d61e90ec57342b3731a7c2cba528e2a1a298eb87 Mon Sep 17 00:00:00 2001 From: Gessio Mori Date: Tue, 13 Sep 2022 16:39:43 -0300 Subject: [PATCH 02/20] Implements dark mode --- package.json | 1 + src/components/collapsible-panel/index.tsx | 16 +- src/components/dialog/index.tsx | 16 +- src/components/file-title/index.tsx | 10 +- src/components/file-tree-view/index.tsx | 15 +- src/components/footer/index.tsx | 8 +- src/components/header/index.tsx | 7 +- src/components/loading-container/index.tsx | 17 +- src/components/monaco-editor/diff-dialog.tsx | 9 +- src/components/monaco-editor/index.tsx | 5 + .../theme-toggle/icons/MoonLogo.tsx | 18 ++ src/components/theme-toggle/icons/SunLogo.tsx | 18 ++ src/components/theme-toggle/index.tsx | 50 ++++ src/components/validation-errors/index.tsx | 7 +- src/containers/app/index.tsx | 39 ++- src/containers/exercise/index.tsx | 49 ++-- src/containers/navigation/index.tsx | 22 +- src/containers/page-layout/index.tsx | 4 +- src/styles/emotion.d.ts | 9 + src/styles/themes.ts | 11 + yarn.lock | 240 ++++++++++++++++-- 21 files changed, 481 insertions(+), 90 deletions(-) create mode 100644 src/components/theme-toggle/icons/MoonLogo.tsx create mode 100644 src/components/theme-toggle/icons/SunLogo.tsx create mode 100644 src/components/theme-toggle/index.tsx create mode 100644 src/styles/emotion.d.ts create mode 100644 src/styles/themes.ts diff --git a/package.json b/package.json index 36211a87..9656b715 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "license": "MIT", "dependencies": { "@emotion/core": "^10.0.28", + "@emotion/react": "^11.10.4", "@emotion/styled": "^10.0.27", "@types/debounce": "^1.2.0", "@types/jest": "^24.0.0", diff --git a/src/components/collapsible-panel/index.tsx b/src/components/collapsible-panel/index.tsx index f642682e..79825b20 100644 --- a/src/components/collapsible-panel/index.tsx +++ b/src/components/collapsible-panel/index.tsx @@ -1,3 +1,4 @@ +import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import React, {useMemo} from 'react'; import {load} from 'components/loading-container'; @@ -19,7 +20,6 @@ const Header = styled.div<{collapsed: boolean; orientation: Orientation}>` transform: rotate(0) translateX(0); transform-origin: top left; cursor: pointer; - background: #eee; border-bottom: 1px #ddd solid; ${({collapsed, orientation}) => collapsed && orientation === 'vertical' @@ -32,12 +32,13 @@ const Header = styled.div<{collapsed: boolean; orientation: Orientation}>` : ''} `; -const HeaderLabel = styled.div` +const HeaderLabel = styled.div<{color: string}>` flex: 1 1 auto; font-weight: bold; color: rgba(0, 0, 0, 0.75); line-height: 30px; padding: 0 10px; + color: ${(props) => props.color}; `; const Content = styled.div<{collapsed: boolean; orientation: Orientation}>` @@ -70,9 +71,9 @@ const CollapseButton = styled.button` } `; -const Wrapper = styled.div<{collapsed: boolean; orientation: Orientation}>` +const Wrapper = styled.div<{collapsed: boolean; orientation: Orientation; background: string}>` position: relative; - background-color: ${({collapsed}) => (collapsed ? '#eee' : `#f6f6f6`)}; + background-color: ${(props) => props.background}; display: flex; flex-direction: column; flex: 0 0 ${({collapsed, orientation}) => (collapsed ? '30px' : `${orientation === 'vertical' ? width : height}px`)}; @@ -81,7 +82,7 @@ const Wrapper = styled.div<{collapsed: boolean; orientation: Orientation}>` ${({orientation}) => (orientation === 'vertical' ? 'border-right' : 'border-bottom')}: none; ${({orientation}) => (orientation === 'vertical' ? 'border-left' : 'border-top')}: 1px #ddd solid; } - transition: flex-basis 0.2s linear, background-color 0.2s linear; + transition: flex-basis 0.2s linear; `; export function CollapsiblePanel({ @@ -96,14 +97,15 @@ export function CollapsiblePanel({ children: React.ReactNode; }) { const [collapse, expand] = useMemo(() => [() => ui.collapsePanel(id), () => ui.expandPanel(id)], [id]); + const theme = useTheme(); return load(ui.observable$, ({panels}) => ( - +
- {header} + {header}
diff --git a/src/components/dialog/index.tsx b/src/components/dialog/index.tsx index bd9ebc41..d5f855bf 100644 --- a/src/components/dialog/index.tsx +++ b/src/components/dialog/index.tsx @@ -1,10 +1,11 @@ +import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import React from 'react'; const DialogBackground = styled.div` display: block; position: fixed; - background: rgba(0, 0, 0, 0.5); + background: rgba(0, 0, 0, 0.8); top: 0; right: 0; bottom: 0; @@ -12,29 +13,34 @@ const DialogBackground = styled.div` z-index: 1000; `; -const DialogWrapper = styled.div` +const DialogWrapper = styled.div<{backgroundColor: string}>` position: absolute; top: 50px; right: 50px; bottom: 50px; left: 50px; padding: 40px 10px 10px; - background: white; + background: ${(props) => props.backgroundColor} + border: 1px solid #dddddd; + border-radius: 2px; `; const CloseButton = styled.button` position: absolute; top: 10px; right: 10px; + border: none; + border-radius: 4px; `; const stopEventPropagation = (e: React.UIEvent) => e.stopPropagation(); export function Dialog({children, onClose}: {children: React.ReactNode; onClose: () => void}) { + const theme = useTheme(); return ( - - Close + + {children} diff --git a/src/components/file-title/index.tsx b/src/components/file-title/index.tsx index bb16c2eb..9f5a3f7c 100644 --- a/src/components/file-title/index.tsx +++ b/src/components/file-title/index.tsx @@ -1,15 +1,16 @@ +import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import React from 'react'; -const FileTitleWrapper = styled.div` +const FileTitleWrapper = styled.div<{backgroundColor: string}>` flex: 0 0 auto; padding: 0 10px; height: 30px; line-height: 30px; border-bottom: 1px #ddd solid; - background: #eee; + background: ${(props) => props.backgroundColor}; font-size: 14px; - color: #0033aa; + color: ${(props) => (props.backgroundColor === '#1e1e1e' ? '#3a869e' : '#0033aa')}; `; const FileTitleLabel = styled.span` @@ -19,8 +20,9 @@ const FileTitleLabel = styled.span` `; export function FileTitle({filename, readOnly}: {filename: string; readOnly: boolean}) { + const theme = useTheme(); return ( - + {filename.replace(/^\//, '')} {readOnly && Read Only} diff --git a/src/components/file-tree-view/index.tsx b/src/components/file-tree-view/index.tsx index 36e05a0b..dae45b4d 100644 --- a/src/components/file-tree-view/index.tsx +++ b/src/components/file-tree-view/index.tsx @@ -1,3 +1,4 @@ +import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import React, {useCallback, useMemo} from 'react'; import {FileTree} from 'lib/file-tree'; @@ -85,11 +86,17 @@ const FileTreeBranchRevert = styled.button` } `; -const FileTreeBranchLabel = styled.div<{selected: boolean; selectable: boolean; readOnly: boolean}>` +const FileTreeBranchLabel = styled.div<{ + selected: boolean; + selectable: boolean; + readOnly: boolean; + mode: 'light' | 'dark'; +}>` display: block; cursor: ${({selectable}) => (selectable ? 'pointer' : 'default')}; pointer-events: ${({selectable}) => (selectable ? 'all' : 'none')}; - color: ${({selected, readOnly}) => (selected ? 'white' : readOnly ? '#555' : 'black')}; + color: ${({selected, readOnly, mode}) => + selected ? 'white' : readOnly ? '#555' : mode === 'light' ? 'black' : 'white'}; height: 30px; line-height: 30px; font-size: 14px; @@ -130,13 +137,15 @@ function FileTreeViewBranch({ const selected = selectedFilename === branch.filename; const isDirectory = branch.children.length > 0; const revert = useCallback(() => revertFile(branch.filename), [branch, revertFile]); + const theme = useTheme(); return ( + readOnly={branch.readOnly} + mode={theme.style}> {isDirectory ? ( diff --git a/src/components/footer/index.tsx b/src/components/footer/index.tsx index a9924306..7ec1eedb 100644 --- a/src/components/footer/index.tsx +++ b/src/components/footer/index.tsx @@ -1,13 +1,16 @@ +import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import React from 'react'; -const FooterWrapper = styled.footer` +const FooterWrapper = styled.footer<{backgroundColor: string}>` flex: 0 0 auto; border-top: 1px solid #294e80; padding: 5px 0; text-align: center; color: gray; font-size: 12px; + background-color: ${(props) => props.backgroundColor}; + border-color: #dddddd; #faf9f8; a, a:hover, a:visited, @@ -20,8 +23,9 @@ const FooterWrapper = styled.footer` const currentYear = new Date().getFullYear(); export function Footer() { + const theme = useTheme(); return ( - + © {currentYear} Marat Dulin,{' '} contribute,{' '} support this project diff --git a/src/components/header/index.tsx b/src/components/header/index.tsx index 594fd613..2f2ed62e 100644 --- a/src/components/header/index.tsx +++ b/src/components/header/index.tsx @@ -1,12 +1,16 @@ import styled from '@emotion/styled'; import React from 'react'; import {TsLogo} from 'components/header/ts-logo'; +import {ThemeToggle} from 'components/theme-toggle'; const HeaderWrapper = styled.header` + display: flex; flex: 0 0 auto; background: #294e80; padding: 10px 16px; z-index: 1; + justify-content: space-between; + align-items: center; `; const HeaderLogo = styled.h1` @@ -34,7 +38,7 @@ const HeaderSubLogo = styled.span` opacity: 0.9; `; -export function Header() { +export function Header({toggleTheme}: {toggleTheme: () => void}) { return ( @@ -42,6 +46,7 @@ export function Header() { TypeScript exercises + ); } diff --git a/src/components/loading-container/index.tsx b/src/components/loading-container/index.tsx index b35c232d..d59840f7 100644 --- a/src/components/loading-container/index.tsx +++ b/src/components/loading-container/index.tsx @@ -1,3 +1,5 @@ +import {useTheme} from '@emotion/react'; +import styled from '@emotion/styled'; import React, {useLayoutEffect, useMemo, useState} from 'react'; import PulseLoader from 'react-spinners/PulseLoader'; import {Observable, Subscription} from 'rxjs'; @@ -32,11 +34,24 @@ function useObservable(observable: Observable): T | undefined { return value; } +const LoadingDiv = styled.div<{backgroundColor: string}>` + width: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: ${(props) => props.backgroundColor}; +`; + function LoadingContainer({observable, render}: {observable: Observable; render: RenderCallback}) { const update = useObservable(observable); + const theme = useTheme(); if (update === undefined) { - return ; + return ( + + + + ); } return <>{render(update)}; diff --git a/src/components/monaco-editor/diff-dialog.tsx b/src/components/monaco-editor/diff-dialog.tsx index 810c4b49..5366d0a8 100644 --- a/src/components/monaco-editor/diff-dialog.tsx +++ b/src/components/monaco-editor/diff-dialog.tsx @@ -1,3 +1,4 @@ +import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import {editor} from 'monaco-editor'; import React, {useMemo} from 'react'; @@ -29,13 +30,14 @@ const EditorArea = styled.div` flex-direction: column; `; -const EditorAreaCaption = styled.div` +const EditorAreaCaption = styled.div<{backgroundColor: string; color: string}>` display: flex; flex-direction: row; justify-content: space-around; align-items: center; flex: 0 0 30px; - background: #f6f6f6; + background: ${(props) => props.backgroundColor}; + color: ${(props) => props.color}; `; const EditorAreaTitle = styled.div` @@ -60,6 +62,7 @@ function createTreeFromFilenames(filenames: string[]): FileTree { export function DiffDialog(props: DiffDialogProps) { const {onClose, filenames, selectedFilename, onSelectFile, ...otherProps} = props; const fileTree = useMemo(() => createTreeFromFilenames(filenames), [filenames]); + const theme = useTheme(); return ( @@ -74,7 +77,7 @@ export function DiffDialog(props: DiffDialogProps) { - + Possible solution Current version diff --git a/src/components/monaco-editor/index.tsx b/src/components/monaco-editor/index.tsx index 9951d4d2..95dcb080 100644 --- a/src/components/monaco-editor/index.tsx +++ b/src/components/monaco-editor/index.tsx @@ -55,6 +55,7 @@ export const MonacoEditor = decorateWithAutoResize( constructor(props: MonacoEditorProps) { super(props); this.state = {}; + editor.setTheme(props.theme || 'vs'); } public componentDidMount() { @@ -106,6 +107,7 @@ export const MonacoEditor = decorateWithAutoResize( if (!this.instance) { return; } + const newSelectedFilename = this.props.selectedFilename; if (newSelectedFilename !== prevProps.selectedFilename) { const model = this.models[newSelectedFilename]; @@ -121,6 +123,9 @@ export const MonacoEditor = decorateWithAutoResize( } this.instance.focus(); } + if (this.props.theme !== prevProps.theme) { + editor.setTheme(this.props.theme || 'vs'); + } if (this.props.position !== prevProps.position) { if (this.props.position !== undefined) { const model = this.models[this.props.selectedFilename]; diff --git a/src/components/theme-toggle/icons/MoonLogo.tsx b/src/components/theme-toggle/icons/MoonLogo.tsx new file mode 100644 index 00000000..1f80fe21 --- /dev/null +++ b/src/components/theme-toggle/icons/MoonLogo.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +export function MoonLogo() { + return ( + + + + ); +} diff --git a/src/components/theme-toggle/icons/SunLogo.tsx b/src/components/theme-toggle/icons/SunLogo.tsx new file mode 100644 index 00000000..0d0112ce --- /dev/null +++ b/src/components/theme-toggle/icons/SunLogo.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +export function SunLogo() { + return ( + + + + ); +} diff --git a/src/components/theme-toggle/index.tsx b/src/components/theme-toggle/index.tsx new file mode 100644 index 00000000..c130230f --- /dev/null +++ b/src/components/theme-toggle/index.tsx @@ -0,0 +1,50 @@ +import styled from '@emotion/styled'; +import React from 'react'; +import {MoonLogo} from './icons/MoonLogo'; +import {SunLogo} from './icons/SunLogo'; + +interface ToggleProps { + isDark: boolean; +} + +const Toggle = styled.div` + width: 50px; + max-height: 30px; + display: flex; + padding: 5px; + background-color: #1a202c; + display: block; + border-radius: 1000px; + cursor: pointer; + box-shadow: 0px 5px 20px -10px #000000; + transition: background-color 0.2s ease-in; + background-color: #ddd; + + .toggle-inner { + display: flex; + align-items: center; + width: 20px; + height: 20px; + background-color: white; + border-radius: 1000px; + transition: margin-left 0.2s ease-in, background-color 0.2s ease-in; + margin-left: ${(props) => (props.isDark ? '30px' : '0')}; + background-color: #294e80; + } +`; + +export function ThemeToggle({toggleTheme}: {toggleTheme: () => void}) { + const [darkMode, setDarkMode] = React.useState(localStorage.getItem('theme') === 'dark'); + + const click = () => { + toggleTheme(); + setDarkMode((current) => !current); + localStorage.setItem('theme', darkMode ? 'light' : 'dark'); + }; + + return ( + click()}> +
{darkMode ? : }
+
+ ); +} diff --git a/src/components/validation-errors/index.tsx b/src/components/validation-errors/index.tsx index b5fc3d87..058b3b0f 100644 --- a/src/components/validation-errors/index.tsx +++ b/src/components/validation-errors/index.tsx @@ -1,12 +1,14 @@ +import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import React from 'react'; import {ValidationError} from 'lib/validation-error'; -const ValidationErrorsWrapper = styled.ol` +const ValidationErrorsWrapper = styled.ol<{color: string}>` padding: 0; margin: 5px 0 5px 40px; list-style: none; counter-reset: errors; + color: ${(props) => props.color}; `; const ValidationErrorsItem = styled.li` @@ -39,8 +41,9 @@ export const ValidationErrors = React.memo(function ValidationErrors({ errors: ValidationError[]; onClick: (error: ValidationError) => void; }) { + const theme = useTheme(); return ( - + {errors.map((error, index) => ( onClick(error)}> {error.messageText} diff --git a/src/containers/app/index.tsx b/src/containers/app/index.tsx index 266e5290..c96d38cd 100644 --- a/src/containers/app/index.tsx +++ b/src/containers/app/index.tsx @@ -1,12 +1,29 @@ import {Global, css} from '@emotion/core'; -import React from 'react'; +import {ThemeProvider} from '@emotion/react'; +import React, {useState} from 'react'; +import {darkTheme, lightTheme} from 'styles/themes'; import {load} from 'components/loading-container'; import {Exercise} from 'containers/exercise'; import {PageLayout} from 'containers/page-layout'; import {urlParams} from 'observables/url-params'; import {fonts} from './fonts'; -const globalStyles = css` +const globalStylesLight = css` + html, + body, + #root { + margin: 0; + height: 100%; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + font-family: 'Segoe UI Web (West European)', Segoe UI, -apple-system, BlinkMacSystemFont, Roboto, Helvetica Neue, + sans-serif; + font-size: 16px; + } + ${fonts} +`; + +const globalStylesDark = css` html, body, #root { @@ -22,14 +39,24 @@ const globalStyles = css` `; export function App() { + const [theme, setTheme] = useState(localStorage.getItem('theme') || 'light'); + + const toggleTheme = () => { + if (theme === 'light') { + setTheme('dark'); + } else { + setTheme('light'); + } + }; + return ( - <> - - + + + {load(urlParams.observable$, (params) => ( ))} - + ); } diff --git a/src/containers/exercise/index.tsx b/src/containers/exercise/index.tsx index 69f9776d..a0363f35 100644 --- a/src/containers/exercise/index.tsx +++ b/src/containers/exercise/index.tsx @@ -1,3 +1,4 @@ +import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {CollapsiblePanel} from 'components/collapsible-panel'; @@ -47,20 +48,29 @@ const CompletedExerciseWrapper = styled.div` text-align: center; `; -const CompletedExerciseLabel = styled.div` +const CompletedExerciseLabel = styled.div<{color: string}>` margin: 20px 0 0; + color: ${(props) => props.color}; `; -const ButtonsWrapper = styled.div` +const ButtonsWrapper = styled.div<{color: string; backgroundColor: string}>` text-align: center; margin: 20px 0; -`; + color: ${(props) => props.color}; -const ExerciseButton = styled.button` - font-size: 16px; - text-align: center; - & + & { - margin-left: 10px; + button { + border: 1px solid ${(props) => props.color}; + color: ${(props) => props.color}; + border-radius: 4px; + font-size: 16px; + text-align: center; + margin: 0 10px; + color: ${(props) => props.color}; + background-color: ${(props) => props.backgroundColor}; + + :hover { + filter: ${(props) => (props.backgroundColor === '#1e1e1e' ? 'brightness(1.3)' : 'brightness(0.9)')}; + } } `; @@ -81,6 +91,8 @@ export function Exercise({exerciseNumber}: {exerciseNumber: number}) { const [solutionsVisible, setSolutionsVisible] = useState(false); const validationErrors$ = useMemo(() => exercise.observable$.pipe(checkTypeScriptProject()), [exercise]); const [selectedFilename, setSelectedFilename] = useState('/index.ts'); + const theme = useTheme(); + useEffect(() => { const subscription = urlParams.observable$.subscribe((params) => { setSelectedFilename(String(params.file)); @@ -130,7 +142,7 @@ export function Exercise({exerciseNumber}: {exerciseNumber: number}) { selectedFilename={selectedFilename} values={fileTree} onChange={onChange} - theme='vs-light' + theme={theme.style === 'light' ? 'vs' : 'vs-dark'} position={position} onNavigate={() => null} options={{ @@ -151,19 +163,16 @@ export function Exercise({exerciseNumber}: {exerciseNumber: number}) { {errors.length > 0 && ( <> - + {'I give up, '} - - show a possible solution - {' '} -   or - skip + or + )} {errors.length === 0 && ( - + {exerciseNumber === lastExerciseNumber ? ( <>Congratulations! That was the last exercise. ) : ( @@ -171,11 +180,9 @@ export function Exercise({exerciseNumber}: {exerciseNumber: number}) { )} {exerciseNumber !== lastExerciseNumber && ( - - - Next exercise - - Compare my solution + + + )} diff --git a/src/containers/navigation/index.tsx b/src/containers/navigation/index.tsx index fc28f8f7..0a1b0ae3 100644 --- a/src/containers/navigation/index.tsx +++ b/src/containers/navigation/index.tsx @@ -1,3 +1,4 @@ +import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import React from 'react'; import {combineLatest} from 'rxjs'; @@ -6,12 +7,12 @@ import {exerciseStructures} from 'lib/exercise-structures'; import {exercisesProgress} from 'observables/exercises-progress'; import {urlParams} from 'observables/url-params'; -const Wrapper = styled.div` +const Wrapper = styled.div<{mode: 'light' | 'dark'}>` flex: 0 0 auto; display: flex; flex-direction: row; - background: #faf9f8; - border-bottom: 1px solid #294e80; + background: ${(props) => (props.mode === 'light' ? '#faf9f8' : '#171717')}; + border-bottom: 1px solid ${(props) => (props.mode === 'light' ? '#294e80' : '#ddd')}; align-items: center; padding: 0 10px; user-select: none; @@ -22,20 +23,21 @@ const NavBar = styled.nav` list-style-type: none; `; -const NavBarLabel = styled.li` +const NavBarLabel = styled.li<{color: string}>` display: inline-block; font-weight: bold; margin-right: 20px; opacity: 0.75; + color: ${(props) => props.color}; `; -const NavBarItem = styled.li<{completed: boolean; current: boolean}>` +const NavBarItem = styled.li<{completed: boolean; current: boolean; mode: 'light' | 'dark'}>` display: inline-block; margin: 0; padding: 0; position: relative; line-height: 30px; - color: ${({completed}) => (completed ? 'inherit' : 'gray')}; + color: ${({completed, mode}) => (completed ? (mode === 'light' ? 'inherit' : 'white') : 'gray')}; cursor: pointer; font-weight: ${({current}) => (current ? 'bold' : 'normal')}; &::after { @@ -49,20 +51,22 @@ const NavBarItem = styled.li<{completed: boolean; current: boolean}>` `; export function Navigation() { + const theme = useTheme(); return ( - + {load( combineLatest([exercisesProgress.observable$, urlParams.observable$]), ([{completedExercises}, params]) => ( <> - Exercises + Exercises {Object.keys(exerciseStructures).map((exerciseNumber) => ( exercisesProgress.goToExercise(Number(exerciseNumber))} - key={exerciseNumber}> + key={exerciseNumber} + mode={theme.style}> {exerciseNumber} ))} diff --git a/src/containers/page-layout/index.tsx b/src/containers/page-layout/index.tsx index 61807b15..6c5965b2 100644 --- a/src/containers/page-layout/index.tsx +++ b/src/containers/page-layout/index.tsx @@ -17,10 +17,10 @@ const PageLayoutMain = styled.main` position: relative; `; -export function PageLayout({children}: {children: React.ReactNode}) { +export function PageLayout({children, toggleTheme}: {children: React.ReactNode; toggleTheme: () => void}) { return ( -
+
{children}