Pull request: add UI theme controls

Updates #5620

Squashed commit of the following:

commit a7095b8ba35c34d333b0bbe772ac02a4ba90fbfd
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Mar 24 17:23:27 2023 +0300

    fix icon

commit ea565f152a1ce4ad8797cf36cacdfd67146a125d
Merge: 5ebb20d2 67c9abe1
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Mar 24 17:20:42 2023 +0300

    Merge branch 'master' into AG-20691

commit 5ebb20d298735982f3c547c81ade4384f4f62196
Merge: 41b0d597 0bc3ef89
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Mar 24 14:37:05 2023 +0300

    Merge branch 'master' into AG-20691

commit 41b0d597a68006397a7561dfffb1fb51068ded2c
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Mar 24 14:20:20 2023 +0300

    fix string

commit 775afb99fb5ffd17118becba4f1e83d4ce77be48
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Mar 23 19:03:45 2023 +0300

    client: add ui theme controls
This commit is contained in:
Ildar Kamalov 2023-03-24 17:28:31 +03:00
parent 67c9abe119
commit d859a33787
8 changed files with 120 additions and 42 deletions

View File

@ -651,6 +651,9 @@
"cache_cleared": "DNS cache successfully cleared", "cache_cleared": "DNS cache successfully cleared",
"clear_cache": "Clear cache", "clear_cache": "Clear cache",
"make_static": "Make static", "make_static": "Make static",
"theme_auto_desc": "Auto (based on the color scheme of your device)",
"theme_dark_desc": "Dark theme",
"theme_light_desc": "Light theme",
"disable_for_seconds": "For {{count}} second", "disable_for_seconds": "For {{count}} second",
"disable_for_seconds_plural": "For {{count}} seconds", "disable_for_seconds_plural": "For {{count}} seconds",
"disable_for_minutes": "For {{count}} minute", "disable_for_minutes": "For {{count}} minute",

View File

@ -22,6 +22,14 @@
margin-bottom: 25px; margin-bottom: 25px;
} }
.form__group--inner .form__group--checkbox {
margin-bottom: 12px;
}
.form__group--inner .form__group--checkbox:last-child {
margin-bottom: 0;
}
.form__inline { .form__inline {
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;

View File

@ -54,6 +54,11 @@
cursor: pointer; cursor: pointer;
} }
.toast__dismiss:hover,
.toast__dismiss:focus {
outline: none;
}
.toast-enter { .toast-enter {
opacity: 0.01; opacity: 0.01;
} }

View File

@ -71,3 +71,38 @@
margin: 0 20px 0 0; margin: 0 20px 0 0;
} }
} }
.btn-secondary.footer__theme-button,
[data-theme="dark"] .btn-secondary.footer__theme-button {
height: 38px;
border-color: var(--ctrl-select-bgcolor);
}
.footer__theme-icon {
display: inline-block;
vertical-align: middle;
width: 24px;
height: 24px;
color: var(--gray-ac);
}
[data-theme="dark"] .footer__theme-icon {
color: var(--mcolor);
}
.footer__theme-icon--active,
[data-theme="dark"] .footer__theme-icon--active {
color: var(--btn-success-bgcolor);
}
.footer__themes {
margin: 0 auto 24px;
text-align: center;
}
@media screen and (min-width: 768px) {
.footer__themes {
margin: 0;
text-align: left;
}
}

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import classNames from 'classnames'; import cn from 'classnames';
import { REPOSITORY, PRIVACY_POLICY_LINK, THEMES } from '../../helpers/constants'; import { REPOSITORY, PRIVACY_POLICY_LINK, THEMES } from '../../helpers/constants';
import { LANGUAGES } from '../../helpers/twosky'; import { LANGUAGES } from '../../helpers/twosky';
@ -55,15 +55,13 @@ const Footer = () => {
setHtmlLangAttr(value); setHtmlLangAttr(value);
}; };
const onThemeChanged = (event) => { const onThemeChange = (value) => {
const { value } = event.target; if (isLoggedIn) {
dispatch(changeTheme(value)); dispatch(changeTheme(value));
}; } else {
const onThemeChangedLocal = (event) => {
const { value } = event.target;
setUITheme(value); setUITheme(value);
setCurrentThemeLocal(value); setCurrentThemeLocal(value);
}
}; };
const renderCopyright = () => <div className="footer__column"> const renderCopyright = () => <div className="footer__column">
@ -76,41 +74,53 @@ const Footer = () => {
const renderLinks = (linksData) => linksData.map(({ name, href, className = '' }) => <a const renderLinks = (linksData) => linksData.map(({ name, href, className = '' }) => <a
key={name} key={name}
href={href} href={href}
className={classNames('footer__link', className)} className={cn('footer__link', className)}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
{t(name)} {t(name)}
</a>); </a>);
const themeSelectOptions = () => ( const renderThemeButtons = () => {
const currentValue = isLoggedIn ? currentTheme : currentThemeLocal;
const content = {
auto: {
desc: t('theme_auto_desc'),
icon: '#auto',
},
dark: {
desc: t('theme_dark_desc'),
icon: '#dark',
},
light: {
desc: t('theme_light_desc'),
icon: '#light',
},
};
return (
Object.values(THEMES) Object.values(THEMES)
.map((theme) => ( .map((theme) => (
<option key={theme} value={theme}> <button
{t(`theme_${theme}`)} key={theme}
</option> type="button"
className="btn btn-sm btn-secondary footer__theme-button"
onClick={() => onThemeChange(theme)}
title={content[theme].desc}
>
<svg
className={cn(
'footer__theme-icon',
{ 'footer__theme-icon--active': currentValue === theme },
)}
>
<use xlinkHref={content[theme].icon} />
</svg>
</button>
)) ))
); );
};
const renderThemeSelect = () => (
<select
className="form-control select select--theme"
value={currentTheme}
onChange={onThemeChanged}
>
{themeSelectOptions()}
</select>
);
const renderThemeSelectLocal = () => (
<select
className="form-control select select--theme"
value={currentThemeLocal}
onChange={onThemeChangedLocal}
>
{themeSelectOptions()}
</select>
);
return ( return (
<> <>
@ -121,7 +131,11 @@ const Footer = () => {
{renderLinks(linksData)} {renderLinks(linksData)}
</div> </div>
<div className="footer__column footer__column--theme"> <div className="footer__column footer__column--theme">
{isLoggedIn ? renderThemeSelect() : renderThemeSelectLocal()} <div className="footer__themes">
<div className="btn-group">
{renderThemeButtons()}
</div>
</div>
</div> </div>
<div className="footer__column footer__column--language"> <div className="footer__column footer__column--language">
<select <select

View File

@ -7,7 +7,6 @@ import { useSelector } from 'react-redux';
import { MOBILE_CONFIG_LINKS } from '../../../helpers/constants'; import { MOBILE_CONFIG_LINKS } from '../../../helpers/constants';
import Tabs from '../Tabs'; import Tabs from '../Tabs';
import Icons from '../Icons';
import MobileConfigForm from './MobileConfigForm'; import MobileConfigForm from './MobileConfigForm';
const renderLi = ({ label, components }) => <li key={label}> const renderLi = ({ label, components }) => <li key={label}>
@ -341,7 +340,6 @@ const Guide = ({ dnsAddresses }) => {
> >
{activeTab} {activeTab}
</Tabs> </Tabs>
<Icons />
</div> </div>
); );
}; };

View File

@ -204,11 +204,24 @@ const Icons = () => (
</svg> </svg>
</symbol> </symbol>
<symbol id="auto" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path fillRule="evenodd" clipRule="evenodd" d="M12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3Z" stroke="currentColor" strokeWidth="1.5" />
<path fillRule="evenodd" clipRule="evenodd" d="M12 3V21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3Z" fill="currentColor" stroke="currentColor" strokeWidth="1.5" />
</symbol>
<symbol id="dark" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M3.80737 15.731L3.9895 15.0034C3.71002 14.9335 3.41517 15.0298 3.23088 15.2512C3.0466 15.4727 3.00545 15.7801 3.12501 16.0422L3.80737 15.731ZM14.1926 3.26892L14.3747 2.54137C14.0953 2.47141 13.8004 2.56772 13.6161 2.78917C13.4318 3.01062 13.3907 3.31806 13.5102 3.58018L14.1926 3.26892ZM12 20.2499C8.66479 20.2499 5.79026 18.2708 4.48974 15.4197L3.12501 16.0422C4.66034 19.4081 8.05588 21.7499 12 21.7499V20.2499ZM20.25 11.9999C20.25 16.5563 16.5563 20.2499 12 20.2499V21.7499C17.3848 21.7499 21.75 17.3847 21.75 11.9999H20.25ZM14.0105 3.99647C17.5955 4.89391 20.25 8.13787 20.25 11.9999H21.75C21.75 7.43347 18.6114 3.60193 14.3747 2.54137L14.0105 3.99647ZM13.5102 3.58018C13.9851 4.6211 14.25 5.77857 14.25 6.99995H15.75C15.75 5.5595 15.4371 4.1901 14.875 2.95766L13.5102 3.58018ZM14.25 6.99995C14.25 11.5563 10.5563 15.2499 5.99999 15.2499V16.7499C11.3848 16.7499 15.75 12.3847 15.75 6.99995H14.25ZM5.99999 15.2499C5.30559 15.2499 4.63225 15.1643 3.9895 15.0034L3.62525 16.4585C4.38616 16.649 5.18181 16.7499 5.99999 16.7499V15.2499Z" fill="currentColor" />
</symbol>
<symbol id="light" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M12 3.75C16.5563 3.75 20.25 7.44365 20.25 12H21.75C21.75 6.61522 17.3848 2.25 12 2.25V3.75ZM20.25 12C20.25 16.5563 16.5563 20.25 12 20.25V21.75C17.3848 21.75 21.75 17.3848 21.75 12H20.25ZM12 20.25C7.44365 20.25 3.75 16.5563 3.75 12H2.25C2.25 17.3848 6.61522 21.75 12 21.75V20.25ZM3.75 12C3.75 7.44365 7.44365 3.75 12 3.75V2.25C6.61522 2.25 2.25 6.61522 2.25 12H3.75Z" fill="currentColor" />
<path fillRule="evenodd" clipRule="evenodd" d="M12 10C10.8954 10 10 10.8954 10 12C10 13.1046 10.8954 14 12 14C13.1046 14 14 13.1046 14 12C13.9987 10.896 13.104 10.0013 12 10Z" fill="currentColor" />
</symbol>
<symbol id="chevron-down" width="24" height="24" viewBox="0 0 24 24"> <symbol id="chevron-down" width="24" height="24" viewBox="0 0 24 24">
<g fill="none" fillRule="evenodd"> <g fill="none" fillRule="evenodd">
<path d="M0 0h24v24H0z" fill="#878787" fillOpacity=".01" /> <path d="M0 0h24v24H0z" fill="#878787" fillOpacity=".01" />
<path stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" <path stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" d="M8.036 10.93l3.93 4.07 4.068-3.93" />
d="M8.036 10.93l3.93 4.07 4.068-3.93" />
</g> </g>
</symbol> </symbol>
</svg> </svg>

View File

@ -22,6 +22,7 @@ import Progress from './Progress';
import Toasts from '../../components/Toasts'; import Toasts from '../../components/Toasts';
import Footer from '../../components/ui/Footer'; import Footer from '../../components/ui/Footer';
import Icons from '../../components/ui/Icons';
import logo from '../../components/ui/svg/logo.svg'; import logo from '../../components/ui/svg/logo.svg';
import './Setup.css'; import './Setup.css';
@ -123,6 +124,7 @@ class Setup extends Component {
</div> </div>
<Footer /> <Footer />
<Toasts /> <Toasts />
<Icons />
</Fragment> </Fragment>
} }
</Fragment> </Fragment>