diff --git a/src/app/events/handles/app.ts b/src/app/events/handles/app.ts new file mode 100644 index 00000000..0d5f4748 --- /dev/null +++ b/src/app/events/handles/app.ts @@ -0,0 +1,17 @@ +import { themes } from "styles" +import { Events } from 'types'; + +import { BaseEventHandle } from './base'; + +class AppHandleEvents extends BaseEventHandle { + constructor() { + super(); + + } + + theme = (theme: keyof typeof themes) => { + this.emit(Events.APP_SET_THEME, theme); + } +} + +export { AppHandleEvents }; \ No newline at end of file diff --git a/src/app/events/handles/index.ts b/src/app/events/handles/index.ts index f97aeb46..b0bae3f6 100644 --- a/src/app/events/handles/index.ts +++ b/src/app/events/handles/index.ts @@ -1,3 +1,4 @@ +import { AppHandleEvents } from './app'; import { CanvasHandleEvents } from './canvas'; import { ContextMenuHandleEvents } from './context-menu'; import { SettingsHandleEvents } from './settings'; @@ -8,6 +9,7 @@ import { TemplateHandleEvents } from './template'; import { ExtensionsHandleEvents } from './extensions'; class Handles { + app = new AppHandleEvents(); canvas = new CanvasHandleEvents(); contextmenu = new ContextMenuHandleEvents(); settings = new SettingsHandleEvents(); diff --git a/src/components/canvas/index.tsx b/src/components/canvas/index.tsx index e95daae7..0764de31 100644 --- a/src/components/canvas/index.tsx +++ b/src/components/canvas/index.tsx @@ -1,9 +1,12 @@ import { MouseEvent, useMemo, useState } from 'react'; +import { useTheme } from 'styled-components'; import { Reorder } from 'framer-motion'; import { Trash as TrashIcon, Check as CheckIcon, + Moon as MoonIcon, + Sun as SunIcon, X as CloseIcon, } from '@styled-icons/feather'; @@ -27,6 +30,8 @@ const Canvas = () => { const { sections, currentSection, previewMode } = useCanvas(); const [hasError, setHasError] = useState(false); + const currentTheme = useTheme(); + const sectionIds = sections.map(section => section.id); const hasSection = !!sections.length; @@ -36,25 +41,49 @@ const Canvas = () => { !previewMode && events.contextmenu.open(ContextMenus.SECTION, e); }; + const getNextThemeName = (themeName: string) => { + if(themeName === 'dark') return 'light'; + return 'dark'; + } + + const handleSetTheme = () => { + events.app.theme(getNextThemeName(currentTheme.NAME)); + } + return ( - {hasSection && !previewMode && ( - - - - - - - - )} + + + handleSetTheme()} + > + {getNextThemeName(currentTheme.NAME) === 'dark' ? : } + + + + {hasSection && !previewMode && ( + + + + + + + + )} + } diff --git a/src/components/canvas/styles.ts b/src/components/canvas/styles.ts index a30029bc..ff3d132d 100644 --- a/src/components/canvas/styles.ts +++ b/src/components/canvas/styles.ts @@ -16,6 +16,8 @@ export const Container = styled.div` padding-right: ${theme.spacings.small}; height: ${fullHeight ? '100%' : 'auto'}; + border: 2px solid ${theme.colors.border}; + &::-webkit-scrollbar { width: 0.8rem; overflow: hidden; @@ -75,6 +77,12 @@ const buttonModifiers = { color: ${theme.colors.secondary}; } `, + + info: (theme: DefaultTheme) => css` + &:hover { + color: ${theme.colors.primary}; + } + `, }; export const Button = styled.button` diff --git a/src/contexts/canvas.tsx b/src/contexts/canvas.tsx index 4cdf88aa..cd1f0728 100644 --- a/src/contexts/canvas.tsx +++ b/src/contexts/canvas.tsx @@ -182,7 +182,11 @@ const CanvasProvider = ({ children }: CanvasProviderProps) => { return ( {children} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 77eb154c..8752f412 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,4 +1,4 @@ -import { MouseEvent, useEffect } from 'react'; +import React, { MouseEvent, useEffect, useState, useRef } from 'react'; import Head from 'next/head'; import { AppProps } from 'next/app'; @@ -10,22 +10,41 @@ import { config, events } from 'app'; import { ContextMenu, Modal } from 'components'; import { Features } from 'features'; -import { theme, GlobalStyles } from 'styles'; +import { themes, GlobalStyles } from 'styles'; +import { Events } from 'types'; const App = ({ Component, pageProps }: AppProps) => { const appUrl = config.general.urls.app; + const [currTheme, setCurrTheme] = useState(themes['dark']); + const isTransition = useRef(false); + const handlePreventRightClick = (e: MouseEvent) => { e.preventDefault(); events.contextmenu.close(); }; + const handleSetTheme = async(e: CustomEvent) => { + document.querySelectorAll(`ul`).forEach(el => el.classList.add('no-animate')); + if(!isTransition.current) { + isTransition.current = true; + setCurrTheme(themes[e.detail]); + document.body.animate([{}], { duration: 450, iterations: 1, direction: 'alternate'}); + Promise.all(document.body.getAnimations().map((animation) => animation.finished)).then(async() => { + return new Promise((resolve) => + resolve(document.querySelectorAll(`ul`).forEach(el => el.classList.remove('no-animate'))) + ).then(() => isTransition.current = false) + }); + }} + useEffect(() => { events.on('contextmenu', handlePreventRightClick); + events.on(Events.APP_SET_THEME, handleSetTheme); return () => { events.on('contextmenu', handlePreventRightClick); + events.off(Events.APP_SET_THEME, handleSetTheme); }; }, []); @@ -34,7 +53,7 @@ const App = ({ Component, pageProps }: AppProps) => { '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!'; return ( - + {title} diff --git a/src/styles/global.ts b/src/styles/global.ts index 87cbc551..b13bfe4e 100644 --- a/src/styles/global.ts +++ b/src/styles/global.ts @@ -15,11 +15,21 @@ const GlobalStyles = createGlobalStyle` html { font-size: 10px; + transition: background-color .45s linear !important; + } + + ul.no-animate li { + -webkit-transition: none !important; + -moz-transition: none !important; + -o-transition: none !important; + transition: none !important; + transform: none !important; } ul, li { list-style: none; + transition: 0 all linear; } a { diff --git a/src/styles/themes/default.ts b/src/styles/themes/default.ts index 6a2bebf1..6fdf308f 100644 --- a/src/styles/themes/default.ts +++ b/src/styles/themes/default.ts @@ -1,4 +1,6 @@ const defaultTheme = { + NAME: 'dark', + grid: { container: '104rem', }, diff --git a/src/styles/themes/index.ts b/src/styles/themes/index.ts index 6a65b733..ccdcf6b5 100644 --- a/src/styles/themes/index.ts +++ b/src/styles/themes/index.ts @@ -1 +1,16 @@ -export { defaultTheme as theme } from './default'; +import { DefaultTheme } from 'styled-components'; +import { defaultTheme } from './default'; +import { lightTheme } from './light'; + +const themes: ThemeObject = { + dark: defaultTheme, + light: lightTheme +} + +type ThemeObject = { + [key: string]: DefaultTheme; + dark: DefaultTheme, + light: DefaultTheme +} + +export { themes } \ No newline at end of file diff --git a/src/styles/themes/light.ts b/src/styles/themes/light.ts new file mode 100644 index 00000000..5d45a2f1 --- /dev/null +++ b/src/styles/themes/light.ts @@ -0,0 +1,50 @@ +const lightTheme = { + NAME: 'light', + + grid: { + container: '104rem', + }, + + border: { + width: '1px', + radius: '6px', + }, + + font: { + family: + "-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'", + + weights: { + normal: 400, + bold: 600, + }, + + sizes: { + xsmall: '1.2rem', + small: '1.4rem', + medium: '1.6rem', + large: '2.0rem', + xlarge: '2.6rem', + }, + }, + + colors: { + primary: '#1f75d7', + secondary: '#3dd264', + tertiary: '#f78166', + border: '#30363d', + text: '#0d1117', // #0d1117 #c9d1d9 + bg: '#eee', + error: '#f85149', + }, + + spacings: { + xsmall: '0.8rem', + small: '1.2rem', + medium: '1.6rem', + large: '2.0rem', + xlarge: '2.4rem', + }, +} + +export { lightTheme }; \ No newline at end of file diff --git a/src/types/events.ts b/src/types/events.ts index 34975d98..34ca1b72 100644 --- a/src/types/events.ts +++ b/src/types/events.ts @@ -6,6 +6,7 @@ export enum Events { CANVAS_REORDER_SECTIONS = 'canvas.section.reorder', CANVAS_DUPLICATE_SECTION = 'canvas.section.duplicate', CANVAS_CLEAR_SECTIONS = 'canvas.clear', + APP_SET_THEME = 'app.set.theme', TEMPLATE_USE = 'template.use', TEMPLATE_PREVIEW = 'template.preview', diff --git a/src/types/styled-components.d.ts b/src/types/styled-components.d.ts index 6995809a..1b0ff262 100644 --- a/src/types/styled-components.d.ts +++ b/src/types/styled-components.d.ts @@ -5,5 +5,44 @@ type Theme = typeof theme; declare module 'styled-components' { // eslint-disable-next-line @typescript-eslint/no-empty-interface - export interface DefaultTheme extends Theme {} -} + export interface DefaultTheme extends Theme { + NAME: string, + grid: { + container: string + }, + border: { + width: string, + radius: string + }, + font: { + family: string, + weights: { + normal: number, + bold: number + }, + sizes: { + xsmall: string, + small: string, + medium: string, + large: string, + xlarge: string + } + }, + colors: { + primary: string, + secondary: string, + tertiary: string, + border: string, + text: string, + bg: string, + error: string + }, + spacings: { + xsmall: string, + small: string, + medium: string, + large: string, + xlarge: string + } + } +}; \ No newline at end of file