mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* Add KuiContextMenu. - Update KuiPopover to use K7 code. - Add KuiPanelSimple for use within KuiPopover; it's just the K7 KuiPanel renamed. - Refactor/rewrite KuiExpression and KuiExpressionButton to depend upon KuiPopover. - Add K7 shadow mixins and size and z-index vars to global_styling. * Update Dashboard panel to use KuiContextMenu. - Fix reloading issue when editing a visualization from within a dashboard. * Completely refactor KuiContextMenu to enable a single panel. - Move keyboard navigation logic into KuiContextPanel. - Set focus on the item which shows the panel we're leaving within KuiContextMenu. - Remove unnecessary logic from KuiPopoverTitle. - Replace confusing idToPanelMap and idToPreviousPanelIdMap props with a panels prop. - Replace panelRef prop with onHeightChange prop. - Migrate transition state and logic from KuiContextMenu into KuiContextMenuPanel. - Rename 'current panel' to 'incoming panel' for cohesion with 'outgoing panel.' - Map panel items to panels up-front. - Convert maps from state variables into instance variables.
This commit is contained in:
parent
b1c759b0a2
commit
20742822bc
76 changed files with 3224 additions and 838 deletions
|
@ -122,6 +122,7 @@
|
|||
"expose-loader": "0.7.0",
|
||||
"extract-text-webpack-plugin": "0.8.2",
|
||||
"file-loader": "0.8.4",
|
||||
"focus-trap-react": "3.0.3",
|
||||
"font-awesome": "4.4.0",
|
||||
"glob": "5.0.13",
|
||||
"glob-all": "3.0.1",
|
||||
|
@ -194,6 +195,7 @@
|
|||
"script-loader": "0.6.1",
|
||||
"semver": "5.1.0",
|
||||
"style-loader": "0.12.3",
|
||||
"tabbable": "1.1.0",
|
||||
"tar": "2.2.0",
|
||||
"tinygradient": "0.3.0",
|
||||
"trunc-html": "1.0.2",
|
||||
|
|
|
@ -79,10 +79,17 @@ export class DashboardGrid extends React.Component {
|
|||
};
|
||||
|
||||
onPanelFocused = panelIndex => {
|
||||
this.gridItems[panelIndex].style.zIndex = '1';
|
||||
const gridItem = this.gridItems[panelIndex];
|
||||
if (gridItem) {
|
||||
gridItem.style.zIndex = '1';
|
||||
}
|
||||
};
|
||||
|
||||
onPanelBlurred = panelIndex => {
|
||||
this.gridItems[panelIndex].style.zIndex = 'auto';
|
||||
const gridItem = this.gridItems[panelIndex];
|
||||
if (gridItem) {
|
||||
gridItem.style.zIndex = 'auto';
|
||||
}
|
||||
};
|
||||
|
||||
renderDOM() {
|
||||
|
|
|
@ -29,56 +29,6 @@ exports[`DashboardPanel matches snapshot 1`] = `
|
|||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
class="kuiPopover__body"
|
||||
>
|
||||
<ul
|
||||
class="kuiMenu"
|
||||
>
|
||||
<li
|
||||
class="kuiMenuItem dashboardPanelMenuItem"
|
||||
data-test-subj="dashboardPanelEditLink"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="kuiButton__icon kuiIcon fa-edit"
|
||||
/>
|
||||
<p
|
||||
class="kuiText"
|
||||
>
|
||||
Edit Visualization
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="kuiMenuItem dashboardPanelMenuItem"
|
||||
data-test-subj="dashboardPanelExpandIcon"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="kuiButton__icon kuiIcon fa-expand"
|
||||
/>
|
||||
<p
|
||||
class="kuiText"
|
||||
>
|
||||
Full screen
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="kuiMenuItem dashboardPanelMenuItem"
|
||||
data-test-subj="dashboardPanelRemoveIcon"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="kuiButton__icon kuiIcon fa-trash"
|
||||
/>
|
||||
<p
|
||||
class="kuiText"
|
||||
>
|
||||
Delete from dashboard
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -60,9 +60,11 @@ export class DashboardPanel extends React.Component {
|
|||
}
|
||||
|
||||
toggleExpandedPanel = () => this.props.onToggleExpanded(this.props.panel.panelIndex);
|
||||
|
||||
deletePanel = () => {
|
||||
this.props.onDeletePanel(this.props.panel.panelIndex);
|
||||
};
|
||||
|
||||
onEditPanel = () => window.location = this.state.editUrl;
|
||||
|
||||
onFocus = () => {
|
||||
|
@ -71,6 +73,7 @@ export class DashboardPanel extends React.Component {
|
|||
onPanelFocused(this.props.panel.panelIndex);
|
||||
}
|
||||
};
|
||||
|
||||
onBlur = () => {
|
||||
const { onPanelBlurred } = this.props;
|
||||
if (onPanelBlurred) {
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { KuiMenuItem } from 'ui_framework/components';
|
||||
|
||||
export function PanelMenuItem({ iconClass, onClick, label, ...props }) {
|
||||
const iconClasses = classNames('kuiButton__icon kuiIcon', iconClass);
|
||||
return (
|
||||
<KuiMenuItem
|
||||
className="dashboardPanelMenuItem"
|
||||
onClick={onClick}
|
||||
{...props}
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={iconClasses}
|
||||
/>
|
||||
<p className="kuiText">{label}</p>
|
||||
</KuiMenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
PanelMenuItem.propTypes = {
|
||||
iconClass: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
label: PropTypes.string.isRequired
|
||||
};
|
|
@ -1,78 +1,100 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { PanelMenuItem } from './panel_menu_item';
|
||||
import {
|
||||
KuiPopover,
|
||||
KuiMenu,
|
||||
KuiKeyboardAccessible
|
||||
KuiContextMenuPanel,
|
||||
KuiContextMenuItem,
|
||||
KuiKeyboardAccessible,
|
||||
} from 'ui_framework/components';
|
||||
|
||||
export class PanelOptionsMenu extends React.Component {
|
||||
state = {
|
||||
showMenu: false
|
||||
isPopoverOpen: false
|
||||
};
|
||||
|
||||
toggleMenu = () => {
|
||||
this.setState({ showMenu: !this.state.showMenu });
|
||||
this.setState({ isPopoverOpen: !this.state.isPopoverOpen });
|
||||
};
|
||||
closeMenu = () => this.setState({ showMenu: false });
|
||||
|
||||
renderEditVisualizationMenuItem() {
|
||||
return (
|
||||
<PanelMenuItem
|
||||
onClick={this.props.onEditPanel}
|
||||
closePopover = () => this.setState({ isPopoverOpen: false });
|
||||
|
||||
renderItems() {
|
||||
const items = [(
|
||||
<KuiContextMenuItem
|
||||
key="0"
|
||||
data-test-subj="dashboardPanelEditLink"
|
||||
label="Edit Visualization"
|
||||
iconClass="fa-edit"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderDeleteMenuItem() {
|
||||
return (
|
||||
<PanelMenuItem
|
||||
onClick={this.props.onDeletePanel}
|
||||
data-test-subj="dashboardPanelRemoveIcon"
|
||||
label="Delete from dashboard"
|
||||
iconClass="fa-trash"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderToggleExpandMenuItem() {
|
||||
return (
|
||||
<PanelMenuItem
|
||||
onClick={this.props.onToggleExpandPanel}
|
||||
onClick={this.props.onEditPanel}
|
||||
icon={(
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="kuiButton__icon kuiIcon fa-edit"
|
||||
/>
|
||||
)}
|
||||
>
|
||||
Edit Visualization
|
||||
</KuiContextMenuItem>
|
||||
), (
|
||||
<KuiContextMenuItem
|
||||
key="1"
|
||||
data-test-subj="dashboardPanelExpandIcon"
|
||||
label={this.props.isExpanded ? 'Minimize' : 'Full screen'}
|
||||
iconClass={this.props.isExpanded ? 'fa-compress' : 'fa-expand'}
|
||||
/>
|
||||
);
|
||||
onClick={this.props.onToggleExpandPanel}
|
||||
icon={(
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`kuiButton__icon kuiIcon ${this.props.isExpanded ? 'fa-compress' : 'fa-expand'}`}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{this.props.isExpanded ? 'Minimize' : 'Full screen'}
|
||||
</KuiContextMenuItem>
|
||||
)];
|
||||
|
||||
if (!this.props.isExpanded) {
|
||||
items.push(
|
||||
<KuiContextMenuItem
|
||||
key="2"
|
||||
data-test-subj="dashboardPanelRemoveIcon"
|
||||
onClick={this.props.onDeletePanel}
|
||||
icon={(
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="kuiButton__icon kuiIcon fa-trash"
|
||||
/>
|
||||
)}
|
||||
>
|
||||
Delete from dashboard
|
||||
</KuiContextMenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
render() {
|
||||
const button = (
|
||||
<KuiKeyboardAccessible>
|
||||
<span
|
||||
aria-label="Click for more panel options"
|
||||
className="kuiButton__icon kuiIcon panel-dropdown fa fa-caret-down"
|
||||
data-test-subj="dashboardPanelToggleMenuIcon"
|
||||
onClick={this.toggleMenu}
|
||||
/>
|
||||
</KuiKeyboardAccessible>
|
||||
);
|
||||
|
||||
return (
|
||||
<KuiPopover
|
||||
className="dashboardPanelPopOver"
|
||||
button={(
|
||||
<KuiKeyboardAccessible>
|
||||
<span
|
||||
aria-label="Click for more panel options"
|
||||
className="kuiButton__icon kuiIcon panel-dropdown fa fa-caret-down"
|
||||
data-test-subj="dashboardPanelToggleMenuIcon"
|
||||
onClick={this.toggleMenu}
|
||||
/>
|
||||
</KuiKeyboardAccessible>
|
||||
)}
|
||||
isOpen={this.state.showMenu}
|
||||
button={button}
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
closePopover={this.closePopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="right"
|
||||
closePopover={this.closeMenu}
|
||||
>
|
||||
<KuiMenu>
|
||||
{this.renderEditVisualizationMenuItem()}
|
||||
{this.renderToggleExpandMenuItem()}
|
||||
{this.props.isExpanded ? null : this.renderDeleteMenuItem()}
|
||||
</KuiMenu>
|
||||
<KuiContextMenuPanel
|
||||
onClose={this.closePopover}
|
||||
items={this.renderItems()}
|
||||
/>
|
||||
</KuiPopover>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -215,21 +215,6 @@ dashboard-panel {
|
|||
z-index: 25;
|
||||
}
|
||||
|
||||
.dashboardPanelMenuItem {
|
||||
padding: 10px;
|
||||
color: @text-color;
|
||||
|
||||
p {
|
||||
display: inline;
|
||||
padding: 0 0 0 5px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: @link-hover-color;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: inherit;
|
||||
|
||||
|
|
387
ui_framework/dist/ui_framework.css
vendored
387
ui_framework/dist/ui_framework.css
vendored
|
@ -856,6 +856,213 @@ main {
|
|||
/* 2 */
|
||||
width: 100%; }
|
||||
|
||||
.kuiContextMenu {
|
||||
width: 256px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: height 150ms cubic-bezier(0.694, 0.0482, 0.335, 1);
|
||||
border-radius: 4px; }
|
||||
.kuiContextMenu .kuiContextMenu__content {
|
||||
padding: 8px; }
|
||||
|
||||
.kuiContextMenu__panel {
|
||||
position: absolute; }
|
||||
|
||||
.kuiContextMenu__icon {
|
||||
margin-right: 8px; }
|
||||
|
||||
.kuiContextMenu__itemLayout {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center; }
|
||||
|
||||
.kuiContextMenuPanel {
|
||||
width: 100%;
|
||||
visibility: visible;
|
||||
background-color: #ffffff; }
|
||||
.kuiContextMenuPanel.kuiContextMenuPanel-txInLeft {
|
||||
pointer-events: none;
|
||||
-webkit-animation: kuiContextMenuPanelTxInLeft 250ms cubic-bezier(0.694, 0.0482, 0.335, 1);
|
||||
animation: kuiContextMenuPanelTxInLeft 250ms cubic-bezier(0.694, 0.0482, 0.335, 1); }
|
||||
.kuiContextMenuPanel.kuiContextMenuPanel-txOutLeft {
|
||||
pointer-events: none;
|
||||
-webkit-animation: kuiContextMenuPanelTxOutLeft 250ms cubic-bezier(0.694, 0.0482, 0.335, 1);
|
||||
animation: kuiContextMenuPanelTxOutLeft 250ms cubic-bezier(0.694, 0.0482, 0.335, 1); }
|
||||
.kuiContextMenuPanel.kuiContextMenuPanel-txInRight {
|
||||
pointer-events: none;
|
||||
-webkit-animation: kuiContextMenuPanelTxInRight 250ms cubic-bezier(0.694, 0.0482, 0.335, 1);
|
||||
animation: kuiContextMenuPanelTxInRight 250ms cubic-bezier(0.694, 0.0482, 0.335, 1); }
|
||||
.kuiContextMenuPanel.kuiContextMenuPanel-txOutRight {
|
||||
pointer-events: none;
|
||||
-webkit-animation: kuiContextMenuPanelTxOutRight 250ms cubic-bezier(0.694, 0.0482, 0.335, 1);
|
||||
animation: kuiContextMenuPanelTxOutRight 250ms cubic-bezier(0.694, 0.0482, 0.335, 1); }
|
||||
.theme-dark .kuiContextMenuPanel {
|
||||
background-color: #777777; }
|
||||
|
||||
.kuiContextMenuPanel--next {
|
||||
-webkit-transform: translateX(256px);
|
||||
transform: translateX(256px);
|
||||
visibility: hidden; }
|
||||
|
||||
.kuiContextMenuPanel--previous {
|
||||
-webkit-transform: translateX(-256px);
|
||||
transform: translateX(-256px);
|
||||
visibility: hidden; }
|
||||
|
||||
/**
|
||||
* 1. Button reset.
|
||||
*/
|
||||
.kuiContextMenuPanelTitle {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
/* 1 */
|
||||
border: none;
|
||||
/* 1 */
|
||||
cursor: pointer;
|
||||
/* 1 */
|
||||
background-color: #e6e6e6;
|
||||
border-bottom: 1px solid #D9D9D9;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
/**
|
||||
* 1. Overwrite default style.
|
||||
*/ }
|
||||
.theme-dark .kuiContextMenuPanelTitle {
|
||||
background-color: #777777;
|
||||
border-color: #444444;
|
||||
color: #ffffff; }
|
||||
.kuiContextMenuPanelTitle:hover .kuiContextMenu__text, .kuiContextMenuPanelTitle:focus .kuiContextMenu__text {
|
||||
text-decoration: underline; }
|
||||
.kuiContextMenuPanelTitle:focus {
|
||||
box-shadow: none;
|
||||
/* 1 */ }
|
||||
|
||||
@-webkit-keyframes kuiContextMenuPanelTxInLeft {
|
||||
0% {
|
||||
-webkit-transform: translateX(100%);
|
||||
transform: translateX(100%); }
|
||||
100% {
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0); } }
|
||||
|
||||
@keyframes kuiContextMenuPanelTxInLeft {
|
||||
0% {
|
||||
-webkit-transform: translateX(100%);
|
||||
transform: translateX(100%); }
|
||||
100% {
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0); } }
|
||||
|
||||
@-webkit-keyframes kuiContextMenuPanelTxOutLeft {
|
||||
0% {
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0); }
|
||||
100% {
|
||||
-webkit-transform: translateX(-100%);
|
||||
transform: translateX(-100%); } }
|
||||
|
||||
@keyframes kuiContextMenuPanelTxOutLeft {
|
||||
0% {
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0); }
|
||||
100% {
|
||||
-webkit-transform: translateX(-100%);
|
||||
transform: translateX(-100%); } }
|
||||
|
||||
@-webkit-keyframes kuiContextMenuPanelTxInRight {
|
||||
0% {
|
||||
-webkit-transform: translateX(-100%);
|
||||
transform: translateX(-100%); }
|
||||
100% {
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0); } }
|
||||
|
||||
@keyframes kuiContextMenuPanelTxInRight {
|
||||
0% {
|
||||
-webkit-transform: translateX(-100%);
|
||||
transform: translateX(-100%); }
|
||||
100% {
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0); } }
|
||||
|
||||
@-webkit-keyframes kuiContextMenuPanelTxOutRight {
|
||||
0% {
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0); }
|
||||
100% {
|
||||
-webkit-transform: translateX(100%);
|
||||
transform: translateX(100%); } }
|
||||
|
||||
@keyframes kuiContextMenuPanelTxOutRight {
|
||||
0% {
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0); }
|
||||
100% {
|
||||
-webkit-transform: translateX(100%);
|
||||
transform: translateX(100%); } }
|
||||
|
||||
/**
|
||||
* 1. Button reset.
|
||||
* 2. Ensure buttons stack.
|
||||
*/
|
||||
.kuiContextMenuItem {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
/* 1 */
|
||||
background-color: transparent;
|
||||
/* 1 */
|
||||
font-size: 14px;
|
||||
/* 1 */
|
||||
border: none;
|
||||
/* 1 */
|
||||
cursor: pointer;
|
||||
/* 1 */
|
||||
display: block;
|
||||
/* 2 */
|
||||
padding: 12px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
color: #2d2d2d;
|
||||
/**
|
||||
* 1. Overwrite default style.
|
||||
*/ }
|
||||
.kuiContextMenuItem:hover .kuiContextMenuItem__text, .kuiContextMenuItem:focus .kuiContextMenuItem__text {
|
||||
text-decoration: underline; }
|
||||
.kuiContextMenuItem:focus {
|
||||
background-color: rgba(63, 168, 199, 0.2);
|
||||
box-shadow: none;
|
||||
/* 1 */ }
|
||||
.theme-dark .kuiContextMenuItem:focus {
|
||||
background-color: transparent; }
|
||||
.theme-dark .kuiContextMenuItem {
|
||||
color: #ffffff; }
|
||||
|
||||
.kuiContextMenuItem__inner {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex; }
|
||||
|
||||
.kuiContextMenuItem__text {
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex-grow: 1;
|
||||
-ms-flex-positive: 1;
|
||||
flex-grow: 1; }
|
||||
|
||||
.kuiContextMenuItem__arrow {
|
||||
-webkit-align-self: flex-end;
|
||||
-ms-flex-item-align: end;
|
||||
align-self: flex-end; }
|
||||
|
||||
.kuiEvent {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
|
@ -887,13 +1094,11 @@ main {
|
|||
line-height: 1.5;
|
||||
color: #666; }
|
||||
|
||||
.kuiExpressionItem {
|
||||
display: inline-block;
|
||||
position: relative; }
|
||||
.kuiExpressionItem + .kuiExpressionItem {
|
||||
margin-left: 10px; }
|
||||
.kuiExpression {
|
||||
padding: 20px;
|
||||
white-space: nowrap; }
|
||||
|
||||
.kuiExpressionItem__button {
|
||||
.kuiExpressionButton {
|
||||
background-color: transparent;
|
||||
padding: 5px 0px;
|
||||
border: none;
|
||||
|
@ -901,95 +1106,17 @@ main {
|
|||
font-size: 14px;
|
||||
cursor: pointer; }
|
||||
|
||||
.kuiExpressionItem__buttonDescription {
|
||||
.kuiExpressionButton__description {
|
||||
color: #00A69B;
|
||||
text-transform: uppercase; }
|
||||
|
||||
.kuiExpressionItem__buttonValue {
|
||||
.kuiExpressionButton__value {
|
||||
color: #2d2d2d;
|
||||
text-transform: lowercase; }
|
||||
|
||||
.kuiExpressionItem__button--isActive {
|
||||
.kuiExpressionButton-isActive {
|
||||
border-bottom: solid 2px #00A69B; }
|
||||
|
||||
.kuiExpressionItem__popover {
|
||||
position: absolute;
|
||||
top: calc(100% + 15px);
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex: 1 1 auto;
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
background-color: white;
|
||||
border: 1px solid #D9D9D9;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.1);
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(-5px) translateZ(0);
|
||||
transform: translateY(-5px) translateZ(0);
|
||||
transition: opacity 250ms cubic-bezier(0.34, 1.61, 0.7, 1), -webkit-transform 250ms cubic-bezier(0.34, 1.61, 0.7, 1);
|
||||
transition: transform 250ms cubic-bezier(0.34, 1.61, 0.7, 1), opacity 250ms cubic-bezier(0.34, 1.61, 0.7, 1);
|
||||
transition: transform 250ms cubic-bezier(0.34, 1.61, 0.7, 1), opacity 250ms cubic-bezier(0.34, 1.61, 0.7, 1), -webkit-transform 250ms cubic-bezier(0.34, 1.61, 0.7, 1); }
|
||||
.kuiExpressionItem__popover.ng-hide {
|
||||
display: block !important;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(0px) translateZ(0);
|
||||
transform: translateY(0px) translateZ(0); }
|
||||
.kuiExpressionItem__popover:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: -8px;
|
||||
left: 20px;
|
||||
height: 0;
|
||||
width: 0;
|
||||
border-left: 8px solid transparent;
|
||||
border-right: 8px solid transparent;
|
||||
border-bottom: 8px solid #D9D9D9; }
|
||||
.kuiExpressionItem__popover:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: -7px;
|
||||
left: 20px;
|
||||
height: 0;
|
||||
width: 0;
|
||||
border-left: 8px solid transparent;
|
||||
border-right: 8px solid transparent;
|
||||
border-bottom: 8px solid #e6e6e6; }
|
||||
.kuiExpressionItem__popover.kuiExpressionItem__popover--alignRight {
|
||||
right: 0; }
|
||||
.kuiExpressionItem__popover.kuiExpressionItem__popover--alignRight:before, .kuiExpressionItem__popover.kuiExpressionItem__popover--alignRight:after {
|
||||
left: auto;
|
||||
right: 20px; }
|
||||
|
||||
.kuiExpressionItem__popoverTitle {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex: 1 1 auto;
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
background-color: #e6e6e6;
|
||||
border-radius: 4px 4px 0 0;
|
||||
color: #2d2d2d;
|
||||
padding: 5px 10px;
|
||||
line-height: 1.5; }
|
||||
|
||||
.kuiExpressionItem__popoverContent {
|
||||
padding: 20px;
|
||||
white-space: nowrap; }
|
||||
|
||||
.kuiFlexGroup {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
|
@ -3112,38 +3239,59 @@ main {
|
|||
.kuiPanelBody {
|
||||
padding: 10px; }
|
||||
|
||||
.kuiPanelSimple {
|
||||
box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1);
|
||||
background-color: #FFF;
|
||||
border: 1px solid #D9D9D9;
|
||||
border-radius: 4px;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex-grow: 1;
|
||||
-ms-flex-positive: 1;
|
||||
flex-grow: 1; }
|
||||
.kuiPanelSimple.kuiPanelSimple--paddingSmall {
|
||||
padding: 8px; }
|
||||
.kuiPanelSimple.kuiPanelSimple--paddingMedium {
|
||||
padding: 16px; }
|
||||
.kuiPanelSimple.kuiPanelSimple--paddingLarge {
|
||||
padding: 24px; }
|
||||
.kuiPanelSimple.kuiPanelSimple--shadow {
|
||||
box-shadow: 0 16px 16px -8px rgba(0, 0, 0, 0.1); }
|
||||
.kuiPanelSimple.kuiPanelSimple--flexGrowZero {
|
||||
-webkit-box-flex: 0;
|
||||
-webkit-flex-grow: 0;
|
||||
-ms-flex-positive: 0;
|
||||
flex-grow: 0; }
|
||||
.theme-dark .kuiPanelSimple {
|
||||
background-color: #777777;
|
||||
border-color: #444444; }
|
||||
|
||||
.kuiPopover {
|
||||
display: inline-block;
|
||||
position: relative; }
|
||||
.kuiPopover.kuiPopover-isOpen .kuiPopover__body {
|
||||
.kuiPopover.kuiPopover-isOpen .kuiPopover__panel {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
display: inline-block;
|
||||
z-index: 1;
|
||||
margin-top: 10px;
|
||||
box-shadow: 0 16px 16px -8px rgba(0, 0, 0, 0.1); }
|
||||
z-index: 2000;
|
||||
margin-top: 8px;
|
||||
pointer-events: auto; }
|
||||
|
||||
.kuiPopover__body {
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
.kuiPopover__panel {
|
||||
position: absolute;
|
||||
min-width: 256px;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
background: #FFF;
|
||||
border: 1px solid #D9D9D9;
|
||||
border-radius: 4px 0 4px 4px;
|
||||
padding: 16px;
|
||||
-webkit-transform: translateX(-50%) translateY(8px) translateZ(0);
|
||||
transform: translateX(-50%) translateY(8px) translateZ(0);
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
transition: opacity cubic-bezier(0.34, 1.61, 0.7, 1) 350ms, visibility cubic-bezier(0.34, 1.61, 0.7, 1) 350ms, margin-top cubic-bezier(0.34, 1.61, 0.7, 1) 350ms;
|
||||
-webkit-transform-origin: center top;
|
||||
transform-origin: center top;
|
||||
opacity: 0;
|
||||
display: none;
|
||||
margin-top: 32px; }
|
||||
.kuiPopover__body:before {
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
margin-top: 24px; }
|
||||
.kuiPopover__panel:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: -16px;
|
||||
|
@ -3154,7 +3302,9 @@ main {
|
|||
border-left: 16px solid transparent;
|
||||
border-right: 16px solid transparent;
|
||||
border-bottom: 16px solid #D9D9D9; }
|
||||
.kuiPopover__body:after {
|
||||
.theme-dark .kuiPopover__panel:before {
|
||||
border-bottom-color: #444444; }
|
||||
.kuiPopover__panel:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: -15px;
|
||||
|
@ -3165,25 +3315,42 @@ main {
|
|||
width: 0;
|
||||
border-left: 16px solid transparent;
|
||||
border-right: 16px solid transparent;
|
||||
border-bottom: 16px solid #FFF; }
|
||||
border-bottom: 16px solid #ffffff; }
|
||||
.theme-dark .kuiPopover__panel:after {
|
||||
border-bottom-color: #777777; }
|
||||
|
||||
.kuiPopover--anchorLeft .kuiPopover__body {
|
||||
.kuiPopover--withTitle .kuiPopover__panel:after {
|
||||
border-bottom-color: #e6e6e6; }
|
||||
.theme-dark .kuiPopover--withTitle .kuiPopover__panel:after {
|
||||
border-bottom-color: #777777; }
|
||||
|
||||
.kuiPopover--anchorLeft .kuiPopover__panel {
|
||||
left: 0;
|
||||
-webkit-transform: translateX(0%) translateY(8px) translateZ(0);
|
||||
transform: translateX(0%) translateY(8px) translateZ(0); }
|
||||
.kuiPopover--anchorLeft .kuiPopover__body:before, .kuiPopover--anchorLeft .kuiPopover__body:after {
|
||||
.kuiPopover--anchorLeft .kuiPopover__panel:before, .kuiPopover--anchorLeft .kuiPopover__panel:after {
|
||||
right: auto;
|
||||
left: 8px;
|
||||
left: 16px;
|
||||
margin: 0; }
|
||||
|
||||
.kuiPopover--anchorRight .kuiPopover__body {
|
||||
.kuiPopover--anchorRight .kuiPopover__panel {
|
||||
left: 100%;
|
||||
-webkit-transform: translateX(-100%) translateY(8px) translateZ(0);
|
||||
transform: translateX(-100%) translateY(8px) translateZ(0); }
|
||||
.kuiPopover--anchorRight .kuiPopover__body:before, .kuiPopover--anchorRight .kuiPopover__body:after {
|
||||
right: 8px;
|
||||
.kuiPopover--anchorRight .kuiPopover__panel:before, .kuiPopover--anchorRight .kuiPopover__panel:after {
|
||||
right: 16px;
|
||||
left: auto; }
|
||||
|
||||
.kuiPopoverTitle {
|
||||
background-color: #e6e6e6;
|
||||
border-bottom: 1px solid #D9D9D9;
|
||||
padding: 12px;
|
||||
font-size: 14px; }
|
||||
.theme-dark .kuiPopoverTitle {
|
||||
background-color: #777777;
|
||||
border-color: #444444;
|
||||
color: #ffffff; }
|
||||
|
||||
.kuiEmptyTablePrompt {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
|
|
|
@ -34,15 +34,30 @@ export class GuideDemo extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const classes = classNames('guideDemo', this.props.className, {
|
||||
'guideDemo--fullScreen': this.props.isFullScreen,
|
||||
'guideDemo--darkTheme': this.props.isDarkTheme,
|
||||
'theme-dark': this.props.isDarkTheme,
|
||||
const {
|
||||
isFullScreen,
|
||||
isDarkTheme,
|
||||
children,
|
||||
className,
|
||||
js, // eslint-disable-line no-unused-vars
|
||||
html, // eslint-disable-line no-unused-vars
|
||||
css, // eslint-disable-line no-unused-vars
|
||||
...rest,
|
||||
} = this.props;
|
||||
|
||||
const classes = classNames('guideDemo', className, {
|
||||
'guideDemo--fullScreen': isFullScreen,
|
||||
'guideDemo--darkTheme': isDarkTheme,
|
||||
'theme-dark': isDarkTheme,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={classes} ref={c => (this.content = c)}>
|
||||
{this.props.children}
|
||||
<div
|
||||
className={classes}
|
||||
ref={c => (this.content = c)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,9 @@ import ColorPickerExample
|
|||
import ColumnExample
|
||||
from '../../views/column/column_example';
|
||||
|
||||
import ContextMenuExample
|
||||
from '../../views/context_menu/context_menu_example';
|
||||
|
||||
import EventExample
|
||||
from '../../views/event/event_example';
|
||||
|
||||
|
@ -93,6 +96,9 @@ import PagerExample
|
|||
import PanelExample
|
||||
from '../../views/panel/panel_example';
|
||||
|
||||
import PanelSimpleExample
|
||||
from '../../views/panel_simple/panel_simple_example';
|
||||
|
||||
import PopoverExample
|
||||
from '../../views/popover/popover_example';
|
||||
|
||||
|
@ -162,6 +168,14 @@ const components = [{
|
|||
}, {
|
||||
name: 'Column',
|
||||
component: ColumnExample,
|
||||
}, {
|
||||
name: 'CollapseButton',
|
||||
component: CollapseButtonExample,
|
||||
hasReact: true,
|
||||
}, {
|
||||
name: 'ContextMenu',
|
||||
component: ContextMenuExample,
|
||||
hasReact: true,
|
||||
}, {
|
||||
name: 'EmptyTablePrompt',
|
||||
component: EmptyTablePromptExample,
|
||||
|
@ -230,6 +244,10 @@ const components = [{
|
|||
}, {
|
||||
name: 'Panel',
|
||||
component: PanelExample,
|
||||
}, {
|
||||
name: 'PanelSimple',
|
||||
component: PanelSimpleExample,
|
||||
hasReact: true,
|
||||
}, {
|
||||
name: 'Popover',
|
||||
component: PopoverExample,
|
||||
|
|
151
ui_framework/doc_site/src/views/context_menu/context_menu.js
Normal file
151
ui_framework/doc_site/src/views/context_menu/context_menu.js
Normal file
|
@ -0,0 +1,151 @@
|
|||
import React, {
|
||||
Component,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
KuiButton,
|
||||
KuiContextMenu,
|
||||
KuiFieldGroup,
|
||||
KuiFieldGroupSection,
|
||||
KuiPopover,
|
||||
} from '../../../../components';
|
||||
|
||||
function flattenPanelTree(tree, array = []) {
|
||||
array.push(tree);
|
||||
|
||||
if (tree.items) {
|
||||
tree.items.forEach(item => {
|
||||
if (item.panel) {
|
||||
flattenPanelTree(item.panel, array);
|
||||
item.panel = item.panel.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
export default class extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isPopoverOpen: false,
|
||||
};
|
||||
|
||||
const panelTree = {
|
||||
id: 0,
|
||||
title: 'View options',
|
||||
items: [{
|
||||
name: 'Show fullscreen',
|
||||
icon: (
|
||||
<span className="kuiIcon fa-search" />
|
||||
),
|
||||
onClick: () => window.alert('Show fullscreen'),
|
||||
}, {
|
||||
name: 'Share this dasbhoard',
|
||||
icon: <span className="kuiIcon fa-user" />,
|
||||
panel: {
|
||||
id: 1,
|
||||
title: 'Share this dashboard',
|
||||
items: [{
|
||||
name: 'PDF reports',
|
||||
icon: <span className="kuiIcon fa-user" />,
|
||||
onClick: () => window.alert('PDF reports'),
|
||||
}, {
|
||||
name: 'CSV reports',
|
||||
icon: <span className="kuiIcon fa-user" />,
|
||||
onClick: () => window.alert('CSV reports'),
|
||||
}, {
|
||||
name: 'Embed code',
|
||||
icon: <span className="kuiIcon fa-user" />,
|
||||
panel: {
|
||||
id: 2,
|
||||
title: 'Embed code',
|
||||
content: (
|
||||
<div style={{ padding: 16 }}>
|
||||
<div className="kuiVerticalRhythmSmall">
|
||||
<KuiFieldGroup>
|
||||
<KuiFieldGroupSection isWide>
|
||||
<div className="kuiSearchInput">
|
||||
<div className="kuiSearchInput__icon kuiIcon fa-search" />
|
||||
<input
|
||||
className="kuiSearchInput__input"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
</KuiFieldGroupSection>
|
||||
|
||||
<KuiFieldGroupSection>
|
||||
<select className="kuiSelect">
|
||||
<option>Animal</option>
|
||||
<option>Mineral</option>
|
||||
<option>Vegetable</option>
|
||||
</select>
|
||||
</KuiFieldGroupSection>
|
||||
</KuiFieldGroup>
|
||||
</div>
|
||||
|
||||
<div className="kuiVerticalRhythmSmall">
|
||||
<KuiButton buttonType="primary">Save</KuiButton>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
}, {
|
||||
name: 'Permalinks',
|
||||
icon: <span className="kuiIcon fa-user" />,
|
||||
onClick: () => window.alert('Permalinks'),
|
||||
}],
|
||||
},
|
||||
}, {
|
||||
name: 'Edit / add panels',
|
||||
icon: <span className="kuiIcon fa-user" />,
|
||||
onClick: () => window.alert('Edit / add panels'),
|
||||
}, {
|
||||
name: 'Display options',
|
||||
icon: <span className="kuiIcon fa-user" />,
|
||||
onClick: () => window.alert('Display options'),
|
||||
}],
|
||||
};
|
||||
|
||||
this.panels = flattenPanelTree(panelTree);
|
||||
}
|
||||
|
||||
onButtonClick() {
|
||||
this.setState({
|
||||
isPopoverOpen: !this.state.isPopoverOpen,
|
||||
});
|
||||
}
|
||||
|
||||
closePopover() {
|
||||
this.setState({
|
||||
isPopoverOpen: false,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const button = (
|
||||
<KuiButton buttonType="basic" onClick={this.onButtonClick.bind(this)}>
|
||||
Click me to load a context menu
|
||||
</KuiButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<KuiPopover
|
||||
button={button}
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
closePopover={this.closePopover.bind(this)}
|
||||
panelPaddingSize="none"
|
||||
withTitle
|
||||
anchorPosition="left"
|
||||
>
|
||||
<KuiContextMenu
|
||||
initialPanelId={0}
|
||||
isVisible={this.state.isPopoverOpen}
|
||||
panels={this.panels}
|
||||
/>
|
||||
</KuiPopover>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
import React from 'react';
|
||||
|
||||
import { renderToHtml } from '../../services';
|
||||
|
||||
import {
|
||||
GuideCode,
|
||||
GuideDemo,
|
||||
GuidePage,
|
||||
GuideSection,
|
||||
GuideSectionTypes,
|
||||
GuideText,
|
||||
} from '../../components';
|
||||
|
||||
import ContextMenu from './context_menu';
|
||||
const contextMenuSource = require('!!raw!./context_menu');
|
||||
const contextMenuHtml = renderToHtml(ContextMenu);
|
||||
|
||||
import SinglePanel from './single_panel';
|
||||
const singlePanelSource = require('!!raw!./single_panel');
|
||||
const singlePanelHtml = renderToHtml(SinglePanel);
|
||||
|
||||
export default props => (
|
||||
<GuidePage title={props.route.name}>
|
||||
<GuideSection
|
||||
title="Context Menu"
|
||||
source={[{
|
||||
type: GuideSectionTypes.JS,
|
||||
code: contextMenuSource,
|
||||
}, {
|
||||
type: GuideSectionTypes.HTML,
|
||||
code: contextMenuHtml,
|
||||
}]}
|
||||
>
|
||||
<GuideText>
|
||||
<GuideCode>KuiContextMenu</GuideCode> is a nested menu system useful
|
||||
for navigating complicated trees. It lives within a <GuideCode>KuiPopover</GuideCode>
|
||||
which itself can be wrapped around any component (like a button in this example).
|
||||
</GuideText>
|
||||
|
||||
<GuideDemo style={{ height: 280 }}>
|
||||
<ContextMenu />
|
||||
</GuideDemo>
|
||||
|
||||
<GuideDemo isDarkTheme={true} style={{ height: 280 }}>
|
||||
<ContextMenu />
|
||||
</GuideDemo>
|
||||
</GuideSection>
|
||||
|
||||
<GuideSection
|
||||
title="Single panel"
|
||||
source={[{
|
||||
type: GuideSectionTypes.JS,
|
||||
code: singlePanelSource,
|
||||
}, {
|
||||
type: GuideSectionTypes.HTML,
|
||||
code: singlePanelHtml,
|
||||
}]}
|
||||
>
|
||||
<GuideText>
|
||||
You can put a single panel inside of the menu using the
|
||||
<GuideCode>KuiContextMenuPanel</GuideCode> component directly.
|
||||
</GuideText>
|
||||
|
||||
<GuideDemo style={{ height: 280 }}>
|
||||
<SinglePanel />
|
||||
</GuideDemo>
|
||||
</GuideSection>
|
||||
</GuidePage>
|
||||
);
|
82
ui_framework/doc_site/src/views/context_menu/single_panel.js
Normal file
82
ui_framework/doc_site/src/views/context_menu/single_panel.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
import React, {
|
||||
Component,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
KuiButton,
|
||||
KuiContextMenuPanel,
|
||||
KuiContextMenuItem,
|
||||
KuiPopover,
|
||||
} from '../../../../components';
|
||||
|
||||
export default class extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isPopoverOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
onButtonClick() {
|
||||
this.setState({
|
||||
isPopoverOpen: !this.state.isPopoverOpen,
|
||||
});
|
||||
}
|
||||
|
||||
closePopover() {
|
||||
this.setState({
|
||||
isPopoverOpen: false,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const button = (
|
||||
<KuiButton buttonType="basic" onClick={this.onButtonClick.bind(this)}>
|
||||
Click me to load a context menu
|
||||
</KuiButton>
|
||||
);
|
||||
|
||||
const items = [(
|
||||
<KuiContextMenuItem
|
||||
key="A"
|
||||
icon={<span className="kuiIcon fa-user" />}
|
||||
onClick={() => { window.alert('A'); }}
|
||||
>
|
||||
Option A
|
||||
</KuiContextMenuItem>
|
||||
), (
|
||||
<KuiContextMenuItem
|
||||
key="B"
|
||||
icon={<span className="kuiIcon fa-user" />}
|
||||
onClick={() => { window.alert('B'); }}
|
||||
>
|
||||
Option B
|
||||
</KuiContextMenuItem>
|
||||
), (
|
||||
<KuiContextMenuItem
|
||||
key="C"
|
||||
icon={<span className="kuiIcon fa-user" />}
|
||||
onClick={() => { window.alert('C'); }}
|
||||
>
|
||||
Option C
|
||||
</KuiContextMenuItem>
|
||||
)];
|
||||
|
||||
return (
|
||||
<KuiPopover
|
||||
button={button}
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
closePopover={this.closePopover.bind(this)}
|
||||
panelPaddingSize="none"
|
||||
withTitle
|
||||
anchorPosition="left"
|
||||
>
|
||||
<KuiContextMenuPanel
|
||||
title="Options"
|
||||
items={items}
|
||||
/>
|
||||
</KuiPopover>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import {
|
||||
KuiExpressionItem,
|
||||
KuiExpressionItemButton,
|
||||
KuiExpressionItemPopover,
|
||||
KuiExpression,
|
||||
KuiExpressionButton,
|
||||
KuiFieldGroup,
|
||||
KuiFieldGroupSection,
|
||||
KuiPopover,
|
||||
KuiPopoverTitle,
|
||||
} from '../../../../components';
|
||||
|
||||
|
||||
|
@ -12,6 +15,7 @@ class KuiExpressionItemExample extends React.Component {
|
|||
|
||||
this.state = {
|
||||
example1: {
|
||||
isOpen: false,
|
||||
value: 'count()'
|
||||
},
|
||||
example2: {
|
||||
|
@ -19,10 +23,53 @@ class KuiExpressionItemExample extends React.Component {
|
|||
value: '100',
|
||||
description: 'Is above'
|
||||
},
|
||||
activeButton: props.defaultActiveButton
|
||||
};
|
||||
}
|
||||
|
||||
openExample1 = () => {
|
||||
this.setState({
|
||||
example1: {
|
||||
...this.state.example1,
|
||||
isOpen: true,
|
||||
},
|
||||
example2: {
|
||||
...this.state.example2,
|
||||
isOpen: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
closeExample1 = () => {
|
||||
this.setState({
|
||||
example1: {
|
||||
...this.state.example1,
|
||||
isOpen: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
openExample2 = () => {
|
||||
this.setState({
|
||||
example1: {
|
||||
...this.state.example1,
|
||||
isOpen: false,
|
||||
},
|
||||
example2: {
|
||||
...this.state.example2,
|
||||
isOpen: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
closeExample2 = () => {
|
||||
this.setState({
|
||||
example2: {
|
||||
...this.state.example2,
|
||||
isOpen: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
changeExample1 = (event) => {
|
||||
this.setState({ example1: { ...this.state.example1, value: event.target.value } });
|
||||
}
|
||||
|
@ -39,73 +86,81 @@ class KuiExpressionItemExample extends React.Component {
|
|||
this.setState({ example2: { ...this.state.example2, description: event.target.value } });
|
||||
}
|
||||
|
||||
onOutsideClick = () => {
|
||||
this.setState({ activeButton:null });
|
||||
}
|
||||
|
||||
render() {
|
||||
//Rise the popovers above GuidePageSideNav
|
||||
const popoverStyle = { zIndex:'200' };
|
||||
|
||||
const popover1 = (this.state.activeButton === 'example1') ? this.getPopover1(popoverStyle) : null;
|
||||
const popover2 = (this.state.activeButton === 'example2') ? this.getPopover2(popoverStyle) : null;
|
||||
// Rise the popovers above GuidePageSideNav
|
||||
const popoverStyle = { zIndex: '200' };
|
||||
|
||||
return (
|
||||
<div>
|
||||
<KuiExpressionItem key="example1">
|
||||
<KuiExpressionItemButton
|
||||
description="when"
|
||||
buttonValue={this.state.example1.value}
|
||||
isActive={this.state.activeButton === 'example1'}
|
||||
onClick={()=>this.setState({ activeButton:'example1' })}
|
||||
/>
|
||||
{popover1}
|
||||
</KuiExpressionItem>
|
||||
<KuiExpressionItem key="example2">
|
||||
<KuiExpressionItemButton
|
||||
description={this.state.example2.description}
|
||||
buttonValue={this.state.example2.value}
|
||||
isActive={this.state.activeButton === 'example2'}
|
||||
onClick={()=>this.setState({ activeButton:'example2' })}
|
||||
/>
|
||||
{popover2}
|
||||
</KuiExpressionItem>
|
||||
</div>
|
||||
<KuiFieldGroup>
|
||||
<KuiFieldGroupSection>
|
||||
<KuiPopover
|
||||
button={(
|
||||
<KuiExpressionButton
|
||||
description="when"
|
||||
buttonValue={this.state.example1.value}
|
||||
isActive={this.state.example1.isOpen}
|
||||
onClick={this.openExample1}
|
||||
/>
|
||||
)}
|
||||
isOpen={this.state.example1.isOpen}
|
||||
closePopover={this.closeExample1}
|
||||
panelPaddingSize="none"
|
||||
withTitle
|
||||
>
|
||||
{this.getPopover1(popoverStyle)}
|
||||
</KuiPopover>
|
||||
</KuiFieldGroupSection>
|
||||
|
||||
<KuiFieldGroupSection>
|
||||
<KuiPopover
|
||||
button={(
|
||||
<KuiExpressionButton
|
||||
description={this.state.example2.description}
|
||||
buttonValue={this.state.example2.value}
|
||||
isActive={this.state.example2.isOpen}
|
||||
onClick={this.openExample2}
|
||||
/>
|
||||
)}
|
||||
isOpen={this.state.example2.isOpen}
|
||||
closePopover={this.closeExample2}
|
||||
panelPaddingSize="none"
|
||||
withTitle
|
||||
anchorPosition="left"
|
||||
>
|
||||
{this.getPopover2(popoverStyle)}
|
||||
</KuiPopover>
|
||||
</KuiFieldGroupSection>
|
||||
</KuiFieldGroup>
|
||||
);
|
||||
}
|
||||
|
||||
getPopover1(popoverStyle) {
|
||||
return (
|
||||
<KuiExpressionItemPopover
|
||||
title="When"
|
||||
onOutsideClick={this.onOutsideClick}
|
||||
style={popoverStyle}
|
||||
>
|
||||
<select
|
||||
className="kuiSelect"
|
||||
value={this.state.example1.value}
|
||||
onChange={this.changeExample1}
|
||||
>
|
||||
<option label="count()">count()</option>
|
||||
<option label="average()">average()</option>
|
||||
<option label="sum()">sum()</option>
|
||||
<option label="median()">median()</option>
|
||||
<option label="min()">min()</option>
|
||||
<option label="max()">max()</option>
|
||||
</select>
|
||||
</KuiExpressionItemPopover>
|
||||
<div style={popoverStyle}>
|
||||
<KuiPopoverTitle>When</KuiPopoverTitle>
|
||||
<KuiExpression>
|
||||
<select
|
||||
className="kuiSelect"
|
||||
value={this.state.example1.value}
|
||||
onChange={this.changeExample1}
|
||||
>
|
||||
<option label="count()">count()</option>
|
||||
<option label="average()">average()</option>
|
||||
<option label="sum()">sum()</option>
|
||||
<option label="median()">median()</option>
|
||||
<option label="min()">min()</option>
|
||||
<option label="max()">max()</option>
|
||||
</select>
|
||||
</KuiExpression>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getPopover2(popoverStyle) {
|
||||
return (
|
||||
<KuiExpressionItemPopover
|
||||
title={this.state.example2.description}
|
||||
onOutsideClick={this.onOutsideClick}
|
||||
align="right"
|
||||
style={popoverStyle}
|
||||
>
|
||||
<div>
|
||||
<div style={popoverStyle}>
|
||||
<KuiPopoverTitle>{this.state.example2.description}</KuiPopoverTitle>
|
||||
<KuiExpression>
|
||||
<select
|
||||
className="kuiSelect"
|
||||
value={this.state.example2.object}
|
||||
|
@ -132,8 +187,8 @@ class KuiExpressionItemExample extends React.Component {
|
|||
<option label="Is below">Is below</option>
|
||||
<option label="Is exactly">Is exactly</option>
|
||||
</select>
|
||||
</div>
|
||||
</KuiExpressionItemPopover>
|
||||
</KuiExpression>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ const expressionHtml = renderToHtml(Expression, { defaultActiveButton: 'example2
|
|||
export default props => (
|
||||
<GuidePage title={props.route.name}>
|
||||
<GuideSection
|
||||
title="ExpressionItem"
|
||||
title="ExpressionButton"
|
||||
source={[{
|
||||
type: GuideSectionTypes.JS,
|
||||
code: expressionSource,
|
||||
|
@ -26,8 +26,7 @@ export default props => (
|
|||
}]}
|
||||
>
|
||||
<GuideText>
|
||||
ExpressionItems allow you to compress a complicated form into a small space.
|
||||
Left aligned to the button by default. Can be optionally right aligned (as in the last example).
|
||||
ExpressionButtons allow you to compress a complicated form into a small space.
|
||||
</GuideText>
|
||||
|
||||
<GuideDemo>
|
||||
|
|
37
ui_framework/doc_site/src/views/panel_simple/panel_simple.js
Normal file
37
ui_framework/doc_site/src/views/panel_simple/panel_simple.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
import React from 'react';
|
||||
|
||||
import {
|
||||
KuiPanelSimple,
|
||||
} from '../../../../components';
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<KuiPanelSimple paddingSize="none">
|
||||
sizePadding="none"
|
||||
</KuiPanelSimple>
|
||||
|
||||
<br />
|
||||
|
||||
<KuiPanelSimple paddingSize="s">
|
||||
sizePadding="s"
|
||||
</KuiPanelSimple>
|
||||
|
||||
<br />
|
||||
|
||||
<KuiPanelSimple paddingSize="m">
|
||||
sizePadding="m"
|
||||
</KuiPanelSimple>
|
||||
|
||||
<br />
|
||||
|
||||
<KuiPanelSimple paddingSize="l">
|
||||
sizePadding="l"
|
||||
</KuiPanelSimple>
|
||||
|
||||
<br />
|
||||
|
||||
<KuiPanelSimple paddingSize="l" hasShadow>
|
||||
sizePadding="l", hasShadow
|
||||
</KuiPanelSimple>
|
||||
</div>
|
||||
);
|
|
@ -0,0 +1,43 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import { renderToHtml } from '../../services';
|
||||
|
||||
import {
|
||||
GuideCode,
|
||||
GuideDemo,
|
||||
GuidePage,
|
||||
GuideSection,
|
||||
GuideSectionTypes,
|
||||
GuideText,
|
||||
} from '../../components';
|
||||
|
||||
import PanelSimple from './panel_simple';
|
||||
const panelSimpleSource = require('!!raw!./panel_simple');
|
||||
const panelSimpleHtml = renderToHtml(PanelSimple);
|
||||
|
||||
export default props => (
|
||||
<GuidePage title={props.route.name}>
|
||||
<GuideSection
|
||||
title="PanelSimple"
|
||||
source={[{
|
||||
type: GuideSectionTypes.JS,
|
||||
code: panelSimpleSource,
|
||||
}, {
|
||||
type: GuideSectionTypes.HTML,
|
||||
code: panelSimpleHtml,
|
||||
}]}
|
||||
>
|
||||
<GuideText>
|
||||
<GuideCode>PanelSimple</GuideCode> is a simple wrapper component to add
|
||||
depth to a contained layout. It it commonly used as a base for
|
||||
other larger components like <Link className="guideLink" to="/popover">Popover</Link>.
|
||||
</GuideText>
|
||||
|
||||
<GuideDemo>
|
||||
<PanelSimple />
|
||||
</GuideDemo>
|
||||
</GuideSection>
|
||||
</GuidePage>
|
||||
);
|
|
@ -31,7 +31,7 @@ export default class extends Component {
|
|||
render() {
|
||||
const button = (
|
||||
<KuiButton buttonType="basic" onClick={this.onButtonClick.bind(this)}>
|
||||
Click me
|
||||
Show popover
|
||||
</KuiButton>
|
||||
);
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ export default class extends Component {
|
|||
<KuiPopover
|
||||
button={(
|
||||
<KuiButton buttonType="basic" onClick={this.onButtonClick2.bind(this)}>
|
||||
Popover anchored to the right.
|
||||
Popover anchored to the left.
|
||||
</KuiButton>
|
||||
)}
|
||||
isOpen={this.state.isPopoverOpen2}
|
||||
|
|
|
@ -18,9 +18,13 @@ import PopoverAnchorPosition from './popover_anchor_position';
|
|||
const popoverAnchorPositionSource = require('!!raw!./popover_anchor_position');
|
||||
const popoverAnchorPositionHtml = renderToHtml(PopoverAnchorPosition);
|
||||
|
||||
import PopoverBodyClassName from './popover_body_class_name';
|
||||
const popoverBodyClassNameSource = require('!!raw!./popover_body_class_name');
|
||||
const popoverBodyClassNameHtml = renderToHtml(PopoverBodyClassName);
|
||||
import PopoverPanelClassName from './popover_panel_class_name';
|
||||
const popoverPanelClassNameSource = require('!!raw!./popover_panel_class_name');
|
||||
const popoverPanelClassNameHtml = renderToHtml(PopoverPanelClassName);
|
||||
|
||||
import PopoverWithTitle from './popover_with_title';
|
||||
const popoverWithTitleSource = require('!!raw!./popover_with_title');
|
||||
const popoverWithTitleHtml = renderToHtml(PopoverWithTitle);
|
||||
|
||||
export default props => (
|
||||
<GuidePage title={props.route.name}>
|
||||
|
@ -43,6 +47,28 @@ export default props => (
|
|||
</GuideDemo>
|
||||
</GuideSection>
|
||||
|
||||
<GuideSection
|
||||
title="Popover with title"
|
||||
source={[{
|
||||
type: GuideSectionTypes.JS,
|
||||
code: popoverWithTitleSource,
|
||||
}, {
|
||||
type: GuideSectionTypes.HTML,
|
||||
code: popoverWithTitleHtml,
|
||||
}]}
|
||||
>
|
||||
<GuideText>
|
||||
Popovers often have need for titling. This can be applied through
|
||||
a prop or used separately as its own component
|
||||
KuiPopoverTitle nested somwhere in the child
|
||||
prop.
|
||||
</GuideText>
|
||||
|
||||
<GuideDemo>
|
||||
<PopoverWithTitle />
|
||||
</GuideDemo>
|
||||
</GuideSection>
|
||||
|
||||
<GuideSection
|
||||
title="Anchor position"
|
||||
source={[{
|
||||
|
@ -53,23 +79,35 @@ export default props => (
|
|||
code: popoverAnchorPositionHtml,
|
||||
}]}
|
||||
>
|
||||
<GuideText>
|
||||
The alignment and arrow on your popover can be set with
|
||||
the anchorPostion prop.
|
||||
</GuideText>
|
||||
|
||||
<GuideDemo>
|
||||
<PopoverAnchorPosition />
|
||||
</GuideDemo>
|
||||
</GuideSection>
|
||||
|
||||
<GuideSection
|
||||
title="Body class name"
|
||||
title="Panel class name and padding size"
|
||||
source={[{
|
||||
type: GuideSectionTypes.JS,
|
||||
code: popoverBodyClassNameSource,
|
||||
code: popoverPanelClassNameSource,
|
||||
}, {
|
||||
type: GuideSectionTypes.HTML,
|
||||
code: popoverBodyClassNameHtml,
|
||||
code: popoverPanelClassNameHtml,
|
||||
}]}
|
||||
>
|
||||
<GuideText>
|
||||
Use the panelPaddingSize prop to adjust the padding
|
||||
on the panel within the panel. Use the panelClassName
|
||||
prop to pass a custom class to the panel.
|
||||
inside a popover.
|
||||
</GuideText>
|
||||
|
||||
<GuideDemo>
|
||||
<PopoverBodyClassName />
|
||||
<PopoverPanelClassName />
|
||||
</GuideDemo>
|
||||
</GuideSection>
|
||||
</GuidePage>
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import React, {
|
||||
Component,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
KuiPopover,
|
||||
KuiButton,
|
||||
} from '../../../../components';
|
||||
|
||||
export default class extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isPopoverOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
onButtonClick() {
|
||||
this.setState({
|
||||
isPopoverOpen: !this.state.isPopoverOpen,
|
||||
});
|
||||
}
|
||||
|
||||
closePopover() {
|
||||
this.setState({
|
||||
isPopoverOpen: false,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<KuiPopover
|
||||
button={(
|
||||
<KuiButton buttonType="basic" onClick={this.onButtonClick.bind(this)}>
|
||||
Turn padding off and apply a custom class
|
||||
</KuiButton>
|
||||
)}
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
closePopover={this.closePopover.bind(this)}
|
||||
panelClassName="yourClassNameHere"
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
This should have no padding, and if you inspect, also a custom class.
|
||||
</KuiPopover>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
import React, {
|
||||
Component,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
KuiPopover,
|
||||
KuiPopoverTitle,
|
||||
KuiButton,
|
||||
} from '../../../../components';
|
||||
|
||||
export default class extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isPopoverOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
onButtonClick() {
|
||||
this.setState({
|
||||
isPopoverOpen: !this.state.isPopoverOpen,
|
||||
});
|
||||
}
|
||||
|
||||
closePopover() {
|
||||
this.setState({
|
||||
isPopoverOpen: false,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const button = (
|
||||
<KuiButton
|
||||
buttonType="basic"
|
||||
onClick={this.onButtonClick.bind(this)}
|
||||
>
|
||||
Show popover with Title
|
||||
</KuiButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<KuiPopover
|
||||
button={button}
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
closePopover={this.closePopover.bind(this)}
|
||||
panelPaddingSize="none"
|
||||
withTitle
|
||||
>
|
||||
<div style={{ width: '300px' }}>
|
||||
<KuiPopoverTitle>Hello, I’m a popover title</KuiPopoverTitle>
|
||||
<p className="kuiText" style={{ padding: 20 }}>
|
||||
Popover content that’s wider than the default width
|
||||
</p>
|
||||
</div>
|
||||
</KuiPopover>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`KuiContextMenu is rendered 1`] = `
|
||||
<div
|
||||
aria-label="aria-label"
|
||||
class="kuiContextMenu testClass1 testClass2"
|
||||
data-test-subj="test subject string"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`KuiContextMenu props idToPanelMap and initialPanelId renders the referenced panel 1`] = `
|
||||
<div
|
||||
class="kuiContextMenu"
|
||||
>
|
||||
<div
|
||||
class="kuiContextMenuPanel kuiContextMenu__panel"
|
||||
>
|
||||
<button
|
||||
class="kuiContextMenuPanelTitle"
|
||||
data-test-subj="contextMenuPanelTitleButton"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__icon kuiIcon fa-angle-left"
|
||||
/>
|
||||
<span
|
||||
class="kuiContextMenu__text"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<div>
|
||||
2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`KuiContextMenu props idToPreviousPanelIdMap allows you to click the title button to go back to the previous panel 1`] = `
|
||||
<div
|
||||
class="kuiContextMenu"
|
||||
style="height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="kuiContextMenuPanel kuiContextMenu__panel"
|
||||
>
|
||||
<button
|
||||
class="kuiContextMenuPanelTitle"
|
||||
data-test-subj="contextMenuPanelTitleButton"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__icon kuiIcon fa-angle-left"
|
||||
/>
|
||||
<span
|
||||
class="kuiContextMenu__text"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<div>
|
||||
2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`KuiContextMenu props idToPreviousPanelIdMap allows you to click the title button to go back to the previous panel 2`] = `
|
||||
<div
|
||||
class="kuiContextMenu"
|
||||
style="height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="kuiContextMenuPanel kuiContextMenu__panel kuiContextMenuPanel-txOutRight"
|
||||
>
|
||||
<button
|
||||
class="kuiContextMenuPanelTitle"
|
||||
data-test-subj="contextMenuPanelTitleButton"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__icon kuiIcon fa-angle-left"
|
||||
/>
|
||||
<span
|
||||
class="kuiContextMenu__text"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<div>
|
||||
2
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="kuiContextMenuPanel kuiContextMenu__panel kuiContextMenuPanel-txInRight"
|
||||
>
|
||||
<button
|
||||
class="kuiContextMenuPanelTitle"
|
||||
data-test-subj="contextMenuPanelTitleButton"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__icon kuiIcon fa-angle-left"
|
||||
/>
|
||||
<span
|
||||
class="kuiContextMenu__text"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="kuiContextMenuItem"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenuItem__text"
|
||||
>
|
||||
2a
|
||||
</span>
|
||||
<span
|
||||
class="kuiContextMenu__arrow kuiIcon fa-angle-right"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="kuiContextMenuItem"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenuItem__text"
|
||||
>
|
||||
2b
|
||||
</span>
|
||||
<span
|
||||
class="kuiContextMenu__arrow kuiIcon fa-angle-right"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="kuiContextMenuItem"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenuItem__text"
|
||||
>
|
||||
2c
|
||||
</span>
|
||||
<span
|
||||
class="kuiContextMenu__arrow kuiIcon fa-angle-right"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`KuiContextMenu props isVisible causes the first panel to be shown when it becomes true 1`] = `
|
||||
<div
|
||||
class="kuiContextMenu"
|
||||
style="height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="kuiContextMenuPanel kuiContextMenu__panel"
|
||||
>
|
||||
<button
|
||||
class="kuiContextMenuPanelTitle"
|
||||
data-test-subj="contextMenuPanelTitleButton"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__icon kuiIcon fa-angle-left"
|
||||
/>
|
||||
<span
|
||||
class="kuiContextMenu__text"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<div>
|
||||
2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,53 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`KuiContextMenuItem is rendered 1`] = `
|
||||
<button
|
||||
aria-label="aria-label"
|
||||
class="kuiContextMenuItem testClass1 testClass2"
|
||||
data-test-subj="test subject string"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenuItem__text"
|
||||
>
|
||||
Hello
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`KuiContextMenuItem props hasPanel is rendered 1`] = `
|
||||
<button
|
||||
class="kuiContextMenuItem"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenuItem__text"
|
||||
/>
|
||||
<span
|
||||
class="kuiContextMenu__arrow kuiIcon fa-angle-right"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`KuiContextMenuItem props icon is rendered 1`] = `
|
||||
<button
|
||||
class="kuiContextMenuItem"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="kuiIcon fa-user kuiContextMenu__icon"
|
||||
/>
|
||||
<span
|
||||
class="kuiContextMenuItem__text"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
`;
|
|
@ -0,0 +1,75 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`KuiContextMenuPanel is rendered 1`] = `
|
||||
<div
|
||||
aria-label="aria-label"
|
||||
class="kuiContextMenuPanel testClass1 testClass2"
|
||||
data-test-subj="test subject string"
|
||||
>
|
||||
Hello
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`KuiContextMenuPanel props onClose renders a button as a title 1`] = `
|
||||
<div
|
||||
class="kuiContextMenuPanel"
|
||||
>
|
||||
<button
|
||||
class="kuiContextMenuPanelTitle"
|
||||
data-test-subj="contextMenuPanelTitleButton"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__icon kuiIcon fa-angle-left"
|
||||
/>
|
||||
<span
|
||||
class="kuiContextMenu__text"
|
||||
>
|
||||
Title
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`KuiContextMenuPanel props title is rendered 1`] = `
|
||||
<div
|
||||
class="kuiContextMenuPanel"
|
||||
>
|
||||
<div
|
||||
class="kuiPopoverTitle"
|
||||
>
|
||||
<span
|
||||
class="kuiContextMenu__itemLayout"
|
||||
>
|
||||
Title
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`KuiContextMenuPanel props transitionDirection next with transitionType in is rendered 1`] = `
|
||||
<div
|
||||
class="kuiContextMenuPanel kuiContextMenuPanel-txInLeft"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`KuiContextMenuPanel props transitionDirection next with transitionType out is rendered 1`] = `
|
||||
<div
|
||||
class="kuiContextMenuPanel kuiContextMenuPanel-txOutLeft"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`KuiContextMenuPanel props transitionDirection previous with transitionType in is rendered 1`] = `
|
||||
<div
|
||||
class="kuiContextMenuPanel kuiContextMenuPanel-txInRight"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`KuiContextMenuPanel props transitionDirection previous with transitionType out is rendered 1`] = `
|
||||
<div
|
||||
class="kuiContextMenuPanel kuiContextMenuPanel-txOutRight"
|
||||
/>
|
||||
`;
|
26
ui_framework/src/components/context_menu/_context_menu.scss
Normal file
26
ui_framework/src/components/context_menu/_context_menu.scss
Normal file
|
@ -0,0 +1,26 @@
|
|||
$kuiContextMenuWidth: $kuiSize * 16;
|
||||
|
||||
.kuiContextMenu {
|
||||
width: $kuiContextMenuWidth;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: height $kuiAnimSpeedFast $kuiAnimSlightResistance;
|
||||
border-radius: $kuiBorderRadius;
|
||||
|
||||
.kuiContextMenu__content {
|
||||
padding: $kuiSizeS;
|
||||
}
|
||||
}
|
||||
|
||||
.kuiContextMenu__panel {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.kuiContextMenu__icon {
|
||||
margin-right: $kuiSizeS;
|
||||
}
|
||||
|
||||
.kuiContextMenu__itemLayout {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* 1. Button reset.
|
||||
* 2. Ensure buttons stack.
|
||||
*/
|
||||
.kuiContextMenuItem {
|
||||
appearance: none; /* 1 */
|
||||
background-color: transparent; /* 1 */
|
||||
font-size: $kuiFontSize; /* 1 */
|
||||
border: none; /* 1 */
|
||||
cursor: pointer; /* 1 */
|
||||
display: block; /* 2 */
|
||||
padding: 12px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
color: $kuiTextColor;
|
||||
|
||||
&:hover, &:focus {
|
||||
.kuiContextMenuItem__text {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Overwrite default style.
|
||||
*/
|
||||
&:focus {
|
||||
background-color: $kuiFocusAltBackgroundColor;
|
||||
box-shadow: none; /* 1 */
|
||||
|
||||
@include darkTheme {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@include darkTheme {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.kuiContextMenuItem__inner {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.kuiContextMenuItem__text {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.kuiContextMenuItem__arrow {
|
||||
align-self: flex-end;
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
@import '../popover/mixins';
|
||||
|
||||
.kuiContextMenuPanel {
|
||||
width: 100%;
|
||||
visibility: visible;
|
||||
background-color: #ffffff;
|
||||
|
||||
&.kuiContextMenuPanel-txInLeft {
|
||||
pointer-events: none;
|
||||
animation: kuiContextMenuPanelTxInLeft $kuiAnimSpeedNormal $kuiAnimSlightResistance;
|
||||
}
|
||||
|
||||
&.kuiContextMenuPanel-txOutLeft {
|
||||
pointer-events: none;
|
||||
animation: kuiContextMenuPanelTxOutLeft $kuiAnimSpeedNormal $kuiAnimSlightResistance;
|
||||
}
|
||||
|
||||
&.kuiContextMenuPanel-txInRight {
|
||||
pointer-events: none;
|
||||
animation: kuiContextMenuPanelTxInRight $kuiAnimSpeedNormal $kuiAnimSlightResistance;
|
||||
}
|
||||
|
||||
&.kuiContextMenuPanel-txOutRight {
|
||||
pointer-events: none;
|
||||
animation: kuiContextMenuPanelTxOutRight $kuiAnimSpeedNormal $kuiAnimSlightResistance;
|
||||
}
|
||||
|
||||
@include darkTheme {
|
||||
background-color: $kuiBackgroundColor--darkTheme;
|
||||
}
|
||||
}
|
||||
|
||||
.kuiContextMenuPanel--next {
|
||||
transform: translateX($kuiContextMenuWidth);
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.kuiContextMenuPanel--previous {
|
||||
transform: translateX(-$kuiContextMenuWidth);
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Button reset.
|
||||
*/
|
||||
.kuiContextMenuPanelTitle {
|
||||
appearance: none; /* 1 */
|
||||
border: none; /* 1 */
|
||||
cursor: pointer; /* 1 */
|
||||
|
||||
@include kuiPopoverTitle;
|
||||
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
|
||||
&:hover, &:focus {
|
||||
.kuiContextMenu__text {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Overwrite default style.
|
||||
*/
|
||||
&:focus {
|
||||
box-shadow: none; /* 1 */
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes kuiContextMenuPanelTxInLeft {
|
||||
0% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes kuiContextMenuPanelTxOutLeft {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes kuiContextMenuPanelTxInRight {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes kuiContextMenuPanelTxOutRight {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
3
ui_framework/src/components/context_menu/_index.scss
Normal file
3
ui_framework/src/components/context_menu/_index.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
@import 'context_menu';
|
||||
@import 'context_menu_panel';
|
||||
@import 'context_menu_item';
|
269
ui_framework/src/components/context_menu/context_menu.js
Normal file
269
ui_framework/src/components/context_menu/context_menu.js
Normal file
|
@ -0,0 +1,269 @@
|
|||
import React, {
|
||||
Component,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { KuiContextMenuPanel } from './context_menu_panel';
|
||||
import { KuiContextMenuItem } from './context_menu_item';
|
||||
|
||||
function mapIdsToPanels(panels) {
|
||||
const map = {};
|
||||
|
||||
panels.forEach(panel => {
|
||||
map[panel.id] = panel;
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
function mapIdsToPreviousPanels(panels) {
|
||||
const idToPreviousPanelIdMap = {};
|
||||
|
||||
panels.forEach(panel => {
|
||||
if (Array.isArray(panel.items)) {
|
||||
panel.items.forEach(item => {
|
||||
const isCloseable = item.panel !== undefined;
|
||||
if (isCloseable) {
|
||||
idToPreviousPanelIdMap[item.panel] = panel.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return idToPreviousPanelIdMap;
|
||||
}
|
||||
|
||||
function mapPanelItemsToPanels(panels) {
|
||||
const idAndItemIndexToPanelIdMap = {};
|
||||
|
||||
panels.forEach(panel => {
|
||||
idAndItemIndexToPanelIdMap[panel.id] = {};
|
||||
|
||||
if (panel.items) {
|
||||
panel.items.forEach((item, index) => {
|
||||
if (item.panel) {
|
||||
idAndItemIndexToPanelIdMap[panel.id][index] = item.panel;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return idAndItemIndexToPanelIdMap;
|
||||
}
|
||||
|
||||
export class KuiContextMenu extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
panels: PropTypes.array,
|
||||
initialPanelId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
isVisible: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
panels: [],
|
||||
isVisible: true,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.idToPanelMap = {};
|
||||
this.idToPreviousPanelIdMap = {};
|
||||
this.idAndItemIndexToPanelIdMap = {};
|
||||
|
||||
this.state = {
|
||||
height: undefined,
|
||||
outgoingPanelId: undefined,
|
||||
incomingPanelId: props.initialPanelId,
|
||||
transitionDirection: undefined,
|
||||
isOutgoingPanelVisible: false,
|
||||
focusedItemIndex: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
hasPreviousPanel = panelId => {
|
||||
const previousPanelId = this.idToPreviousPanelIdMap[panelId];
|
||||
return typeof previousPanelId !== 'undefined';
|
||||
};
|
||||
|
||||
showPanel(panelId, direction) {
|
||||
this.setState({
|
||||
outgoingPanelId: this.state.incomingPanelId,
|
||||
incomingPanelId: panelId,
|
||||
transitionDirection: direction,
|
||||
isOutgoingPanelVisible: true,
|
||||
});
|
||||
}
|
||||
|
||||
showNextPanel = itemIndex => {
|
||||
const nextPanelId = this.idAndItemIndexToPanelIdMap[this.state.incomingPanelId][itemIndex];
|
||||
if (nextPanelId) {
|
||||
this.showPanel(nextPanelId, 'next');
|
||||
}
|
||||
};
|
||||
|
||||
showPreviousPanel = () => {
|
||||
// If there's a previous panel, then we can close the current panel to go back to it.
|
||||
if (this.hasPreviousPanel(this.state.incomingPanelId)) {
|
||||
const previousPanelId = this.idToPreviousPanelIdMap[this.state.incomingPanelId];
|
||||
|
||||
// Set focus on the item which shows the panel we're leaving.
|
||||
const previousPanel = this.idToPanelMap[previousPanelId];
|
||||
const focusedItemIndex = previousPanel.items.findIndex(
|
||||
item => item.panel === this.state.incomingPanelId
|
||||
);
|
||||
|
||||
if (focusedItemIndex !== -1) {
|
||||
this.setState({
|
||||
focusedItemIndex,
|
||||
});
|
||||
}
|
||||
|
||||
this.showPanel(previousPanelId, 'previous');
|
||||
}
|
||||
};
|
||||
|
||||
onIncomingPanelHeightChange = height => {
|
||||
this.setState({
|
||||
height,
|
||||
});
|
||||
}
|
||||
|
||||
onOutGoingPanelTransitionComplete = () => {
|
||||
this.setState({
|
||||
isOutgoingPanelVisible: false,
|
||||
});
|
||||
}
|
||||
|
||||
updatePanelMaps(panels) {
|
||||
this.idToPanelMap = mapIdsToPanels(panels);
|
||||
this.idToPreviousPanelIdMap = mapIdsToPreviousPanels(panels);
|
||||
this.idAndItemIndexToPanelIdMap = mapPanelItemsToPanels(panels);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.updatePanelMaps(this.props.panels);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// If the user is opening the context menu, reset the state.
|
||||
if (nextProps.isVisible && !this.props.isVisible) {
|
||||
this.setState({
|
||||
outgoingPanelId: undefined,
|
||||
incomingPanelId: nextProps.initialPanelId,
|
||||
transitionDirection: undefined,
|
||||
focusedItemIndex: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
if (nextProps.panels !== this.props.panels) {
|
||||
this.updatePanelMaps(nextProps.panels);
|
||||
}
|
||||
}
|
||||
|
||||
renderItems(items = []) {
|
||||
return items.map((item, index) => {
|
||||
const {
|
||||
panel,
|
||||
name,
|
||||
icon,
|
||||
onClick,
|
||||
...rest,
|
||||
} = item;
|
||||
|
||||
const onClickHandler = panel
|
||||
? () => {
|
||||
// This component is commonly wrapped in a KuiOutsideClickDetector, which means we'll
|
||||
// need to wait for that logic to complete before re-rendering the DOM via showPanel.
|
||||
window.requestAnimationFrame(() => {
|
||||
if (onClick) onClick();
|
||||
this.showNextPanel(index);
|
||||
});
|
||||
} : onClick;
|
||||
|
||||
return (
|
||||
<KuiContextMenuItem
|
||||
key={name}
|
||||
icon={icon}
|
||||
onClick={onClickHandler}
|
||||
hasPanel={Boolean(panel)}
|
||||
{...rest}
|
||||
>
|
||||
{name}
|
||||
</KuiContextMenuItem>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
renderPanel(panelId, transitionType) {
|
||||
const panel = this.idToPanelMap[panelId];
|
||||
|
||||
if (!panel) {
|
||||
return;
|
||||
}
|
||||
|
||||
// As above, we need to wait for KuiOutsideClickDetector to complete its logic before
|
||||
// re-rendering via showPanel.
|
||||
let onClose;
|
||||
if (this.hasPreviousPanel(panelId)) {
|
||||
onClose = () => window.requestAnimationFrame(this.showPreviousPanel);
|
||||
}
|
||||
|
||||
return (
|
||||
<KuiContextMenuPanel
|
||||
key={panelId}
|
||||
className="kuiContextMenu__panel"
|
||||
onHeightChange={(transitionType === 'in') ? this.onIncomingPanelHeightChange : undefined}
|
||||
onTransitionComplete={(transitionType === 'out') ? this.onOutGoingPanelTransitionComplete : undefined}
|
||||
title={panel.title}
|
||||
onClose={onClose}
|
||||
transitionType={this.state.isOutgoingPanelVisible ? transitionType : undefined}
|
||||
transitionDirection={this.state.isOutgoingPanelVisible ? this.state.transitionDirection : undefined}
|
||||
hasFocus={transitionType === 'in'}
|
||||
items={this.renderItems(panel.items)}
|
||||
focusedItemIndex={
|
||||
// Set focus on the item which shows the panel we're leaving.
|
||||
transitionType === 'in' && this.state.transitionDirection === 'previous'
|
||||
? this.state.focusedItemIndex
|
||||
: undefined
|
||||
}
|
||||
showNextPanel={this.showNextPanel}
|
||||
showPreviousPanel={this.showPreviousPanel}
|
||||
>
|
||||
{panel.content}
|
||||
</KuiContextMenuPanel>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
panels, // eslint-disable-line no-unused-vars
|
||||
className,
|
||||
initialPanelId, // eslint-disable-line no-unused-vars
|
||||
isVisible, // eslint-disable-line no-unused-vars
|
||||
...rest,
|
||||
} = this.props;
|
||||
|
||||
const incomingPanel = this.renderPanel(this.state.incomingPanelId, 'in');
|
||||
let outgoingPanel;
|
||||
|
||||
if (this.state.isOutgoingPanelVisible) {
|
||||
outgoingPanel = this.renderPanel(this.state.outgoingPanelId, 'out');
|
||||
}
|
||||
|
||||
const classes = classNames('kuiContextMenu', className);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={node => { this.menu = node; }}
|
||||
className={classes}
|
||||
style={{ height: this.state.height }}
|
||||
{...rest}
|
||||
>
|
||||
{outgoingPanel}
|
||||
{incomingPanel}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
115
ui_framework/src/components/context_menu/context_menu.test.js
Normal file
115
ui_framework/src/components/context_menu/context_menu.test.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
import React from 'react';
|
||||
import { render, mount } from 'enzyme';
|
||||
import {
|
||||
requiredProps,
|
||||
takeMountedSnapshot,
|
||||
} from '../../test';
|
||||
|
||||
import { KuiContextMenu } from './context_menu';
|
||||
|
||||
const panel2 = {
|
||||
id: 2,
|
||||
title: '2',
|
||||
content: <div>2</div>,
|
||||
};
|
||||
|
||||
const panel1 = {
|
||||
id: 1,
|
||||
title: '1',
|
||||
items: [{
|
||||
name: '2a',
|
||||
panel: 2,
|
||||
}, {
|
||||
name: '2b',
|
||||
panel: 2,
|
||||
}, {
|
||||
name: '2c',
|
||||
panel: 2,
|
||||
}],
|
||||
};
|
||||
|
||||
const panel0 = {
|
||||
id: 0,
|
||||
title: '0',
|
||||
items: [{
|
||||
name: '1',
|
||||
panel: 1,
|
||||
}],
|
||||
};
|
||||
|
||||
const panels = [
|
||||
panel0,
|
||||
panel1,
|
||||
panel2,
|
||||
];
|
||||
|
||||
describe('KuiContextMenu', () => {
|
||||
test('is rendered', () => {
|
||||
const component = render(
|
||||
<KuiContextMenu {...requiredProps} />
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('props', () => {
|
||||
describe('idToPanelMap and initialPanelId', () => {
|
||||
it('renders the referenced panel', () => {
|
||||
const component = render(
|
||||
<KuiContextMenu
|
||||
panels={panels}
|
||||
initialPanelId={2}
|
||||
isVisible
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('idToPreviousPanelIdMap', () => {
|
||||
it('allows you to click the title button to go back to the previous panel', () => {
|
||||
const component = mount(
|
||||
<KuiContextMenu
|
||||
panels={panels}
|
||||
initialPanelId={2}
|
||||
isVisible
|
||||
/>
|
||||
);
|
||||
|
||||
expect(takeMountedSnapshot(component))
|
||||
.toMatchSnapshot();
|
||||
|
||||
// Navigate to a different panel.
|
||||
component.find('[data-test-subj="contextMenuPanelTitleButton"]').simulate('click');
|
||||
|
||||
expect(takeMountedSnapshot(component))
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isVisible', () => {
|
||||
it('causes the first panel to be shown when it becomes true', () => {
|
||||
const component = mount(
|
||||
<KuiContextMenu
|
||||
panels={panels}
|
||||
initialPanelId={2}
|
||||
isVisible
|
||||
/>
|
||||
);
|
||||
|
||||
// Navigate to a different panel.
|
||||
component.find('[data-test-subj="contextMenuPanelTitleButton"]').simulate('click');
|
||||
|
||||
// Hide and then show the menu to reset the panel to the initial one.
|
||||
component.setProps({ isVisible: false });
|
||||
component.setProps({ isVisible: true });
|
||||
|
||||
expect(takeMountedSnapshot(component))
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
import React, {
|
||||
cloneElement,
|
||||
Component,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export class KuiContextMenuItem extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
icon: PropTypes.element,
|
||||
onClick: PropTypes.func,
|
||||
hasPanel: PropTypes.bool,
|
||||
buttonRef: PropTypes.func,
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
children,
|
||||
className,
|
||||
hasPanel,
|
||||
icon,
|
||||
buttonRef,
|
||||
...rest,
|
||||
} = this.props;
|
||||
|
||||
let iconInstance;
|
||||
|
||||
if (icon) {
|
||||
iconInstance = cloneElement(icon, {
|
||||
className: classNames(icon.props.className, 'kuiContextMenu__icon'),
|
||||
});
|
||||
}
|
||||
|
||||
let arrow;
|
||||
|
||||
if (hasPanel) {
|
||||
arrow = <span className="kuiContextMenu__arrow kuiIcon fa-angle-right" />;
|
||||
}
|
||||
|
||||
const classes = classNames('kuiContextMenuItem', className);
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classes}
|
||||
ref={buttonRef}
|
||||
{...rest}
|
||||
>
|
||||
<span className="kuiContextMenu__itemLayout">
|
||||
{iconInstance}
|
||||
<span className="kuiContextMenuItem__text">
|
||||
{children}
|
||||
</span>
|
||||
{arrow}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import React from 'react';
|
||||
import { render, shallow } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
import { requiredProps } from '../../test/required_props';
|
||||
|
||||
import { KuiContextMenuItem } from './context_menu_item';
|
||||
|
||||
describe('KuiContextMenuItem', () => {
|
||||
test('is rendered', () => {
|
||||
const component = render(
|
||||
<KuiContextMenuItem {...requiredProps}>
|
||||
Hello
|
||||
</KuiContextMenuItem>
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('props', () => {
|
||||
describe('icon', () => {
|
||||
test('is rendered', () => {
|
||||
const component = render(
|
||||
<KuiContextMenuItem icon={<span className="kuiIcon fa-user" />} />
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onClick', () => {
|
||||
test(`isn't called upon instantiation`, () => {
|
||||
const onClickHandler = sinon.stub();
|
||||
|
||||
shallow(
|
||||
<KuiContextMenuItem onClick={onClickHandler} />
|
||||
);
|
||||
|
||||
sinon.assert.notCalled(onClickHandler);
|
||||
});
|
||||
|
||||
test('is called when the item is clicked', () => {
|
||||
const onClickHandler = sinon.stub();
|
||||
|
||||
const component = shallow(
|
||||
<KuiContextMenuItem onClick={onClickHandler} />
|
||||
);
|
||||
|
||||
component.simulate('click');
|
||||
|
||||
sinon.assert.calledOnce(onClickHandler);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasPanel', () => {
|
||||
test('is rendered', () => {
|
||||
const component = render(
|
||||
<KuiContextMenuItem hasPanel />
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
296
ui_framework/src/components/context_menu/context_menu_panel.js
Normal file
296
ui_framework/src/components/context_menu/context_menu_panel.js
Normal file
|
@ -0,0 +1,296 @@
|
|||
import React, {
|
||||
cloneElement,
|
||||
Component,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import tabbable from 'tabbable';
|
||||
|
||||
import { KuiPopoverTitle } from '../../components';
|
||||
import { cascadingMenuKeyCodes } from '../../services';
|
||||
|
||||
const transitionDirectionAndTypeToClassNameMap = {
|
||||
next: {
|
||||
in: 'kuiContextMenuPanel-txInLeft',
|
||||
out: 'kuiContextMenuPanel-txOutLeft',
|
||||
},
|
||||
previous: {
|
||||
in: 'kuiContextMenuPanel-txInRight',
|
||||
out: 'kuiContextMenuPanel-txOutRight',
|
||||
},
|
||||
};
|
||||
|
||||
export class KuiContextMenuPanel extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
onClose: PropTypes.func,
|
||||
onHeightChange: PropTypes.func,
|
||||
transitionType: PropTypes.oneOf(['in', 'out']),
|
||||
transitionDirection: PropTypes.oneOf(['next', 'previous']),
|
||||
onTransitionComplete: PropTypes.func,
|
||||
hasFocus: PropTypes.bool,
|
||||
items: PropTypes.array,
|
||||
showNextPanel: PropTypes.func,
|
||||
showPreviousPanel: PropTypes.func,
|
||||
focusedItemIndex: PropTypes.number,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
hasFocus: true,
|
||||
items: [],
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.menuItems = [];
|
||||
this.state = {
|
||||
pressedArrowDirection: undefined,
|
||||
isTransitioning: Boolean(props.transitionType),
|
||||
};
|
||||
}
|
||||
|
||||
onKeyDown = e => {
|
||||
// If this panel contains items you can use the left arrow key to go back at any time.
|
||||
// But if it doesn't contain items, then you have to focus on the back button specifically,
|
||||
// since there could be content inside the panel which requires use of the left arrow key,
|
||||
// e.g. text inputs.
|
||||
if (this.props.items.length || document.activeElement === this.backButton) {
|
||||
if (e.keyCode === cascadingMenuKeyCodes.LEFT) {
|
||||
if (this.props.showPreviousPanel) {
|
||||
this.props.showPreviousPanel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.items.length) {
|
||||
switch (e.keyCode) {
|
||||
case cascadingMenuKeyCodes.TAB:
|
||||
// Normal tabbing doesn't work within panels with items.
|
||||
e.preventDefault();
|
||||
break;
|
||||
|
||||
case cascadingMenuKeyCodes.UP:
|
||||
e.preventDefault();
|
||||
this.setState({ pressedArrowDirection: 'up' });
|
||||
break;
|
||||
|
||||
case cascadingMenuKeyCodes.DOWN:
|
||||
e.preventDefault();
|
||||
this.setState({ pressedArrowDirection: 'down' });
|
||||
break;
|
||||
|
||||
case cascadingMenuKeyCodes.RIGHT:
|
||||
if (this.props.showNextPanel) {
|
||||
this.props.showNextPanel(this.getFocusedMenuItemIndex());
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
isMenuItemFocused() {
|
||||
const indexOfActiveElement = this.menuItems.indexOf(document.activeElement);
|
||||
return indexOfActiveElement !== -1;
|
||||
}
|
||||
|
||||
getFocusedMenuItemIndex() {
|
||||
return this.menuItems.indexOf(document.activeElement);
|
||||
}
|
||||
|
||||
updateFocusedMenuItem() {
|
||||
// If this panel isn't active, don't focus any items.
|
||||
if (!this.props.hasFocus) {
|
||||
if (this.isMenuItemFocused()) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Setting focus while transitioning causes the animation to glitch, so we have to wait
|
||||
// until it's finished before we focus anything.
|
||||
if (this.state.isTransitioning) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're active, but nothing is focused then we should focus the first item.
|
||||
if (!this.isMenuItemFocused()) {
|
||||
if (this.props.focusedItemIndex !== undefined) {
|
||||
this.menuItems[this.props.focusedItemIndex].focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.menuItems.length !== 0) {
|
||||
this.menuItems[0].focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Focus first tabbable item.
|
||||
const tabbableItems = tabbable(this.panel);
|
||||
if (tabbableItems.length) {
|
||||
tabbableItems[0].focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Update focused state based on arrow key navigation.
|
||||
if (this.state.pressedArrowDirection) {
|
||||
const indexOfActiveElement = this.getFocusedMenuItemIndex();
|
||||
let nextFocusedMenuItemIndex;
|
||||
|
||||
switch (this.state.pressedArrowDirection) {
|
||||
case 'up':
|
||||
nextFocusedMenuItemIndex =
|
||||
(indexOfActiveElement - 1) !== -1
|
||||
? indexOfActiveElement - 1
|
||||
: this.menuItems.length - 1;
|
||||
break;
|
||||
|
||||
case 'down':
|
||||
nextFocusedMenuItemIndex =
|
||||
(indexOfActiveElement + 1) !== this.menuItems.length
|
||||
? indexOfActiveElement + 1
|
||||
: 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.menuItems[nextFocusedMenuItemIndex].focus();
|
||||
this.setState({ pressedArrowDirection: undefined });
|
||||
}
|
||||
}
|
||||
|
||||
onTransitionComplete = () => {
|
||||
this.setState({
|
||||
isTransitioning: false,
|
||||
});
|
||||
|
||||
if (this.props.onTransitionComplete) {
|
||||
this.props.onTransitionComplete();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// Clear refs to menuItems if we're getting new ones.
|
||||
if (nextProps.items !== this.props.items) {
|
||||
this.menuItems = [];
|
||||
}
|
||||
|
||||
if (nextProps.transitionType) {
|
||||
this.setState({
|
||||
isTransitioning: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateFocusedMenuItem();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.updateFocusedMenuItem();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.panel.removeEventListener('animationend', this.onTransitionComplete);
|
||||
}
|
||||
|
||||
menuItemRef = (index, node) => {
|
||||
// There's a weird bug where if you navigate to a panel without items, then this callback
|
||||
// is still invoked, so we have to do a truthiness check.
|
||||
if (node) {
|
||||
// Store all menu items.
|
||||
this.menuItems[index] = node;
|
||||
}
|
||||
};
|
||||
|
||||
panelRef = node => {
|
||||
if (node) {
|
||||
this.panel = node;
|
||||
this.panel.addEventListener('animationend', this.onTransitionComplete);
|
||||
|
||||
if (this.props.onHeightChange) {
|
||||
this.props.onHeightChange(node.clientHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
children,
|
||||
className,
|
||||
onClose,
|
||||
title,
|
||||
onHeightChange, // eslint-disable-line no-unused-vars
|
||||
transitionType,
|
||||
transitionDirection,
|
||||
onTransitionComplete, // eslint-disable-line no-unused-vars
|
||||
hasFocus, // eslint-disable-line no-unused-vars
|
||||
items,
|
||||
focusedItemIndex, // eslint-disable-line no-unused-vars
|
||||
showNextPanel, // eslint-disable-line no-unused-vars
|
||||
showPreviousPanel, // eslint-disable-line no-unused-vars
|
||||
...rest,
|
||||
} = this.props;
|
||||
let panelTitle;
|
||||
|
||||
if (title) {
|
||||
if (Boolean(onClose)) {
|
||||
panelTitle = (
|
||||
<button
|
||||
className="kuiContextMenuPanelTitle"
|
||||
onClick={onClose}
|
||||
ref={node => { this.backButton = node; }}
|
||||
data-test-subj="contextMenuPanelTitleButton"
|
||||
>
|
||||
<span className="kuiContextMenu__itemLayout">
|
||||
<span className="kuiContextMenu__icon kuiIcon fa-angle-left" />
|
||||
<span className="kuiContextMenu__text">
|
||||
{title}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
panelTitle = (
|
||||
<KuiPopoverTitle>
|
||||
<span className="kuiContextMenu__itemLayout">
|
||||
{title}
|
||||
</span>
|
||||
</KuiPopoverTitle>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const classes = classNames('kuiContextMenuPanel', className, (
|
||||
this.state.isTransitioning && transitionDirectionAndTypeToClassNameMap[transitionDirection]
|
||||
? transitionDirectionAndTypeToClassNameMap[transitionDirection][transitionType]
|
||||
: undefined
|
||||
));
|
||||
|
||||
const content = items.length
|
||||
? items.map((MenuItem, index) => cloneElement(MenuItem, {
|
||||
buttonRef: this.menuItemRef.bind(this, index),
|
||||
}))
|
||||
: children;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.panelRef}
|
||||
className={classes}
|
||||
onKeyDown={this.onKeyDown}
|
||||
{...rest}
|
||||
>
|
||||
{panelTitle}
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
import React from 'react';
|
||||
import { render, shallow, mount } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
import { requiredProps } from '../../test/required_props';
|
||||
|
||||
import {
|
||||
KuiContextMenuPanel,
|
||||
} from './context_menu_panel';
|
||||
|
||||
import {
|
||||
KuiContextMenuItem,
|
||||
} from './context_menu_item';
|
||||
|
||||
import { keyCodes } from '../../services';
|
||||
|
||||
const items = [(
|
||||
<KuiContextMenuItem
|
||||
key="A"
|
||||
data-test-subj="itemA"
|
||||
>
|
||||
Option A
|
||||
</KuiContextMenuItem>
|
||||
), (
|
||||
<KuiContextMenuItem
|
||||
key="B"
|
||||
data-test-subj="itemB"
|
||||
>
|
||||
Option B
|
||||
</KuiContextMenuItem>
|
||||
), (
|
||||
<KuiContextMenuItem
|
||||
key="C"
|
||||
data-test-subj="itemC"
|
||||
>
|
||||
Option C
|
||||
</KuiContextMenuItem>
|
||||
)];
|
||||
|
||||
describe('KuiContextMenuPanel', () => {
|
||||
test('is rendered', () => {
|
||||
const component = render(
|
||||
<KuiContextMenuPanel {...requiredProps}>
|
||||
Hello
|
||||
</KuiContextMenuPanel>
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('props', () => {
|
||||
describe('title', () => {
|
||||
test('is rendered', () => {
|
||||
const component = render(
|
||||
<KuiContextMenuPanel title="Title" />
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onClose', () => {
|
||||
test('renders a button as a title', () => {
|
||||
const component = render(
|
||||
<KuiContextMenuPanel title="Title" onClose={() =>{}} />
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
|
||||
test(`isn't called upon instantiation`, () => {
|
||||
const onCloseHandler = sinon.stub();
|
||||
|
||||
shallow(
|
||||
<KuiContextMenuPanel title="Title" onClose={onCloseHandler} />
|
||||
);
|
||||
|
||||
sinon.assert.notCalled(onCloseHandler);
|
||||
});
|
||||
|
||||
test('is called when the title is clicked', () => {
|
||||
const onCloseHandler = sinon.stub();
|
||||
|
||||
const component = shallow(
|
||||
<KuiContextMenuPanel title="Title" onClose={onCloseHandler} />
|
||||
);
|
||||
|
||||
component.find('button').simulate('click');
|
||||
|
||||
sinon.assert.calledOnce(onCloseHandler);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onHeightChange', () => {
|
||||
it('is called with a height value', () => {
|
||||
const onHeightChange = sinon.stub();
|
||||
|
||||
mount(
|
||||
<KuiContextMenuPanel onHeightChange={onHeightChange} />
|
||||
);
|
||||
|
||||
sinon.assert.calledWith(onHeightChange, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transitionDirection', () => {
|
||||
describe('next', () => {
|
||||
describe('with transitionType', () => {
|
||||
describe('in', () => {
|
||||
test('is rendered', () => {
|
||||
const component = render(
|
||||
<KuiContextMenuPanel transitionDirection="next" transitionType="in" />
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('out', () => {
|
||||
test('is rendered', () => {
|
||||
const component = render(
|
||||
<KuiContextMenuPanel transitionDirection="next" transitionType="out" />
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('previous', () => {
|
||||
describe('with transitionType', () => {
|
||||
describe('in', () => {
|
||||
test('is rendered', () => {
|
||||
const component = render(
|
||||
<KuiContextMenuPanel transitionDirection="previous" transitionType="in" />
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('out', () => {
|
||||
test('is rendered', () => {
|
||||
const component = render(
|
||||
<KuiContextMenuPanel transitionDirection="previous" transitionType="out" />
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('focusedItemIndex', () => {
|
||||
it('sets focus on the item occupying that index', () => {
|
||||
const component = mount(
|
||||
<KuiContextMenuPanel
|
||||
items={items}
|
||||
focusedItemIndex={1}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
component.find('[data-test-subj="itemB"]').matchesElement(document.activeElement)
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('behavior', () => {
|
||||
describe('focus', () => {
|
||||
it('is set on the first focusable element by default, if there are no items', () => {
|
||||
const component = mount(
|
||||
<KuiContextMenuPanel>
|
||||
<button data-test-subj="button" />
|
||||
</KuiContextMenuPanel>
|
||||
);
|
||||
|
||||
expect(
|
||||
component.find('[data-test-subj="button"]').matchesElement(document.activeElement)
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('keyboard navigation of items', () => {
|
||||
let component;
|
||||
let showNextPanelHandler;
|
||||
let showPreviousPanelHandler;
|
||||
|
||||
beforeEach(() => {
|
||||
showNextPanelHandler = sinon.stub();
|
||||
showPreviousPanelHandler = sinon.stub();
|
||||
|
||||
component = mount(
|
||||
<KuiContextMenuPanel
|
||||
items={items}
|
||||
showNextPanel={showNextPanelHandler}
|
||||
showPreviousPanel={showPreviousPanelHandler}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('focuses the first menu item by default, if there are items', () => {
|
||||
expect(
|
||||
component.find('[data-test-subj="itemA"]').matchesElement(document.activeElement)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('down arrow key focuses the next menu item', () => {
|
||||
component.simulate('keydown', { keyCode: keyCodes.DOWN });
|
||||
|
||||
expect(
|
||||
component.find('[data-test-subj="itemB"]').matchesElement(document.activeElement)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('up arrow key focuses the previous menu item', () => {
|
||||
component.simulate('keydown', { keyCode: keyCodes.UP });
|
||||
|
||||
expect(
|
||||
component.find('[data-test-subj="itemC"]').matchesElement(document.activeElement)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('right arrow key shows next panel', () => {
|
||||
component.simulate('keydown', { keyCode: keyCodes.RIGHT });
|
||||
sinon.assert.calledWith(showNextPanelHandler, 0);
|
||||
});
|
||||
|
||||
it('left arrow key shows previous panel', () => {
|
||||
component.simulate('keydown', { keyCode: keyCodes.LEFT });
|
||||
sinon.assert.calledOnce(showPreviousPanelHandler);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
11
ui_framework/src/components/context_menu/index.js
Normal file
11
ui_framework/src/components/context_menu/index.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export {
|
||||
KuiContextMenu,
|
||||
} from './context_menu';
|
||||
|
||||
export {
|
||||
KuiContextMenuPanel,
|
||||
} from './context_menu_panel';
|
||||
|
||||
export {
|
||||
KuiContextMenuItem,
|
||||
} from './context_menu_item';
|
|
@ -0,0 +1,17 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`KuiExpression Props children is rendered 1`] = `
|
||||
<div
|
||||
class="kuiExpression"
|
||||
>
|
||||
some expression
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`KuiExpression renders 1`] = `
|
||||
<div
|
||||
aria-label="aria-label"
|
||||
class="kuiExpression testClass1 testClass2"
|
||||
data-test-subj="test subject string"
|
||||
/>
|
||||
`;
|
|
@ -0,0 +1,57 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`KuiExpressionButton Props isActive false renders inactive 1`] = `
|
||||
<button
|
||||
class="kuiExpressionButton"
|
||||
>
|
||||
<span
|
||||
class="kuiExpressionButton__description"
|
||||
>
|
||||
the answer is
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="kuiExpressionButton__value"
|
||||
>
|
||||
42
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`KuiExpressionButton Props isActive true renders active 1`] = `
|
||||
<button
|
||||
class="kuiExpressionButton kuiExpressionButton-isActive"
|
||||
>
|
||||
<span
|
||||
class="kuiExpressionButton__description"
|
||||
>
|
||||
the answer is
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="kuiExpressionButton__value"
|
||||
>
|
||||
42
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`KuiExpressionButton renders 1`] = `
|
||||
<button
|
||||
aria-label="aria-label"
|
||||
class="kuiExpressionButton testClass1 testClass2"
|
||||
data-test-subj="test subject string"
|
||||
>
|
||||
<span
|
||||
class="kuiExpressionButton__description"
|
||||
>
|
||||
the answer is
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="kuiExpressionButton__value"
|
||||
>
|
||||
42
|
||||
</span>
|
||||
</button>
|
||||
`;
|
|
@ -1,17 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`KuiExpressionItem Props children is rendered 1`] = `
|
||||
<div
|
||||
class="kuiExpressionItem"
|
||||
>
|
||||
some expression
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`KuiExpressionItem renders 1`] = `
|
||||
<div
|
||||
aria-label="aria-label"
|
||||
class="kuiExpressionItem testClass1 testClass2"
|
||||
data-test-subj="test subject string"
|
||||
/>
|
||||
`;
|
|
@ -1,57 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`KuiExpressionItemButton Props isActive false renders inactive 1`] = `
|
||||
<button
|
||||
class="kuiExpressionItem__button"
|
||||
>
|
||||
<span
|
||||
class="kuiExpressionItem__buttonDescription"
|
||||
>
|
||||
the answer is
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="kuiExpressionItem__buttonValue"
|
||||
>
|
||||
42
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`KuiExpressionItemButton Props isActive true renders active 1`] = `
|
||||
<button
|
||||
class="kuiExpressionItem__button kuiExpressionItem__button--isActive"
|
||||
>
|
||||
<span
|
||||
class="kuiExpressionItem__buttonDescription"
|
||||
>
|
||||
the answer is
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="kuiExpressionItem__buttonValue"
|
||||
>
|
||||
42
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`KuiExpressionItemButton renders 1`] = `
|
||||
<button
|
||||
aria-label="aria-label"
|
||||
class="kuiExpressionItem__button testClass1 testClass2"
|
||||
data-test-subj="test subject string"
|
||||
>
|
||||
<span
|
||||
class="kuiExpressionItem__buttonDescription"
|
||||
>
|
||||
the answer is
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="kuiExpressionItem__buttonValue"
|
||||
>
|
||||
42
|
||||
</span>
|
||||
</button>
|
||||
`;
|
|
@ -1,80 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`KuiExpressionItemPopover Props align renders default 1`] = `
|
||||
<div
|
||||
class="kuiExpressionItem__popover"
|
||||
>
|
||||
<div
|
||||
class="kuiExpressionItem__popoverTitle"
|
||||
>
|
||||
title
|
||||
</div>
|
||||
<div
|
||||
class="kuiExpressionItem__popoverContent"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`KuiExpressionItemPopover Props align renders the left class 1`] = `
|
||||
<div
|
||||
class="kuiExpressionItem__popover"
|
||||
>
|
||||
<div
|
||||
class="kuiExpressionItem__popoverTitle"
|
||||
>
|
||||
title
|
||||
</div>
|
||||
<div
|
||||
class="kuiExpressionItem__popoverContent"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`KuiExpressionItemPopover Props align renders the right class 1`] = `
|
||||
<div
|
||||
class="kuiExpressionItem__popover kuiExpressionItem__popover--alignRight"
|
||||
>
|
||||
<div
|
||||
class="kuiExpressionItem__popoverTitle"
|
||||
>
|
||||
title
|
||||
</div>
|
||||
<div
|
||||
class="kuiExpressionItem__popoverContent"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`KuiExpressionItemPopover Props children is rendered 1`] = `
|
||||
<div
|
||||
class="kuiExpressionItem__popover"
|
||||
>
|
||||
<div
|
||||
class="kuiExpressionItem__popoverTitle"
|
||||
>
|
||||
title
|
||||
</div>
|
||||
<div
|
||||
class="kuiExpressionItem__popoverContent"
|
||||
>
|
||||
popover content
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`KuiExpressionItemPopover renders 1`] = `
|
||||
<div
|
||||
aria-label="aria-label"
|
||||
class="kuiExpressionItem__popover testClass1 testClass2"
|
||||
data-test-subj="test subject string"
|
||||
>
|
||||
<div
|
||||
class="kuiExpressionItem__popoverTitle"
|
||||
>
|
||||
title
|
||||
</div>
|
||||
<div
|
||||
class="kuiExpressionItem__popoverContent"
|
||||
/>
|
||||
</div>
|
||||
`;
|
|
@ -1,103 +1,27 @@
|
|||
.kuiExpressionItem {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
& + & {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.kuiExpression {
|
||||
padding: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.kuiExpressionItem__button {
|
||||
background-color: transparent;
|
||||
padding: 5px 0px;
|
||||
border: none;
|
||||
border-bottom: dotted 2px $kuiBorderColor;
|
||||
font-size: $kuiFontSize;
|
||||
cursor: pointer;
|
||||
}
|
||||
.kuiExpressionButton {
|
||||
background-color: transparent;
|
||||
padding: 5px 0px;
|
||||
border: none;
|
||||
border-bottom: dotted 2px $kuiBorderColor;
|
||||
font-size: $kuiFontSize;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.kuiExpressionItem__buttonDescription {
|
||||
color: $expressionColorHighlight;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.kuiExpressionButton__description {
|
||||
color: $expressionColorHighlight;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.kuiExpressionItem__buttonValue {
|
||||
color: $kuiTextColor;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
.kuiExpressionButton__value {
|
||||
color: $kuiTextColor;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
|
||||
.kuiExpressionItem__button--isActive {
|
||||
border-bottom: solid 2px $expressionColorHighlight;
|
||||
}
|
||||
|
||||
.kuiExpressionItem__popover {
|
||||
position: absolute;
|
||||
top: calc(100% + 15px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
background-color: white;
|
||||
border: 1px solid $kuiBorderColor;
|
||||
border-radius: 6px;
|
||||
box-shadow: $kuiBoxShadow;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transform: translateY(-5px) translateZ(0);
|
||||
transition: transform $kuiAnimSpeedNormal $kuiAnimSlightBounce, opacity $kuiAnimSpeedNormal $kuiAnimSlightBounce;
|
||||
|
||||
// 1. Angulars ng-hide uses display: none. To use animations we need to use visibility instead.
|
||||
&.ng-hide {
|
||||
display: block !important; // 1
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transform: translateY(0px) translateZ(0);
|
||||
}
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: -($kuiBorderRadius * 2);
|
||||
left: 20px;
|
||||
height: 0;
|
||||
width: 0;
|
||||
border-left: $kuiBorderRadius * 2 solid transparent;
|
||||
border-right: $kuiBorderRadius * 2 solid transparent;
|
||||
border-bottom: $kuiBorderRadius * 2 solid $kuiBorderColor;
|
||||
}
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: -($kuiBorderRadius * 2) + 1;
|
||||
left: 20px;
|
||||
height: 0;
|
||||
width: 0;
|
||||
border-left: $kuiBorderRadius * 2 solid transparent;
|
||||
border-right: $kuiBorderRadius * 2 solid transparent;
|
||||
border-bottom: $kuiBorderRadius * 2 solid lighten($kuiBorderColor, 5%);
|
||||
}
|
||||
|
||||
&.kuiExpressionItem__popover--alignRight {
|
||||
right: 0;
|
||||
&:before, &:after {
|
||||
left: auto;
|
||||
right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.kuiExpressionItem__popoverTitle {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
background-color: lighten($kuiBorderColor, 5%);
|
||||
border-radius: $kuiBorderRadius $kuiBorderRadius 0 0;
|
||||
color: $kuiTextColor;
|
||||
padding: 5px 10px;
|
||||
line-height: $kuiLineHeight;
|
||||
}
|
||||
|
||||
.kuiExpressionItem__popoverContent {
|
||||
padding: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.kuiExpressionButton-isActive {
|
||||
border-bottom: solid 2px $expressionColorHighlight;
|
||||
}
|
||||
|
|
|
@ -2,12 +2,12 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const KuiExpressionItem = ({
|
||||
export const KuiExpression = ({
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}) => {
|
||||
const classes = classNames('kuiExpressionItem', className);
|
||||
const classes = classNames('kuiExpression', className);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -19,7 +19,7 @@ export const KuiExpressionItem = ({
|
|||
);
|
||||
};
|
||||
|
||||
KuiExpressionItem.propTypes = {
|
||||
KuiExpression.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
|
@ -3,13 +3,13 @@ import { render } from 'enzyme';
|
|||
import { requiredProps } from '../../test/required_props';
|
||||
|
||||
import {
|
||||
KuiExpressionItem,
|
||||
} from './expression_item';
|
||||
KuiExpression,
|
||||
} from './expression';
|
||||
|
||||
describe('KuiExpressionItem', () => {
|
||||
describe('KuiExpression', () => {
|
||||
test('renders', () => {
|
||||
const component = (
|
||||
<KuiExpressionItem {...requiredProps} />
|
||||
<KuiExpression {...requiredProps} />
|
||||
);
|
||||
|
||||
expect(render(component)).toMatchSnapshot();
|
||||
|
@ -19,9 +19,9 @@ describe('KuiExpressionItem', () => {
|
|||
describe('children', () => {
|
||||
test('is rendered', () => {
|
||||
const component = render(
|
||||
<KuiExpressionItem>
|
||||
<KuiExpression>
|
||||
some expression
|
||||
</KuiExpressionItem>
|
||||
</KuiExpression>
|
||||
);
|
||||
|
||||
expect(component)
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const KuiExpressionItemButton = ({
|
||||
export const KuiExpressionButton = ({
|
||||
className,
|
||||
description,
|
||||
buttonValue,
|
||||
|
@ -10,8 +10,8 @@ export const KuiExpressionItemButton = ({
|
|||
onClick,
|
||||
...rest
|
||||
}) => {
|
||||
const classes = classNames('kuiExpressionItem__button', className, {
|
||||
'kuiExpressionItem__button--isActive': isActive
|
||||
const classes = classNames('kuiExpressionButton', className, {
|
||||
'kuiExpressionButton-isActive': isActive
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -20,16 +20,20 @@ export const KuiExpressionItemButton = ({
|
|||
onClick={onClick}
|
||||
{...rest}
|
||||
>
|
||||
<span className="kuiExpressionItem__buttonDescription">{description}</span>{' '}
|
||||
<span className="kuiExpressionItem__buttonValue">{buttonValue}</span>
|
||||
<span className="kuiExpressionButton__description">{description}</span>{' '}
|
||||
<span className="kuiExpressionButton__value">{buttonValue}</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
KuiExpressionItemButton.propTypes = {
|
||||
KuiExpressionButton.propTypes = {
|
||||
className: PropTypes.string,
|
||||
description: PropTypes.string.isRequired,
|
||||
buttonValue: PropTypes.string.isRequired,
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
KuiExpressionButton.defaultProps = {
|
||||
isActive: false,
|
||||
};
|
|
@ -4,13 +4,13 @@ import { requiredProps } from '../../test/required_props';
|
|||
import sinon from 'sinon';
|
||||
|
||||
import {
|
||||
KuiExpressionItemButton,
|
||||
} from './expression_item_button';
|
||||
KuiExpressionButton,
|
||||
} from './expression_button';
|
||||
|
||||
describe('KuiExpressionItemButton', () => {
|
||||
describe('KuiExpressionButton', () => {
|
||||
test('renders', () => {
|
||||
const component = (
|
||||
<KuiExpressionItemButton
|
||||
<KuiExpressionButton
|
||||
description="the answer is"
|
||||
buttonValue="42"
|
||||
isActive={false}
|
||||
|
@ -26,7 +26,7 @@ describe('KuiExpressionItemButton', () => {
|
|||
describe('isActive', () => {
|
||||
test('true renders active', () => {
|
||||
const component = (
|
||||
<KuiExpressionItemButton
|
||||
<KuiExpressionButton
|
||||
description="the answer is"
|
||||
buttonValue="42"
|
||||
isActive={true}
|
||||
|
@ -39,7 +39,7 @@ describe('KuiExpressionItemButton', () => {
|
|||
|
||||
test('false renders inactive', () => {
|
||||
const component = (
|
||||
<KuiExpressionItemButton
|
||||
<KuiExpressionButton
|
||||
description="the answer is"
|
||||
buttonValue="42"
|
||||
isActive={false}
|
||||
|
@ -56,7 +56,7 @@ describe('KuiExpressionItemButton', () => {
|
|||
const onClickHandler = sinon.spy();
|
||||
|
||||
const button = shallow(
|
||||
<KuiExpressionItemButton
|
||||
<KuiExpressionButton
|
||||
description="the answer is"
|
||||
buttonValue="42"
|
||||
isActive={false}
|
|
@ -1,54 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { KuiOutsideClickDetector } from '../outside_click_detector';
|
||||
|
||||
const POPOVER_ALIGN = [
|
||||
'left',
|
||||
'right',
|
||||
];
|
||||
|
||||
const KuiExpressionItemPopover = ({
|
||||
className,
|
||||
title,
|
||||
children,
|
||||
align,
|
||||
onOutsideClick,
|
||||
...rest
|
||||
}) => {
|
||||
const classes = classNames('kuiExpressionItem__popover', className, {
|
||||
'kuiExpressionItem__popover--alignRight': align === 'right'
|
||||
});
|
||||
return (
|
||||
<KuiOutsideClickDetector onOutsideClick={onOutsideClick}>
|
||||
<div
|
||||
className={classes}
|
||||
{...rest}
|
||||
>
|
||||
<div className="kuiExpressionItem__popoverTitle">
|
||||
{title}
|
||||
</div>
|
||||
<div className="kuiExpressionItem__popoverContent">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</KuiOutsideClickDetector>
|
||||
);
|
||||
};
|
||||
|
||||
KuiExpressionItemPopover.defaultProps = {
|
||||
align: 'left',
|
||||
};
|
||||
|
||||
KuiExpressionItemPopover.propTypes = {
|
||||
className: PropTypes.string,
|
||||
title: PropTypes.string.isRequired,
|
||||
children: PropTypes.node,
|
||||
align: PropTypes.oneOf(POPOVER_ALIGN),
|
||||
onOutsideClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export {
|
||||
POPOVER_ALIGN,
|
||||
KuiExpressionItemPopover
|
||||
};
|
|
@ -1,68 +0,0 @@
|
|||
import React from 'react';
|
||||
import { render } from 'enzyme';
|
||||
import { requiredProps } from '../../test/required_props';
|
||||
|
||||
import {
|
||||
KuiExpressionItemPopover,
|
||||
POPOVER_ALIGN
|
||||
} from './expression_item_popover';
|
||||
|
||||
describe('KuiExpressionItemPopover', () => {
|
||||
test('renders', () => {
|
||||
const component = (
|
||||
<KuiExpressionItemPopover
|
||||
title="title"
|
||||
align="left"
|
||||
onOutsideClick={()=>{}}
|
||||
{...requiredProps}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(render(component)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('Props', () => {
|
||||
describe('children', () => {
|
||||
test('is rendered', () => {
|
||||
const component = render(
|
||||
<KuiExpressionItemPopover
|
||||
title="title"
|
||||
align="left"
|
||||
onOutsideClick={()=>{}}
|
||||
>
|
||||
popover content
|
||||
</KuiExpressionItemPopover>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('align', () => {
|
||||
test('renders default', () => {
|
||||
const component = render(
|
||||
<KuiExpressionItemPopover
|
||||
title="title"
|
||||
onOutsideClick={()=>{}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
POPOVER_ALIGN.forEach(align => {
|
||||
test(`renders the ${align} class`, () => {
|
||||
const component = render(
|
||||
<KuiExpressionItemPopover
|
||||
title="title"
|
||||
align={align}
|
||||
onOutsideClick={()=>{}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,3 +1,2 @@
|
|||
export { KuiExpressionItem } from './expression_item';
|
||||
export { KuiExpressionItemButton } from './expression_item_button';
|
||||
export { KuiExpressionItemPopover } from './expression_item_popover';
|
||||
export { KuiExpression } from './expression';
|
||||
export { KuiExpressionButton } from './expression_button';
|
||||
|
|
|
@ -39,6 +39,12 @@ export {
|
|||
KuiCollapseButton,
|
||||
} from './collapse_button';
|
||||
|
||||
export {
|
||||
KuiContextMenu,
|
||||
KuiContextMenuPanel,
|
||||
KuiContextMenuItem,
|
||||
} from './context_menu';
|
||||
|
||||
export {
|
||||
KuiEmptyTablePrompt,
|
||||
KuiEmptyTablePromptMessage,
|
||||
|
@ -54,9 +60,8 @@ export {
|
|||
} from './event';
|
||||
|
||||
export {
|
||||
KuiExpressionItem,
|
||||
KuiExpressionItemButton,
|
||||
KuiExpressionItemPopover,
|
||||
KuiExpression,
|
||||
KuiExpressionButton,
|
||||
} from './expression';
|
||||
|
||||
export {
|
||||
|
@ -121,8 +126,13 @@ export {
|
|||
KuiPagerButtonGroup,
|
||||
} from './pager';
|
||||
|
||||
export {
|
||||
KuiPanelSimple,
|
||||
} from './panel_simple';
|
||||
|
||||
export {
|
||||
KuiPopover,
|
||||
KuiPopoverTitle,
|
||||
} from './popover';
|
||||
|
||||
export {
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
@import "collapse_button/index";
|
||||
@import "color_picker/index";
|
||||
@import "column/index";
|
||||
@import 'context_menu/index';
|
||||
@import "event/index";
|
||||
@import "expression/index";
|
||||
@import "flex/index";
|
||||
|
@ -41,6 +42,7 @@
|
|||
@import "notice/index";
|
||||
@import "pager/index";
|
||||
@import "panel/index";
|
||||
@import "panel_simple/index";
|
||||
@import "popover/index";
|
||||
@import "empty_table_prompt/index";
|
||||
@import "status_text/index";
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`KuiPanelSimple is rendered 1`] = `
|
||||
<div
|
||||
aria-label="aria-label"
|
||||
class="kuiPanelSimple kuiPanelSimple--paddingMedium testClass1 testClass2"
|
||||
data-test-subj="test subject string"
|
||||
/>
|
||||
`;
|
1
ui_framework/src/components/panel_simple/_index.scss
Normal file
1
ui_framework/src/components/panel_simple/_index.scss
Normal file
|
@ -0,0 +1 @@
|
|||
@import 'panel_simple';
|
33
ui_framework/src/components/panel_simple/_panel_simple.scss
Normal file
33
ui_framework/src/components/panel_simple/_panel_simple.scss
Normal file
|
@ -0,0 +1,33 @@
|
|||
.kuiPanelSimple {
|
||||
@include kuiBottomShadowSmall;
|
||||
|
||||
background-color: $kuiColorEmptyShade;
|
||||
border: $kuiBorderThin;
|
||||
border-radius: $kuiBorderRadius;
|
||||
flex-grow: 1;
|
||||
|
||||
&.kuiPanelSimple--paddingSmall {
|
||||
padding: $kuiSizeS;
|
||||
}
|
||||
|
||||
&.kuiPanelSimple--paddingMedium {
|
||||
padding: $kuiSize;
|
||||
}
|
||||
|
||||
&.kuiPanelSimple--paddingLarge {
|
||||
padding: $kuiSizeL;
|
||||
}
|
||||
|
||||
&.kuiPanelSimple--shadow {
|
||||
@include kuiBottomShadow;
|
||||
}
|
||||
|
||||
&.kuiPanelSimple--flexGrowZero {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
@include darkTheme {
|
||||
background-color: $kuiBackgroundColor--darkTheme;
|
||||
border-color: $kuiInputBorderColor--darkTheme;
|
||||
}
|
||||
}
|
4
ui_framework/src/components/panel_simple/index.js
Normal file
4
ui_framework/src/components/panel_simple/index.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
export {
|
||||
KuiPanelSimple,
|
||||
SIZES,
|
||||
} from './panel_simple';
|
59
ui_framework/src/components/panel_simple/panel_simple.js
Normal file
59
ui_framework/src/components/panel_simple/panel_simple.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const paddingSizeToClassNameMap = {
|
||||
'none': null,
|
||||
's': 'kuiPanelSimple--paddingSmall',
|
||||
'm': 'kuiPanelSimple--paddingMedium',
|
||||
'l': 'kuiPanelSimple--paddingLarge',
|
||||
};
|
||||
|
||||
export const SIZES = Object.keys(paddingSizeToClassNameMap);
|
||||
|
||||
export const KuiPanelSimple = ({
|
||||
children,
|
||||
className,
|
||||
paddingSize,
|
||||
hasShadow,
|
||||
grow,
|
||||
panelRef,
|
||||
...rest,
|
||||
}) => {
|
||||
|
||||
const classes = classNames(
|
||||
'kuiPanelSimple',
|
||||
paddingSizeToClassNameMap[paddingSize],
|
||||
{
|
||||
'kuiPanelSimple--shadow': hasShadow,
|
||||
'kuiPanelSimple--flexGrowZero': !grow,
|
||||
},
|
||||
className
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={panelRef}
|
||||
className={classes}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
KuiPanelSimple.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
hasShadow: PropTypes.bool,
|
||||
paddingSize: PropTypes.oneOf(SIZES),
|
||||
grow: PropTypes.bool,
|
||||
panelRef: PropTypes.func,
|
||||
};
|
||||
|
||||
KuiPanelSimple.defaultProps = {
|
||||
paddingSize: 'm',
|
||||
hasShadow: false,
|
||||
grow: true,
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
import React from 'react';
|
||||
import { render } from 'enzyme';
|
||||
import { requiredProps } from '../../test/required_props';
|
||||
|
||||
import { KuiPanelSimple } from './panel_simple';
|
||||
|
||||
describe('KuiPanelSimple', () => {
|
||||
test('is rendered', () => {
|
||||
const component = render(
|
||||
<KuiPanelSimple {...requiredProps} />
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -5,9 +5,6 @@ exports[`KuiPopover anchorPosition defaults to center 1`] = `
|
|||
class="kuiPopover"
|
||||
>
|
||||
<button />
|
||||
<div
|
||||
class="kuiPopover__body"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
@ -16,9 +13,6 @@ exports[`KuiPopover anchorPosition left is rendered 1`] = `
|
|||
class="kuiPopover kuiPopover--anchorLeft"
|
||||
>
|
||||
<button />
|
||||
<div
|
||||
class="kuiPopover__body"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
@ -27,9 +21,6 @@ exports[`KuiPopover anchorPosition right is rendered 1`] = `
|
|||
class="kuiPopover kuiPopover--anchorRight"
|
||||
>
|
||||
<button />
|
||||
<div
|
||||
class="kuiPopover__body"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
@ -38,11 +29,6 @@ exports[`KuiPopover children is rendered 1`] = `
|
|||
class="kuiPopover"
|
||||
>
|
||||
<button />
|
||||
<div
|
||||
class="kuiPopover__body"
|
||||
>
|
||||
Children
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
@ -53,9 +39,6 @@ exports[`KuiPopover is rendered 1`] = `
|
|||
data-test-subj="test subject string"
|
||||
>
|
||||
<button />
|
||||
<div
|
||||
class="kuiPopover__body"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
@ -64,19 +47,18 @@ exports[`KuiPopover isOpen defaults to false 1`] = `
|
|||
class="kuiPopover"
|
||||
>
|
||||
<button />
|
||||
<div
|
||||
class="kuiPopover__body"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`KuiPopover isOpen renders true 1`] = `
|
||||
<div
|
||||
class="kuiPopover kuiPopover-isOpen"
|
||||
class="kuiPopover"
|
||||
>
|
||||
<button />
|
||||
<div
|
||||
class="kuiPopover__body"
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
class="kuiPanelSimple kuiPanelSimple--paddingMedium kuiPanelSimple--shadow kuiPopover__panel"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`KuiPopoverTitle is rendered 1`] = `
|
||||
<div
|
||||
aria-label="aria-label"
|
||||
class="kuiPopoverTitle testClass1 testClass2"
|
||||
data-test-subj="test subject string"
|
||||
/>
|
||||
`;
|
|
@ -1,3 +1,3 @@
|
|||
$popOverBackgroundColor: #FFF;
|
||||
|
||||
@import 'mixins';
|
||||
@import 'popover';
|
||||
@import 'popover_title';
|
||||
|
|
12
ui_framework/src/components/popover/_mixins.scss
Normal file
12
ui_framework/src/components/popover/_mixins.scss
Normal file
|
@ -0,0 +1,12 @@
|
|||
@mixin kuiPopoverTitle {
|
||||
background-color: $kuiColorLightestShade;
|
||||
border-bottom: $kuiBorderThin;
|
||||
padding: $kuiSizeM;
|
||||
font-size: $kuiFontSize;
|
||||
|
||||
@include darkTheme {
|
||||
background-color: $kuiBackgroundColor--darkTheme;
|
||||
border-color: $kuiInputBorderColor--darkTheme;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
|
@ -5,78 +5,91 @@
|
|||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
// Open state happens on the wrapper and applies to the body.
|
||||
// Open state happens on the wrapper and applies to the panel.
|
||||
&.kuiPopover-isOpen {
|
||||
.kuiPopover__body {
|
||||
.kuiPopover__panel {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
display: inline-block;
|
||||
z-index: 1;
|
||||
margin-top: 10px;
|
||||
box-shadow: 0 16px 16px -8px rgba(0, 0, 0, 0.1);
|
||||
z-index: $kuiZContentMenu;
|
||||
margin-top: $kuiSizeS;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animation happens on the body.
|
||||
.kuiPopover__body {
|
||||
line-height: $kuiLineHeight;
|
||||
font-size: $kuiFontSize;
|
||||
// Animation happens on the panel.
|
||||
.kuiPopover__panel {
|
||||
position: absolute;
|
||||
min-width: 256px; // Can expand further, but this size is good for our menus.
|
||||
min-width: $kuiSize * 16; // Can expand further, but this size is good for our menus.
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
background: $popOverBackgroundColor;
|
||||
border: 1px solid $kuiBorderColor;
|
||||
border-radius: $kuiBorderRadius 0 $kuiBorderRadius $kuiBorderRadius;
|
||||
padding: 16px;
|
||||
transform: translateX(-50%) translateY(8px) translateZ(0);
|
||||
transform: translateX(-50%) translateY($kuiSizeS) translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
transition:
|
||||
opacity $kuiAnimSlightBounce $kuiAnimSpeedSlow,
|
||||
visibility $kuiAnimSlightBounce $kuiAnimSpeedSlow,
|
||||
margin-top $kuiAnimSlightBounce $kuiAnimSpeedSlow;
|
||||
transform-origin: center top;
|
||||
opacity: 0;
|
||||
display: none;
|
||||
margin-top: 32px;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
margin-top: $kuiSizeL;
|
||||
|
||||
// This fakes a border on the arrow.
|
||||
&:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: -16px;
|
||||
top: -$kuiSize;
|
||||
height: 0;
|
||||
width: 0;
|
||||
left: 50%;
|
||||
margin-left: -16px;
|
||||
border-left: 16px solid transparent;
|
||||
border-right: 16px solid transparent;
|
||||
border-bottom: 16px solid $kuiBorderColor;
|
||||
margin-left: -$kuiSize;
|
||||
border-left: $kuiSize solid transparent;
|
||||
border-right: $kuiSize solid transparent;
|
||||
border-bottom: $kuiSize solid $kuiBorderColor;
|
||||
|
||||
@include darkTheme {
|
||||
border-bottom-color: $kuiInputBorderColor--darkTheme;
|
||||
}
|
||||
}
|
||||
|
||||
// This part of the arrow matches the body.
|
||||
// This part of the arrow matches the panel.
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: -16px + 1;
|
||||
top: -$kuiSize + 1;
|
||||
right: 0;
|
||||
height: 0;
|
||||
left: 50%;
|
||||
margin-left: -16px;
|
||||
margin-left: -$kuiSize;
|
||||
width: 0;
|
||||
border-left: 16px solid transparent;
|
||||
border-right: 16px solid transparent;
|
||||
border-bottom: 16px solid $popOverBackgroundColor;
|
||||
border-left: $kuiSize solid transparent;
|
||||
border-right: $kuiSize solid transparent;
|
||||
border-bottom: $kuiSize solid #ffffff;
|
||||
|
||||
@include darkTheme {
|
||||
border-bottom-color: $kuiBackgroundColor--darkTheme;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.kuiPopover--withTitle .kuiPopover__panel:after {
|
||||
border-bottom-color: $kuiColorLightestShade;
|
||||
|
||||
@include darkTheme {
|
||||
border-bottom-color: $kuiBackgroundColor--darkTheme;
|
||||
}
|
||||
}
|
||||
|
||||
// Positions the menu and arrow to the left of the parent.
|
||||
.kuiPopover--anchorLeft {
|
||||
.kuiPopover__body {
|
||||
.kuiPopover__panel {
|
||||
left: 0;
|
||||
transform: translateX(0%) translateY(8px) translateZ(0);
|
||||
transform: translateX(0%) translateY($kuiSizeS) translateZ(0);
|
||||
|
||||
&:before, &:after {
|
||||
right: auto;
|
||||
left: 8px;
|
||||
left: $kuiSize;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
@ -84,12 +97,12 @@
|
|||
|
||||
// Positions the menu and arrow to the right of the parent.
|
||||
.kuiPopover--anchorRight {
|
||||
.kuiPopover__body {
|
||||
.kuiPopover__panel {
|
||||
left: 100%;
|
||||
transform: translateX(-100%) translateY(8px) translateZ(0);
|
||||
transform: translateX(-100%) translateY($kuiSizeS) translateZ(0);
|
||||
|
||||
&:before, &:after {
|
||||
right: 8px;
|
||||
right: $kuiSize;
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
|
|
3
ui_framework/src/components/popover/_popover_title.scss
Normal file
3
ui_framework/src/components/popover/_popover_title.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
.kuiPopoverTitle {
|
||||
@include kuiPopoverTitle;
|
||||
}
|
|
@ -1,3 +1,2 @@
|
|||
export {
|
||||
KuiPopover,
|
||||
} from './popover';
|
||||
export { KuiPopover, } from './popover';
|
||||
export { KuiPopoverTitle } from './popover_title';
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import React from 'react';
|
||||
import React, {
|
||||
Component,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
|
||||
import { cascadingMenuKeyCodes } from '../../services';
|
||||
|
||||
import { KuiOutsideClickDetector } from '../outside_click_detector';
|
||||
|
||||
import { KuiPanelSimple, SIZES } from '../../components/panel_simple';
|
||||
|
||||
const anchorPositionToClassNameMap = {
|
||||
'center': '',
|
||||
'left': 'kuiPopover--anchorLeft',
|
||||
|
@ -12,56 +19,134 @@ const anchorPositionToClassNameMap = {
|
|||
|
||||
export const ANCHOR_POSITIONS = Object.keys(anchorPositionToClassNameMap);
|
||||
|
||||
export const KuiPopover = ({
|
||||
anchorPosition,
|
||||
bodyClassName,
|
||||
button,
|
||||
isOpen,
|
||||
children,
|
||||
className,
|
||||
closePopover,
|
||||
...rest,
|
||||
}) => {
|
||||
const classes = classNames(
|
||||
'kuiPopover',
|
||||
anchorPositionToClassNameMap[anchorPosition],
|
||||
className,
|
||||
{
|
||||
'kuiPopover-isOpen': isOpen,
|
||||
},
|
||||
);
|
||||
export class KuiPopover extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const bodyClasses = classNames('kuiPopover__body', bodyClassName);
|
||||
this.closingTransitionTimeout = undefined;
|
||||
|
||||
const body = (
|
||||
<div className={bodyClasses}>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
this.state = {
|
||||
isClosing: false,
|
||||
isOpening: false,
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<KuiOutsideClickDetector onOutsideClick={closePopover}>
|
||||
<div
|
||||
className={classes}
|
||||
{...rest}
|
||||
>
|
||||
{ button }
|
||||
{ body }
|
||||
</div>
|
||||
</KuiOutsideClickDetector>
|
||||
);
|
||||
};
|
||||
onKeyDown = e => {
|
||||
if (e.keyCode === cascadingMenuKeyCodes.ESCAPE) {
|
||||
this.props.closePopover();
|
||||
}
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// The popover is being opened.
|
||||
if (!this.props.isOpen && nextProps.isOpen) {
|
||||
clearTimeout(this.closingTransitionTimeout);
|
||||
// We need to set this state a beat after the render takes place, so that the CSS
|
||||
// transition can take effect.
|
||||
window.requestAnimationFrame(() => {
|
||||
this.setState({
|
||||
isOpening: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// The popover is being closed.
|
||||
if (this.props.isOpen && !nextProps.isOpen) {
|
||||
// If the user has just closed the popover, queue up the removal of the content after the
|
||||
// transition is complete.
|
||||
this.setState({
|
||||
isClosing: true,
|
||||
isOpening: false,
|
||||
});
|
||||
|
||||
this.closingTransitionTimeout = setTimeout(() => {
|
||||
this.setState({
|
||||
isClosing: false,
|
||||
});
|
||||
}, 250);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.closingTransitionTimeout);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
anchorPosition,
|
||||
button,
|
||||
isOpen,
|
||||
withTitle,
|
||||
children,
|
||||
className,
|
||||
closePopover,
|
||||
panelClassName,
|
||||
panelPaddingSize,
|
||||
...rest,
|
||||
} = this.props;
|
||||
|
||||
const classes = classNames(
|
||||
'kuiPopover',
|
||||
anchorPositionToClassNameMap[anchorPosition],
|
||||
className,
|
||||
{
|
||||
'kuiPopover-isOpen': this.state.isOpening,
|
||||
'kuiPopover--withTitle': withTitle,
|
||||
},
|
||||
);
|
||||
|
||||
const panelClasses = classNames('kuiPopover__panel', panelClassName);
|
||||
|
||||
let panel;
|
||||
|
||||
if (isOpen || this.state.isClosing) {
|
||||
panel = (
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
clickOutsideDeactivates: true,
|
||||
fallbackFocus: () => this.panel,
|
||||
}}
|
||||
>
|
||||
<KuiPanelSimple
|
||||
panelRef={node => { this.panel = node; }}
|
||||
className={panelClasses}
|
||||
paddingSize={panelPaddingSize}
|
||||
hasShadow
|
||||
>
|
||||
{children}
|
||||
</KuiPanelSimple>
|
||||
</FocusTrap>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<KuiOutsideClickDetector onOutsideClick={closePopover}>
|
||||
<div
|
||||
className={classes}
|
||||
onKeyDown={this.onKeyDown}
|
||||
{...rest}
|
||||
>
|
||||
{button}
|
||||
{panel}
|
||||
</div>
|
||||
</KuiOutsideClickDetector>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
KuiPopover.propTypes = {
|
||||
isOpen: PropTypes.bool,
|
||||
withTitle: PropTypes.bool,
|
||||
closePopover: PropTypes.func.isRequired,
|
||||
button: PropTypes.node.isRequired,
|
||||
children: PropTypes.node,
|
||||
anchorPosition: PropTypes.oneOf(ANCHOR_POSITIONS),
|
||||
bodyClassName: PropTypes.string,
|
||||
panelClassName: PropTypes.string,
|
||||
panelPaddingSize: PropTypes.oneOf(SIZES),
|
||||
};
|
||||
|
||||
KuiPopover.defaultProps = {
|
||||
isOpen: false,
|
||||
anchorPosition: 'center',
|
||||
panelPaddingSize: 'm',
|
||||
};
|
||||
|
|
21
ui_framework/src/components/popover/popover_title.js
Normal file
21
ui_framework/src/components/popover/popover_title.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const KuiPopoverTitle = ({ children, className, ...rest }) => {
|
||||
const classes = classNames('kuiPopoverTitle', className);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
KuiPopoverTitle.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
};
|
16
ui_framework/src/components/popover/popover_title.test.js
Normal file
16
ui_framework/src/components/popover/popover_title.test.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import React from 'react';
|
||||
import { render } from 'enzyme';
|
||||
import { requiredProps } from '../../test/required_props';
|
||||
|
||||
import { KuiPopoverTitle } from './popover_title';
|
||||
|
||||
describe('KuiPopoverTitle', () => {
|
||||
test('is rendered', () => {
|
||||
const component = render(
|
||||
<KuiPopoverTitle {...requiredProps} />
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -1,2 +1,3 @@
|
|||
@import 'responsive';
|
||||
@import 'shadow';
|
||||
@import 'global_mixins';
|
||||
|
|
26
ui_framework/src/global_styling/mixins/_shadow.scss
Normal file
26
ui_framework/src/global_styling/mixins/_shadow.scss
Normal file
|
@ -0,0 +1,26 @@
|
|||
@mixin kuiBottomShadow {
|
||||
box-shadow: 0 16px 16px -8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@mixin kuiBottomShadowSmall {
|
||||
box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@mixin kuiBottomShadowMedium {
|
||||
box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@mixin kuiSlightShadow {
|
||||
box-shadow:
|
||||
0 2px 2px -1px rgba(0, 0, 0, 0.15),
|
||||
}
|
||||
|
||||
@mixin kuiSlightShadowHover {
|
||||
box-shadow:
|
||||
0 4px 4px -2px rgba(0, 0, 0, 0.1),
|
||||
}
|
||||
|
||||
@mixin kuiSlightShadowActive {
|
||||
box-shadow:
|
||||
0 1px 1px 0px rgba(0, 0, 0, 0.2),
|
||||
}
|
|
@ -10,6 +10,8 @@ $kuiColorDarkGray: #666;
|
|||
$kuiColorDarkestGray: #3F3F3F;
|
||||
$kuiColorBlack: #000;
|
||||
$kuiColorWhite: #FFF;
|
||||
$kuiColorEmptyShade: $kuiColorWhite;
|
||||
$kuiColorLightestShade: lighten($kuiColorLightGray, 5%);
|
||||
|
||||
// Normal colors
|
||||
|
||||
|
@ -38,6 +40,7 @@ $kuiSuccessColor: #417505;
|
|||
$kuiWarningColor: #ec9800;
|
||||
$kuiDangerColor: $kuiColorRed;
|
||||
$kuiFocusColor: $kuiColorBlue;
|
||||
$kuiFocusAltBackgroundColor: rgba($kuiInfoColor, 0.2);
|
||||
$kuiFocusDangerColor: #ff523c;
|
||||
$kuiFocusWarningColor: #ffa500;
|
||||
$kuiFocusBackgroundColor: #ffffff;
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// Z-Index
|
||||
|
||||
$kuiZLevel0: 0;
|
||||
$kuiZLevel1: 1000;
|
||||
$kuiZLevel2: 2000;
|
||||
$kuiZLevel3: 3000;
|
||||
$kuiZLevel4: 4000;
|
||||
$kuiZLevel5: 5000;
|
||||
$kuiZLevel6: 6000;
|
||||
$kuiZLevel7: 7000;
|
||||
$kuiZLevel8: 8000;
|
||||
$kuiZLevel9: 9000;
|
||||
|
||||
$kuiZContent: $kuiZLevel0;
|
||||
$kuiZContentMenu: $kuiZLevel2;
|
||||
$kuiZNavigation: $kuiZLevel4;
|
||||
$kuiZToastList: $kuiZLevel5;
|
||||
$kuiZMask: $kuiZLevel6;
|
||||
$kuiZModal: $kuiZLevel8;
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* These keys are used for navigating cascading menu UI components.
|
||||
*
|
||||
* UP: Select the previous item in the list.
|
||||
* DOWN: Select the next item in the list.
|
||||
* LEFT: Show the previous menu.
|
||||
* RIGHT: Show the next menu for the selected item.
|
||||
* ESC: Deselect the current selection and hide the list.
|
||||
*/
|
||||
|
||||
import {
|
||||
DOWN,
|
||||
ESCAPE,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
TAB,
|
||||
UP,
|
||||
} from '../key_codes';
|
||||
|
||||
export const cascadingMenuKeyCodes = {
|
||||
DOWN,
|
||||
ESCAPE,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
TAB,
|
||||
UP,
|
||||
};
|
|
@ -1,3 +1,4 @@
|
|||
export { accessibleClickKeys } from './accessible_click_keys';
|
||||
export { cascadingMenuKeyCodes } from './cascading_menu_key_codes';
|
||||
export { comboBoxKeyCodes } from './combo_box_key_codes';
|
||||
export { htmlIdGenerator } from './html_id_generator';
|
||||
|
|
|
@ -4,6 +4,7 @@ export { keyCodes };
|
|||
|
||||
export {
|
||||
accessibleClickKeys,
|
||||
cascadingMenuKeyCodes,
|
||||
comboBoxKeyCodes,
|
||||
htmlIdGenerator
|
||||
} from './accessibility';
|
||||
|
|
|
@ -6,3 +6,5 @@ export const TAB = 9;
|
|||
// Arrow keys
|
||||
export const DOWN = 40;
|
||||
export const UP = 38;
|
||||
export const LEFT = 37;
|
||||
export const RIGHT = 39;
|
||||
|
|
6
ui_framework/src/test/snapshot_component.js
Normal file
6
ui_framework/src/test/snapshot_component.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
export const snapshotComponent = component => {
|
||||
const html = component.html();
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = html;
|
||||
return template.content.firstChild;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue