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:
146
client2/src/components/common/controls/Input/Input.tsx
Normal file
146
client2/src/components/common/controls/Input/Input.tsx
Normal 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;
|
||||
1
client2/src/components/common/controls/Input/index.ts
Normal file
1
client2/src/components/common/controls/Input/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Input } from './Input';
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
57
client2/src/components/common/controls/Radio/Radio.tsx
Normal file
57
client2/src/components/common/controls/Radio/Radio.tsx
Normal 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;
|
||||
1
client2/src/components/common/controls/Radio/index.ts
Normal file
1
client2/src/components/common/controls/Radio/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Radio';
|
||||
3
client2/src/components/common/controls/Switch/Switch.tsx
Normal file
3
client2/src/components/common/controls/Switch/Switch.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Switch as SwitchE } from 'antd';
|
||||
|
||||
export default SwitchE;
|
||||
1
client2/src/components/common/controls/Switch/index.ts
Normal file
1
client2/src/components/common/controls/Switch/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Switch } from './Switch';
|
||||
3
client2/src/components/common/controls/index.ts
Normal file
3
client2/src/components/common/controls/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as Radio } from './Radio';
|
||||
export { Input } from './Input';
|
||||
export { Switch } from './Switch';
|
||||
12
client2/src/components/common/formating/danger.tsx
Normal file
12
client2/src/components/common/formating/danger.tsx
Normal 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;
|
||||
2
client2/src/components/common/formating/index.ts
Normal file
2
client2/src/components/common/formating/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as danger } from './danger';
|
||||
export { default as p } from './p';
|
||||
11
client2/src/components/common/formating/p.tsx
Normal file
11
client2/src/components/common/formating/p.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
const danger = (e: string) => {
|
||||
return (
|
||||
<p>
|
||||
{e}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export default danger;
|
||||
0
client2/src/components/common/index.ts
Normal file
0
client2/src/components/common/index.ts
Normal file
7
client2/src/components/common/ui/Icon/Icon.module.pcss
Normal file
7
client2/src/components/common/ui/Icon/Icon.module.pcss
Normal file
@@ -0,0 +1,7 @@
|
||||
.icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
25
client2/src/components/common/ui/Icon/Icon.tsx
Normal file
25
client2/src/components/common/ui/Icon/Icon.tsx
Normal 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';
|
||||
1
client2/src/components/common/ui/Icon/index.ts
Normal file
1
client2/src/components/common/ui/Icon/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default, IconType } from './Icon';
|
||||
1
client2/src/components/common/ui/Notifications/index.ts
Normal file
1
client2/src/components/common/ui/Notifications/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { notifyError, notifySuccess } from './notifications';
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
2
client2/src/components/common/ui/index.ts
Normal file
2
client2/src/components/common/ui/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as Icon } from './Icon';
|
||||
export { notifyError, notifySuccess } from './Notifications';
|
||||
Reference in New Issue
Block a user