Initial commit
This commit is contained in:
50
client/src/components/ui/Card.css
Normal file
50
client/src/components/ui/Card.css
Normal file
@@ -0,0 +1,50 @@
|
||||
.card-header {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.6rem 1.5rem;
|
||||
}
|
||||
|
||||
.card-subtitle {
|
||||
margin: 4px 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.card-table-overflow {
|
||||
overflow-y: auto;
|
||||
max-height: 280px;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.card-actions-top {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-graph {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.card-body--status {
|
||||
padding: 2.5rem 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-refresh {
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
background-size: 14px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI0IiBzdHJva2U9IiM0NjdmY2YiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMjMgNHY2aC02Ii8+PHBhdGggZD0ibTEgMjB2LTZoNiIvPjxwYXRoIGQ9Im0zLjUxIDlhOSA5IDAgMCAxIDE0Ljg1LTMuMzZsNC42NCA0LjM2bS0yMiA0IDQuNjQgNC4zNmE5IDkgMCAwIDAgMTQuODUtMy4zNiIvPjwvc3ZnPg==');
|
||||
}
|
||||
|
||||
.card-refresh:hover,
|
||||
.card-refresh:not(:disabled):not(.disabled):active,
|
||||
.card-refresh:focus:active {
|
||||
background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI0IiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMjMgNHY2aC02Ii8+PHBhdGggZD0ibTEgMjB2LTZoNiIvPjxwYXRoIGQ9Im0zLjUxIDlhOSA5IDAgMCAxIDE0Ljg1LTMuMzZsNC42NCA0LjM2bS0yMiA0IDQuNjQgNC4zNmE5IDkgMCAwIDAgMTQuODUtMy4zNiIvPjwvc3ZnPg==');
|
||||
}
|
||||
40
client/src/components/ui/Card.js
Normal file
40
client/src/components/ui/Card.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './Card.css';
|
||||
|
||||
const Card = props => (
|
||||
<div className="card">
|
||||
{ props.title &&
|
||||
<div className="card-header with-border">
|
||||
<div className="card-inner">
|
||||
<div className="card-title">
|
||||
{props.title}
|
||||
</div>
|
||||
|
||||
{props.subtitle &&
|
||||
<div className="card-subtitle" dangerouslySetInnerHTML={{ __html: props.subtitle }} />
|
||||
}
|
||||
</div>
|
||||
|
||||
{props.refresh &&
|
||||
<div className="card-options">
|
||||
{props.refresh}
|
||||
</div>
|
||||
}
|
||||
</div>}
|
||||
<div className={props.bodyType ? props.bodyType : 'card-body'}>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Card.propTypes = {
|
||||
title: PropTypes.string,
|
||||
subtitle: PropTypes.string,
|
||||
bodyType: PropTypes.string,
|
||||
refresh: PropTypes.node,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default Card;
|
||||
93
client/src/components/ui/Checkbox.css
Normal file
93
client/src/components/ui/Checkbox.css
Normal file
@@ -0,0 +1,93 @@
|
||||
.checkbox {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.checkbox--single {
|
||||
display: block;
|
||||
margin: 2px auto 6px;
|
||||
}
|
||||
|
||||
.checkbox--single .checkbox__label:before {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.checkbox--settings .checkbox__label:before {
|
||||
top: 2px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.checkbox--settings .checkbox__label-title {
|
||||
margin-bottom: 5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.checkbox__label {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox__label:before {
|
||||
content: "";
|
||||
position: relative;
|
||||
top: 1px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
min-width: 20px;
|
||||
margin-right: 10px;
|
||||
background-color: #e2e2e2;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 12px 10px;
|
||||
border-radius: 3px;
|
||||
transition: 0.3s ease box-shadow;
|
||||
}
|
||||
|
||||
.checkbox__label .checkbox__label-text {
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.checkbox__label .checkbox__label-text .md__paragraph {
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.checkbox__input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.checkbox__input:checked+.checkbox__label:before {
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMi4zIDkuMiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiPjxwYXRoIGQ9Ik0xMS44IDAuNUw1LjMgOC41IDAuNSA0LjIiLz48L3N2Zz4=);
|
||||
}
|
||||
|
||||
.checkbox__input:focus+.checkbox__label:before {
|
||||
box-shadow: 0 0 1px 1px rgba(74, 74, 74, 0.32);
|
||||
}
|
||||
|
||||
.checkbox__label-text {
|
||||
max-width: 515px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.checkbox__label-title {
|
||||
display: block;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.checkbox__label-subtitle {
|
||||
display: block;
|
||||
line-height: 1.5;
|
||||
color: rgba(74, 74, 74, 0.7);
|
||||
}
|
||||
38
client/src/components/ui/Checkbox.js
Normal file
38
client/src/components/ui/Checkbox.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './Checkbox.css';
|
||||
|
||||
class Checkbox extends Component {
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
subtitle,
|
||||
enabled,
|
||||
handleChange,
|
||||
} = this.props;
|
||||
return (
|
||||
<div className="form__group">
|
||||
<label className="checkbox checkbox--settings">
|
||||
<span className="checkbox__marker"/>
|
||||
<input type="checkbox" className="checkbox__input" onChange={handleChange} checked={enabled}/>
|
||||
<span className="checkbox__label">
|
||||
<span className="checkbox__label-text">
|
||||
<span className="checkbox__label-title">{title}</span>
|
||||
<span className="checkbox__label-subtitle" dangerouslySetInnerHTML={{ __html: subtitle }}/>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Checkbox.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
subtitle: PropTypes.string.isRequired,
|
||||
enabled: PropTypes.bool.isRequired,
|
||||
handleChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Checkbox;
|
||||
37
client/src/components/ui/Footer.js
Normal file
37
client/src/components/ui/Footer.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
class Footer extends Component {
|
||||
getYear = () => {
|
||||
const today = new Date();
|
||||
return today.getFullYear();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<footer className="footer">
|
||||
<div className="container">
|
||||
<div className="row align-items-center flex-row-reverse">
|
||||
<div className="col-12 col-lg-auto ml-lg-auto">
|
||||
<ul className="list-inline list-inline-dots text-center mb-0">
|
||||
<li className="list-inline-item">
|
||||
<a href="https://adguard.com/welcome.html" target="_blank" rel="noopener noreferrer">Homepage</a>
|
||||
</li>
|
||||
<li className="list-inline-item">
|
||||
<a href="https://github.com/AdguardTeam/" target="_blank" rel="noopener noreferrer">Github</a>
|
||||
</li>
|
||||
<li className="list-inline-item">
|
||||
<a href="https://adguard.com/privacy.html" target="_blank" rel="noopener noreferrer">Privacy Policy</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="col-12 col-lg-auto mt-3 mt-lg-0 text-center">
|
||||
© AdGuard {this.getYear()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Footer;
|
||||
53
client/src/components/ui/Loading.css
Normal file
53
client/src/components/ui/Loading.css
Normal file
@@ -0,0 +1,53 @@
|
||||
.loading {
|
||||
position: relative;
|
||||
z-index: 101;
|
||||
opacity: 0;
|
||||
animation: opacity 0.2s linear 0.2s forwards;
|
||||
}
|
||||
|
||||
.loading:before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.loading:after {
|
||||
content: "";
|
||||
position: fixed;
|
||||
z-index: 101;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-top: -20px;
|
||||
margin-left: -20px;
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2047.6%2047.6%22%20height%3D%22100%25%22%20width%3D%22100%25%22%3E%3Cpath%20opacity%3D%22.235%22%20fill%3D%22%23979797%22%20d%3D%22M44.4%2011.9l-5.2%203c1.5%202.6%202.4%205.6%202.4%208.9%200%209.8-8%2017.8-17.8%2017.8-6.6%200-12.3-3.6-15.4-8.9l-5.2%203C7.3%2042.8%2015%2047.6%2023.8%2047.6c13.1%200%2023.8-10.7%2023.8-23.8%200-4.3-1.2-8.4-3.2-11.9z%22%2F%3E%3Cpath%20fill%3D%22%2366b574%22%20d%3D%22M3.2%2035.7C0%2030.2-.8%2023.8.8%2017.6%202.5%2011.5%206.4%206.4%2011.9%203.2%2017.4%200%2023.8-.8%2030%20.8c6.1%201.6%2011.3%205.6%2014.4%2011.1l-5.2%203c-2.4-4.1-6.2-7.1-10.8-8.3C23.8%205.4%2019%206%2014.9%208.4s-7.1%206.2-8.3%2010.8c-1.2%204.6-.6%209.4%201.8%2013.5l-5.2%203z%22%2F%3E%3C%2Fsvg%3E");
|
||||
will-change: transform;
|
||||
animation: clockwise 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes opacity {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes clockwise {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
9
client/src/components/ui/Loading.js
Normal file
9
client/src/components/ui/Loading.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import './Loading.css';
|
||||
|
||||
const Loading = () => (
|
||||
<div className="loading"></div>
|
||||
);
|
||||
|
||||
export default Loading;
|
||||
40
client/src/components/ui/Modal.css
Normal file
40
client/src/components/ui/Modal.css
Normal file
@@ -0,0 +1,40 @@
|
||||
.ReactModal__Overlay {
|
||||
-webkit-perspective: 600;
|
||||
perspective: 600;
|
||||
opacity: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ReactModal__Overlay--after-open {
|
||||
opacity: 1;
|
||||
transition: opacity 150ms ease-out;
|
||||
}
|
||||
|
||||
.ReactModal__Content {
|
||||
-webkit-transform: scale(0.5) rotateX(-30deg);
|
||||
transform: scale(0.5) rotateX(-30deg);
|
||||
}
|
||||
|
||||
.ReactModal__Content--after-open {
|
||||
-webkit-transform: scale(1) rotateX(0deg);
|
||||
transform: scale(1) rotateX(0deg);
|
||||
transition: all 150ms ease-in;
|
||||
}
|
||||
|
||||
.ReactModal__Overlay--before-close {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.ReactModal__Content--before-close {
|
||||
-webkit-transform: scale(0.5) rotateX(30deg);
|
||||
transform: scale(0.5) rotateX(30deg);
|
||||
transition: all 150ms ease-in;
|
||||
}
|
||||
|
||||
.ReactModal__Content.modal-dialog {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
112
client/src/components/ui/Modal.js
Normal file
112
client/src/components/ui/Modal.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactModal from 'react-modal';
|
||||
import classnames from 'classnames';
|
||||
import { R_URL_REQUIRES_PROTOCOL } from '../../helpers/constants';
|
||||
import './Modal.css';
|
||||
|
||||
ReactModal.setAppElement('#root');
|
||||
|
||||
export default class Modal extends Component {
|
||||
state = {
|
||||
url: '',
|
||||
isUrlValid: false,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line
|
||||
isUrlValid = url => {
|
||||
return R_URL_REQUIRES_PROTOCOL.test(url);
|
||||
};
|
||||
|
||||
handleUrlChange = async (e) => {
|
||||
const { value: url } = e.currentTarget;
|
||||
if (this.isUrlValid(url)) {
|
||||
this.setState(...this.state, { url, isUrlValid: true });
|
||||
} else {
|
||||
this.setState(...this.state, { url, isUrlValid: false });
|
||||
}
|
||||
};
|
||||
|
||||
handleNext = () => {
|
||||
this.props.addFilter(this.state.url);
|
||||
setTimeout(() => {
|
||||
if (this.props.isFilterAdded) {
|
||||
this.props.toggleModal();
|
||||
}
|
||||
}, 2000);
|
||||
};
|
||||
render() {
|
||||
const {
|
||||
isOpen,
|
||||
toggleModal,
|
||||
title,
|
||||
inputDescription,
|
||||
} = this.props;
|
||||
const { isUrlValid, url } = this.state;
|
||||
const inputClass = classnames({
|
||||
'form-control mb-2': true,
|
||||
'is-invalid': url.length > 0 && !isUrlValid,
|
||||
'is-valid': url.length > 0 && isUrlValid,
|
||||
});
|
||||
|
||||
const renderBody = () => {
|
||||
if (!this.props.isFilterAdded) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<input type="text" className={inputClass} placeholder="Enter URL or path" onChange={this.handleUrlChange}/>
|
||||
{inputDescription &&
|
||||
<div className="description">
|
||||
{inputDescription}
|
||||
</div>}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="description">
|
||||
Url added successfully
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const isValidForSubmit = !(url.length > 0 && isUrlValid);
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
className="Modal__Bootstrap modal-dialog modal-dialog-centered"
|
||||
closeTimeoutMS={0}
|
||||
isOpen={ isOpen }
|
||||
onRequestClose={toggleModal}
|
||||
>
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">
|
||||
{title}
|
||||
</h4>
|
||||
<button type="button" className="close" onClick={toggleModal}>
|
||||
<span className="sr-only">Close</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{ renderBody()}
|
||||
</div>
|
||||
{
|
||||
!this.props.isFilterAdded &&
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" onClick={toggleModal}>Cancel</button>
|
||||
<button type="button" className="btn btn-success" onClick={this.handleNext} disabled={isValidForSubmit}>Add filter</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</ReactModal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Modal.propTypes = {
|
||||
toggleModal: PropTypes.func.isRequired,
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
inputDescription: PropTypes.string,
|
||||
addFilter: PropTypes.func.isRequired,
|
||||
isFilterAdded: PropTypes.bool,
|
||||
};
|
||||
10
client/src/components/ui/PageTitle.css
Normal file
10
client/src/components/ui/PageTitle.css
Normal file
@@ -0,0 +1,10 @@
|
||||
.page-subtitle {
|
||||
margin-left: 0.7rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.page-title__actions {
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
margin-left: 20px;
|
||||
}
|
||||
22
client/src/components/ui/PageTitle.js
Normal file
22
client/src/components/ui/PageTitle.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './PageTitle.css';
|
||||
|
||||
const PageTitle = props => (
|
||||
<div className="page-header">
|
||||
<h1 className="page-title">
|
||||
{props.title}
|
||||
{props.subtitle && <span className="page-subtitle">{props.subtitle}</span>}
|
||||
{props.children}
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
|
||||
PageTitle.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
subtitle: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
export default PageTitle;
|
||||
4
client/src/components/ui/ReactTable.css
Normal file
4
client/src/components/ui/ReactTable.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.ReactTable .rt-th,
|
||||
.ReactTable .rt-td {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
23
client/src/components/ui/Status.js
Normal file
23
client/src/components/ui/Status.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Card from '../ui/Card';
|
||||
|
||||
const Status = props => (
|
||||
<div className="status">
|
||||
<Card bodyType="card-body card-body--status">
|
||||
<div className="h4 font-weight-light mb-4">
|
||||
You are currently not using AdGuard DNS
|
||||
</div>
|
||||
<button className="btn btn-success" onClick={props.handleStatusChange}>
|
||||
Enable AdGuard DNS
|
||||
</button>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
Status.propTypes = {
|
||||
handleStatusChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Status;
|
||||
16243
client/src/components/ui/Tabler.css
Normal file
16243
client/src/components/ui/Tabler.css
Normal file
File diff suppressed because one or more lines are too long
49
client/src/components/ui/Tooltip.css
Normal file
49
client/src/components/ui/Tooltip.css
Normal file
@@ -0,0 +1,49 @@
|
||||
.tooltip-custom {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-left: 5px;
|
||||
background-image: url("./svg/help-circle.svg");
|
||||
background-size: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tooltip-custom:before {
|
||||
content: attr(data-tooltip);
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: calc(100% + 12px);
|
||||
left: calc(50% - 103px);
|
||||
width: 206px;
|
||||
padding: 10px 15px;
|
||||
font-size: 0.85rem;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
background-color: #585965;
|
||||
border-radius: 3px;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.tooltip-custom:after {
|
||||
content: "";
|
||||
position: relative;
|
||||
top: -9px;
|
||||
left: calc(50% - 6px);
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-top: 6px solid #585965;
|
||||
}
|
||||
|
||||
.tooltip-custom:hover:before,
|
||||
.tooltip-custom:hover:after {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
14
client/src/components/ui/Tooltip.js
Normal file
14
client/src/components/ui/Tooltip.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './Tooltip.css';
|
||||
|
||||
const Tooltip = props => (
|
||||
<div data-tooltip={props.text} className="tooltip-custom"></div>
|
||||
);
|
||||
|
||||
Tooltip.propTypes = {
|
||||
text: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Tooltip;
|
||||
1
client/src/components/ui/svg/help-circle.svg
Normal file
1
client/src/components/ui/svg/help-circle.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#66b574" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12" y2="17"></line></svg>
|
||||
|
After Width: | Height: | Size: 357 B |
1
client/src/components/ui/svg/trash-2.svg
Normal file
1
client/src/components/ui/svg/trash-2.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m3 6h2 16"/><path d="m19 6v14a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2-2v-14m3 0v-2a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="m10 11v6"/><path d="m14 11v6"/></svg>
|
||||
|
After Width: | Height: | Size: 340 B |
1
client/src/components/ui/svg/x.svg
Normal file
1
client/src/components/ui/svg/x.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m18 6-12 12"/><path d="m6 6 12 12"/></svg>
|
||||
|
After Width: | Height: | Size: 227 B |
Reference in New Issue
Block a user