Features/dark mode (#37)

* Added use local storage hook
* Added theme
* Added theme support
* Added theme provider
* Added dark theme for sidebar
* Added dark theme for main content area
* Added theme
* Added theme support
* Added theme provider
* Added dark theme for sidebar
* Added dark theme for main content area
This commit is contained in:
Sean 2022-10-21 03:14:09 +08:00 committed by GitHub
parent 3c3c9a6026
commit cbdfabb4db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 248 additions and 50 deletions

View File

@ -0,0 +1,30 @@
const darkTheme = {
brand: '#546de5',
text: 'rgb(52 52 52)',
'primary-text': '#ffffff',
'primary-theme': '#1e1e1e',
'secondary-text': '#929292',
'sidebar-collection-item-active-indent-border': '#d0d0d0',
'sidebar-collection-item-active-background': '#e1e1e1',
'sidebar-background': '#252526',
'sidebar-bottom-bg': '#68217a',
'request-dragbar-background': '#efefef',
'request-dragbar-background-active': 'rgb(200, 200, 200)',
'tab-inactive': 'rgb(155 155 155)',
'tab-active-border': '#546de5',
'layout-border': '#dedede',
'codemirror-border': '#efefef',
'codemirror-background': 'rgb(243, 243, 243)',
'text-link': '#1663bb',
'text-danger': 'rgb(185, 28, 28)',
'background-danger': '#dc3545',
'method-get': 'rgb(5, 150, 105)',
'method-post': '#8e44ad',
'method-delete': 'rgb(185, 28, 28)',
'method-patch': 'rgb(52 52 52)',
'method-options': 'rgb(52 52 52)',
'method-head': 'rgb(52 52 52)',
'table-stripe': '#f3f3f3'
};
export default darkTheme;

View File

@ -0,0 +1,7 @@
import darkTheme from './dark';
import lightTheme from './light';
export default {
Light: lightTheme,
Dark: darkTheme
};

View File

@ -0,0 +1,30 @@
const lightTheme = {
brand: '#546de5',
text: 'rgb(52 52 52)',
'primary-text': 'rgb(52 52 52)',
'primary-theme': '#ffffff',
'secondary-text': '#929292',
'sidebar-collection-item-active-indent-border': '#d0d0d0',
'sidebar-collection-item-active-background': '#e1e1e1',
'sidebar-background': '#f3f3f3',
'sidebar-bottom-bg': '#f3f3f3',
'request-dragbar-background': '#efefef',
'request-dragbar-background-active': 'rgb(200, 200, 200)',
'tab-inactive': 'rgb(155 155 155)',
'tab-active-border': '#546de5',
'layout-border': '#dedede',
'codemirror-border': '#efefef',
'codemirror-background': 'rgb(243, 243, 243)',
'text-link': '#1663bb',
'text-danger': 'rgb(185, 28, 28)',
'background-danger': '#dc3545',
'method-get': 'rgb(5, 150, 105)',
'method-post': '#8e44ad',
'method-delete': 'rgb(185, 28, 28)',
'method-patch': 'rgb(52 52 52)',
'method-options': 'rgb(52 52 52)',
'method-head': 'rgb(52 52 52)',
'table-stripe': '#f3f3f3'
};
export default lightTheme;

View File

@ -1,12 +1,20 @@
import React, { useState } from 'react'; import { useState } from 'react';
import toast from 'react-hot-toast'; import { useTheme } from '../../../../providers/Theme';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import CreateCollection from 'components/Sidebar/CreateCollection';
import SelectCollection from 'components/Sidebar/Collections/SelectCollection';
import { createCollection } from 'providers/ReduxStore/slices/collections/actions'; import { createCollection } from 'providers/ReduxStore/slices/collections/actions';
import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions'; import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import toast from 'react-hot-toast';
import styled from 'styled-components';
import CreateCollection from 'components/Sidebar/CreateCollection';
import SelectCollection from 'components/Sidebar/Collections/SelectCollection';
const LinkStyle = styled.span`
color: ${(props) => props.theme['text-link']};
`;
const CreateOrAddCollection = () => { const CreateOrAddCollection = () => {
const { theme } = useTheme();
const dispatch = useDispatch(); const dispatch = useDispatch();
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false); const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
const [addCollectionToWSModalOpen, setAddCollectionToWSModalOpen] = useState(false); const [addCollectionToWSModalOpen, setAddCollectionToWSModalOpen] = useState(false);
@ -31,14 +39,14 @@ const CreateOrAddCollection = () => {
}; };
const CreateLink = () => ( const CreateLink = () => (
<span className="underline text-link cursor-pointer" onClick={() => setCreateCollectionModalOpen(true)}> <LinkStyle className="underline text-link cursor-pointer" theme={theme} onClick={() => setCreateCollectionModalOpen(true)}>
Create Create
</span> </LinkStyle>
); );
const AddLink = () => ( const AddLink = () => (
<span className="underline text-link cursor-pointer" onClick={() => setAddCollectionToWSModalOpen(true)}> <LinkStyle className="underline text-link cursor-pointer" theme={theme} onClick={() => setAddCollectionToWSModalOpen(true)}>
Add Add
</span> </LinkStyle>
); );
return ( return (

View File

@ -1,16 +1,19 @@
import React, { useState } from 'react'; import { useState } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { IconCode, IconFiles, IconUser, IconUsers, IconSettings, IconChevronsLeft, IconLifebuoy } from '@tabler/icons';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { toggleLeftMenuBar } from 'providers/ReduxStore/slices/app';
import BrunoSupport from 'components/BrunoSupport';
import { isElectron } from 'utils/common/platform'; import { isElectron } from 'utils/common/platform';
import { toggleLeftMenuBar } from 'providers/ReduxStore/slices/app';
import { IconCode, IconFiles, IconMoon, IconChevronsLeft, IconLifebuoy } from '@tabler/icons';
import Link from 'next/link';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import BrunoSupport from 'components/BrunoSupport';
import ThemeSupport from 'components/ThemeSupport/index';
const MenuBar = () => { const MenuBar = () => {
const router = useRouter(); const router = useRouter();
const dispatch = useDispatch(); const dispatch = useDispatch();
const [openTheme, setOpenTheme] = useState(false);
const [openBrunoSupport, setOpenBrunoSupport] = useState(false); const [openBrunoSupport, setOpenBrunoSupport] = useState(false);
const isPlatformElectron = isElectron(); const isPlatformElectron = isElectron();
@ -46,11 +49,15 @@ const MenuBar = () => {
<div className="menu-item"> <div className="menu-item">
<IconLifebuoy size={28} strokeWidth={1.5} onClick={() => setOpenBrunoSupport(true)} /> <IconLifebuoy size={28} strokeWidth={1.5} onClick={() => setOpenBrunoSupport(true)} />
</div> </div>
<div className="menu-item">
<IconMoon size={28} strokeWidth={1.5} onClick={() => setOpenTheme(true)} />
</div>
<div className="menu-item" onClick={() => dispatch(toggleLeftMenuBar())}> <div className="menu-item" onClick={() => dispatch(toggleLeftMenuBar())}>
<IconChevronsLeft size={28} strokeWidth={1.5} /> <IconChevronsLeft size={28} strokeWidth={1.5} />
</div> </div>
</div> </div>
{openBrunoSupport && <BrunoSupport onClose={() => setOpenBrunoSupport(false)} />} {openBrunoSupport && <BrunoSupport onClose={() => setOpenBrunoSupport(false)} />}
{openTheme && <ThemeSupport onClose={() => setOpenTheme(false)} />}
</StyledWrapper> </StyledWrapper>
); );
}; };

View File

@ -2,7 +2,7 @@ import styled from 'styled-components';
const Wrapper = styled.div` const Wrapper = styled.div`
aside { aside {
background-color: var(--color-sidebar-background); background-color: ${(props) => props.theme.theme['sidebar-background']};
.collection-title { .collection-title {
line-height: 1.5; line-height: 1.5;
@ -45,4 +45,12 @@ const Wrapper = styled.div`
} }
`; `;
export const BottomWrapper = styled.div`
background-color: ${(props) => props.theme.theme['sidebar-bottom-bg']};
`;
export const VersionNumber = styled.div`
color: ${(props) => props.theme.theme['primary-text']};
`;
export default Wrapper; export default Wrapper;

View File

@ -25,4 +25,8 @@ const StyledWrapper = styled.div`
} }
`; `;
export const SiteTitle = styled.div`
color: ${(props) => props.theme.theme['primary-text']};
`;
export default StyledWrapper; export default StyledWrapper;

View File

@ -1,19 +1,20 @@
import React, { useState, forwardRef, useRef } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import Dropdown from 'components/Dropdown';
import Bruno from 'components/Bruno'; import Bruno from 'components/Bruno';
import Dropdown from 'components/Dropdown';
import CreateCollection from '../CreateCollection';
import importCollection from 'utils/collections/import';
import SelectCollection from 'components/Sidebar/Collections/SelectCollection';
import { IconDots } from '@tabler/icons';
import { IconFolders } from '@tabler/icons'; import { IconFolders } from '@tabler/icons';
import { isElectron } from 'utils/common/platform';
import { useState, forwardRef, useRef } from 'react';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import StyledWrapper, { SiteTitle } from './StyledWrapper';
import { showHomePage } from 'providers/ReduxStore/slices/app';
import { collectionImported } from 'providers/ReduxStore/slices/collections'; import { collectionImported } from 'providers/ReduxStore/slices/collections';
import { openLocalCollection } from 'providers/ReduxStore/slices/collections/actions'; import { openLocalCollection } from 'providers/ReduxStore/slices/collections/actions';
import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions'; import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import { showHomePage } from 'providers/ReduxStore/slices/app';
import { IconDots } from '@tabler/icons';
import CreateCollection from '../CreateCollection';
import SelectCollection from 'components/Sidebar/Collections/SelectCollection';
import importCollection from 'utils/collections/import';
import { isElectron } from 'utils/common/platform';
import StyledWrapper from './StyledWrapper';
const TitleBar = () => { const TitleBar = () => {
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false); const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
@ -68,13 +69,13 @@ const TitleBar = () => {
<div className="flex items-center cursor-pointer" onClick={handleTitleClick}> <div className="flex items-center cursor-pointer" onClick={handleTitleClick}>
<Bruno width={30} /> <Bruno width={30} />
</div> </div>
<div <SiteTitle
onClick={handleTitleClick} onClick={handleTitleClick}
className=" flex items-center font-medium select-none cursor-pointer" className=" flex items-center font-medium select-none cursor-pointer"
style={{ fontSize: 14, paddingLeft: 6, position: 'relative', top: -1 }} style={{ fontSize: 14, paddingLeft: 6, position: 'relative', top: -1 }}
> >
bruno bruno
</div> </SiteTitle>
<div className="collection-dropdown flex flex-grow items-center justify-end"> <div className="collection-dropdown flex flex-grow items-center justify-end">
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start"> <Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
<div <div

View File

@ -1,13 +1,14 @@
import React, { useState, useEffect } from 'react'; import MenuBar from './MenuBar';
import { useSelector, useDispatch } from 'react-redux'; import TitleBar from './TitleBar';
import Collections from './Collections'; import Collections from './Collections';
import LocalCollections from './LocalCollections'; import LocalCollections from './LocalCollections';
import TitleBar from './TitleBar'; import StyledWrapper, { BottomWrapper, VersionNumber } from './StyledWrapper';
import MenuBar from './MenuBar'; import WorkspaceSelector from 'components/Workspaces/WorkspaceSelector';
import { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { IconSearch, IconChevronsRight } from '@tabler/icons'; import { IconSearch, IconChevronsRight } from '@tabler/icons';
import { updateLeftSidebarWidth, updateIsDragging, toggleLeftMenuBar } from 'providers/ReduxStore/slices/app'; import { updateLeftSidebarWidth, updateIsDragging, toggleLeftMenuBar } from 'providers/ReduxStore/slices/app';
import StyledWrapper from './StyledWrapper';
import WorkspaceSelector from 'components/Workspaces/WorkspaceSelector';
const MIN_LEFT_SIDEBAR_WIDTH = 222; const MIN_LEFT_SIDEBAR_WIDTH = 222;
const MAX_LEFT_SIDEBAR_WIDTH = 600; const MAX_LEFT_SIDEBAR_WIDTH = 600;
@ -106,7 +107,8 @@ const Sidebar = () => {
<Collections searchText={searchText} /> <Collections searchText={searchText} />
<LocalCollections searchText={searchText} /> <LocalCollections searchText={searchText} />
</div> </div>
<div className="flex px-1 py-2 items-center cursor-pointer text-gray-500 select-none">
<BottomWrapper className="flex px-1 py-2 items-center cursor-pointer text-gray-500 select-none">
<div className="flex items-center ml-1 text-xs "> <div className="flex items-center ml-1 text-xs ">
{!leftMenuBarOpen && <IconChevronsRight size={24} strokeWidth={1.5} className="mr-2 hover:text-gray-700" onClick={() => dispatch(toggleLeftMenuBar())} />} {!leftMenuBarOpen && <IconChevronsRight size={24} strokeWidth={1.5} className="mr-2 hover:text-gray-700" onClick={() => dispatch(toggleLeftMenuBar())} />}
{/* <IconLayoutGrid size={20} strokeWidth={1.5} className="mr-2"/> */} {/* <IconLayoutGrid size={20} strokeWidth={1.5} className="mr-2"/> */}
@ -122,8 +124,8 @@ const Sidebar = () => {
title="GitHub" title="GitHub"
></iframe> ></iframe>
</div> </div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.25.2</div> <VersionNumber className="flex flex-grow items-center justify-end text-xs mr-2">v1.25.2</VersionNumber>
</div> </BottomWrapper>
</div> </div>
</div> </div>
</aside> </aside>

View File

@ -0,0 +1,20 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
color: var(--color-text);
.collection-options {
svg {
position: relative;
top: -1px;
}
.label {
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
}
`;
export default StyledWrapper;

View File

@ -0,0 +1,32 @@
import React from 'react';
import Modal from 'components/Modal/index';
import StyledWrapper from './StyledWrapper';
import { useTheme } from 'styled-components';
const ThemeSupport = ({ onClose }) => {
const { storedTheme, themeKeys, setStoredTheme } = useTheme();
const handleThemeChange = (e) => {
setStoredTheme(e.target.value);
};
return (
<StyledWrapper>
<Modal size="sm" title={'Support'} handleCancel={onClose} hideFooter={true}>
<div className="collection-options">
<select name="theme_switcher" onChange={handleThemeChange} defaultValue={storedTheme}>
{themeKeys.map((tk, index) => {
return (
<option value={tk} key={index}>
{tk}
</option>
);
})}
</select>
</div>
</Modal>
</StyledWrapper>
);
};
export default ThemeSupport;

View File

@ -1,6 +1,8 @@
import styled from 'styled-components'; import styled from 'styled-components';
const StyledWrapper = styled.div` const StyledWrapper = styled.div`
color: ${(props) => props.theme.theme['primary-text']};
.create-request { .create-request {
color: #737373; color: #737373;
font-size: 0.75rem; font-size: 0.75rem;
@ -21,4 +23,8 @@ const StyledWrapper = styled.div`
} }
`; `;
export const SiteTitle = styled.div`
color: ${(props) => props.theme.theme['primary-text']};
`;
export default StyledWrapper; export default StyledWrapper;

View File

@ -1,17 +1,18 @@
import React, { useState } from 'react'; import { useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { IconPlus, IconUpload, IconFiles, IconFolders, IconPlayerPlay, IconBrandChrome, IconSpeakerphone, IconDeviceDesktop } from '@tabler/icons'; import { isElectron } from 'utils/common/platform';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { collectionImported } from 'providers/ReduxStore/slices/collections'; import { collectionImported } from 'providers/ReduxStore/slices/collections';
import { openLocalCollection } from 'providers/ReduxStore/slices/collections/actions'; import { openLocalCollection } from 'providers/ReduxStore/slices/collections/actions';
import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions'; import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import { IconPlus, IconUpload, IconFiles, IconFolders, IconPlayerPlay, IconBrandChrome, IconSpeakerphone, IconDeviceDesktop } from '@tabler/icons';
import Bruno from 'components/Bruno'; import Bruno from 'components/Bruno';
import GithubSvg from 'assets/github.svg';
import StyledWrapper, { SiteTitle } from './StyledWrapper';
import CreateCollection from 'components/Sidebar/CreateCollection'; import CreateCollection from 'components/Sidebar/CreateCollection';
import SelectCollection from 'components/Sidebar/Collections/SelectCollection'; import SelectCollection from 'components/Sidebar/Collections/SelectCollection';
import importCollection, { importSampleCollection } from 'utils/collections/import'; import importCollection, { importSampleCollection } from 'utils/collections/import';
import { isElectron } from 'utils/common/platform';
import GithubSvg from 'assets/github.svg';
import StyledWrapper from './StyledWrapper';
const Welcome = () => { const Welcome = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -66,7 +67,7 @@ const Welcome = () => {
<div className=""> <div className="">
<Bruno width={50} /> <Bruno width={50} />
</div> </div>
<div className="text-xl font-semibold select-none">bruno</div> <SiteTitle className="text-xl font-semibold select-none">bruno</SiteTitle>
<div className="mt-4">Opensource API Client.</div> <div className="mt-4">Opensource API Client.</div>
<div className="uppercase font-semibold create-request mt-10">Collections</div> <div className="uppercase font-semibold create-request mt-10">Collections</div>

View File

@ -6,6 +6,8 @@ const Wrapper = styled.div`
height: 100%; height: 100%;
min-height: 100vh; min-height: 100vh;
background-color: ${(props) => props.theme.theme['primary-theme']};
&.is-dragging { &.is-dragging {
cursor: col-resize !important; cursor: col-resize !important;
} }

View File

@ -1,18 +1,19 @@
import { HotkeysProvider } from 'providers/Hotkeys';
import { AuthProvider } from 'providers/Auth';
import { AppProvider } from 'providers/App';
import ReduxStore from 'providers/ReduxStore';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { Toaster } from 'react-hot-toast'; import { Toaster } from 'react-hot-toast';
import { AppProvider } from 'providers/App';
import { AuthProvider } from 'providers/Auth';
import { HotkeysProvider } from 'providers/Hotkeys';
import ReduxStore from 'providers/ReduxStore';
import ThemeProvider from 'providers/Theme/index';
import '../styles/app.scss';
import '../styles/globals.css'; import '../styles/globals.css';
import 'tailwindcss/dist/tailwind.min.css'; import 'tailwindcss/dist/tailwind.min.css';
import 'react-tabs/style/react-tabs.css'; import 'react-tabs/style/react-tabs.css';
import 'codemirror/lib/codemirror.css'; import 'codemirror/lib/codemirror.css';
import 'graphiql/graphiql.min.css'; import 'graphiql/graphiql.min.css';
import '../styles/app.scss';
function SafeHydrate({ children }) { function SafeHydrate({ children }) {
return <div suppressHydrationWarning>{typeof window === 'undefined' ? null : children}</div>; return <div suppressHydrationWarning>{typeof window === 'undefined' ? null : children}</div>;
} }
@ -33,10 +34,12 @@ function MyApp({ Component, pageProps }) {
<NoSsr> <NoSsr>
<Provider store={ReduxStore}> <Provider store={ReduxStore}>
<AppProvider> <AppProvider>
<HotkeysProvider> <ThemeProvider>
<Toaster toastOptions={{ duration: 2000 }} /> <HotkeysProvider>
<Component {...pageProps} /> <Toaster toastOptions={{ duration: 2000 }} />
</HotkeysProvider> <Component {...pageProps} />
</HotkeysProvider>
</ThemeProvider>
</AppProvider> </AppProvider>
</Provider> </Provider>
</NoSsr> </NoSsr>

View File

@ -0,0 +1,37 @@
import ThemeManager from '../../../public/theme/index';
import useLocalStorage from 'src/hooks/useLocalStorage/index';
import { createContext, useContext } from 'react';
import { ThemeProvider as SCThemeProvider } from 'styled-components';
export const ThemeContext = createContext();
export const ThemeProvider = (props) => {
const [storedTheme, setStoredTheme] = useLocalStorage('bruno.theme', 'Light');
const theme = ThemeManager[storedTheme];
const themeKeys = Object.keys(ThemeManager);
const value = {
theme,
themeKeys,
storedTheme,
setStoredTheme
};
return (
<ThemeContext.Provider value={value}>
<SCThemeProvider theme={value} {...props} />
</ThemeContext.Provider>
);
};
export const useTheme = () => {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error(`useTheme must be used within a ThemeProvider`);
}
return context;
};
export default ThemeProvider;