diff --git a/package.json b/package.json index 9494fd2af12cc..2718f629fdb80 100644 --- a/package.json +++ b/package.json @@ -160,6 +160,7 @@ "node-uuid": "1.4.7", "pegjs": "0.9.0", "postcss-loader": "1.2.1", + "prop-types": "15.5.8", "pui-react-overlay-trigger": "7.5.4", "pui-react-tooltip": "7.5.4", "querystring-browser": "1.0.4", diff --git a/src/core_plugins/elasticsearch/lib/create_kibana_index.js b/src/core_plugins/elasticsearch/lib/create_kibana_index.js index ad759378c3bcf..ddf3514520217 100644 --- a/src/core_plugins/elasticsearch/lib/create_kibana_index.js +++ b/src/core_plugins/elasticsearch/lib/create_kibana_index.js @@ -7,6 +7,7 @@ module.exports = function (server, mappings) { settings: { number_of_shards: 1, 'index.mapper.dynamic': false, + 'index.mapping.single_type': false }, mappings } diff --git a/src/core_plugins/kibana/public/visualize/listing/visualize_listing.html b/src/core_plugins/kibana/public/visualize/listing/visualize_listing.html index cdbcebe534831..9edb5ab913377 100644 --- a/src/core_plugins/kibana/public/visualize/listing/visualize_listing.html +++ b/src/core_plugins/kibana/public/visualize/listing/visualize_listing.html @@ -119,7 +119,7 @@ Name @@ -131,7 +131,7 @@ Type diff --git a/src/core_plugins/timelion/public/app.less b/src/core_plugins/timelion/public/app.less index 0095eaa710480..97428574bf3de 100644 --- a/src/core_plugins/timelion/public/app.less +++ b/src/core_plugins/timelion/public/app.less @@ -24,6 +24,18 @@ } } +/** + * 1. Needs to be relative to contain absolutely-positioned typeahead suggestions. + */ +.timelionLocalSearch { + position: relative; /* 1 */ +} + +.timelionSearchInputContainer { + flex: 1 1 auto; + display: flex; +} + .timelion-container { margin: 20px; } @@ -38,10 +50,6 @@ background-color: @gray-lighter; } -.timelion-expression { - position: relative !important; -} - .timelion-subnav { background-color: @gray-lighter; margin: 0px @@ -88,33 +96,17 @@ opacity: 0.50; } -.timelion-interval { - width: 70px !important; - height: auto; - -webkit-appearance: none; - -moz-appearance: none; - padding: .5em; - padding-right: 1.5em; - line-height: 1; - border-top: 0; - border-right: 0; - border-bottom: 0; - border-radius: 0; - text-align: center; - - &:focus { - border-color: #ecf0f1; - } +.timelion-interval-custom { + width: 60px; } -.timelion-interval--select { - background-position: right 50%; - background-repeat: no-repeat; - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAMCAYAAABSgIzaAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDZFNDEwNjlGNzFEMTFFMkJEQ0VDRTM1N0RCMzMyMkIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDZFNDEwNkFGNzFEMTFFMkJEQ0VDRTM1N0RCMzMyMkIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0NkU0MTA2N0Y3MUQxMUUyQkRDRUNFMzU3REIzMzIyQiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0NkU0MTA2OEY3MUQxMUUyQkRDRUNFMzU3REIzMzIyQiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PuGsgwQAAAA5SURBVHjaYvz//z8DOYCJgUxAf42MQIzTk0D/M+KzkRGPoQSdykiKJrBGpOhgJFYTWNEIiEeAAAMAzNENEOH+do8AAAAASUVORK5CYII=); +.timelion-interval-presets { + width: 90px; } -.timelion-interval-other { - width: 10px !important; +.timelion-interval-presets-compact { + width: 10px; + padding-left: 0; } timelion-interval { @@ -214,10 +206,3 @@ timelion-interval { margin-right: 5px; font-weight: bold; } - -/** - * 1. Needs to be relative to contain absolutely-positioned typeahead suggestions. - */ -.timelionLocalSearch { - position: relative; /* 1 */ -} diff --git a/src/core_plugins/timelion/public/directives/expression_directive.html b/src/core_plugins/timelion/public/directives/expression_directive.html new file mode 100644 index 0000000000000..314528c634042 --- /dev/null +++ b/src/core_plugins/timelion/public/directives/expression_directive.html @@ -0,0 +1,23 @@ +
+ + + +
diff --git a/src/core_plugins/timelion/public/directives/expression_directive.js b/src/core_plugins/timelion/public/directives/expression_directive.js index 9d90b36b5cc6b..3f33918d7a3d1 100644 --- a/src/core_plugins/timelion/public/directives/expression_directive.js +++ b/src/core_plugins/timelion/public/directives/expression_directive.js @@ -2,10 +2,14 @@ import _ from 'lodash'; import $ from 'jquery'; import grammar from 'raw!../chain.peg'; import PEG from 'pegjs'; -const Parser = PEG.buildParser(grammar); -import template from './partials/suggestion.html'; -const app = require('ui/modules').get('apps/timelion', []); +import './partials/suggestion'; +import timelionExpressionInputTemplate from './expression_directive.html'; +import { + FunctionSuggestions, + suggest, + insertAtLocation, +} from './expression_directive_helpers'; /* Autocomplete proposal, this file doesn't actually work like this @@ -30,17 +34,22 @@ Only named arguments, necessarily provided optional by a plugin. Must be inside a function, and start must be adjacent to the argument name .function(arg=b|) +*/ +const Parser = PEG.buildParser(grammar); -*/ +const app = require('ui/modules').get('apps/timelion', []); -app.directive('timelionExpression', function ($compile, $http, $timeout, $rootScope) { +app.directive('timelionExpressionInput', function ($compile, $http, $timeout) { return { - restrict: 'A', - require: 'ngModel', - link: function ($scope, $elem, $attrs, ngModelCtrl) { - - const keys = { + restrict: 'E', + scope: { + sheet: '=', + }, + replace: true, + template: timelionExpressionInputTemplate, + link: function ($scope, $elem) { + const navigationalKeys = { ESC: 27, UP: 38, DOWN: 40, @@ -49,184 +58,144 @@ app.directive('timelionExpression', function ($compile, $http, $timeout, $rootSc }; const functionReference = {}; + const input = $elem.find('#timelionSearchInput'); + const caretLocation = {}; - function init() { - resetSuggestions(); - $elem.on('mouseup', function () { - suggest($attrs.timelionExpression); - digest(); - }); - $elem.on('keydown', keyDownHandler); - $elem.on('keyup', keyUpHandler); - $elem.on('blur', function () { - $timeout(function () { - $scope.suggestions.show = false; - }, 100); - }); + $scope.functionSuggestions = new FunctionSuggestions(); - $elem.after($compile(template)($scope)); + function init() { $http.get('../api/timelion/functions').then(function (resp) { - functionReference.byName = _.indexBy(resp.data, 'name'); - functionReference.list = resp.data; + Object.assign(functionReference, { + byName: _.indexBy(resp.data, 'name'), + list: resp.data, + }); }); } - $scope.$on('$destroy', function () { - $elem.off('mouseup'); - $elem.off('keydown'); - $elem.off('keyup'); - $elem.off('blur'); - }); - - function suggest(val) { - try { - // Inside an existing function providing suggestion only as a reference. Maybe suggest an argument? - const possible = findFunction(getCaretPos(), Parser.parse(val).functions); - // TODO: Reference suggestors. Only supporting completion right now; - resetSuggestions(); + function completeExpression(suggestionIndex) { + if ($scope.functionSuggestions.isEmpty()) { return; - - - if (functionReference.byName) { - if (functionReference.byName[possible.function]) { - $scope.suggestions.list = [functionReference.byName[possible.function]]; - $scope.suggestions.show = true; - } else { - resetSuggestions(); - } - } - } catch (e) { - try { // Is this a structured exception? - e = JSON.parse(e.message); - if (e.location.min > getCaretPos() || e.location.max <= getCaretPos()) { - resetSuggestions(); - return; - } - // TODO: Abstract structured exception handling; - if (e.type === 'incompleteFunction') { - if (e.function == null) { - $scope.suggestions.list = functionReference.list; - } else { - $scope.suggestions.list = _.compact(_.map(functionReference.list, function (func) { - if (_.startsWith(func.name, e.function)) { - return func; - } - })); - } - $scope.suggestions.show = true; - } - $scope.suggestions.location = e.location; - } catch (e) { - resetSuggestions(); - } } - digest(); - } - - function validateSelection() { - const maxSelection = $scope.suggestions.list.length - 1; - if ($scope.suggestions.selected > maxSelection) $scope.suggestions.selected = maxSelection; - else if ($scope.suggestions.selected < 0) $scope.suggestions.selected = 0; - } - - $scope.completeExpression = function (selected) { - if (!$scope.suggestions.list.length) return; - const expression = $attrs.timelionExpression; - const startOf = expression.slice(0, $scope.suggestions.location.min); - const endOf = expression.slice($scope.suggestions.location.max, expression.length); - const completeFunction = $scope.suggestions.list[selected].name + '()'; + const functionName = `${$scope.functionSuggestions.list[suggestionIndex].name}()`; + const expression = $scope.sheet; + const { min, max } = caretLocation; - const newVal = startOf + completeFunction + endOf; + const newExpression = insertAtLocation(functionName, expression, min, max); + input.val(newExpression); - $elem.val(newVal); - $elem[0].selectionStart = $elem[0].selectionEnd = - (startOf + completeFunction).length - 1; - ngModelCtrl.$setViewValue(newVal); + const newCaretPosition = min + functionName.length - 1; + input[0].selectionStart = input[0].selectionEnd = newCaretPosition; - resetSuggestions(); - }; + $scope.functionSuggestions.reset(); + } + function scrollTo(selected) { + const suggestionsListElem = $('[data-suggestions-list]'); + const suggestedElem = $($('[data-suggestion-list-item]')[selected]); - function keyDownHandler(e) { - if (_.contains(_.values(keys), e.keyCode)) e.preventDefault(); - switch (e.keyCode) { - case keys.UP: - if ($scope.suggestions.selected > 0) $scope.suggestions.selected--; - break; - case keys.DOWN: - $scope.suggestions.selected++; - break; - case keys.TAB: - $scope.completeExpression($scope.suggestions.selected); - break; - case keys.ENTER: - if ($scope.suggestions.list.length) { - $scope.completeExpression($scope.suggestions.selected); - } else { - $elem.submit(); - } - break; - case keys.ESC: - $scope.suggestions.show = false; - break; + if (!suggestedElem.position() || !suggestedElem.position().top) { + return; } - scrollTo($scope.suggestions); - digest(); - } - function keyUpHandler(e) { - if (_.contains(_.values(keys), e.keyCode)) return; + suggestionsListElem.scrollTop(suggestionsListElem.scrollTop() + suggestedElem.position().top); + } - suggest($attrs.timelionExpression); - validateSelection(); - digest(); + function getSuggestions() { + const caretPosition = input[0].selectionStart; + + suggest( + $scope.sheet, + caretPosition, + functionReference.list, + Parser + ).then(({ list, location }) => { + // We're using ES6 Promises, not $q, so we have to wrap this in $apply. + $scope.$apply(() => { + $scope.functionSuggestions.setList(list); + $scope.functionSuggestions.show(); + Object.assign(caretLocation, location); + }); + }, ({ location } = {}) => { + $scope.$apply(() => { + Object.assign(caretLocation, location); + $scope.functionSuggestions.reset(); + }); + }); } - function resetSuggestions() { - $scope.suggestions = { - selected: 0, - list: [], - position: {}, - show: false - }; - return $scope.suggestions; + function isNavigationalKey(keyCode) { + const keyCodes = _.values(navigationalKeys); + return keyCodes.includes(keyCode); } - function scrollTo(suggestions) { - validateSelection(); - const suggestionsListElem = $('.suggestions'); - const suggestedElem = $($('.suggestion')[suggestions.selected]); + $scope.mouseUpHandler = () => { + getSuggestions(); + }; - if (!suggestedElem.position() || !suggestedElem.position().top) return; + $scope.blurHandler = () => { + $timeout(() => { + $scope.functionSuggestions.hide(); + }, 100); + }; - suggestionsListElem.scrollTop(suggestionsListElem.scrollTop() + suggestedElem.position().top); - } + $scope.keyDownHandler = e => { + // If we've pressed any non-navigational keys, then the user has typed something and we + // can exit early without doing any navigation. + if (!isNavigationalKey(e.keyCode)) { + return; + } - function findFunction(position, functionList) { - let bestFunction; + switch (e.keyCode) { + case navigationalKeys.UP: + // Up and down keys navigate through suggestions. + e.preventDefault(); + $scope.functionSuggestions.stepForward(); + scrollTo($scope.functionSuggestions.index); + break; + + case navigationalKeys.DOWN: + // Up and down keys navigate through suggestions. + e.preventDefault(); + $scope.functionSuggestions.stepBackward(); + scrollTo($scope.functionSuggestions.index); + break; - _.each(functionList, function (func) { - if ((func.location.min) < position && position < (func.location.max)) { - if (!bestFunction || func.text.length < bestFunction.text.length) { - bestFunction = func; + case navigationalKeys.TAB: + // If there are no suggestions, the user tabs to the next input. + if ($scope.functionSuggestions.isEmpty()) { + return; } - } - }); - return bestFunction; - } + // If we have suggestions, complete the selected one. + e.preventDefault(); + completeExpression($scope.functionSuggestions.index); + break; - function getCaretPos() { - return $elem[0].selectionStart; - } + case navigationalKeys.ENTER: + // If the suggestions are open, complete the expression with the suggestion. + // Otherwise, the default action of submitting the input value will occur. + if (!$scope.functionSuggestions.isEmpty()) { + e.preventDefault(); + completeExpression($scope.functionSuggestions.index); + } + break; - function digest() { - $rootScope.$$phase || $scope.$digest(); - } + case navigationalKeys.ESC: + e.preventDefault(); + $scope.functionSuggestions.hide(); + break; + } + }; - init(); + $scope.keyUpHandler = e => { + // If the user isn't navigating, then we should update the suggestions based on their input. + if (!isNavigationalKey(e.keyCode)) { + getSuggestions(); + } + }; + init(); } }; }); diff --git a/src/core_plugins/timelion/public/directives/expression_directive_helpers.js b/src/core_plugins/timelion/public/directives/expression_directive_helpers.js new file mode 100644 index 0000000000000..7537f060ab666 --- /dev/null +++ b/src/core_plugins/timelion/public/directives/expression_directive_helpers.js @@ -0,0 +1,113 @@ +import _ from 'lodash'; + +export class FunctionSuggestions { + constructor() { + this.reset(); + } + + reset() { + this.index = 0; + this.list = []; + this.isVisible = false; + } + + setList(list) { + this.list = list; + + // We may get a shorter list than the one we have now, so we need to make sure our index doesn't + // fall outside of the new list's range. + this.index = Math.max(0, Math.min(this.index, this.list.length - 1)); + } + + getCount() { + return this.list.length; + } + + isEmpty() { + return this.list.length === 0; + } + + show() { + this.isVisible = true; + } + + hide() { + this.isVisible = false; + } + + stepForward() { + if (this.index > 0) { + this.index -= 1; + } + } + + stepBackward() { + if (this.index < this.list.length - 1) { + this.index += 1; + } + } +} + +export function findFunction(position, functionList) { + let matchingFunction; + + functionList.forEach(func => { + if ((func.location.min) < position && position < (func.location.max)) { + if (!matchingFunction || func.text.length < matchingFunction.text.length) { + matchingFunction = func; + } + } + }); + + return matchingFunction; +} + +export function suggest(val, caretPosition, functionList, Parser) { + return new Promise((resolve, reject) => { + try { + // Inside an existing function providing suggestion only as a reference. Maybe suggest an argument? + findFunction(caretPosition, Parser.parse(val).functions); + + // TODO: Reference suggestors. Only supporting completion right now; + + // We rely on peg to throw an error in order to suggest function(s). If peg doesn't + // throw an error, then we have no suggestions to offer. + return reject(); + } catch (e) { + // NOTE: This is a peg SyntaxError. + try { // Is this a structured exception? + const error = JSON.parse(e.message); + const location = error.location; + + if (location.min > caretPosition || location.max <= caretPosition) { + return reject({ location }); + } + // TODO: Abstract structured exception handling; + if (error.type === 'incompleteFunction') { + if (error.function == null) { + return resolve({ + list: functionList, + location, + }); + } else { + const list = _.compact(_.map(functionList, func => { + if (_.startsWith(func.name, error.function)) { + return func; + } + })); + return resolve({ list, location }); + } + } + } catch (e) { + return reject(); + } + } + }); +} + +export function insertAtLocation(value, destination, min, max) { + // Insert the value at a location caret within the destination. + const startOf = destination.slice(0, min); + const endOf = destination.slice(max, destination.length); + return `${startOf}${value}${endOf}`; +} diff --git a/src/core_plugins/timelion/public/directives/interval/interval.html b/src/core_plugins/timelion/public/directives/interval/interval.html index 557f84dd41417..4e7014ed0addb 100644 --- a/src/core_plugins/timelion/public/directives/interval/interval.html +++ b/src/core_plugins/timelion/public/directives/interval/interval.html @@ -1,8 +1,13 @@ - + - + diff --git a/src/core_plugins/timelion/public/directives/interval/interval.js b/src/core_plugins/timelion/public/directives/interval/interval.js index 8787192a61aee..d7fc360913285 100644 --- a/src/core_plugins/timelion/public/directives/interval/interval.js +++ b/src/core_plugins/timelion/public/directives/interval/interval.js @@ -8,7 +8,11 @@ app.directive('timelionInterval', function ($compile, $timeout) { return { restrict: 'E', scope: { - model: '=', // The interval model + // The interval model + model: '=', + // Differentiates between contexts, e.g. when this directive is used in the header in the + // Timelion app or in the sidebar in the Visualize app. + inHeader: '=', }, template: html, link: function ($scope, $elem) { diff --git a/src/core_plugins/timelion/public/directives/partials/suggestion.html b/src/core_plugins/timelion/public/directives/partials/suggestion.html index 35bf083587a16..054bfc6cfacce 100644 --- a/src/core_plugins/timelion/public/directives/partials/suggestion.html +++ b/src/core_plugins/timelion/public/directives/partials/suggestion.html @@ -1,8 +1,14 @@ -
-
+
+ ng-class="{active: $index === selectedIndex}" + ng-repeat="suggestion in suggestions track by $index | orderBy:'name'" + >

.{{suggestion.name}}() @@ -12,7 +18,7 @@

-
+
Arguments: {{arg.name}}=({{arg.types.join(' | ')}}) @@ -20,7 +26,7 @@

-
+
@@ -36,4 +42,4 @@

- \ No newline at end of file + diff --git a/src/core_plugins/timelion/public/directives/partials/suggestion.js b/src/core_plugins/timelion/public/directives/partials/suggestion.js new file mode 100644 index 0000000000000..f40bdacd9f0b8 --- /dev/null +++ b/src/core_plugins/timelion/public/directives/partials/suggestion.js @@ -0,0 +1,18 @@ +import timelionExpressionSuggestionsTemplate from './suggestion.html'; + +const app = require('ui/modules').get('apps/timelion', []); + +app.directive('timelionExpressionSuggestions', () => { + return { + restrict: 'E', + scope: { + suggestions: '=', + selectedIndex: '=', + onClickSuggestion: '&', + }, + replace: true, + template: timelionExpressionSuggestionsTemplate, + link: function () { + } + }; +}); diff --git a/src/core_plugins/timelion/public/index.html b/src/core_plugins/timelion/public/index.html index 2eed7996b7ad7..cb372f382c023 100644 --- a/src/core_plugins/timelion/public/index.html +++ b/src/core_plugins/timelion/public/index.html @@ -24,17 +24,14 @@ ng-submit="search()" >
- + - + - -
- + diff --git a/src/ui/public/react_components.js b/src/ui/public/react_components.js index 86a33e102d651..e6e262e9b8b89 100644 --- a/src/ui/public/react_components.js +++ b/src/ui/public/react_components.js @@ -2,10 +2,15 @@ import 'ngreact'; import { KuiToolBarSearchBox, + KuiConfirmModal, } from 'ui_framework/components'; import { uiModules } from 'ui/modules'; + const app = uiModules.get('app/kibana', ['react']); app.directive('toolBarSearchBox', function (reactDirective) { return reactDirective(KuiToolBarSearchBox); }); +app.directive('confirmModal', function (reactDirective) { + return reactDirective(KuiConfirmModal); +}); diff --git a/src/ui/public/styles/bootstrap/buttons.less b/src/ui/public/styles/bootstrap/buttons.less new file mode 100644 index 0000000000000..910365822cb3d --- /dev/null +++ b/src/ui/public/styles/bootstrap/buttons.less @@ -0,0 +1,69 @@ +/** + * angular-ui-select depends upon these styles. Don't use them in your markup. + * Please use the UI Framework styles instead. + */ + +.btn { + display: inline-block; + margin-bottom: 0; // For input.btn + font-weight: @btn-font-weight; + text-align: center; + vertical-align: middle; + touch-action: manipulation; + cursor: pointer; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid transparent; + white-space: nowrap; + .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @btn-border-radius-base); + .user-select(none); + + &, + &:active, + &.active { + &:focus, + &.focus { + .tab-focus(); + } + } + + &:hover, + &:focus, + &.focus { + color: @btn-default-color; + text-decoration: none; + } + + &:active, + &.active { + outline: 0; + background-image: none; + .box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); + } + + &.disabled, + &[disabled], + fieldset[disabled] & { + cursor: @cursor-disabled; + .opacity(.65); + .box-shadow(none); + } + + a& { + &.disabled, + fieldset[disabled] & { + pointer-events: none; // Future-proof disabling of clicks on `` elements + } + } +} + +.btn-default { + .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border); +} + +.btn-primary { + .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border); +} + +.btn-xs { + .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small); +} diff --git a/src/ui/public/styles/theme/bootstrap.less b/src/ui/public/styles/theme/bootstrap.less index d912efcae82cb..727620702439a 100644 --- a/src/ui/public/styles/theme/bootstrap.less +++ b/src/ui/public/styles/theme/bootstrap.less @@ -20,6 +20,7 @@ // Components @import "~ui/styles/bootstrap/component-animations.less"; @import "~ui/styles/bootstrap/dropdowns.less"; +@import "~ui/styles/bootstrap/buttons.less"; @import "~ui/styles/bootstrap/input-groups.less"; @import "~ui/styles/bootstrap/navs.less"; @import "~ui/styles/bootstrap/navbar.less"; diff --git a/src/ui/public/vis_maps/kibana_map.js b/src/ui/public/vis_maps/kibana_map.js index fb92f6b21713c..946bf50d308e9 100644 --- a/src/ui/public/vis_maps/kibana_map.js +++ b/src/ui/public/vis_maps/kibana_map.js @@ -347,18 +347,19 @@ export class KibanaMap extends EventEmitter { } addDrawControl() { + const shapeOptions = { + shapeOptions: { + stroke: false, + color: '#000' + } + }; const drawOptions = { draw: { polyline: false, marker: false, circle: false, - polygon: false, - rectangle: { - shapeOptions: { - stroke: false, - color: '#000' - } - } + rectangle: shapeOptions, + polygon: shapeOptions } }; this._leafletDrawControl = new L.Control.Draw(drawOptions); diff --git a/src/ui/public/vis_maps/maps_renderbot.js b/src/ui/public/vis_maps/maps_renderbot.js index 598c5b2000896..8641f5c50a070 100644 --- a/src/ui/public/vis_maps/maps_renderbot.js +++ b/src/ui/public/vis_maps/maps_renderbot.js @@ -92,6 +92,9 @@ module.exports = function MapsRenderbotFactory(Private, $injector, tilemapSettin this._kibanaMap.on('drawCreated:rectangle', event => { addSpatialFilter(_.get(this._chartData, 'geohashGridAgg'), 'geo_bounding_box', event.bounds); }); + this._kibanaMap.on('drawCreated:polygon', event => { + addSpatialFilter(_.get(this._chartData, 'geohashGridAgg'), 'geo_polygon', { points: event.points }); + }); this._kibanaMap.on('baseLayer:loaded', () => { this._baseLayerDirty = false; this._doRenderComplete(); diff --git a/ui_framework/components/card/_card.scss b/ui_framework/components/card/_card.scss index 923e1327eb136..b278a4d1636bb 100644 --- a/ui_framework/components/card/_card.scss +++ b/ui_framework/components/card/_card.scss @@ -13,6 +13,7 @@ align-items: center; justify-content: flex-start; padding: 18px 0; + text-align: center; } .kuiCard__descriptionTitle { diff --git a/ui_framework/components/card/_card_group.scss b/ui_framework/components/card/_card_group.scss index 70b7e03c295ea..4f9f56ff42417 100644 --- a/ui_framework/components/card/_card_group.scss +++ b/ui_framework/components/card/_card_group.scss @@ -1,21 +1,31 @@ .kuiCardGroup { display: flex; - border: 1px solid $cardBorderColor; - border-radius: $globalBorderRadius; - overflow: hidden; - margin-bottom: 18px; } .kuiCardGroup__card { flex: 1 1 0%; - border: none; - border-radius: 0; & + & { - border-left: 1px solid $cardBorderColor; + margin-left: 30px; } } .kuiCardGroup__cardDescription { flex: 1 1 auto; } + +.kuiCardGroup--united { + border: 1px solid $cardBorderColor; + border-radius: $globalBorderRadius; + overflow: hidden; + + .kuiCardGroup__card { + border: none; + border-radius: 0; + } + + .kuiCardGroup__card + .kuiCardGroup__card { + margin-left: 0; + border-left: 1px solid $cardBorderColor; + } +} diff --git a/ui_framework/components/index.js b/ui_framework/components/index.js index 1e5c618f9f616..7556e907a2883 100644 --- a/ui_framework/components/index.js +++ b/ui_framework/components/index.js @@ -5,8 +5,14 @@ export { KuiLinkButton, KuiSubmitButton, } from './button'; + export { KuiToolBarSearchBox, KuiToolBar, KuiToolBarFooter, } from './tool_bar'; + +export { + KuiConfirmModal, + KuiModalOverlay +} from './modal'; diff --git a/ui_framework/components/modal/__snapshots__/confirm_modal.test.js.snap b/ui_framework/components/modal/__snapshots__/confirm_modal.test.js.snap new file mode 100644 index 0000000000000..075e5691dda64 --- /dev/null +++ b/ui_framework/components/modal/__snapshots__/confirm_modal.test.js.snap @@ -0,0 +1,59 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders KuiConfirmModal 1`] = ` +
+
+
+ A confirmation modal +
+
+
+
+ This is a confirmation modal example +
+
+
+ + +
+
+`; diff --git a/ui_framework/components/modal/__snapshots__/modal.test.js.snap b/ui_framework/components/modal/__snapshots__/modal.test.js.snap new file mode 100644 index 0000000000000..17a4ad53156a7 --- /dev/null +++ b/ui_framework/components/modal/__snapshots__/modal.test.js.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders KuiModal 1`] = ` +
+ children +
+`; diff --git a/ui_framework/components/modal/__snapshots__/modal_body.test.js.snap b/ui_framework/components/modal/__snapshots__/modal_body.test.js.snap new file mode 100644 index 0000000000000..b0ddad312d8bc --- /dev/null +++ b/ui_framework/components/modal/__snapshots__/modal_body.test.js.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders KuiModalBody 1`] = ` +
+ children +
+`; diff --git a/ui_framework/components/modal/__snapshots__/modal_body_text.test.js.snap b/ui_framework/components/modal/__snapshots__/modal_body_text.test.js.snap new file mode 100644 index 0000000000000..d7434e72b5430 --- /dev/null +++ b/ui_framework/components/modal/__snapshots__/modal_body_text.test.js.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders KuiModalBodyText 1`] = ` +
+ children +
+`; diff --git a/ui_framework/components/modal/__snapshots__/modal_footer.test.js.snap b/ui_framework/components/modal/__snapshots__/modal_footer.test.js.snap new file mode 100644 index 0000000000000..c4aaebfe75773 --- /dev/null +++ b/ui_framework/components/modal/__snapshots__/modal_footer.test.js.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders KuiModalFooter 1`] = ` +
+ children +
+`; diff --git a/ui_framework/components/modal/__snapshots__/modal_header.test.js.snap b/ui_framework/components/modal/__snapshots__/modal_header.test.js.snap new file mode 100644 index 0000000000000..61fa239e72322 --- /dev/null +++ b/ui_framework/components/modal/__snapshots__/modal_header.test.js.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders KuiModalHeader 1`] = ` +
+ children +
+`; diff --git a/ui_framework/components/modal/__snapshots__/modal_header_title.test.js.snap b/ui_framework/components/modal/__snapshots__/modal_header_title.test.js.snap new file mode 100644 index 0000000000000..938e828985786 --- /dev/null +++ b/ui_framework/components/modal/__snapshots__/modal_header_title.test.js.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders KuiModalHeaderTitle 1`] = ` +
+ children +
+`; diff --git a/ui_framework/components/modal/__snapshots__/modal_overlay.test.js.snap b/ui_framework/components/modal/__snapshots__/modal_overlay.test.js.snap new file mode 100644 index 0000000000000..536461942dd3e --- /dev/null +++ b/ui_framework/components/modal/__snapshots__/modal_overlay.test.js.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders KuiModalOverlay 1`] = ` +
+ children +
+`; diff --git a/ui_framework/components/modal/_index.scss b/ui_framework/components/modal/_index.scss index 20ed0eb3d6cfb..eebd74e114045 100644 --- a/ui_framework/components/modal/_index.scss +++ b/ui_framework/components/modal/_index.scss @@ -2,6 +2,7 @@ $modalPadding: 10px; $modalBorderColor: #6EADC1; $modalBackgroundColor: #FFF; $modalOverlayBackground: rgba(#000000, 0.3); +$globalModalDepth: 1000; @import "modal_overlay"; @import "modal"; diff --git a/ui_framework/components/modal/_modal.scss b/ui_framework/components/modal/_modal.scss index 8415b7fbd84a5..fbeb66fd8b847 100644 --- a/ui_framework/components/modal/_modal.scss +++ b/ui_framework/components/modal/_modal.scss @@ -4,6 +4,7 @@ border: 1px solid $modalBorderColor; border-radius: $globalBorderRadius; box-shadow: 0 5px 22px rgba(#000000, 0.25); + z-index: $globalModalDepth + 1; } .kuiModalHeader { diff --git a/ui_framework/components/modal/_modal_overlay.scss b/ui_framework/components/modal/_modal_overlay.scss index 7b3c40d5b0b3e..8b2e54b471c3e 100644 --- a/ui_framework/components/modal/_modal_overlay.scss +++ b/ui_framework/components/modal/_modal_overlay.scss @@ -1,6 +1,6 @@ .kuiModalOverlay { position: fixed; - z-index: 1000; + z-index: $globalModalDepth; top: 0; left: 0; right: 0; diff --git a/ui_framework/components/modal/confirm_modal.js b/ui_framework/components/modal/confirm_modal.js new file mode 100644 index 0000000000000..ad715fc52b942 --- /dev/null +++ b/ui_framework/components/modal/confirm_modal.js @@ -0,0 +1,75 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { KuiModal } from './modal'; +import { KuiModalFooter } from './modal_footer'; +import { KuiModalHeader } from './modal_header'; +import { KuiModalHeaderTitle } from './modal_header_title'; +import { KuiModalBody } from './modal_body'; +import { KuiModalBodyText } from './modal_body_text'; +import { KuiButton } from '../index'; + +export function KuiConfirmModal({ + message, + title, + onCancel, + onConfirm, + cancelButtonText, + confirmButtonText, + className, + ...rest }) { + const ariaLabel = rest['aria-label']; + const dataTestSubj = rest['data-test-subj']; + return ( + + { + title ? + + + { title } + + + : null + } + + + { message } + + + + + + {cancelButtonText} + + + {confirmButtonText} + + + + ); +} + +KuiConfirmModal.propTypes = { + message: PropTypes.string, + title: PropTypes.string, + cancelButtonText: PropTypes.string, + confirmButtonText: PropTypes.string, + onCancel: PropTypes.func, + onConfirm: PropTypes.func, + dataTestSubj: PropTypes.string, + ariaLabel: PropTypes.string, + className: PropTypes.string, +}; diff --git a/ui_framework/components/modal/confirm_modal.test.js b/ui_framework/components/modal/confirm_modal.test.js new file mode 100644 index 0000000000000..fe3314bf66a90 --- /dev/null +++ b/ui_framework/components/modal/confirm_modal.test.js @@ -0,0 +1,61 @@ +import React from 'react'; +import sinon from 'sinon'; +import { mount, render } from 'enzyme'; + +import { requiredProps } from '../../test/required_props'; + +import { + KuiConfirmModal, +} from './confirm_modal'; + +let onConfirm; +let onCancel; + +beforeEach(() => { + onConfirm = sinon.spy(); + onCancel = sinon.spy(); +}); + +test('renders KuiConfirmModal', () => { + const component = render( {}} + onConfirm={onConfirm} + cancelButtonText="Cancel Button Text" + confirmButtonText="Confirm Button Text" + { ...requiredProps } + />); + expect(component).toMatchSnapshot(); +}); + +test('onConfirm', () => { + const component = mount(); + component.find('[data-test-subj="confirmModalConfirmButton"]').simulate('click'); + sinon.assert.calledOnce(onConfirm); + sinon.assert.notCalled(onCancel); +}); + +test('onCancel', () => { + const component = mount(); + component.find('[data-test-subj="confirmModalCancelButton"]').simulate('click'); + sinon.assert.notCalled(onConfirm); + sinon.assert.calledOnce(onCancel); +}); + diff --git a/ui_framework/components/modal/index.js b/ui_framework/components/modal/index.js new file mode 100644 index 0000000000000..5484b93dd8a40 --- /dev/null +++ b/ui_framework/components/modal/index.js @@ -0,0 +1,5 @@ +export { KuiConfirmModal } from './confirm_modal'; +export { KuiModal } from './modal'; +export { KuiModalFooter } from './modal_footer'; +export { KuiModalHeader } from './modal_header'; +export { KuiModalOverlay } from './modal_overlay'; diff --git a/ui_framework/components/modal/modal.js b/ui_framework/components/modal/modal.js new file mode 100644 index 0000000000000..8ea7a08b96869 --- /dev/null +++ b/ui_framework/components/modal/modal.js @@ -0,0 +1,17 @@ +import React from 'react'; +import classnames from 'classnames'; +import PropTypes from 'prop-types'; + +export function KuiModal({ className, children, ...rest }) { + const classes = classnames('kuiModal', className); + return ( +
+ { children } +
+ ); +} + +KuiModal.propTypes = { + className: PropTypes.string, + children: PropTypes.node +}; diff --git a/ui_framework/components/modal/modal.test.js b/ui_framework/components/modal/modal.test.js new file mode 100644 index 0000000000000..4a393c27a008d --- /dev/null +++ b/ui_framework/components/modal/modal.test.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../test/required_props'; + +import { + KuiModal, +} from './modal'; + +test('renders KuiModal', () => { + const component = children; + expect(render(component)).toMatchSnapshot(); +}); diff --git a/ui_framework/components/modal/modal_body.js b/ui_framework/components/modal/modal_body.js new file mode 100644 index 0000000000000..efd954b834f12 --- /dev/null +++ b/ui_framework/components/modal/modal_body.js @@ -0,0 +1,17 @@ +import React from 'react'; +import classnames from 'classnames'; +import PropTypes from 'prop-types'; + +export function KuiModalBody({ className, children, ...rest }) { + const classes = classnames('kuiModalBody', className); + return ( +
+ { children } +
+ ); +} + +KuiModalBody.propTypes = { + className: PropTypes.string, + children: PropTypes.node +}; diff --git a/ui_framework/components/modal/modal_body.test.js b/ui_framework/components/modal/modal_body.test.js new file mode 100644 index 0000000000000..b8b4d8e02d802 --- /dev/null +++ b/ui_framework/components/modal/modal_body.test.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../test/required_props'; + +import { + KuiModalBody, +} from './modal_body'; + +test('renders KuiModalBody', () => { + const component = children; + expect(render(component)).toMatchSnapshot(); +}); diff --git a/ui_framework/components/modal/modal_body_text.js b/ui_framework/components/modal/modal_body_text.js new file mode 100644 index 0000000000000..197bf7f0a7975 --- /dev/null +++ b/ui_framework/components/modal/modal_body_text.js @@ -0,0 +1,17 @@ +import React from 'react'; +import classnames from 'classnames'; +import PropTypes from 'prop-types'; + +export function KuiModalBodyText({ className, children, ...rest }) { + const classes = classnames('kuiModalBodyText', className); + return ( +
+ { children } +
+ ); +} + +KuiModalBodyText.propTypes = { + className: PropTypes.string, + children: PropTypes.node +}; diff --git a/ui_framework/components/modal/modal_body_text.test.js b/ui_framework/components/modal/modal_body_text.test.js new file mode 100644 index 0000000000000..f71a5f6ed0bee --- /dev/null +++ b/ui_framework/components/modal/modal_body_text.test.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../test/required_props'; + +import { + KuiModalBodyText, +} from './modal_body_text'; + +test('renders KuiModalBodyText', () => { + const component = children; + expect(render(component)).toMatchSnapshot(); +}); diff --git a/ui_framework/components/modal/modal_footer.js b/ui_framework/components/modal/modal_footer.js new file mode 100644 index 0000000000000..a62ba3dccef2e --- /dev/null +++ b/ui_framework/components/modal/modal_footer.js @@ -0,0 +1,17 @@ +import React from 'react'; +import classnames from 'classnames'; +import PropTypes from 'prop-types'; + +export function KuiModalFooter({ className, children, ...rest }) { + const classes = classnames('kuiModalFooter', className); + return ( +
+ { children } +
+ ); +} + +KuiModalFooter.propTypes = { + className: PropTypes.string, + children: PropTypes.node +}; diff --git a/ui_framework/components/modal/modal_footer.test.js b/ui_framework/components/modal/modal_footer.test.js new file mode 100644 index 0000000000000..ab80fa6b14079 --- /dev/null +++ b/ui_framework/components/modal/modal_footer.test.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../test/required_props'; + +import { + KuiModalFooter, +} from './modal_footer'; + +test('renders KuiModalFooter', () => { + const component = children; + expect(render(component)).toMatchSnapshot(); +}); diff --git a/ui_framework/components/modal/modal_header.js b/ui_framework/components/modal/modal_header.js new file mode 100644 index 0000000000000..c6ccc30e2e110 --- /dev/null +++ b/ui_framework/components/modal/modal_header.js @@ -0,0 +1,17 @@ +import React from 'react'; +import classnames from 'classnames'; +import PropTypes from 'prop-types'; + +export function KuiModalHeader({ className, children, ...rest }) { + const classes = classnames('kuiModalHeader', className); + return ( +
+ { children } +
+ ); +} + +KuiModalHeader.propTypes = { + className: PropTypes.string, + children: PropTypes.node +}; diff --git a/ui_framework/components/modal/modal_header.test.js b/ui_framework/components/modal/modal_header.test.js new file mode 100644 index 0000000000000..0caaaa07ae896 --- /dev/null +++ b/ui_framework/components/modal/modal_header.test.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../test/required_props'; + +import { + KuiModalHeader, +} from './modal_header'; + +test('renders KuiModalHeader', () => { + const component = children; + expect(render(component)).toMatchSnapshot(); +}); diff --git a/ui_framework/components/modal/modal_header_title.js b/ui_framework/components/modal/modal_header_title.js new file mode 100644 index 0000000000000..a4a237ad8f1d3 --- /dev/null +++ b/ui_framework/components/modal/modal_header_title.js @@ -0,0 +1,17 @@ +import React from 'react'; +import classnames from 'classnames'; +import PropTypes from 'prop-types'; + +export function KuiModalHeaderTitle({ className, children, ...rest }) { + const classes = classnames('kuiModalHeader__title', className); + return ( +
+ { children } +
+ ); +} + +KuiModalHeaderTitle.propTypes = { + className: PropTypes.string, + children: PropTypes.node +}; diff --git a/ui_framework/components/modal/modal_header_title.test.js b/ui_framework/components/modal/modal_header_title.test.js new file mode 100644 index 0000000000000..35fbbca89c95c --- /dev/null +++ b/ui_framework/components/modal/modal_header_title.test.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../test/required_props'; + +import { + KuiModalHeaderTitle, +} from './modal_header_title'; + +test('renders KuiModalHeaderTitle', () => { + const component = children; + expect(render(component)).toMatchSnapshot(); +}); diff --git a/ui_framework/components/modal/modal_overlay.js b/ui_framework/components/modal/modal_overlay.js new file mode 100644 index 0000000000000..34334e1a75072 --- /dev/null +++ b/ui_framework/components/modal/modal_overlay.js @@ -0,0 +1,17 @@ +import React from 'react'; +import classnames from 'classnames'; +import PropTypes from 'prop-types'; + +export function KuiModalOverlay({ className, ...rest }) { + const classes = classnames('kuiModalOverlay', className); + return ( +
+ ); +} + +KuiModalOverlay.propTypes = { + className: PropTypes.string, +}; diff --git a/ui_framework/components/modal/modal_overlay.test.js b/ui_framework/components/modal/modal_overlay.test.js new file mode 100644 index 0000000000000..94f5015de662c --- /dev/null +++ b/ui_framework/components/modal/modal_overlay.test.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../test/required_props'; + +import { + KuiModalOverlay, +} from './modal_overlay'; + +test('renders KuiModalOverlay', () => { + const component = children; + expect(render(component)).toMatchSnapshot(); +}); diff --git a/ui_framework/components/notice/_notice.scss b/ui_framework/components/notice/_notice.scss index 82de82eceb329..4d3c82ce55e49 100644 --- a/ui_framework/components/notice/_notice.scss +++ b/ui_framework/components/notice/_notice.scss @@ -4,21 +4,3 @@ background-color: $noticeBackgroundColor; line-height: $globalLineHeight; } - - .kuiNotice__header { - margin-bottom: 18px; - } - -/** - * 1. Override h1 styles. - */ -.kuiNoticeTitle { - font-size: $noticeTitleFontSize; - margin-bottom: 12px; - margin-top: 0 !important; /* 1 */ -} - -.kuiNoticeText { - font-size: $globalFontSize; - margin-bottom: 12px; -} diff --git a/ui_framework/dist/ui_framework.css b/ui_framework/dist/ui_framework.css index c624ddc812e31..ff8ff8a6dc8e2 100644 --- a/ui_framework/dist/ui_framework.css +++ b/ui_framework/dist/ui_framework.css @@ -486,7 +486,8 @@ body { -webkit-justify-content: flex-start; -ms-flex-pack: start; justify-content: flex-start; - padding: 18px 0; } + padding: 18px 0; + text-align: center; } .kuiCard__descriptionTitle { font-size: 14px; @@ -508,21 +509,15 @@ body { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; - display: flex; - border: 1px solid #E0E0E0; - border-radius: 4px; - overflow: hidden; - margin-bottom: 18px; } + display: flex; } .kuiCardGroup__card { -webkit-box-flex: 1; -webkit-flex: 1 1 0%; -ms-flex: 1 1 0%; - flex: 1 1 0%; - border: none; - border-radius: 0; } + flex: 1 1 0%; } .kuiCardGroup__card + .kuiCardGroup__card { - border-left: 1px solid #E0E0E0; } + margin-left: 30px; } .kuiCardGroup__cardDescription { -webkit-box-flex: 1; @@ -530,6 +525,17 @@ body { -ms-flex: 1 1 auto; flex: 1 1 auto; } +.kuiCardGroup--united { + border: 1px solid #E0E0E0; + border-radius: 4px; + overflow: hidden; } + .kuiCardGroup--united .kuiCardGroup__card { + border: none; + border-radius: 0; } + .kuiCardGroup--united .kuiCardGroup__card + .kuiCardGroup__card { + margin-left: 0; + border-left: 1px solid #E0E0E0; } + /** * 1. If we use margins instead, columns get pushed to the next line. */ @@ -2112,7 +2118,8 @@ body { background-color: #FFF; border: 1px solid #6EADC1; border-radius: 4px; - box-shadow: 0 5px 22px rgba(0, 0, 0, 0.25); } + box-shadow: 0 5px 22px rgba(0, 0, 0, 0.25); + z-index: 1001; } .kuiModalHeader { display: -webkit-box; @@ -2181,22 +2188,6 @@ body { background-color: #FFF; line-height: 1.5; } -.kuiNotice__header { - margin-bottom: 18px; } - -/** - * 1. Override h1 styles. - */ -.kuiNoticeTitle { - font-size: 22px; - margin-bottom: 12px; - margin-top: 0 !important; - /* 1 */ } - -.kuiNoticeText { - font-size: 14px; - margin-bottom: 12px; } - .kuiPanel { border: 2px solid #E4E4E4; } diff --git a/ui_framework/doc_site/src/views/card/card_group.html b/ui_framework/doc_site/src/views/card/card_group.html index 38d1ec3acceeb..adce4280f4330 100644 --- a/ui_framework/doc_site/src/views/card/card_group.html +++ b/ui_framework/doc_site/src/views/card/card_group.html @@ -39,3 +39,47 @@
+ +
+ +
+ + +
+
+
+ Get a pteradactyl +
+ +
+ Pteradactyls can fly, like to squawk all the time, and are difficult to spell correctly. +
+
+ + +
+
diff --git a/ui_framework/doc_site/src/views/modal/confirm_modal_example.js b/ui_framework/doc_site/src/views/modal/confirm_modal_example.js new file mode 100644 index 0000000000000..e6f7719474235 --- /dev/null +++ b/ui_framework/doc_site/src/views/modal/confirm_modal_example.js @@ -0,0 +1,50 @@ +import React from 'react'; + +import { + KuiConfirmModal, + KuiModalOverlay, + KuiButton +} from '../../../../components/index'; + +export class ConfirmModalExample extends React.Component { + constructor(props) { + super(props); + this.state = { + showConfirmModal: false + }; + this.closeModal = this.closeModal.bind(this); + this.showModal = this.showModal.bind(this); + } + + closeModal() { + this.setState({ showConfirmModal: false }); + } + + showModal() { + this.setState({ showConfirmModal: true }); + } + + render() { + return ( +
+ + Show Modal + + { + this.state.showConfirmModal ? + + + + : null + } +
+ ); + } +} diff --git a/ui_framework/doc_site/src/views/modal/modal.html b/ui_framework/doc_site/src/views/modal/modal.html deleted file mode 100644 index 1f0c5be708d2b..0000000000000 --- a/ui_framework/doc_site/src/views/modal/modal.html +++ /dev/null @@ -1,25 +0,0 @@ -
-
-
- Delete object -
- -
-
- -
-
- Are you sure you want to delete this object? You can’t undo this. -
-
- -
- - - -
-
diff --git a/ui_framework/doc_site/src/views/modal/modal_example.js b/ui_framework/doc_site/src/views/modal/modal_example.js index 91c918d028486..b6a3b000ed4f8 100644 --- a/ui_framework/doc_site/src/views/modal/modal_example.js +++ b/ui_framework/doc_site/src/views/modal/modal_example.js @@ -1,5 +1,7 @@ import React from 'react'; +import { renderToHtml } from '../../services'; + import { GuideDemo, GuidePage, @@ -7,38 +9,55 @@ import { GuideSectionTypes, } from '../../components'; -const modalHtml = require('./modal.html'); -const modalOverlayHtml = require('./modal_overlay.html'); -const modalOverlayJs = require('raw!./modal_overlay.js'); +import { + KuiConfirmModal, +} from '../../../../components'; + +import { ConfirmModalExample } from './confirm_modal_example'; +const showConfirmModalSource = require('!!raw!./confirm_modal_example'); +const showConfirmModalHtml = renderToHtml(ConfirmModalExample); + +const kuiConfirmModalSource = require('!!raw!../../../../components/modal/confirm_modal'); +const kuiConfirmModalHtml = renderToHtml(KuiConfirmModal); export default props => ( + - + + {}} + onConfirm={() => {}} + confirmButtonText="Confirm" + cancelButtonText="Cancel" + message="This is a confirmation modal" + title="Confirm Modal Title" + /> + - + + + ); diff --git a/ui_framework/doc_site/src/views/modal/modal_overlay.html b/ui_framework/doc_site/src/views/modal/modal_overlay.html deleted file mode 100644 index a9d62e6665387..0000000000000 --- a/ui_framework/doc_site/src/views/modal/modal_overlay.html +++ /dev/null @@ -1,29 +0,0 @@ - - -
-
-
-
- Delete object -
- -
-
- -
-
- Are you sure you want to delete this object? You can’t undo this. -
-
- -
- - - -
-
-
diff --git a/ui_framework/doc_site/src/views/modal/modal_overlay.js b/ui_framework/doc_site/src/views/modal/modal_overlay.js deleted file mode 100644 index 1f9fb98d9073b..0000000000000 --- a/ui_framework/doc_site/src/views/modal/modal_overlay.js +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-disable */ - -const $showModalOverlayButton = $('[data-id="showModalOverlay"]'); -const $modalOverlay = $('.kuiModalOverlay'); -const $modalOverlayCloseButton = $('.kuiModalOverlay .kuiModalHeaderCloseButton'); -const $modalOverlayCancelButton = $('.kuiModalOverlay .kuiButton--hollow'); -const $modalOverlayConfirmButton = $('.kuiModalOverlay .kuiButton--primary'); - -if (!$showModalOverlayButton.length) { - throw new Error('$showModalOverlayButton missing'); -} - -if (!$modalOverlay.length) { - throw new Error('$modalOverlay missing'); -} - -if (!$modalOverlayCloseButton.length) { - throw new Error('$modalOverlayCloseButton missing'); -} - -if (!$modalOverlayCancelButton.length) { - throw new Error('$modalOverlayCancelButton missing'); -} - -if (!$modalOverlayConfirmButton.length) { - throw new Error('$modalOverlayConfirmButton missing'); -} - -$modalOverlay.hide(); - -$showModalOverlayButton.on('click', () => { - $modalOverlay.show(); -}); - -$modalOverlayCloseButton.on('click', () => { - $modalOverlay.hide(); -}); - -$modalOverlayCancelButton.on('click', () => { - $modalOverlay.hide(); -}); - -$modalOverlayConfirmButton.on('click', () => { - $modalOverlay.hide(); -});

Argument Name