Pull request: beta client squashed

Merge in DNS/adguard-home from beta-client-2 to master

Squashed commit of the following:

commit b2640cc49a6c5484d730b534dcf5a8013d7fa478
Merge: 659def862 aef4659e9
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Tue Dec 29 19:23:09 2020 +0300

    Merge branch 'master' into beta-client-2

commit 659def8626467949c35b7a6a0c99ffafb07b4385
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Tue Dec 29 17:25:14 2020 +0300

    all: upgrade github actions node version

commit b4b8cf8dd75672e9155da5d111ac66e8f5ba1535
Author: Vladislav Abdulmyanov <v.abdulmyanov@adguard.com>
Date:   Tue Dec 29 16:57:14 2020 +0300

    all: beta client squashed
This commit is contained in:
Eugene Burkov
2020-12-29 19:53:56 +03:00
parent aef4659e93
commit 5e20ac7ed5
200 changed files with 20843 additions and 55 deletions

View File

@@ -0,0 +1,146 @@
import React, { FC, FocusEvent, KeyboardEvent, ClipboardEvent, ChangeEvent, useState } from 'react';
import { Input as InputControl } from 'antd';
import { InputProps as InputControlProps } from 'antd/lib/input';
import cn from 'classnames';
import { Icon } from 'Common/ui';
import theme from 'Lib/theme';
interface AdminInterfaceProps {
autoComplete?: InputControlProps['autoComplete'];
autoFocus?: InputControlProps['autoFocus'];
className?: string;
description?: string;
disabled?: boolean;
error?: boolean;
id?: string;
inputMode?: InputControlProps['inputMode'];
label?: string;
wrapperClassName?: string;
name: string;
onBlur?: (e: FocusEvent<HTMLInputElement>) => void;
onChange?: (data: string, e?: ChangeEvent<HTMLInputElement>) => void;
onFocus?: (e: FocusEvent<HTMLInputElement>) => void;
onKeyDown?: (e: KeyboardEvent<HTMLInputElement>) => void;
onPaste?: (e: ClipboardEvent<HTMLInputElement>) => void;
pattern?: InputControlProps['pattern'];
placeholder: string;
prefix?: InputControlProps['prefix'];
size?: InputControlProps['size'];
suffix?: InputControlProps['suffix'];
type: InputControlProps['type'];
value: string | number;
}
const InputComponent: FC<AdminInterfaceProps> = ({
autoComplete,
autoFocus,
className,
description,
disabled,
error,
id,
inputMode,
label,
wrapperClassName,
name,
onBlur,
onChange,
onFocus,
onKeyDown,
onPaste,
pattern,
placeholder,
prefix,
size = 'middle',
suffix,
type,
value,
}) => {
const [inputType, setInputType] = useState(type);
const inputClass = cn(
'input',
{ input_big: size === 'large' },
{ input_medium: size === 'middle' },
{ input_small: size === 'small' },
className,
);
const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
if (onBlur) {
onBlur(e);
}
};
const showPassword = () => {
if (inputType === 'password') {
setInputType('text');
} else {
setInputType('password');
}
};
const showPasswordIcon = () => {
const icon = inputType === 'password' ? 'visibility_disable' : 'visibility_enable';
return (
<Icon
icon={icon}
className={theme.form.reveal}
onClick={showPassword}
/>
);
};
const validSuffix = (
<>
{!!suffix && suffix}
{(type === 'password') && showPasswordIcon()}
</>
);
let descriptionView = null;
if (description) {
descriptionView = (
<div className={theme.form.label}>
{description}
</div>
);
}
return (
<label htmlFor={id || name} className={cn(theme.form.group, wrapperClassName)}>
{label && (
<div className={theme.form.label}>
{label}
</div>
)}
<InputControl
autoComplete={autoComplete}
autoFocus={autoFocus}
className={inputClass}
disabled={disabled}
formNoValidate
id={id || name}
inputMode={inputMode}
name={name}
onBlur={handleBlur}
onChange={(e) => onChange && onChange(e.target.value ? e.target.value : '', e)}
onFocus={onFocus}
onKeyDown={onKeyDown}
onPaste={onPaste}
pattern={pattern}
placeholder={placeholder}
prefix={prefix}
size="large"
suffix={validSuffix}
type={inputType}
value={value}
data-error={error}
/>
{descriptionView}
</label>
);
};
export default InputComponent;

View File

@@ -0,0 +1 @@
export { default as Input } from './Input';

View File

@@ -0,0 +1,16 @@
.group {
width: 100%;
}
.radio {
display: flex;
align-items: center;
margin-bottom: 16px;
padding-bottom: 16px;
width: 100%;
border-bottom: 1px solid var(--gray300);
&:last-child {
border-bottom: 0;
}
}

View File

@@ -0,0 +1,57 @@
import React, { FC } from 'react';
import { Radio } from 'antd';
import { observer } from 'mobx-react-lite';
import theme from 'Lib/theme';
import s from './Radio.module.pcss';
const { Group } = Radio;
interface AdminInterfaceProps {
options: {
label: string;
desc?: string;
value: string | number;
}[];
onSelect: (value: string | number) => void;
value: string | number;
}
const RadioComponent: FC<AdminInterfaceProps> = observer(({
options, onSelect, value,
}) => {
if (options.length === 0) {
return null;
}
return (
<Group
value={value}
onChange={(e) => {
onSelect(e.target.value);
}}
className={s.group}
>
{options.map((o) => (
<Radio
key={o.value}
value={o.value}
className={s.radio}
>
<div>
{o.label}
</div>
{o.desc && (
<div className={theme.typography.subtext}>
{o.desc}
</div>
)}
</Radio>
))}
</Group>
);
});
export default RadioComponent;

View File

@@ -0,0 +1 @@
export { default } from './Radio';

View File

@@ -0,0 +1,3 @@
import { Switch as SwitchE } from 'antd';
export default SwitchE;

View File

@@ -0,0 +1 @@
export { default as Switch } from './Switch';

View File

@@ -0,0 +1,3 @@
export { default as Radio } from './Radio';
export { Input } from './Input';
export { Switch } from './Switch';

View File

@@ -0,0 +1,12 @@
import React from 'react';
import theme from 'Lib/theme';
const danger = (e: string) => {
return (
<span className={theme.typography.danger}>
{e}
</span>
);
};
export default danger;

View File

@@ -0,0 +1,2 @@
export { default as danger } from './danger';
export { default as p } from './p';

View File

@@ -0,0 +1,11 @@
import React from 'react';
const danger = (e: string) => {
return (
<p>
{e}
</p>
);
};
export default danger;

View File

View File

@@ -0,0 +1,7 @@
.icon {
display: inline-block;
vertical-align: middle;
width: 24px;
height: 24px;
flex-shrink: 0;
}

View File

@@ -0,0 +1,25 @@
import React, { FC } from 'react';
import cn from 'classnames';
import { IconType } from 'Lib/theme/Icons';
import s from './Icon.module.pcss';
interface IconProps {
icon: IconType;
color?: string;
className?: string;
onClick?: () => void;
}
const Icon: FC<IconProps> = ({ icon, color, className, onClick }) => {
const iconClass = cn(s.icon, color, className);
return (
<svg className={iconClass} onClick={onClick}>
<use xlinkHref={`#${icon}`} />
</svg>
);
};
export default Icon;
export { IconType } from 'Lib/theme/Icons';

View File

@@ -0,0 +1 @@
export { default, IconType } from './Icon';

View File

@@ -0,0 +1 @@
export { notifyError, notifySuccess } from './notifications';

View File

@@ -0,0 +1,42 @@
import React from 'react';
import { notification } from 'antd';
import { DEFAULT_NOTIFICATION_DURATION } from 'Consts/common';
export const notifySuccess = (title: string, code?: string) => {
notification.success({
message: (
<div
data-notification={code || 'success'}
>
{title}
</div>
),
placement: 'bottomRight',
duration: DEFAULT_NOTIFICATION_DURATION,
className: 'notification',
});
};
export const notifyError = (
title: string,
options?: {
btn?: React.ReactNode;
duration?: number;
onClose?: () => void;
},
) => {
const { btn, duration, onClose } = options || {};
notification.error({
onClose,
message: (
<div>
{title}
</div>
),
placement: 'bottomRight',
duration: typeof duration === 'number' ? duration : DEFAULT_NOTIFICATION_DURATION,
className: 'notification',
btn,
});
};

View File

@@ -0,0 +1,2 @@
export { default as Icon } from './Icon';
export { notifyError, notifySuccess } from './Notifications';