diff --git a/docs/components/Example/styles.scss b/docs/components/Example/styles.scss
index 1b7d0fc95..3a5357a06 100644
--- a/docs/components/Example/styles.scss
+++ b/docs/components/Example/styles.scss
@@ -17,12 +17,12 @@
}
}
- h2 {
+ & > h2 {
margin-bottom: $spacing-etalon;
font-size: $font-size-header;
}
- h3 {
+ & > h3 {
margin-bottom: 18px;
font-size: $font-size-subheader;
}
diff --git a/docs/components/Layout/index.jsx b/docs/components/Layout/index.jsx
index b6ffb43b7..74aa364ae 100644
--- a/docs/components/Layout/index.jsx
+++ b/docs/components/Layout/index.jsx
@@ -34,6 +34,7 @@ import TabExample from '../../examples/TabExample';
import EmptyExample from '../../examples/EmptyExample';
import GridExample from '../../examples/GridExample';
import PrettyDiffExample from '../../examples/PrettyDiffExample';
+import PopoverExample from '../../examples/PopoverExample';
import SpinnerExample from '../../examples/SpinnerExample';
import SvgSymbolExample from '../../examples/SvgSymbolExample';
import SvgSymbolCircleExample from '../../examples/SvgSymbolCircleExample';
@@ -88,7 +89,7 @@ const componentsBySection = {
'icons-and-graphics': ['svg-symbol', 'svg-symbol-circle'],
navigation: ['breadcrumb', 'tab', 'hover-dropdown-menu', 'navigation-tabs'],
'feedback-and-states': ['alert', 'empty', 'spinner', 'pretty-diff'],
- dialogue: ['help-icon-popover', 'avatar'],
+ dialogue: ['popover', 'help-icon-popover', 'avatar'],
modals: ['confirm-modal'],
search: ['search', 'search-bar', 'tag'],
grouping: [
@@ -211,6 +212,7 @@ class PageLayout extends React.Component {
+
diff --git a/docs/examples/HelpIconPopoverExample.jsx b/docs/examples/HelpIconPopoverExample.jsx
index d40662ffd..68a6ca216 100644
--- a/docs/examples/HelpIconPopoverExample.jsx
+++ b/docs/examples/HelpIconPopoverExample.jsx
@@ -15,7 +15,7 @@ class HelpIconPopoverExample extends React.PureComponent {
}
const exampleProps = {
- componentName: 'Help Icon',
+ componentName: 'Help Icon Popover',
designNotes: (
diff --git a/docs/examples/PopoverExample.jsx b/docs/examples/PopoverExample.jsx
new file mode 100644
index 000000000..d150f6d4b
--- /dev/null
+++ b/docs/examples/PopoverExample.jsx
@@ -0,0 +1,127 @@
+import React from 'react';
+import Example from '../components/Example';
+import { Popover } from '../../src';
+
+class PopoverExample extends React.PureComponent {
+ render() {
+ return (
+ <>
+
+
+
+ LEFT
+
+
+ RIGHT
+
+
+ BOTTOM
+
+
+ TOP
+
+
+
+
+
+ LIGHT
+
+
+ DARK
+
+
+ WARN
+
+
+ ERROR
+
+
+ >
+ );
+ }
+}
+
+const exampleProps = {
+ componentName: 'Popover',
+ exampleCodeSnippet: `
+
+ LEFT
+
+
+
+ RIGHT
+
+
+
+ BOTTOM
+
+
+
+ TOP
+
+
+
+ LIGHT
+
+
+
+ DARK
+
+
+
+ WARN
+
+
+
+ ERROR
+
+ `,
+ propTypeSectionArray: [
+ {
+ propTypes: [
+ {
+ propType: 'id',
+ type: 'string',
+ note: 'A unique identifier for the element.',
+ },
+ {
+ propType: 'title',
+ type: 'node',
+ note: 'Title for this popover.',
+ },
+ {
+ propType: 'children',
+ type: 'node',
+ note: 'Message content for this popover.',
+ },
+ {
+ propType: 'bsClass',
+ type: 'text',
+ defaultValue: 'popover',
+ note: `Base className. The default value is 'popover'.`,
+ },
+ {
+ propType: 'className',
+ type: 'text',
+ note: 'Additional className for the component',
+ },
+ {
+ propType: 'placement',
+ type: 'oneOf[top, right, bottom, left]',
+ defaultValue: 'right',
+ },
+ {
+ propType: 'theme',
+ type: 'oneOf[light, dark, warn, error]',
+ defaultValue: 'light',
+ },
+ ],
+ },
+ ],
+};
+
+export default () => (
+
+
+
+);
diff --git a/docs/examples/styles.scss b/docs/examples/styles.scss
index d00f03d7d..b97126786 100644
--- a/docs/examples/styles.scss
+++ b/docs/examples/styles.scss
@@ -62,6 +62,15 @@
}
}
}
+
+ &.popover-example {
+ .adslot-ui-example {
+ .popover {
+ position: relative;
+ margin: 20px;
+ }
+ }
+ }
}
.full-width {
diff --git a/package-lock.json b/package-lock.json
index 7ca47422f..4f036c0c9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2579,7 +2579,7 @@
},
"string-width": {
"version": "1.0.2",
- "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
@@ -2630,7 +2630,7 @@
},
"string-width": {
"version": "1.0.2",
- "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
@@ -2909,7 +2909,7 @@
},
"content-disposition": {
"version": "0.5.2",
- "resolved": "http://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
"integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=",
"dev": true
},
@@ -6128,7 +6128,7 @@
},
"string-width": {
"version": "1.0.2",
- "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
@@ -7563,7 +7563,7 @@
},
"is-accessor-descriptor": {
"version": "0.1.6",
- "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
"integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
"dev": true,
"requires": {
@@ -7650,7 +7650,7 @@
},
"is-data-descriptor": {
"version": "0.1.4",
- "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
"integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
"dev": true,
"requires": {
@@ -14390,7 +14390,7 @@
},
"string-width": {
"version": "1.0.2",
- "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
@@ -16701,7 +16701,7 @@
},
"string-width": {
"version": "1.0.2",
- "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
@@ -16862,7 +16862,7 @@
},
"string-width": {
"version": "1.0.2",
- "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
@@ -17040,7 +17040,7 @@
},
"string-width": {
"version": "1.0.2",
- "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
diff --git a/src/components/adslot-ui/AlertInput/index.jsx b/src/components/adslot-ui/AlertInput/index.jsx
index 55126df74..ed39db877 100644
--- a/src/components/adslot-ui/AlertInput/index.jsx
+++ b/src/components/adslot-ui/AlertInput/index.jsx
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import Overlay from 'react-bootstrap/lib/Overlay';
-import Popover from 'react-bootstrap/lib/Popover';
+import { Popover } from 'third-party';
import './styles.scss';
export const baseClass = 'alert-input-component';
diff --git a/src/components/adslot-ui/HelpIconPopover/index.jsx b/src/components/adslot-ui/HelpIconPopover/index.jsx
index e3c534175..38db01623 100644
--- a/src/components/adslot-ui/HelpIconPopover/index.jsx
+++ b/src/components/adslot-ui/HelpIconPopover/index.jsx
@@ -1,10 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
-import Popover from 'react-bootstrap/lib/Popover';
import { expandDts } from 'lib/utils';
+import { Popover } from 'third-party';
-require('./styles.scss');
+import './styles.scss';
const HelpIconPopover = ({ children, id, placement }) => {
const popover =
{children};
diff --git a/src/components/adslot-ui/HelpIconPopover/index.spec.jsx b/src/components/adslot-ui/HelpIconPopover/index.spec.jsx
index 28a270d77..92ef5e460 100644
--- a/src/components/adslot-ui/HelpIconPopover/index.spec.jsx
+++ b/src/components/adslot-ui/HelpIconPopover/index.spec.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
-import Popover from 'react-bootstrap/lib/Popover';
+import { Popover } from 'third-party';
import HelpIconPopover from 'adslot-ui/HelpIconPopover';
describe('HelpIconPopoverComponent', () => {
diff --git a/src/components/adslot-ui/HoverDropdownMenu/index.jsx b/src/components/adslot-ui/HoverDropdownMenu/index.jsx
index 4c1be3ada..73a32dc16 100644
--- a/src/components/adslot-ui/HoverDropdownMenu/index.jsx
+++ b/src/components/adslot-ui/HoverDropdownMenu/index.jsx
@@ -1,7 +1,8 @@
import _ from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
-import { Overlay, Popover } from 'react-bootstrap';
+import { Overlay } from 'react-bootstrap';
+import { Popover } from 'third-party';
import PopoverLinkItem from './PopoverLinkItem';
import './styles.scss';
diff --git a/src/components/adslot-ui/TextEllipsis/index.jsx b/src/components/adslot-ui/TextEllipsis/index.jsx
index bae269ac4..5cc2ede95 100644
--- a/src/components/adslot-ui/TextEllipsis/index.jsx
+++ b/src/components/adslot-ui/TextEllipsis/index.jsx
@@ -1,7 +1,8 @@
import _ from 'lodash';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { OverlayTrigger, Popover } from 'react-bootstrap';
+import { OverlayTrigger } from 'react-bootstrap';
+import { Popover } from 'third-party';
require('./styles.scss');
diff --git a/src/components/third-party/bootstrap/Button/index.jsx b/src/components/third-party/Button/index.jsx
similarity index 93%
rename from src/components/third-party/bootstrap/Button/index.jsx
rename to src/components/third-party/Button/index.jsx
index 6cfd0963a..f76b3892b 100644
--- a/src/components/third-party/bootstrap/Button/index.jsx
+++ b/src/components/third-party/Button/index.jsx
@@ -4,8 +4,8 @@ import _ from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
+import { Popover } from 'third-party';
import BootstrapButton from 'react-bootstrap/lib/Button';
-import BootstrapPopover from 'react-bootstrap/lib/Popover';
import BootstrapOverlay from 'react-bootstrap/lib/Overlay';
import Spinner from 'alexandria/Spinner';
import { expandDts } from 'lib/utils';
@@ -47,9 +47,9 @@ class Button extends React.PureComponent {
renderReason() {
return (
-
+
{this.props.reason}
-
+
);
}
diff --git a/src/components/third-party/bootstrap/Button/index.spec.jsx b/src/components/third-party/Button/index.spec.jsx
similarity index 94%
rename from src/components/third-party/bootstrap/Button/index.spec.jsx
rename to src/components/third-party/Button/index.spec.jsx
index f8dc4b16a..bc4f71249 100644
--- a/src/components/third-party/bootstrap/Button/index.spec.jsx
+++ b/src/components/third-party/Button/index.spec.jsx
@@ -1,9 +1,8 @@
import React from 'react';
import { shallow } from 'enzyme';
import Overlay from 'react-bootstrap/lib/Overlay';
-import BootstrapPopover from 'react-bootstrap/lib/Popover';
import BootstrapButton from 'react-bootstrap/lib/Button';
-import { Button } from 'third-party';
+import { Button, Popover } from 'third-party';
import Spinner from 'alexandria/Spinner';
describe('ButtonComponent', () => {
@@ -73,7 +72,7 @@ describe('ButtonComponent', () => {
button.simulate('mouseOver');
expect(wrapper.find(Overlay).prop('show')).to.eql(true);
wrapper.update();
- expect(wrapper.find(BootstrapPopover).prop('children')).to.eql('Because');
+ expect(wrapper.find(Popover).prop('children')).to.eql('Because');
button.simulate('mouseOut');
expect(wrapper.find(Overlay).prop('show')).to.eql(false);
});
diff --git a/src/components/third-party/bootstrap/Button/styles.scss b/src/components/third-party/Button/styles.scss
similarity index 100%
rename from src/components/third-party/bootstrap/Button/styles.scss
rename to src/components/third-party/Button/styles.scss
diff --git a/src/components/third-party/Popover/index.jsx b/src/components/third-party/Popover/index.jsx
new file mode 100644
index 000000000..2cc4eeae0
--- /dev/null
+++ b/src/components/third-party/Popover/index.jsx
@@ -0,0 +1,34 @@
+import _ from 'lodash';
+import React from 'react';
+import PropTypes from 'prop-types';
+import BootstrapPopover from 'react-bootstrap/lib/Popover';
+
+export const themes = ['light', 'dark', 'warn', 'error'];
+
+class Popover extends React.Component {
+ static propTypes = {
+ id: PropTypes.string.isRequired,
+ theme: PropTypes.oneOf(themes),
+ className: PropTypes.string,
+ };
+
+ render() {
+ const { theme, children, className, ...restProps } = this.props;
+
+ const themeClass = _.includes(themes, theme) ? `popover-${theme}` : 'popover-light';
+
+ const popoverClassName = [className, themeClass].join(' ').trim();
+
+ return (
+
+ {children}
+
+ );
+ }
+}
+
+Popover.defaultProps = {
+ theme: 'light',
+};
+
+export default Popover;
diff --git a/src/components/third-party/Popover/index.spec.jsx b/src/components/third-party/Popover/index.spec.jsx
new file mode 100644
index 000000000..743952d95
--- /dev/null
+++ b/src/components/third-party/Popover/index.spec.jsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import { shallow, mount } from 'enzyme';
+import BootstrapPopover from 'react-bootstrap/lib/Popover';
+import { Popover } from 'third-party';
+
+describe('Popover Component', () => {
+ it('should render without error', () => {
+ const wrapper = shallow(
+
+
+
+ );
+
+ expect(wrapper.prop('id')).to.equal('popover-example');
+ expect(wrapper.prop('className')).to.equal('test-class popover-light');
+ expect(wrapper.prop('placement')).to.equal('right');
+ expect(wrapper.find('label').length).to.equal(1);
+ expect(wrapper.find('.message').text()).to.equal('Test message');
+ });
+
+ it('should render with the third-party component', () => {
+ const wrapper = shallow(
Test message);
+ expect(wrapper.find(BootstrapPopover).length).to.equal(1);
+ });
+
+ it('should be able to set theme', () => {
+ let wrapper = mount(
Test message);
+ expect(wrapper.find('div#popover-example').hasClass('popover-light')).to.equal(true);
+
+ wrapper = mount(
+
+ Test message
+
+ );
+ expect(wrapper.find('div#popover-example').hasClass('popover-dark')).to.equal(true);
+
+ wrapper = mount(
+
+ Test message
+
+ );
+ expect(wrapper.find('div#popover-example').hasClass('popover-warn')).to.equal(true);
+
+ wrapper = mount(
+
+ Test message
+
+ );
+ expect(wrapper.find('div#popover-example').hasClass('popover-error')).to.equal(true);
+ });
+
+ it('should not allow random theme', () => {
+ const wrapper = mount(
+
+ Test message
+
+ );
+ expect(wrapper.find('div#popover-example').hasClass('popover-light')).to.equal(true);
+ expect(wrapper.find('div#popover-example').hasClass('popover-random-theme')).to.equal(false);
+ });
+});
diff --git a/src/components/third-party/index.js b/src/components/third-party/index.js
index 303a97de5..7b6dc7ab4 100644
--- a/src/components/third-party/index.js
+++ b/src/components/third-party/index.js
@@ -1,7 +1,2 @@
-import Button from './bootstrap/Button';
-
-export { Button };
-
-export default {
- Button,
-};
+export { default as Button } from './Button';
+export { default as Popover } from './Popover';
diff --git a/src/index.js b/src/index.js
index d4bef581f..9fe68a4f1 100644
--- a/src/index.js
+++ b/src/index.js
@@ -4,14 +4,13 @@ import Select from 'react-select';
import DatePicker from 'react-datepicker';
// React Bootstrap
-import { Button } from 'third-party';
+import { Button, Popover } from 'third-party';
import Dropdown from 'react-bootstrap/lib/Dropdown';
import MenuItem from 'react-bootstrap/lib/MenuItem';
import Modal from 'react-bootstrap/lib/Modal';
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
import Pagination from 'react-bootstrap/lib/Pagination';
-import Popover from 'react-bootstrap/lib/Popover';
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
import Tab from 'react-bootstrap/lib/Tab';
import Tabs from 'react-bootstrap/lib/Tabs';
diff --git a/src/styles/_bootstrap-custom.scss b/src/styles/_bootstrap-custom.scss
index b1bf1cedd..733cbedfe 100644
--- a/src/styles/_bootstrap-custom.scss
+++ b/src/styles/_bootstrap-custom.scss
@@ -50,7 +50,6 @@
// Components w/ JavaScript
@import '../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/modals';
@import '../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/tooltip';
-@import '../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/popovers';
@import '../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/carousel';
//
// // Utility classes
diff --git a/src/styles/bootstrapOverrides/Button.scss b/src/styles/bootstrapOverrides/Button.scss
index 1712bd99e..c1b296dd1 100644
--- a/src/styles/bootstrapOverrides/Button.scss
+++ b/src/styles/bootstrapOverrides/Button.scss
@@ -1,5 +1,5 @@
@import '~styles/variable';
-@import '../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/mixins/buttons';
+@import '~bootstrap-sass/assets/stylesheets/bootstrap/mixins/buttons';
@mixin button-disabled {
background-color: $color-disabled;
diff --git a/src/styles/bootstrapOverrides/Popover.scss b/src/styles/bootstrapOverrides/Popover.scss
index 4ac877726..2d33aa86a 100644
--- a/src/styles/bootstrapOverrides/Popover.scss
+++ b/src/styles/bootstrapOverrides/Popover.scss
@@ -1,14 +1,176 @@
@import '~styles/variable';
+@import '~styles/mixins/popoverTheme';
+@import '~bootstrap-sass/assets/stylesheets/bootstrap/mixins/reset-text';
.popover {
$popover-shadow: 0 1px 0 rgba($color-shadow, .25);
+ $popover-border-radius: $border-radius * 2;
+ $popover-border-width: $border-width;
+ $arrow-outer-size: 6px;
+ $arrow-inner-size: 5px;
+ @include reset-text;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: $zindex-popover;
+ display: none;
+ max-width: $popover-max-width;
+ padding: 1px;
+ font-size: $font-size-base;
+ border: 1px solid $popover-border-color;
+ border-radius: $popover-border-radius;
box-shadow: $popover-shadow;
font-weight: $font-weight-light;
- &-content {
- $popover-vertical-padding: $spacing-etalon / 6;
- $popover-horizontal-padding: $spacing-etalon / 3;
+ // Themes
+ // Light theme
+ &.popover-light {
+ $theme-background-color: $color-white;
+ $theme-border-color: $color-gray-lighter;
+ @include theme-popover($theme-background-color, $theme-border-color);
+ color: $color-gray-darkest;
+
+ .popover-title {
+ background-color: $color-gray-lightest;
+ border-bottom-color: $theme-border-color;
+ }
+ }
+
+ // Dark theme
+ &.popover-dark {
+ $theme-background-color: $color-gray-darker;
+ $theme-border-color: $color-gray-darker;
+ @include theme-popover($theme-background-color, $theme-border-color);
+ color: $color-white;
+
+ .popover-title {
+ background-color: lighten($theme-background-color, 10%);
+ border-bottom-color: $color-white;
+ }
+ }
+
+ // Warn theme
+ &.popover-warn {
+ $theme-background-color: $color-orange;
+ $theme-border-color: $color-orange;
+ @include theme-popover($theme-background-color, $theme-border-color);
+ color: $color-white;
+
+ .popover-title {
+ background-color: lighten($theme-background-color, 10%);
+ border-bottom-color: $color-white;
+ }
+ }
+
+ // Error theme
+ &.popover-error {
+ $theme-background-color: $color-red;
+ $theme-border-color: $color-red;
+ @include theme-popover($theme-background-color, $theme-border-color);
+ color: $color-white;
+
+ .popover-title {
+ background-color: lighten($theme-background-color, 10%);
+ border-bottom-color: $color-white;
+ }
+ }
+
+ // Offset the popover to account for the popover arrow
+ &.top { margin-top: -$arrow-inner-size; }
+ &.right { margin-left: $arrow-inner-size; }
+ &.bottom { margin-top: $arrow-inner-size; }
+ &.left { margin-left: -$arrow-inner-size; }
+
+ // Arrows
+ // .arrow is outer, .arrow:after is inner
+ > .arrow {
+ border-width: $arrow-outer-size;
+
+ &,
+ &::after {
+ position: absolute;
+ display: block;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+ }
+
+ &::after {
+ content: '';
+ border-width: $arrow-inner-size;
+ }
+ }
+
+ &.top > .arrow {
+ bottom: -$arrow-outer-size;
+ left: 50%;
+ margin-left: -$arrow-outer-size;
+ border-bottom-width: 0;
+
+ &::after {
+ bottom: $popover-border-width;
+ margin-left: -$arrow-inner-size;
+ content: '';
+ border-bottom-width: 0;
+ }
+ }
+
+ &.right > .arrow {
+ top: 50%;
+ left: -$arrow-outer-size;
+ margin-top: -$arrow-outer-size;
+ border-left-width: 0;
+
+ &::after {
+ bottom: -$arrow-inner-size;
+ left: $popover-border-width;
+ content: '';
+ border-left-width: 0;
+ }
+ }
+
+ &.bottom > .arrow {
+ top: -$arrow-outer-size;
+ left: 50%;
+ margin-left: -$arrow-outer-size;
+ border-top-width: 0;
+
+ &::after {
+ top: $popover-border-width;
+ margin-left: -$arrow-inner-size;
+ content: '';
+ border-top-width: 0;
+ }
+ }
+
+ &.left > .arrow {
+ top: 50%;
+ right: -$arrow-outer-size;
+ margin-top: -$arrow-outer-size;
+ border-right-width: 0;
+
+ &::after {
+ right: $popover-border-width;
+ bottom: -$arrow-inner-size;
+ content: '';
+ border-right-width: 0;
+ }
+ }
+
+ // Title
+ .popover-title {
+ padding: 8px 14px;
+ margin: 0; // reset heading margin
+ font-size: $font-size-base;
+ border-bottom-width: $popover-border-width;
+ border-bottom-style: solid;
+ border-radius: ($popover-border-radius - 1) ($popover-border-radius - 1) 0 0;
+ }
+
+ // Content
+ .popover-content {
padding: $popover-vertical-padding $popover-horizontal-padding;
p {
diff --git a/src/styles/border.scss b/src/styles/border.scss
index e9765feb1..ae3561e56 100644
--- a/src/styles/border.scss
+++ b/src/styles/border.scss
@@ -1,7 +1,8 @@
@import 'color';
-$border-lighter: 1px solid $color-border-lighter;
-$border-dashed: 1px dashed $color-border-lighter;
+$border-width: 1px;
+$border-lighter: $border-width solid $color-border-lighter;
+$border-dashed: $border-width dashed $color-border-lighter;
$border-radius: 2px;
$border-radius-heavy: 5px;
$border-radius-circle: 50%;
diff --git a/src/styles/mixins/popoverTheme.scss b/src/styles/mixins/popoverTheme.scss
new file mode 100644
index 000000000..f6d6b28a1
--- /dev/null
+++ b/src/styles/mixins/popoverTheme.scss
@@ -0,0 +1,36 @@
+@mixin theme-popover($theme-background-color, $theme-border-color) {
+ background-color: $theme-background-color;
+ border-color: $theme-border-color;
+
+ &.top > .arrow {
+ border-top-color: $theme-border-color;
+
+ &::after {
+ border-top-color: $theme-background-color;
+ }
+ }
+
+ &.right > .arrow {
+ border-right-color: $theme-border-color;
+
+ &::after {
+ border-right-color: $theme-background-color;
+ }
+ }
+
+ &.bottom > .arrow {
+ border-bottom-color: $theme-border-color;
+
+ &::after {
+ border-bottom-color: $theme-background-color;
+ }
+ }
+
+ &.left > .arrow {
+ border-left-color: $theme-border-color;
+
+ &::after {
+ border-left-color: $theme-background-color;
+ }
+ }
+}
diff --git a/src/styles/variable.scss b/src/styles/variable.scss
index 6a7bf4a56..755f40154 100644
--- a/src/styles/variable.scss
+++ b/src/styles/variable.scss
@@ -151,3 +151,7 @@ $state-danger-border: $color-negative-selected;
// == Nav
$grid-float-breakpoint: 0; // Stops the navigation responding to breakpoints.
+
+// === Popover
+$popover-vertical-padding: $spacing-etalon / 6;
+$popover-horizontal-padding: $spacing-etalon / 3;