mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-25 01:14:23 +01:00
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';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
color: var(--color-text);
|
||||
.collection-options {
|
||||
color: ${(props) => props.theme.text};
|
||||
.rows {
|
||||
svg {
|
||||
position: relative;
|
||||
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 Collections from './Collections';
|
||||
import StyledWrapper, { BottomWrapper, VersionNumber } from './StyledWrapper';
|
||||
import GitHubButton from 'react-github-btn'
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import GitHubButton from 'react-github-btn';
|
||||
import Preferences from 'components/Preferences';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { IconChevronsRight } from '@tabler/icons';
|
||||
import { updateLeftSidebarWidth, updateIsDragging, toggleLeftMenuBar } from 'providers/ReduxStore/slices/app';
|
||||
import { IconSettings } from '@tabler/icons';
|
||||
import { updateLeftSidebarWidth, updateIsDragging } from 'providers/ReduxStore/slices/app';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
|
||||
const MIN_LEFT_SIDEBAR_WIDTH = 222;
|
||||
@ -15,7 +15,7 @@ const MAX_LEFT_SIDEBAR_WIDTH = 600;
|
||||
|
||||
const Sidebar = () => {
|
||||
const leftSidebarWidth = useSelector((state) => state.app.leftSidebarWidth);
|
||||
const leftMenuBarOpen = useSelector((state) => state.app.leftMenuBarOpen);
|
||||
const [ preferencesOpen, setPreferencesOpen ] = useState(false);
|
||||
|
||||
const [asideWidth, setAsideWidth] = useState(leftSidebarWidth);
|
||||
|
||||
@ -76,16 +76,14 @@ const Sidebar = () => {
|
||||
setAsideWidth(leftSidebarWidth);
|
||||
}, [leftSidebarWidth]);
|
||||
|
||||
const leftMenuBarWidth = leftMenuBarOpen ? 48 : 0;
|
||||
const collectionsWidth = asideWidth - leftMenuBarWidth;
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex relative">
|
||||
<aside>
|
||||
<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">
|
||||
<TitleBar />
|
||||
<Collections />
|
||||
@ -93,8 +91,7 @@ const Sidebar = () => {
|
||||
|
||||
<div className="footer flex px-1 py-2 items-center cursor-pointer select-none">
|
||||
<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())} />}
|
||||
{/* <IconLayoutGrid size={20} strokeWidth={1.5} className="mr-2"/> */}
|
||||
<IconSettings size={18} strokeWidth={1.5} className="mr-2 hover:text-gray-700" onClick={() => setPreferencesOpen(true)} />
|
||||
</div>
|
||||
<div className="pl-1" style={{position: 'relative', top: '3px'}}>
|
||||
{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,
|
||||
idbConnectionReady: false,
|
||||
leftSidebarWidth: 222,
|
||||
leftMenuBarOpen: false,
|
||||
screenWidth: 500,
|
||||
showHomePage: false
|
||||
};
|
||||
@ -16,14 +15,6 @@ export const appSlice = createSlice({
|
||||
idbConnectionReady: (state) => {
|
||||
state.idbConnectionReady = true;
|
||||
},
|
||||
toggleLeftMenuBar: (state) => {
|
||||
state.leftMenuBarOpen = !state.leftMenuBarOpen;
|
||||
if(state.leftMenuBarOpen) {
|
||||
state.leftSidebarWidth += 48;
|
||||
} else {
|
||||
state.leftSidebarWidth -= 48;
|
||||
}
|
||||
},
|
||||
refreshScreenWidth: (state) => {
|
||||
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;
|
||||
|
Loading…
Reference in New Issue
Block a user