Skip to content

Commit 3ee8e02

Browse files
committed
✨ feat: update light/dark theme switch
Include a new switch button that allows users to preview their README files for both light and dark themes (default theme is dark one). This feature aims to reduce the issue with black colored icons (mostly devicons / simple icons) being barely visible on the dark canvas. Also it is a nice UX enhancement for those users who simply prefer light theming. Fixes: maurodesouza#44
1 parent ebe1d21 commit 3ee8e02

File tree

13 files changed

+201
-57
lines changed

13 files changed

+201
-57
lines changed

src/app/events/handles/app.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { themes } from "styles"
2+
import { Events } from 'types';
3+
4+
import { BaseEventHandle } from './base';
5+
6+
class AppHandleEvents extends BaseEventHandle {
7+
constructor() {
8+
super();
9+
10+
}
11+
12+
theme = (theme: keyof typeof themes) => {
13+
this.emit(Events.APP_SET_THEME, theme);
14+
}
15+
}
16+
17+
export { AppHandleEvents };

src/app/events/handles/canvas.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,6 @@ class CanvasHandleEvents extends BaseEventHandle {
4545
currentSection = (sectionId: string) => {
4646
this.emit(Events.CANVAS_SET_CURRENT_SECTION, sectionId);
4747
};
48-
49-
switchTheme = (theme: boolean) => {
50-
this.emit(Events.CANVAS_SWITCH_THEME, theme);
51-
};
5248
}
5349

5450
export { CanvasHandleEvents };

src/app/events/handles/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AppHandleEvents } from './app';
12
import { CanvasHandleEvents } from './canvas';
23
import { ContextMenuHandleEvents } from './context-menu';
34
import { SettingsHandleEvents } from './settings';
@@ -8,6 +9,7 @@ import { TemplateHandleEvents } from './template';
89
import { ExtensionsHandleEvents } from './extensions';
910

1011
class Handles {
12+
app = new AppHandleEvents();
1113
canvas = new CanvasHandleEvents();
1214
contextmenu = new ContextMenuHandleEvents();
1315
settings = new SettingsHandleEvents();

src/components/canvas/index.tsx

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { MouseEvent, useMemo, useState } from 'react';
2+
import { useTheme } from 'styled-components';
23
import { Reorder } from 'framer-motion';
34

45
import {
@@ -26,9 +27,11 @@ import { CanvasErrorFallback } from './error';
2627

2728
const Canvas = () => {
2829
const { extensions } = useExtensions();
29-
const { sections, currentSection, previewMode, lightTheme } = useCanvas();
30+
const { sections, currentSection, previewMode } = useCanvas();
3031
const [hasError, setHasError] = useState(false);
3132

33+
const currentTheme = useTheme();
34+
3235
const sectionIds = sections.map(section => section.id);
3336
const hasSection = !!sections.length;
3437

@@ -38,49 +41,56 @@ const Canvas = () => {
3841
!previewMode && events.contextmenu.open(ContextMenus.SECTION, e);
3942
};
4043

44+
const getNextThemeName = (themeName: string) => {
45+
if(themeName === 'dark') return 'light';
46+
return 'dark';
47+
}
48+
49+
const handleSetTheme = () => {
50+
events.app.theme(getNextThemeName(currentTheme.NAME));
51+
}
52+
4153
return (
4254
<OnlyClientSide>
4355
<S.Container
4456
onContextMenu={handleOpenContextMenu}
4557
fullHeight={hasError || !hasSection}
46-
isLightTheme={lightTheme}
4758
>
48-
<S.Wrapper isLeftAligned={false}>
59+
<S.Wrapper>
4960
<Tooltip
50-
position="right"
51-
content={`Preview: ${lightTheme ? 'dark' : 'light'} mode`}
61+
position="left"
62+
content={`Preview: ${getNextThemeName(currentTheme.NAME)} mode`}
5263
variant="info"
5364
>
5465
<S.Button
55-
aria-label={`Preview: ${lightTheme ? 'dark' : 'light'} mode`}
56-
onClick={() => events.canvas.switchTheme(lightTheme)}
57-
variant="success"
66+
aria-label={`Preview: ${getNextThemeName(currentTheme.NAME)} mode`}
67+
onClick={() => handleSetTheme()}
5868
>
59-
{lightTheme ? <MoonIcon size={16} /> : <SunIcon size={16} />}
69+
{getNextThemeName(currentTheme.NAME) === 'dark' ? <MoonIcon size={16} /> : <SunIcon size={16} />}
6070
</S.Button>
6171
</Tooltip>
72+
73+
{hasSection && !previewMode && (
74+
75+
<Tooltip position="left" content="Clear canvas" variant="danger">
76+
<S.Button
77+
aria-label="Clear canvas"
78+
onClick={events.canvas.clear}
79+
variant="warn"
80+
>
81+
<TrashIcon size={16} />
82+
</S.Button>
83+
</Tooltip>
84+
85+
)}
6286
</S.Wrapper>
6387

64-
{hasSection && !previewMode && (
65-
<S.Wrapper isLeftAligned={true}>
66-
<Tooltip position="left" content="Clear canvas" variant="danger">
67-
<S.Button
68-
aria-label="Clear canvas"
69-
onClick={events.canvas.clear}
70-
variant="warn"
71-
>
72-
<TrashIcon size={16} />
73-
</S.Button>
74-
</Tooltip>
75-
</S.Wrapper>
76-
)}
77-
7888
<ErrorBoundary
7989
fallback={<CanvasErrorFallback />}
8090
onChange={setHasError}
8191
>
8292
{previewMode && (
83-
<S.Wrapper isLeftAligned={true}>
93+
<S.Wrapper>
8494
<Tooltip position="left" content="Use template" variant="success">
8595
<S.Button
8696
aria-label="Use template"

src/components/canvas/styles.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@ import styled, { css, DefaultTheme } from 'styled-components';
22

33
type ContainerProps = {
44
fullHeight: boolean;
5-
isLightTheme: boolean;
6-
};
7-
8-
type WrapperProps = {
9-
isLeftAligned: boolean;
105
};
116

127
export const Container = styled.div<ContainerProps>`
13-
${({ theme, fullHeight, isLightTheme }) => css`
8+
${({ theme, fullHeight }) => css`
149
padding: ${theme.spacings.xlarge};
1510
border-radius: ${theme.border.radius};
1611
border-width: ${theme.border.width};
@@ -21,9 +16,7 @@ export const Container = styled.div<ContainerProps>`
2116
padding-right: ${theme.spacings.small};
2217
height: ${fullHeight ? '100%' : 'auto'};
2318
24-
background: ${isLightTheme && '#eee'};
25-
color: ${isLightTheme && theme.colors.bg};
26-
transition: color 0.25s linear, background 0.25s linear;
19+
border: 2px solid ${theme.colors.border};
2720
2821
&::-webkit-scrollbar {
2922
width: 0.8rem;
@@ -40,8 +33,8 @@ export const Container = styled.div<ContainerProps>`
4033
`}
4134
`;
4235

43-
export const Wrapper = styled.div<WrapperProps>`
44-
${({ theme, isLeftAligned }) => css`
36+
export const Wrapper = styled.div`
37+
${({ theme }) => css`
4538
width: 3rem;
4639
position: absolute;
4740
display: flex;
@@ -53,7 +46,7 @@ export const Wrapper = styled.div<WrapperProps>`
5346
color: ${theme.colors.text};
5447
5548
top: ${theme.spacings.medium};
56-
left: ${isLeftAligned ? '0%' : '100%'};
49+
left: 0;
5750
transform: translateX(-50%);
5851
transition: 0.3s;
5952

src/contexts/canvas.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ type CanvasContextData = {
1818
sections: CanvasSection[];
1919
currentSection?: CanvasSection;
2020
previewMode: boolean;
21-
lightTheme: boolean;
2221
};
2322

2423
type CanvasProviderProps = {
@@ -33,7 +32,6 @@ const CanvasProvider = ({ children }: CanvasProviderProps) => {
3332
[]
3433
);
3534

36-
const [lightTheme, setLightTheme] = useState(false);
3735
const [currentSection, setCurrentSection] = useState<CanvasSection>();
3836
const [previewTemplate, setPreviewTemplate] = useState<CanvasSection[]>([]);
3937

@@ -137,10 +135,6 @@ const CanvasProvider = ({ children }: CanvasProviderProps) => {
137135

138136
const handleClearCanvas = () => setSections([]);
139137

140-
const handleSwitchTheme = () => {
141-
setLightTheme(!lightTheme);
142-
};
143-
144138
useEffect(() => {
145139
// Canvas events
146140

@@ -150,7 +144,6 @@ const CanvasProvider = ({ children }: CanvasProviderProps) => {
150144
events.on(Events.CANVAS_REORDER_SECTIONS, handleReorderSections);
151145
events.on(Events.CANVAS_DUPLICATE_SECTION, handleDuplicateSection);
152146
events.on(Events.CANVAS_CLEAR_SECTIONS, handleClearCanvas);
153-
events.on(Events.CANVAS_SWITCH_THEME, handleSwitchTheme);
154147

155148
return () => {
156149
events.off(Events.CANVAS_EDIT_SECTION, handleEditSection);
@@ -159,9 +152,8 @@ const CanvasProvider = ({ children }: CanvasProviderProps) => {
159152
events.off(Events.CANVAS_REORDER_SECTIONS, handleReorderSections);
160153
events.off(Events.CANVAS_DUPLICATE_SECTION, handleDuplicateSection);
161154
events.off(Events.CANVAS_CLEAR_SECTIONS, handleClearCanvas);
162-
events.off(Events.CANVAS_SWITCH_THEME, handleSwitchTheme);
163155
};
164-
}, [sections, currentSection, lightTheme]);
156+
}, [sections, currentSection]);
165157

166158
useEffect(() => {
167159
// Canvas events
@@ -194,7 +186,6 @@ const CanvasProvider = ({ children }: CanvasProviderProps) => {
194186
sections: canvas,
195187
currentSection,
196188
previewMode,
197-
lightTheme: lightTheme,
198189
}}
199190
>
200191
{children}

src/pages/_app.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MouseEvent, useEffect } from 'react';
1+
import React, { MouseEvent, useEffect, useState, useRef } from 'react';
22
import Head from 'next/head';
33

44
import { AppProps } from 'next/app';
@@ -10,22 +10,41 @@ import { config, events } from 'app';
1010
import { ContextMenu, Modal } from 'components';
1111

1212
import { Features } from 'features';
13-
import { theme, GlobalStyles } from 'styles';
13+
import { themes, GlobalStyles } from 'styles';
14+
import { Events } from 'types';
1415

1516
const App = ({ Component, pageProps }: AppProps) => {
1617
const appUrl = config.general.urls.app;
1718

19+
const [currTheme, setCurrTheme] = useState(themes['dark']);
20+
const isTransition = useRef(false);
21+
1822
const handlePreventRightClick = (e: MouseEvent) => {
1923
e.preventDefault();
2024

2125
events.contextmenu.close();
2226
};
2327

28+
const handleSetTheme = async(e: CustomEvent) => {
29+
document.querySelectorAll(`ul`).forEach(el => el.classList.add('no-animate'));
30+
if(!isTransition.current) {
31+
isTransition.current = true;
32+
setCurrTheme(themes[e.detail]);
33+
document.body.animate([{}], { duration: 450, iterations: 1, direction: 'alternate'});
34+
Promise.all(document.body.getAnimations().map((animation) => animation.finished)).then(async() => {
35+
return new Promise((resolve) =>
36+
resolve(document.querySelectorAll(`ul`).forEach(el => el.classList.remove('no-animate')))
37+
).then(() => isTransition.current = false)
38+
});
39+
}}
40+
2441
useEffect(() => {
2542
events.on('contextmenu', handlePreventRightClick);
43+
events.on(Events.APP_SET_THEME, handleSetTheme);
2644

2745
return () => {
2846
events.on('contextmenu', handlePreventRightClick);
47+
events.off(Events.APP_SET_THEME, handleSetTheme);
2948
};
3049
}, []);
3150

@@ -34,7 +53,7 @@ const App = ({ Component, pageProps }: AppProps) => {
3453
'Beautify your github profile with this amazing tool, creating the readme your way in a simple and fast way! The best profile readme generator you will find!';
3554

3655
return (
37-
<ThemeProvider theme={theme}>
56+
<ThemeProvider theme={currTheme}>
3857
<Head>
3958
<title>{title}</title>
4059

src/styles/global.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,21 @@ const GlobalStyles = createGlobalStyle`
1515
1616
html {
1717
font-size: 10px;
18+
transition: background-color .45s linear !important;
19+
}
20+
21+
ul.no-animate li {
22+
-webkit-transition: none !important;
23+
-moz-transition: none !important;
24+
-o-transition: none !important;
25+
transition: none !important;
26+
transform: none !important;
1827
}
1928
2029
ul,
2130
li {
2231
list-style: none;
32+
transition: 0 all linear;
2333
}
2434
2535
a {

src/styles/themes/default.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
const defaultTheme = {
2+
NAME: 'dark',
3+
24
grid: {
35
container: '104rem',
46
},

src/styles/themes/index.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,16 @@
1-
export { defaultTheme as theme } from './default';
1+
import { DefaultTheme } from 'styled-components';
2+
import { defaultTheme } from './default';
3+
import { lightTheme } from './light';
4+
5+
const themes: ThemeObject = {
6+
dark: defaultTheme,
7+
light: lightTheme
8+
}
9+
10+
type ThemeObject = {
11+
[key: string]: DefaultTheme;
12+
dark: DefaultTheme,
13+
light: DefaultTheme
14+
}
15+
16+
export { themes }

0 commit comments

Comments
 (0)