forked from extern/bruno
feat: Preferences (Theme and Support)
This commit is contained in:
parent
bf5ee7e409
commit
417b50b0ad
@ -0,0 +1,36 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
div.tabs {
|
||||||
|
margin-top: -0.5rem;
|
||||||
|
|
||||||
|
div.tab {
|
||||||
|
padding: 6px 0px;
|
||||||
|
border: none;
|
||||||
|
border-bottom: solid 2px transparent;
|
||||||
|
margin-right: 1.25rem;
|
||||||
|
color: var(--color-tab-inactive);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:active,
|
||||||
|
&:focus-within,
|
||||||
|
&:focus-visible,
|
||||||
|
&:target {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: ${(props) => props.theme.tabs.active.color} !important;
|
||||||
|
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section.tab-panel {
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -1,8 +1,8 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
color: var(--color-text);
|
color: ${(props) => props.theme.text};
|
||||||
.collection-options {
|
.rows {
|
||||||
svg {
|
svg {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -1px;
|
top: -1px;
|
@ -0,0 +1,44 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { IconSpeakerphone, IconBrandTwitter, IconBrandGithub, IconBrandDiscord, IconBook } from '@tabler/icons';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const Support = () => {
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
<div className="rows">
|
||||||
|
<div>
|
||||||
|
<a href="https://docs.usebruno.com" target="_blank" className="flex items-end">
|
||||||
|
<IconBook size={18} strokeWidth={2} />
|
||||||
|
<span className="label ml-2">Documentation</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2">
|
||||||
|
<a href="https://github.com/usebruno/bruno/issues" target="_blank" className="flex items-end">
|
||||||
|
<IconSpeakerphone size={18} strokeWidth={2} />
|
||||||
|
<span className="label ml-2">Report Issues</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2">
|
||||||
|
<a href="https://discord.com/invite/KgcZUncpjq" target="_blank" className="flex items-end">
|
||||||
|
<IconBrandDiscord size={18} strokeWidth={2} />
|
||||||
|
<span className="label ml-2">Discord</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2">
|
||||||
|
<a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-end">
|
||||||
|
<IconBrandGithub size={18} strokeWidth={2} />
|
||||||
|
<span className="label ml-2">Github</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2">
|
||||||
|
<a href="https://twitter.com/use_bruno" target="_blank" className="flex items-end">
|
||||||
|
<IconBrandTwitter size={18} strokeWidth={2} />
|
||||||
|
<span className="label ml-2">Twitter</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Support;
|
@ -0,0 +1,7 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
color: var(--color-text);
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
64
packages/bruno-app/src/components/Preferences/Theme/index.js
Normal file
64
packages/bruno-app/src/components/Preferences/Theme/index.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
|
||||||
|
const Theme = () => {
|
||||||
|
const { storedTheme, setStoredTheme } = useTheme();
|
||||||
|
|
||||||
|
const formik = useFormik({
|
||||||
|
enableReinitialize: true,
|
||||||
|
initialValues: {
|
||||||
|
theme: storedTheme
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
theme: Yup.string().oneOf(['light', 'dark']).required('theme is required')
|
||||||
|
}),
|
||||||
|
onSubmit: (values) => {
|
||||||
|
setStoredTheme(values.theme);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
<div className='bruno-form'>
|
||||||
|
<div className="flex items-center mt-2">
|
||||||
|
<input
|
||||||
|
id="light-theme"
|
||||||
|
className="cursor-pointer"
|
||||||
|
type="radio"
|
||||||
|
name="theme"
|
||||||
|
onChange={(e) => {
|
||||||
|
formik.handleChange(e);
|
||||||
|
formik.handleSubmit()
|
||||||
|
}}
|
||||||
|
value="light"
|
||||||
|
checked={formik.values.theme === 'light'}
|
||||||
|
/>
|
||||||
|
<label htmlFor="light-theme" className="ml-1 cursor-pointer select-none">
|
||||||
|
Light
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
id="dark-theme"
|
||||||
|
className="ml-4 cursor-pointer"
|
||||||
|
type="radio"
|
||||||
|
name="theme"
|
||||||
|
onChange={(e) => {
|
||||||
|
formik.handleChange(e);
|
||||||
|
formik.handleSubmit()
|
||||||
|
}}
|
||||||
|
value="dark"
|
||||||
|
checked={formik.values.theme === 'dark'}
|
||||||
|
/>
|
||||||
|
<label htmlFor="dark-theme" className="ml-1 cursor-pointer select-none">
|
||||||
|
Dark
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Theme;
|
53
packages/bruno-app/src/components/Preferences/index.js
Normal file
53
packages/bruno-app/src/components/Preferences/index.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import Modal from 'components/Modal/index';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import Support from './Support';
|
||||||
|
import Theme from './Theme';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const Preferences = ({ onClose }) => {
|
||||||
|
const [tab, setTab] = useState('general');
|
||||||
|
|
||||||
|
const getTabClassname = (tabName) => {
|
||||||
|
return classnames(`tab select-none ${tabName}`, {
|
||||||
|
active: tabName === tab
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTabPanel = (tab) => {
|
||||||
|
switch (tab) {
|
||||||
|
case 'general': {
|
||||||
|
return <div>General</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'theme': {
|
||||||
|
return <Theme />;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'support': {
|
||||||
|
return <Support />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
<Modal size="lg" title="Preferences" handleCancel={onClose} hideFooter={true}>
|
||||||
|
<div className="flex items-center px-2 tabs" role="tablist">
|
||||||
|
<div className={getTabClassname('general')} role="tab" onClick={() => setTab('general')}>
|
||||||
|
General
|
||||||
|
</div>
|
||||||
|
<div className={getTabClassname('theme')} role="tab" onClick={() => setTab('theme')}>
|
||||||
|
Theme
|
||||||
|
</div>
|
||||||
|
<div className={getTabClassname('support')} role="tab" onClick={() => setTab('support')}>
|
||||||
|
Support
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<section className="flex flex-grow px-2 mt-4 tab-panel">{getTabPanel(tab)}</section>
|
||||||
|
</Modal>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Preferences;
|
@ -1,19 +0,0 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
|
||||||
background-color: ${(props) => props.theme.menubar.bg};
|
|
||||||
color: rgba(255, 255, 255, 0.4);
|
|
||||||
min-height: 100vh;
|
|
||||||
|
|
||||||
.menu-item {
|
|
||||||
padding: 0.6rem;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&.active {
|
|
||||||
color: rgba(255, 255, 255);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default Wrapper;
|
|
@ -1,60 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
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 BrunoSupport from 'components/BrunoSupport';
|
|
||||||
import SwitchTheme from 'components/SwitchTheme';
|
|
||||||
|
|
||||||
const MenuBar = () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const [openTheme, setOpenTheme] = useState(false);
|
|
||||||
const [openBrunoSupport, setOpenBrunoSupport] = useState(false);
|
|
||||||
const isPlatformElectron = isElectron();
|
|
||||||
|
|
||||||
const getClassName = (menu) => {
|
|
||||||
return router.pathname === menu ? 'active menu-item' : 'menu-item';
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledWrapper className="h-full flex flex-col">
|
|
||||||
{openBrunoSupport && <BrunoSupport onClose={() => setOpenBrunoSupport(false)} />}
|
|
||||||
{openTheme && <SwitchTheme onClose={() => setOpenTheme(false)} />}
|
|
||||||
|
|
||||||
<div className="flex flex-col">
|
|
||||||
{/* Todo: Fix this: Clicking on this crashes the app */}
|
|
||||||
{/* <Link href="/">
|
|
||||||
<div className={getClassName('/')}>
|
|
||||||
<IconCode size={28} strokeWidth={1.5} />
|
|
||||||
</div>
|
|
||||||
</Link> */}
|
|
||||||
{/* <div className="menu-item">
|
|
||||||
<IconUsers size={28} strokeWidth={1.5}/>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col flex-grow justify-end">
|
|
||||||
{/* <Link href="/login">
|
|
||||||
<div className="menu-item">
|
|
||||||
<IconUser size={28} strokeWidth={1.5}/>
|
|
||||||
</div>
|
|
||||||
</Link> */}
|
|
||||||
<div className="menu-item" onClick={() => setOpenBrunoSupport(true)}>
|
|
||||||
<IconLifebuoy size={28} strokeWidth={1.5}/>
|
|
||||||
</div>
|
|
||||||
<div className="menu-item" onClick={() => setOpenTheme(true)}>
|
|
||||||
<IconMoon size={28} strokeWidth={1.5}/>
|
|
||||||
</div>
|
|
||||||
<div className="menu-item" onClick={() => dispatch(toggleLeftMenuBar())}>
|
|
||||||
<IconChevronsLeft size={28} strokeWidth={1.5} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</StyledWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MenuBar;
|
|
@ -1,13 +1,13 @@
|
|||||||
import MenuBar from './MenuBar';
|
|
||||||
import TitleBar from './TitleBar';
|
import TitleBar from './TitleBar';
|
||||||
import Collections from './Collections';
|
import Collections from './Collections';
|
||||||
import StyledWrapper, { BottomWrapper, VersionNumber } from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import GitHubButton from 'react-github-btn'
|
import GitHubButton from 'react-github-btn';
|
||||||
|
import Preferences from 'components/Preferences';
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { IconChevronsRight } from '@tabler/icons';
|
import { IconSettings } from '@tabler/icons';
|
||||||
import { updateLeftSidebarWidth, updateIsDragging, toggleLeftMenuBar } from 'providers/ReduxStore/slices/app';
|
import { updateLeftSidebarWidth, updateIsDragging } from 'providers/ReduxStore/slices/app';
|
||||||
import { useTheme } from 'providers/Theme';
|
import { useTheme } from 'providers/Theme';
|
||||||
|
|
||||||
const MIN_LEFT_SIDEBAR_WIDTH = 222;
|
const MIN_LEFT_SIDEBAR_WIDTH = 222;
|
||||||
@ -15,7 +15,7 @@ const MAX_LEFT_SIDEBAR_WIDTH = 600;
|
|||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
const leftSidebarWidth = useSelector((state) => state.app.leftSidebarWidth);
|
const leftSidebarWidth = useSelector((state) => state.app.leftSidebarWidth);
|
||||||
const leftMenuBarOpen = useSelector((state) => state.app.leftMenuBarOpen);
|
const [ preferencesOpen, setPreferencesOpen ] = useState(false);
|
||||||
|
|
||||||
const [asideWidth, setAsideWidth] = useState(leftSidebarWidth);
|
const [asideWidth, setAsideWidth] = useState(leftSidebarWidth);
|
||||||
|
|
||||||
@ -76,16 +76,14 @@ const Sidebar = () => {
|
|||||||
setAsideWidth(leftSidebarWidth);
|
setAsideWidth(leftSidebarWidth);
|
||||||
}, [leftSidebarWidth]);
|
}, [leftSidebarWidth]);
|
||||||
|
|
||||||
const leftMenuBarWidth = leftMenuBarOpen ? 48 : 0;
|
|
||||||
const collectionsWidth = asideWidth - leftMenuBarWidth;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="flex relative">
|
<StyledWrapper className="flex relative">
|
||||||
<aside>
|
<aside>
|
||||||
<div className="flex flex-row h-full w-full">
|
<div className="flex flex-row h-full w-full">
|
||||||
{leftMenuBarOpen && <MenuBar />}
|
{preferencesOpen && <Preferences onClose={() => setPreferencesOpen(false)} />}
|
||||||
|
|
||||||
<div className="flex flex-col w-full" style={{width: collectionsWidth}}>
|
<div className="flex flex-col w-full" style={{width: asideWidth}}>
|
||||||
<div className="flex flex-col flex-grow">
|
<div className="flex flex-col flex-grow">
|
||||||
<TitleBar />
|
<TitleBar />
|
||||||
<Collections />
|
<Collections />
|
||||||
@ -93,8 +91,7 @@ const Sidebar = () => {
|
|||||||
|
|
||||||
<div className="footer flex px-1 py-2 items-center cursor-pointer select-none">
|
<div className="footer flex px-1 py-2 items-center cursor-pointer 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())} />}
|
<IconSettings size={18} strokeWidth={1.5} className="mr-2 hover:text-gray-700" onClick={() => setPreferencesOpen(true)} />
|
||||||
{/* <IconLayoutGrid size={20} strokeWidth={1.5} className="mr-2"/> */}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="pl-1" style={{position: 'relative', top: '3px'}}>
|
<div className="pl-1" style={{position: 'relative', top: '3px'}}>
|
||||||
{storedTheme === 'dark' ? (
|
{storedTheme === 'dark' ? (
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { useFormik } from 'formik';
|
|
||||||
import * as Yup from 'yup';
|
|
||||||
import Modal from 'components/Modal/index';
|
|
||||||
import StyledWrapper from './StyledWrapper';
|
|
||||||
import { useTheme } from 'providers/Theme';
|
|
||||||
|
|
||||||
const SwitchTheme = ({ onClose }) => {
|
|
||||||
const { storedTheme, setStoredTheme } = useTheme();
|
|
||||||
|
|
||||||
const formik = useFormik({
|
|
||||||
enableReinitialize: true,
|
|
||||||
initialValues: {
|
|
||||||
theme: storedTheme
|
|
||||||
},
|
|
||||||
validationSchema: Yup.object({
|
|
||||||
theme: Yup.string().oneOf(['light', 'dark']).required('theme is required')
|
|
||||||
}),
|
|
||||||
onSubmit: (values) => {
|
|
||||||
setStoredTheme(values.theme);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledWrapper>
|
|
||||||
<Modal size="sm" title={'Switch Theme'} handleCancel={onClose} hideFooter={true}>
|
|
||||||
<div className='bruno-form'>
|
|
||||||
<div className="flex items-center mt-2">
|
|
||||||
<input
|
|
||||||
id="light-theme"
|
|
||||||
className="cursor-pointer"
|
|
||||||
type="radio"
|
|
||||||
name="theme"
|
|
||||||
onChange={(e) => {
|
|
||||||
formik.handleChange(e);
|
|
||||||
formik.handleSubmit()
|
|
||||||
}}
|
|
||||||
value="light"
|
|
||||||
checked={formik.values.theme === 'light'}
|
|
||||||
/>
|
|
||||||
<label htmlFor="light-theme" className="ml-1 cursor-pointer select-none">
|
|
||||||
Light
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<input
|
|
||||||
id="dark-theme"
|
|
||||||
className="ml-4 cursor-pointer"
|
|
||||||
type="radio"
|
|
||||||
name="theme"
|
|
||||||
onChange={(e) => {
|
|
||||||
formik.handleChange(e);
|
|
||||||
formik.handleSubmit()
|
|
||||||
}}
|
|
||||||
value="dark"
|
|
||||||
checked={formik.values.theme === 'dark'}
|
|
||||||
/>
|
|
||||||
<label htmlFor="dark-theme" className="ml-1 cursor-pointer select-none">
|
|
||||||
Dark
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
</StyledWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SwitchTheme;
|
|
@ -4,7 +4,6 @@ const initialState = {
|
|||||||
isDragging: false,
|
isDragging: false,
|
||||||
idbConnectionReady: false,
|
idbConnectionReady: false,
|
||||||
leftSidebarWidth: 222,
|
leftSidebarWidth: 222,
|
||||||
leftMenuBarOpen: false,
|
|
||||||
screenWidth: 500,
|
screenWidth: 500,
|
||||||
showHomePage: false
|
showHomePage: false
|
||||||
};
|
};
|
||||||
@ -16,14 +15,6 @@ export const appSlice = createSlice({
|
|||||||
idbConnectionReady: (state) => {
|
idbConnectionReady: (state) => {
|
||||||
state.idbConnectionReady = true;
|
state.idbConnectionReady = true;
|
||||||
},
|
},
|
||||||
toggleLeftMenuBar: (state) => {
|
|
||||||
state.leftMenuBarOpen = !state.leftMenuBarOpen;
|
|
||||||
if(state.leftMenuBarOpen) {
|
|
||||||
state.leftSidebarWidth += 48;
|
|
||||||
} else {
|
|
||||||
state.leftSidebarWidth -= 48;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
refreshScreenWidth: (state) => {
|
refreshScreenWidth: (state) => {
|
||||||
state.screenWidth = window.innerWidth;
|
state.screenWidth = window.innerWidth;
|
||||||
},
|
},
|
||||||
@ -42,6 +33,6 @@ export const appSlice = createSlice({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { idbConnectionReady, toggleLeftMenuBar, refreshScreenWidth, updateLeftSidebarWidth, updateIsDragging, showHomePage, hideHomePage } = appSlice.actions;
|
export const { idbConnectionReady, refreshScreenWidth, updateLeftSidebarWidth, updateIsDragging, showHomePage, hideHomePage } = appSlice.actions;
|
||||||
|
|
||||||
export default appSlice.reducer;
|
export default appSlice.reducer;
|
||||||
|
Loading…
Reference in New Issue
Block a user