Migrate UI Framework source into Kibana. (#9456)

Backports PR #9192

**Commit 1:**
Migrate UI Framework source into Kibana.
- Add dependencies to package.json.
- Add task for building UI Framework docs and serving locally.
- Import UI Framework scss from autoload/styles.js instead of importing the CSS in base.less.

* Original sha: de3f34f8d5
* Authored by CJ Cenizal <cj@cenizal.com> on 2016-11-22T22:27:11Z

**Commit 2:**
Refactor UI Framework directory structure.

* Original sha: 4e54844577
* Authored by CJ Cenizal <cj@cenizal.com> on 2016-12-05T23:03:01Z

**Commit 3:**
Remove babel-polyfill.

* Original sha: 43854bccc4
* Authored by CJ Cenizal <cj@cenizal.com> on 2016-12-05T23:47:42Z

**Commit 4:**
Fix typos and include SCSS synchronously.

* Original sha: fe5bab1d81
* Authored by CJ Cenizal <cj@cenizal.com> on 2016-12-10T00:33:17Z

**Commit 5:**
Update with latest changes to UI Framework.

* Original sha: a44140a59d
* Authored by CJ Cenizal <cj@cenizal.com> on 2016-12-13T01:47:09Z
This commit is contained in:
jasper 2016-12-12 20:56:52 -05:00 committed by CJ Cenizal
parent 952bf13503
commit 33f2b8fd21
59 changed files with 2648 additions and 5 deletions

1
.gitignore vendored
View file

@ -33,3 +33,4 @@ selenium
*.swp
*.swo
*.out
src/ui_framework/doc_site/build/*.js*

View file

@ -62,7 +62,8 @@
"makelogs": "makelogs",
"mocha": "mocha",
"mocha:debug": "mocha --debug-brk",
"sterilize": "grunt sterilize"
"sterilize": "grunt sterilize",
"uiFramework:start": "webpack-dev-server --config src/ui_framework/doc_site/webpack.config.js --hot --inline --content-base src/ui_framework/doc_site/build"
},
"repository": {
"type": "git",
@ -173,6 +174,8 @@
"cheerio": "0.22.0",
"chokidar": "1.6.0",
"chromedriver": "2.24.1",
"classnames": "2.2.5",
"del": "1.2.1",
"elasticdump": "2.1.1",
"eslint": "3.11.1",
"eslint-plugin-mocha": "4.7.0",
@ -191,6 +194,9 @@
"grunt-simple-mocha": "0.4.0",
"gulp-sourcemaps": "1.7.3",
"handlebars": "4.0.5",
"highlight.js": "9.0.0",
"history": "2.1.1",
"html-loader": "0.4.3",
"husky": "0.8.1",
"image-diff": "1.6.0",
"intern": "3.2.3",
@ -202,6 +208,7 @@
"karma-ie-launcher": "0.2.0",
"karma-mocha": "0.2.0",
"karma-safari-launcher": "0.1.1",
"keymirror": "0.1.1",
"license-checker": "5.1.2",
"load-grunt-config": "0.19.2",
"makelogs": "3.0.2",
@ -210,15 +217,26 @@
"murmurhash3js": "3.0.1",
"ncp": "2.0.0",
"nock": "8.0.0",
"node-sass": "3.8.0",
"npm": "3.10.8",
"portscanner": "1.0.0",
"proxyquire": "1.7.10",
"react": "15.2.0",
"react-addons-test-utils": "15.2.0",
"react-dom": "15.2.0",
"react-redux": "4.4.5",
"react-router": "2.0.0",
"react-router-redux": "4.0.4",
"redux": "3.0.0",
"redux-thunk": "0.1.0",
"sass-loader": "4.0.0",
"simple-git": "1.37.0",
"sinon": "1.17.2",
"source-map": "0.5.6",
"source-map-support": "0.2.10",
"supertest": "1.2.0",
"supertest-as-promised": "2.0.2"
"supertest-as-promised": "2.0.2",
"webpack-dev-server": "1.14.1"
},
"engines": {
"node": "6.9.0",

View file

@ -108,6 +108,13 @@ class BaseOptimizer {
`css${mapQ}!autoprefixer${mapQPre}{ "browsers": ["last 2 versions","> 5%"] }!less${mapQPre}dumpLineNumbers=comments`
)
},
{
test: /\.scss$/,
loader: ExtractTextPlugin.extract(
'style',
`css${mapQ}!autoprefixer${mapQPre}{ "browsers": ["last 2 versions","> 5%"] }!sass${mapQPre}`
)
},
{ test: /\.css$/, loader: ExtractTextPlugin.extract('style', `css${mapQ}`) },
{ test: /\.jade$/, loader: 'jade' },
{ test: /\.json$/, loader: 'json' },

View file

@ -1,2 +1,6 @@
// Kibana UI Framework
require('../../../ui_framework/components/index.scss');
// All Kibana styles inside of the /styles dir
const context = require.context('../styles', false, /[\/\\](?!mixins|variables|_|\.)[^\/\\]+\.less/);
context.keys().forEach(key => context(key));

View file

@ -3,9 +3,6 @@
@import (reference) "./variables";
@import (reference) "~ui/styles/bootstrap/bootstrap";
// Kibana UI Framework
@import (less) "~@elastic/kibana-ui-framework/dist/framework.css";
html,
body {
.flex-parent();

View file

@ -0,0 +1,57 @@
# Kibana UI Framework
## Development
* Start development server `npm run uiFramework:start`.
* View docs on `http://localhost:8080/`.
## What is this?
The UI Framework provides you with UI components you can quickly use to build UIs, as well as interactive examples which document how they're supposed to be used. These UI components are currently only implemented in CSS and markup, but eventually they'll grow to involve JS as well.
When you build a UI using this framework (e.g. a plugin's UI), you can rest assured it will fit into the overall Kibana UI.
## Benefits
### Dynamic, interactive documentation
By having a "living style guide", we relieve our designers of the burden of creating and maintaining static style guides. This also makes it easier for our engineers to translate mockups, prototypes, and wireframes into products.
### Copy-pasteable UI
Engineers can copy and paste sample code into their projects to quickly get reliable, consistent results.
### Remove CSS from the day-to-day
The CSS portion of this framework means engineers don't need to spend mental cycles. These cycles can be spent on the things critical to the identity of the specific project they're working on, like architecture and business logic.
Once this framework also provides JS components, engineers won't even need to _see_ CSS -- it will be encapsulated behind the JS components' interfaces.
### More UI tests === fewer UI bugs
By covering our UI components with great unit tests and having those tests live within the framework itself, we can rest assured that our UI layer is tested and remove some of that burden from out integration/end-to-end tests.
## Why not just use Bootstrap?
In short: we've outgrown it! Third-party CSS frameworks like Bootstrap and Foundation are designed
for a general audience, so they offer things we don't need and _don't_ offer things we _do_ need.
As a result, we've been forced to override their styles until the original framework is no longer
recognizable. When the CSS reaches that point, it's time to take ownership over it and build
your own framework.
We also gain the ability to fix some of the common issues with third-party CSS frameworks:
* They have non-semantic markup.
* They deeply nest their selectors.
For a more in-depth analysis of the problems with Bootstrap (and similar frameworks), check out this article and the links it has at the bottom: ["Bootstrap Bankruptcy"](http://www.matthewcopeland.me/blog/2013/11/04/bootstrap-bankruptcy/).
## Examples of other in-house UI frameworks
* [Ubiquiti CSS Framework](http://ubnt-css.herokuapp.com/#/app/popover)
* [Smaato React UI Framework](http://smaato.github.io/ui-framework/#/modal)
* [Lonely Planet Style Guide](http://rizzo.lonelyplanet.com/styleguide/design-elements/colours)
* [MailChimp Patterns Library](http://ux.mailchimp.com/patterns)
* [Salesforce Lightning Design System](https://www.lightningdesignsystem.com/)
* [Refills](http://refills.bourbon.io/)
* [Formstone](https://formstone.it/)

View file

@ -0,0 +1,27 @@
// Normal colors
$textColor: #2d2d2d;
$buttonTextColor: #ffffff;
$buttonBackgroundColor: #9c9c9c;
$linkColor: #328CAA;
$linkColor-isHover: #105A73;
$inputTextColor: $textColor;
$inputBackgroundColor: #ffffff;
$inputBorderColor: $inputBackgroundColor;
// Dark theme colors
$textColor--darkTheme: #cecece;
$buttonTextColor--darkTheme: #ffffff;
$buttonBackgroundColor--darkTheme: #777777;
$linkColor--darkTheme: #b7e2ea;
$linkColor-isHover--darkTheme: #def2f6;
$inputTextColor--darkTheme: $textColor--darkTheme;
$inputBackgroundColor--darkTheme: #444444;
$inputBorderColor--darkTheme: $inputBackgroundColor--darkTheme;
@mixin darkTheme() {
.theme-dark & {
@content;
}
}
@import "local_nav/index";

View file

@ -0,0 +1,48 @@
// Normal colors
$localNavTextColor: $textColor;
$localNavBackgroundColor: #e4e4e4;
$localNavButtonTextColor: #5a5a5a;
$localNavButtonTextColor-isHover: #000000;
$localNavButtonBackgroundColor: transparent;
$localNavButtonBackgroundColor-isHover: rgba(#000000, 0.1);
$localNavButtonBackgroundColor-isSelected: #f6f6f6;
$localNavBreadcrumbDelimiterColor: #5a5a5a;
$localSearchBackgroundColor: #ffffff;
$localSearchBorderColor-isInvalid: #e74C3c;
$localDropdownBackgroundColor: $localNavButtonBackgroundColor-isSelected;
$localDropdownFormNoteTextColor: #737373;
$localTabTextColor: $localNavButtonTextColor;
$localTabTextColor-isHover: $localNavButtonTextColor-isHover;
$localTabTextColor-isSelected: $localNavButtonTextColor-isHover;
// Dark theme colors
$localNavTextColor--darkTheme: $textColor--darkTheme;
$localNavBackgroundColor--darkTheme: #333333;
$localNavButtonTextColor--darkTheme: #dedede;
$localNavButtonTextColor-isHover--darkTheme: #ffffff;
$localNavButtonBackgroundColor-isHover--darkTheme: #000000;
$localNavButtonBackgroundColor-isSelected--darkTheme: #525252;
$localNavBreadcrumbDelimiterColor--darkTheme: #a5a5a5;
$localSearchBackgroundColor--darkTheme: #4e4e4e;
$localSearchBorderColor-isInvalid--darkTheme: #ff6758;
$localDropdownBackgroundColor--darkTheme: $localNavButtonBackgroundColor-isSelected--darkTheme;
$localDropdownFormNoteTextColor--darkTheme: #a2a2a2;
$localDropdownWarningTextColor--darkTheme: $textColor--darkTheme;
$localDropdownWarningBackgroundColor--darkTheme: #636363;
$localTabTextColor--darkTheme: $localNavButtonTextColor--darkTheme;
$localTabTextColor-isHover--darkTheme: $localNavButtonTextColor-isHover--darkTheme;
$localTabTextColor-isSelected--darkTheme: $localNavButtonTextColor-isHover--darkTheme;
// Spacing
$localNavSideSpacing: 10px;
// Font size
$localNavFontSizeNormal: 14px;
@import "local_breadcrumbs";
@import "local_dropdown";
@import "local_menu";
@import "local_nav";
@import "local_search";
@import "local_tabs";
@import "local_title";

View file

@ -0,0 +1,62 @@
/**
* 1. Breadcrumbs are placed in the top-left corner and need to be bumped over
* a bit.
*/
.localBreadcrumbs {
display: flex;
align-items: center;
height: 100%;
padding-left: $localNavSideSpacing; /* 1 */
}
.localBreadcrumb {
& + & {
margin-left: 6px;
&:before {
content: '/';
user-select: none;
margin-right: 4px;
color: $localNavBreadcrumbDelimiterColor;
@include darkTheme {
color: $localNavBreadcrumbDelimiterColor--darkTheme;
}
}
}
&:last-child {
.localBreadcrumb__link {
color: $localNavTextColor;
cursor: default;
&:hover {
text-decoration: none;
}
@include darkTheme {
color: $localNavTextColor--darkTheme;
}
}
}
}
.localBreadcrumb__link {
font-size: $localNavFontSizeNormal;
color: #5a5a5a;
text-decoration: none;
&:hover {
text-decoration: underline;
}
@include darkTheme {
color: $localNavButtonTextColor--darkTheme;
}
}
.localBreadcrumb__emphasis {
font-weight: 700;
}

View file

@ -0,0 +1,163 @@
.localDropdown {
position: relative;
padding: 10px $localNavSideSpacing 14px;
background-color: $localDropdownBackgroundColor;
line-height: 20px;
@include darkTheme {
background-color: $localDropdownBackgroundColor--darkTheme;
}
}
.localDropdownCloseButton {
appearance: none;
background-color: transparent;
padding: 4px;
border: none;
position: absolute;
top: 1px;
right: 5px;
font-size: 16px;
color: $localNavTextColor;
cursor: pointer;
opacity: 0.35;
&:hover {
opacity: 1;
}
@include darkTheme {
color: $localNavTextColor--darkTheme;
}
}
.localDropdownPanels {
display: flex;
}
.localDropdownPanel {
flex: 1 1 0%;
}
.localDropdownPanel--left {
margin-right: 30px;
}
.localDropdownPanel--right {
margin-left: 30px;
}
.localDropdownTitle {
margin-bottom: 12px;
font-size: 18px;
color: $localNavTextColor;
@include darkTheme {
color: $localNavTextColor--darkTheme;
}
}
.localDropdownSection {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
.localDropdownHeader {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6px;
}
.localDropdownHeader__label {
font-size: 14px;
font-weight: 700;
color: $localNavTextColor;
@include darkTheme {
color: $localNavTextColor--darkTheme;
}
}
.localDropdownHeader__actions {
display: flex;
}
.localDropdownHeader__action {
color: $linkColor;
font-size: 12px;
text-decoration: none;
cursor: pointer;
& + & {
margin-left: 10px;
}
&:hover,
&:active {
color: $linkColor-isHover;
}
@include darkTheme {
color: $linkColor--darkTheme;
&:hover,
&:active {
color: $linkColor-isHover--darkTheme;
}
}
}
.localDropdownInput {
display: block;
width: 100%;
margin-bottom: 12px;
padding: 5px 15px;
font-size: 14px;
color: $inputTextColor;
background-color: $inputBackgroundColor;
border: 2px solid $inputBorderColor;
border-radius: 4px;
@include darkTheme {
color: $inputTextColor--darkTheme;
background-color: $inputBackgroundColor--darkTheme;
border-color: $inputBorderColor--darkTheme;
}
}
.localDropdownFormNote {
font-size: 14px;
color: $localDropdownFormNoteTextColor;
@include darkTheme {
color: $localDropdownFormNoteTextColor--darkTheme;
}
}
.localDropdownWarning {
margin-bottom: 16px;
padding: 6px 10px;
font-size: 14px;
color: $textColor;
background-color: $localNavBackgroundColor;
@include darkTheme {
color: $localDropdownWarningTextColor--darkTheme;
background-color: $localDropdownWarningBackgroundColor--darkTheme;
}
}
.localDropdownHelpText {
margin-bottom: 16px;
font-size: 14px;
color: #2D2D2D;
@include darkTheme {
color: #9e9e9e;
}
}

View file

@ -0,0 +1,62 @@
.localMenu {
display: flex;
align-items: center;
height: 100%;
}
.localMenuItem {
display: flex;
align-items: center;
height: 100%;
padding: 0 $localNavSideSpacing;
font-size: $localNavFontSizeNormal;
background-color: $localNavButtonBackgroundColor;
color: $localNavButtonTextColor;
border: 0;
cursor: pointer;
&:hover {
background-color: $localNavButtonBackgroundColor-isHover;
color: $localNavButtonTextColor-isHover;
}
&.localMenuItem-isSelected {
background-color: $localNavButtonBackgroundColor-isSelected;
}
&.localMenuItem-isDisabled {
opacity: 0.5;
cursor: default;
&:hover {
background-color: $localNavButtonBackgroundColor;
color: $localNavButtonTextColor;
}
}
@include darkTheme {
color: $localNavButtonTextColor--darkTheme;
&:hover {
background-color: $localNavButtonBackgroundColor-isHover--darkTheme;
color: $localNavButtonTextColor-isHover--darkTheme;
}
&.localMenuItem-isSelected {
background-color: $localNavButtonBackgroundColor-isSelected--darkTheme;
}
&.localMenuItem-isDisabled {
&:hover {
background-color: transparent;
color: $localNavButtonTextColor--darkTheme;
}
}
}
}
.localMenuItem__icon {
margin-right: 5px;
margin-bottom: -1px;
}

View file

@ -0,0 +1,34 @@
/**
* 1. Match height of logo in side bar, but allow it to expand to accommodate
* dropdown.
*/
.localNav {
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 70px; /* 1 */
color: $localNavTextColor;
background-color: $localNavBackgroundColor;
@include darkTheme {
color: $localNavTextColor--darkTheme;
background-color: $localNavBackgroundColor--darkTheme;
}
}
.localNavRow {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
}
.localNavRow__section {
height: 100%;
}
.localNavRow--secondary {
height: 38px;
padding: 0 $localNavSideSpacing;
}

View file

@ -0,0 +1,53 @@
$localSearchHeight: 30px;
.localSearch {
display: flex;
width: 100%;
height: $localSearchHeight;
}
.localSearchInput {
flex: 1 1 100%;
padding: 5px 15px;
font-size: $localNavFontSizeNormal;
color: $localNavTextColor;
background-color: $localSearchBackgroundColor;
border: 2px solid $localSearchBackgroundColor;
border-bottom-left-radius: 4px;
border-top-left-radius: 4px;
border-bottom-right-radius: 0;
border-top-right-radius: 0;
&.localSearchInput-isInvalid {
border-color: $localSearchBorderColor-isInvalid;
}
@include darkTheme {
color: $localNavTextColor--darkTheme;
background-color: $localSearchBackgroundColor--darkTheme;
border-color: $localSearchBackgroundColor--darkTheme;
&.localSearchInput-isInvalid {
border-color: $localSearchBorderColor-isInvalid--darkTheme;
}
}
}
.localSearchButton {
width: 43px;
height: $localSearchHeight;
font-size: $localNavFontSizeNormal;
color: $buttonTextColor;
background-color: $buttonBackgroundColor;
border: 0;
border-bottom-left-radius: 0;
border-top-left-radius: 0;
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
@include darkTheme {
color: $buttonTextColor--darkTheme;
background-color: $buttonBackgroundColor--darkTheme;
}
}

View file

@ -0,0 +1,49 @@
/**
* 1. We want the bottom border on selected tabs to be flush with the bottom of the container.
*/
.localTabs {
display: flex;
align-items: flex-end; // 1
height: 100%;
}
/**
* 1. Make sure the bottom border is flush with the bottom of the LocalNav.
*/
.localTab {
padding: 5px 0 6px 0;
font-size: 18px;
line-height: 22px; /* 1 */
color: $localTabTextColor;
border-bottom: 2px solid transparent;
text-decoration: none;
cursor: pointer;
&:hover,
&:active {
color: $localTabTextColor-isHover;
@include darkTheme {
color: $localTabTextColor-isHover--darkTheme;
}
}
&.localTab-isSelected {
color: $localTabTextColor-isSelected;
border-bottom-color: $localTabTextColor-isSelected;
cursor: default;
@include darkTheme {
color: $localTabTextColor-isSelected--darkTheme;
border-bottom-color: $localTabTextColor-isSelected--darkTheme;
}
}
& + & {
margin-left: 15px;
}
@include darkTheme {
color: $localTabTextColor--darkTheme;
}
}

View file

@ -0,0 +1,8 @@
.localTitle {
display: flex;
align-items: center;
height: 100%;
padding-left: $localNavSideSpacing;
font-size: $localNavFontSizeNormal;
font-weight: bold;
}

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF8">
<link href='https://fonts.googleapis.com/css?family=Lato:300,400,700|Ubuntu+Mono:400' rel='stylesheet' type='text/css'>
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<script src="https://use.fontawesome.com/a9649923ac.js"></script>
</head>
<body>
<div id="guide" style="height: 100%"></div>
<script src="bundle.js"></script>
</body>
</html>

View file

@ -0,0 +1,13 @@
import keyMirror from 'keymirror';
export default keyMirror({
// Source code viewer actions
OPEN_CODE_VIEWER: null,
UPDATE_CODE_VIEWER: null,
CLOSE_CODE_VIEWER: null,
REGISTER_CODE: null,
UNREGISTER_CODE: null,
});

View file

@ -0,0 +1,30 @@
import ActionTypes from '../action_types';
export default {
openCodeViewer: slug => ({
type: ActionTypes.OPEN_CODE_VIEWER,
slug,
}),
updateCodeViewer: slug => ({
type: ActionTypes.UPDATE_CODE_VIEWER,
slug,
}),
closeCodeViewer: () => ({
type: ActionTypes.CLOSE_CODE_VIEWER,
}),
registerCode: code => ({
type: ActionTypes.REGISTER_CODE,
code,
}),
unregisterCode: code => ({
type: ActionTypes.UNREGISTER_CODE,
code
}),
};

View file

@ -0,0 +1,4 @@
export {
default as CodeViewerActions,
} from './code_viewer/code_viewer_actions';

View file

@ -0,0 +1,114 @@
.guideCodeViewer {
position: fixed;
top: $guideNavHeight;
right: 0;
bottom: 0;
width: $guideCodeViewerWidth;
padding: 40px 20px 40px 0;
background-color: white;
transform: translateX($guideCodeViewerWidth);
transition: transform $guideCodeViewerTransition;
overflow: auto;
@include scrollbar;
@include whenNarrowerThan($normalBreakpoint) {
width: $guideCodeViewerSmallWidth;
}
&.is-code-viewer-open {
transform: translateX(0);
}
}
.guideCodeViewer__header {
padding-bottom: 10px;
line-height: $guideLineHeight;
border-bottom: 1px solid #d6d6d6;
font-size: 18px;
font-weight: 700;
margin-bottom: 10px;
}
.guideCodeViewer__closeButton {
position: absolute;
top: 5px;
right: 5px;
cursor: pointer;
padding: 10px;
border-radius: 3px;
color: #6b7490;
&:hover {
color: #2b52cc;
}
}
.guideCodeViewer__title {
padding-bottom: 6px;
border-bottom: 1px solid #d6d6d6;
line-height: $guideLineHeight;
font-size: 14px;
}
.guideCodeViewer__content {
margin: 0 0 16px;
}
// HLJS
.hljs {
display: block;
padding: 15px 20px;
color: #637c84;
font-size: 14px;
line-height: 1.3;
font-family: 'Ubuntu Mono', monospace;
}
.hljs-keyword {
color: #b58900;
}
.hljs-function {
.hljs-keyword {
color: #268bd2;
}
.hljs-title {
color: #7441c6;
}
}
.hljs-built_in {
color: #268bd2;
}
.hljs-string {
color: #36958e;
}
.hljs-comment {
color: #9d9d9d;
}
.hljs-number,
.hljs-literal {
color: #d84a7e;
}
.hljs-tag {
.hljs-name {
color: #63a35c;
}
.hljs-attr {
color: #795da3;
}
.hljs-string {
color: #df5000;
}
}

View file

@ -0,0 +1,76 @@
import React, {
Component,
PropTypes,
} from 'react';
import classNames from 'classnames';
import hljs from 'highlight.js';
export default class GuideCodeViewer extends Component {
constructor(props) {
super(props);
}
componentDidUpdate() {
if (this.refs.html) {
hljs.highlightBlock(this.refs.html);
}
if (this.refs.javascript) {
hljs.highlightBlock(this.refs.javascript);
}
}
renderSection(title, content, codeClass) {
if (content) {
return (
<div className="guideCodeViewer__section">
<div className="guideCodeViewer__title">
{title}
</div>
<pre className="guideCodeViewer__content">
<code
ref={codeClass}
className={codeClass}
>
{content}
</code>
</pre>
</div>
);
}
}
render() {
const classes = classNames('guideCodeViewer', {
'is-code-viewer-open': this.props.isOpen,
});
return (
<div className={classes}>
<div className="guideCodeViewer__header">
{this.props.title}
</div>
<div
className="guideCodeViewer__closeButton fa fa-times"
onClick={this.props.onClose}
/>
{this.renderSection('HTML', this.props.html, 'html')}
{this.renderSection('JavaScript', this.props.js, 'javascript')}
</div>
);
}
}
GuideCodeViewer.propTypes = {
isOpen: PropTypes.bool,
onClose: PropTypes.func,
title: PropTypes.string,
html: PropTypes.string,
js: PropTypes.string,
};

View file

@ -0,0 +1,3 @@
.guideExample {
}

View file

@ -0,0 +1,77 @@
import React, {
Component,
PropTypes,
} from 'react';
import {
Slugify,
} from '../../services';
import {
GuidePage,
GuidePageSection,
} from '../';
export default class GuideExample extends Component {
constructor(props, sections) {
super(props);
this.sections = sections.map(section => Object.assign({}, section, {
slug: Slugify.one(section.title),
}));
}
componentWillMount() {
this.sections.forEach(section => {
this.context.registerCode(section);
});
}
componentWillUnmount() {
this.sections.forEach(section => {
this.context.unregisterCode(section);
});
}
renderSections() {
return this.sections.map((section, index) => (
<GuidePageSection
key={index}
title={section.title}
slug={section.slug}
html={section.html}
js={section.js}
>
{section.description}
</GuidePageSection>
));
}
render() {
return (
<GuidePage
title={this.props.route.name}
>
{this.renderSections()}
</GuidePage>
);
}
}
GuideExample.contextTypes = {
registerCode: PropTypes.func,
unregisterCode: PropTypes.func,
};
GuideExample.propTypes = {
route: PropTypes.object.isRequired,
sections: PropTypes.arrayOf(React.PropTypes.shape({
title: React.PropTypes.string.isRequired,
description: React.PropTypes.any,
html: React.PropTypes.string.isRequired,
js: React.PropTypes.string,
})),
};

View file

@ -0,0 +1,67 @@
.guideNav {
position: fixed;
z-index: 1;
top: 0;
left: 0;
right: 0;
height: $guideNavHeight;
padding: 0 20px;
background-color: #e8488b;
color: #ffffff;
box-shadow:
inset 0 -20px 18px rgba(#5a1029, 0.2),
inset 0 -5px 4px rgba(#5a1029, 0.3);
transition: height 0.3s ease;
overflow: hidden;
&.is-guide-nav-open {
height: 100%;
}
}
.guideNav__header {
display: flex;
align-items: center;
height: 60px;
}
.guideNav__menu {
cursor: pointer;
margin-right: 10px;
padding: 10px;
border-radius: 3px;
&.is-menu-button-pinned,
&:hover {
background-color: rgba(black, 0.15);
}
&:active {
background-color: rgba(black, 0.2);
box-shadow: inset 0 2px 8px rgba(black, 0.2);
}
}
.guideNav__title {
color: white;
text-decoration: none;
font-size: 18px;
}
.guideNav__version {
margin-left: 10px;
font-weight: 300;
font-size: 14px;
}
.guideNavItem {
color: white;
text-decoration: none;
font-size: 20px;
padding: 10px;
border-radius: 3px;
&:hover {
background-color: rgba(black, 0.15);
}
}

View file

@ -0,0 +1,65 @@
import React, {
PropTypes,
} from 'react';
import {
Link,
} from 'react-router';
import classNames from 'classnames';
const GuideNav = props => {
const classes = classNames('guideNav', {
'is-guide-nav-open': props.isNavOpen,
});
const buttonClasses = classNames('guideNav__menu fa fa-bars', {
'is-menu-button-pinned': props.isNavOpen,
});
const navItems = props.items.map((item, index) => {
return (
<Link
key={index}
className="guideNavItem"
to={item.path}
onClick={props.onClickNavItem}
>
{item.name}
</Link>
);
});
return (
<div className={classes}>
<div className="guideNav__header">
<div
className={buttonClasses}
onClick={props.onToggleNav}
/>
<Link
className="guideNav__title"
to="/"
onClick={props.onClickNavItem}
>
Kibana UI Framework <span className="guideNav__version">{props.version}</span>
</Link>
</div>
<div className="guideNavItems">
{navItems}
</div>
</div>
);
};
GuideNav.propTypes = {
isNavOpen: PropTypes.bool,
onToggleNav: PropTypes.func,
onClickNavItem: PropTypes.func,
version: PropTypes.string,
items: PropTypes.array,
};
export default GuideNav;

View file

@ -0,0 +1,15 @@
@import "../../variables";
.guidePage {
display: flex;
}
.guidePageBody {
flex: 1 1 auto;
padding: 0 80px 0 80px + $guideSideNavWidth;
@include whenNarrowerThan($normalBreakpoint) {
padding: 0 20px 0 $guideSideNavSmallWidth;
}
}

View file

@ -0,0 +1,72 @@
import React, {
Component,
PropTypes,
} from 'react';
import {
Slugify,
} from '../../services';
import {
GuidePageSideNav,
GuidePageSideNavItem,
} from '../';
export default class GuidePage extends Component {
constructor(props) {
super(props);
this.onClickLink = this.onClickLink.bind(this);
}
onClickLink(slug) {
// Scroll to element.
$('html, body').animate({
scrollTop: $(`#${slug}`).offset().top - 100
}, 250);
// Load in code viewer.
this.context.updateCodeViewer(slug);
}
renderSideNavMenu() {
// Traverse children and build side nav from it.
return this.props.children.map((section, index) => {
return (
<GuidePageSideNavItem
key={index}
slug={Slugify.one(section.props.title)}
onClick={this.onClickLink}
>
{section.props.title}
</GuidePageSideNavItem>
);
});
}
render() {
return (
<div className="guidePage">
<GuidePageSideNav title={this.props.title}>
{this.renderSideNavMenu()}
</GuidePageSideNav>
<div className="guidePageBody">
{this.props.children}
</div>
</div>
);
}
}
GuidePage.contextTypes = {
updateCodeViewer: PropTypes.func,
};
GuidePage.propTypes = {
children: PropTypes.any,
title: PropTypes.string,
};

View file

@ -0,0 +1,56 @@
@import "../../variables";
.guidePageSection {
margin-bottom: 40px;
}
.guidePageSection__header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 10px;
line-height: $guideLineHeight;
border-bottom: 1px solid #d6d6d6;
}
.guidePageSection__title {
font-size: 18px;
font-weight: 700;
}
.guidePageSection__sourceButton {
line-height: 10px;
padding: 4px 10px;
background-color: #19a8e0;
color: white;
border-radius: 3px;
cursor: pointer;
&:hover {
box-shadow:
inset 0 1px 0 rgba(#95e1ff, 1),
0 2px 4px rgba(black, 0.2);
}
&:active {
box-shadow:
inset 0 20px 20px rgba(black, 0.1),
inset 0 2px 8px rgba(black, 0.2);
}
}
.guidePageSection__description {
font-size: 14px;
line-height: 21px;
}
.guidePageSection__example {
& + & {
margin-top: 20px;
}
}
.guidePageSection__example--standalone {
margin-top: 10px;
}

View file

@ -0,0 +1,123 @@
import React, {
Component,
PropTypes,
} from 'react';
import classNames from 'classnames';
import {
JsInjector,
} from '../../services';
export default class GuidePageSection extends Component {
constructor(props) {
super(props);
this.onClickSource = this.onClickSource.bind(this);
}
componentDidMount() {
// NOTE: This will cause a race condition if a GuidePage adds and removes
// GuidePageSection instances during its lifetime (e.g. if a user is allowed
// to click "add" and "delete" buttons to add and remove GuidePageSections).
//
// In such a race condition, we could end up with GuidePageSections with
// identical id values.
//
// As long as all GuidePageSection instances are added when a GuidePage
// is instantiated, and then they're all removed when a GuidePage is
// removed, we won't encounter this race condition.
if (this.props.js) {
this.scriptId = `${GuidePageSection.SCRIPT_ID}${GuidePageSection.count}`;
GuidePageSection.count++;
// JS injection must occur _after_ the component has been mounted, so
// the component DOM is available for the JS to manipulate.
JsInjector.inject(this.props.js, this.scriptId);
}
function trimChildren(node) {
if (node.children.length > 0) {
[...node.children].forEach(trimChildren);
return;
}
node.textContent = node.textContent.trim();
}
trimChildren(this.refs.html);
trimChildren(this.refs.htmlDarkTheme);
}
componentWillUnmount() {
JsInjector.remove(this.scriptId);
GuidePageSection.count--;
}
onClickSource() {
this.context.openCodeViewer(this.props.slug);
}
render() {
let description;
if (this.props.children) {
description = (
<div className="guidePageSection__description">
{this.props.children}
</div>
);
}
const exampleClasses = classNames('guidePageSection__example', {
'guidePageSection__example--standalone': !this.props.children,
});
return (
<div
id={this.props.slug}
className="guidePageSection"
>
<div className="guidePageSection__header">
<div className="guidePageSection__title">
{this.props.title}
</div>
<div
className="guidePageSection__sourceButton fa fa-code"
onClick={this.onClickSource}
/>
</div>
{description}
<div
ref="html"
className={exampleClasses}
dangerouslySetInnerHTML={{ __html: this.props.html }}
/>
<div
ref="htmlDarkTheme"
className={`${exampleClasses} theme-dark`}
dangerouslySetInnerHTML={{ __html: this.props.html }}
/>
</div>
);
}
}
GuidePageSection.count = 0;
GuidePageSection.SCRIPT_ID = 'EXAMPLE_SCRIPT';
GuidePageSection.contextTypes = {
openCodeViewer: PropTypes.func,
};
GuidePageSection.propTypes = {
title: PropTypes.string,
slug: PropTypes.string,
html: PropTypes.string,
js: PropTypes.string,
children: PropTypes.any,
};

View file

@ -0,0 +1,46 @@
.guidePageSideNav {
position: fixed;
top: 100px;
left: 0;
bottom: 0;
width: $guideSideNavWidth;
padding: 0 20px 30px 80px;
overflow: auto;
@include scrollbar;
@include whenNarrowerThan($normalBreakpoint) {
padding: 0 20px 30px 20px;
width: $guideSideNavSmallWidth;
}
}
.guidePageSideNav__title {
padding-bottom: 10px;
margin-bottom: 10px;
font-size: 22px;
line-height: $guideLineHeight;
border-bottom: 1px solid #d6d6d6;
opacity: 0.8;
}
.guidePageSideNavMenu {
line-height: $guideLineHeight;
}
.guidePageSideNavMenu__item {
& + & {
margin-top: 6px;
}
}
.guidePageSideNavMenu__itemLink {
cursor: pointer;
color: #6b7490;
text-decoration: none;
&:hover {
color: #2b52cc;
}
}

View file

@ -0,0 +1,32 @@
import React, {
Component,
PropTypes,
} from 'react';
export default class GuidePageSideNav extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="guidePageSideNav">
<div className="guidePageSideNav__title">
{this.props.title}
</div>
<div className="guidePageSideNavMenu">
{this.props.children}
</div>
</div>
);
}
}
GuidePageSideNav.propTypes = {
title: PropTypes.string,
children: PropTypes.any,
};

View file

@ -0,0 +1,38 @@
import React, {
Component,
PropTypes,
} from 'react';
export default class GuidePageSideNavItem extends Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
this.props.onClick(this.props.slug);
}
render() {
return (
<div className="guidePageSideNavMenu__item">
<div
className="guidePageSideNavMenu__itemLink"
onClick={this.onClick}
>
{this.props.children}
</div>
</div>
);
}
}
GuidePageSideNavItem.propTypes = {
slug: PropTypes.string,
children: PropTypes.any,
onClick: PropTypes.func,
};

View file

@ -0,0 +1,21 @@
export * from './guide_code_viewer/guide_code_viewer.jsx';
export { default as GuideCodeViewer } from './guide_code_viewer/guide_code_viewer.jsx';
export * from './guide_example/guide_example.jsx';
export { default as GuideExample } from './guide_example/guide_example.jsx';
export * from './guide_nav/guide_nav.jsx';
export { default as GuideNav } from './guide_nav/guide_nav.jsx';
export * from './guide_page/guide_page.jsx';
export { default as GuidePage } from './guide_page/guide_page.jsx';
export * from './guide_page_section/guide_page_section.jsx';
export { default as GuidePageSection } from './guide_page_section/guide_page_section.jsx';
export * from './guide_page_side_nav/guide_page_side_nav.jsx';
export { default as GuidePageSideNav } from './guide_page_side_nav/guide_page_side_nav.jsx';
export * from './guide_page_side_nav/guide_page_side_nav_item.jsx';
export { default as GuidePageSideNavItem } from './guide_page_side_nav/guide_page_side_nav_item.jsx';

View file

@ -0,0 +1,79 @@
require('./main.scss');
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import {
Router,
useRouterHistory,
} from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import createHashHistory from 'history/lib/createHashHistory';
// Store.
import configureStore from './store/configure_store';
// Guide views.
import AppContainer from './views/app_container';
import HomeView from './views/home/home_view.jsx';
import NotFoundView from './views/not_found/not_found_view.jsx';
import {
Routes,
} from './services';
const store = configureStore();
const browserHistory = useRouterHistory(createHashHistory)({
queryKey: false,
});
const history = syncHistoryWithStore(browserHistory, store);
const childRoutes = Routes.getAppRoutes();
childRoutes.push({
path: '*',
component: NotFoundView,
name: 'Page Not Found',
});
const routes = [{
path: '/',
component: AppContainer,
indexRoute: {
component: HomeView,
source: 'views/home/HomeView.jsx',
},
childRoutes,
}];
// Update document title with route name.
const onRouteEnter = route => {
const leafRoute = route.routes[route.routes.length - 1];
document.title = leafRoute.name ?
`Kibana UI Framework - ${leafRoute.name}` :
'Kibana UI Framework';
};
const syncTitleWithRoutes = routesList => {
if (!routesList) return;
routesList.forEach(route => {
route.onEnter = onRouteEnter; // eslint-disable-line no-param-reassign
if (route.indexRoute) {
// Index routes have a weird relationship with their "parent" routes,
// so it seems we need to give their own onEnter hooks.
route.indexRoute.onEnter = onRouteEnter; // eslint-disable-line no-param-reassign
}
syncTitleWithRoutes(route.childRoutes);
});
};
syncTitleWithRoutes(routes);
ReactDOM.render(
<Provider store={store}>
<Router
history={history}
routes={routes}
/>
</Provider>,
document.getElementById('guide')
);

View file

@ -0,0 +1,28 @@
@import "../../components/index";
@import "./views/app";
@import "./components/guide_code_viewer/guide_code_viewer";
@import "./components/guide_nav/guide_nav";
@import "./components/guide_page/guide_page";
@import "./components/guide_page_section/guide_page_section";
@import "./components/guide_page_side_nav/guide_page_side_nav";
* {
box-sizing: border-box;
}
html,
body {
height: 100%;
}
/**
* 1. Insane line-height makes it easier to notice when components are relying
* on styles inherited from body.
*/
body {
font-family: 'Lato', 'Helvetica Neue', sans-serif;
background: #ffffff;
line-height: 40px; /* 1 */
margin: 0;
}

View file

@ -0,0 +1,16 @@
import {
GuideExample,
} from '../../components';
export default function createExample(examples) {
class Example extends GuideExample {
constructor(props) {
super(props, examples);
}
}
Example.propTypes = Object.assign({}, GuideExample.propTypes);
return Example;
}

View file

@ -0,0 +1,12 @@
export * from './example/createExample';
export { default as createExample } from './example/createExample';
export * from './js_injector/js_injector';
export { default as JsInjector } from './js_injector/js_injector';
export * from './routes/routes';
export { default as Routes } from './routes/routes';
export * from './string/slugify';
export { default as Slugify } from './string/slugify';

View file

@ -0,0 +1,21 @@
import $ from 'jquery';
const ID_ATTRIBUTE = 'injected-js-tag-id';
export default {
inject(js, id) {
if (id) {
$(`[${ID_ATTRIBUTE}=${id}]`).remove();
}
const script = $(`<script ${ID_ATTRIBUTE}=${id}>${js}</script>`);
$('body').append(script);
},
remove(id) {
$(`[${ID_ATTRIBUTE}=${id}]`).remove();
},
};

View file

@ -0,0 +1,19 @@
import Slugify from '../string/slugify';
import LocalNavExample
from '../../views/local_nav/local_nav_example.jsx';
// Component route names should match the component name exactly.
const components = [{
name: 'LocalNav',
component: LocalNavExample,
}];
export default {
components: Slugify.each(components, 'name', 'path'),
getAppRoutes: function getAppRoutes() {
const list = this.components;
return list.slice(0);
},
};

View file

@ -0,0 +1,27 @@
/**
* Lowercases input and replaces spaces with hyphens:
* e.g. 'GridView Example' -> 'gridview-example'
*/
function one(str) {
const parts = str
.toLowerCase()
.replace(/[-]+/g, ' ')
.replace(/[^\w^\s]+/g, '')
.replace(/ +/g, ' ').split(' ');
return parts.join('-');
}
function each(items, src, dest) {
return items.map(item => {
const _item = item;
_item[dest] = one(_item[src]);
return _item;
});
}
export default {
one,
each,
};

View file

@ -0,0 +1,35 @@
import {
applyMiddleware,
createStore,
compose,
} from 'redux';
import thunk from 'redux-thunk';
import { browserHistory } from 'react-router';
import {
routerMiddleware,
routerReducer,
} from 'react-router-redux';
import codeViewerReducer from './reducers/code_viewer_reducer';
/**
* @param {Object} initialState An object defining the application's initial
* state.
*/
export default function configureStore(initialState) {
function rootReducer(state = {}, action) {
return {
routing: routerReducer(state.routing, action),
codeViewer: codeViewerReducer(state.codeViewer, action),
};
}
const finalStore = compose(
applyMiddleware(
thunk,
routerMiddleware(browserHistory)
)
)(createStore)(rootReducer, initialState);
return finalStore;
}

View file

@ -0,0 +1,69 @@
import ActionTypes from '../../actions/action_types';
const defaultState = {
isOpen: false,
codesBySlug: {},
code: undefined,
};
export default function codeViewerReducer(state = defaultState, action) {
switch (action.type) {
case ActionTypes.OPEN_CODE_VIEWER: {
const newCode = state.codesBySlug[action.slug];
if (state.code === newCode) {
// If we are opening the existing code, then close the viewer.
return Object.assign({}, state, {
isOpen: false,
code: undefined,
});
}
return Object.assign({}, state, {
isOpen: true,
code: newCode,
});
}
case ActionTypes.UPDATE_CODE_VIEWER: {
if (state.isOpen) {
return Object.assign({}, state, {
code: state.codesBySlug[action.slug],
});
}
return state;
}
case ActionTypes.CLOSE_CODE_VIEWER: {
return Object.assign({}, state, {
isOpen: false,
code: undefined,
});
}
case ActionTypes.REGISTER_CODE: {
const codesBySlug = Object.assign({}, state.codesBySlug, {
[action.code.slug]: action.code,
});
return Object.assign({}, state, {
codesBySlug
});
}
case ActionTypes.UNREGISTER_CODE: {
const codesBySlug = Object.assign({}, state.codesBySlug);
delete codesBySlug[action.code.slug];
return Object.assign({}, state, {
codesBySlug
});
}
default:
break;
}
return state;
}

View file

@ -0,0 +1,39 @@
$guideLineHeight: 24px;
$guideNavHeight: 60px;
$guideSideNavWidth: 400px;
$guideSideNavSmallWidth: 220px;
$guideCodeViewerWidth: 700px;
$guideCodeViewerSmallWidth: 580px;
$guideCodeViewerTransition: 0.2s ease;
$normalBreakpoint: 1900px;
@mixin whenNarrowerThan($browserWidth) {
@media only screen and (max-width: #{$browserWidth}) {
@content;
}
}
@mixin whenWiderThan($browserWidth) {
@media only screen and (min-width: #{$browserWidth}) {
@content;
}
}
@mixin scrollbar($color: rgba(#454D58, 0.4)) {
&::-webkit-scrollbar {
width: 16px;
height: 16px;
}
&::-webkit-scrollbar-thumb {
background-color: $color;
border: 6px solid transparent;
background-clip: content-box;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
}

View file

@ -0,0 +1,22 @@
@import "../variables";
@import "./home/home_view";
.guide {
display: flex;
height: 100%;
}
.guideContent {
flex: 1 1 auto;
padding-top: 100px;
transition: padding-right $guideCodeViewerTransition;
&.is-code-viewer-open {
padding-right: $guideCodeViewerWidth;
@include whenNarrowerThan($normalBreakpoint) {
padding-right: $guideCodeViewerSmallWidth;
}
}
}

View file

@ -0,0 +1,30 @@
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import AppView from './app_view.jsx';
import {
CodeViewerActions,
} from '../actions';
function mapStateToProps(state, ownProps) {
return {
routes: ownProps.routes,
isCodeViewerOpen: state.codeViewer.isOpen,
code: state.codeViewer.code,
};
}
function mapDispatchToProps(dispatch) {
const actions = {
openCodeViewer: CodeViewerActions.openCodeViewer,
updateCodeViewer: CodeViewerActions.updateCodeViewer,
closeCodeViewer: CodeViewerActions.closeCodeViewer,
registerCode: CodeViewerActions.registerCode,
unregisterCode: CodeViewerActions.unregisterCode,
};
return bindActionCreators(actions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(AppView);

View file

@ -0,0 +1,113 @@
import React, {
Component,
PropTypes,
} from 'react';
import classNames from 'classnames';
import {
Routes,
} from '../services';
import {
GuideCodeViewer,
GuideNav,
} from '../components';
// Inject version into header.
const pkg = require('json!../../../../../package.json');
export default class AppView extends Component {
constructor(props) {
super(props);
this.state = {
isNavOpen: false,
};
this.onClickNavItem = this.onClickNavItem.bind(this);
this.onToggleNav = this.onToggleNav.bind(this);
this.onCloseCodeViewer = this.onCloseCodeViewer.bind(this);
}
getChildContext() {
return {
openCodeViewer: this.props.openCodeViewer,
updateCodeViewer: this.props.updateCodeViewer,
registerCode: this.props.registerCode,
unregisterCode: this.props.unregisterCode,
};
}
onClickNavItem() {
this.setState({
isNavOpen: false,
});
}
onCloseCodeViewer() {
this.props.closeCodeViewer();
}
onToggleNav() {
this.setState({
isNavOpen: !this.state.isNavOpen,
})
}
render() {
const contentClasses = classNames('guideContent', {
'is-code-viewer-open': this.props.isCodeViewerOpen,
});
return (
<div className="guide">
<GuideNav
isNavOpen={this.state.isNavOpen}
onToggleNav={this.onToggleNav}
onClickNavItem={this.onClickNavItem}
version={pkg.version}
items={Routes.components}
/>
<div className={contentClasses}>
{this.props.children}
</div>
<GuideCodeViewer
isOpen={this.props.isCodeViewerOpen}
onClose={this.onCloseCodeViewer}
title={this.props.code.title}
html={this.props.code.html}
js={this.props.code.js}
/>
</div>
);
}
}
AppView.childContextTypes = {
openCodeViewer: PropTypes.func,
updateCodeViewer: PropTypes.func,
registerCode: PropTypes.func,
unregisterCode: PropTypes.func,
};
AppView.propTypes = {
children: PropTypes.any,
routes: PropTypes.array.isRequired,
openCodeViewer: PropTypes.func,
updateCodeViewer: PropTypes.func,
closeCodeViewer: PropTypes.func,
registerCode: PropTypes.func,
unregisterCode: PropTypes.func,
isCodeViewerOpen: PropTypes.bool,
code: PropTypes.object,
};
AppView.defaultProps = {
code: {},
};

View file

@ -0,0 +1,28 @@
@import "../../variables";
.guideHome {
display: flex;
justify-content: center;
}
.guideHome__panel {
width: 100%;
max-width: 600px;
max-height: 500px;
padding: 60px;
margin-bottom: 20px;
border-radius: 3px;
background-color: #e8e8e8;
line-height: $guideLineHeight;
}
.guideHome__panelTitle {
font-weight: 700;
font-size: 22px;
margin-bottom: 20px;
}
.guideHome__panelText {
font-size: 18px;
}

View file

@ -0,0 +1,28 @@
import React, {
Component,
} from 'react';
export default class HomeView extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="guideHome">
<div className="guideHome__panel">
<div className="guideHome__panelTitle">
Welcome to the Kibana UI Framework
</div>
<div className="guideHome__panelText">
Get started by clicking the menu button in the top left corner of the screen.
</div>
</div>
</div>
);
}
}

View file

@ -0,0 +1,41 @@
<div class="localNav">
<div class="localNavRow">
<div class="localNavRow__section">
<div class="localBreadcrumbs">
<div class="localBreadcrumb">
<a class="localBreadcrumb__link" href="#">
Discover
</a>
</div>
<div class="localBreadcrumb">
<a class="localBreadcrumb__link" href="#">
<span class="localBreadcrumb__emphasis">0</span> hits
</a>
</div>
</div>
</div>
<div class="localNavRow__section">
<div class="localMenu">
<div class="localMenuItem">
New
</div>
<div class="localMenuItem">
Save
</div>
<div class="localMenuItem">
Open
</div>
<button class="localMenuItem">
<div class="localMenuItem__icon fa fa-clock-o"></div>
Last 5 minutes
</button>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,127 @@
<div class="localNav">
<div class="localNavRow">
<div class="localNavRow__section">
<div class="localBreadcrumbs">
<div class="localBreadcrumb">
<a class="localBreadcrumb__link" href="#">
Discover
</a>
</div>
<div class="localBreadcrumb">
<a class="localBreadcrumb__link" href="#">
<span class="localBreadcrumb__emphasis">0</span> hits
</a>
</div>
</div>
</div>
<div class="localNavRow__section">
<div class="localMenu">
<div class="localMenuItem localMenuItem-isSelected">
New
</div>
<div class="localMenuItem">
Save
</div>
<div class="localMenuItem">
Open
</div>
<button class="localMenuItem">
<div class="localMenuItem__icon fa fa-clock-o"></div>
Last 5 minutes
</button>
</div>
</div>
</div>
<div class="localDropdown">
<!-- Dropdown close button -->
<button class="localDropdownCloseButton">
<span class="fa fa-chevron-circle-up"></span>
</button>
<!-- Title -->
<div class="localDropdownTitle">Dropdown title</div>
<!-- Help text -->
<div class="localDropdownHelpText">
Here's some help text to explain the purpose of the dropdown.
</div>
<!-- Warning -->
<div class="localDropdownWarning">
Here's some warning text in case the user has something misconfigured.
</div>
<div class="localDropdownSection">
<!-- Header -->
<div class="localDropdownHeader">
<div class="localDropdownHeader__label">
Header for a section of content
</div>
</div>
<!-- Input -->
<input
class="localDropdownInput"
type="text"
placeholder="Input something here"
/>
</div>
<div class="localDropdownSection">
<!-- Header -->
<div class="localDropdownHeader">
<div class="localDropdownHeader__label">
Header for another section of content
</div>
<div class="localDropdownHeader__actions">
<a
class="localDropdownHeader__action"
href=""
>
Action A
</a>
<a
class="localDropdownHeader__action"
href=""
>
Action B
</a>
</div>
</div>
<!-- Input -->
<input
class="localDropdownInput"
type="text"
readonly
value="This is some text inside of a read-only input"
/>
<!-- Notes -->
<div class="localDropdownFormNote">
Here are some notes to explain the purpose of this section of the dropdown.
</div>
</div>
</div>
<div class="localNavRow localNavRow--secondary">
<div class="localSearch">
<input
class="localSearchInput"
type="text"
placeholder="Filter..."
autocomplete="off"
>
<button class="localSearchButton">
<span class="fa fa-search"></span>
</button>
</div>
</div>
</div>

View file

@ -0,0 +1,86 @@
<div class="localNav">
<div class="localNavRow">
<div class="localNavRow__section">
<div class="localBreadcrumbs">
<div class="localBreadcrumb">
<a class="localBreadcrumb__link" href="#">
Discover
</a>
</div>
<div class="localBreadcrumb">
<a class="localBreadcrumb__link" href="#">
<span class="localBreadcrumb__emphasis">0</span> hits
</a>
</div>
</div>
</div>
<div class="localNavRow__section">
<div class="localMenu">
<div class="localMenuItem localMenuItem-isSelected">
New
</div>
<div class="localMenuItem">
Save
</div>
<div class="localMenuItem">
Open
</div>
<button class="localMenuItem">
<div class="localMenuItem__icon fa fa-clock-o"></div>
Last 5 minutes
</button>
</div>
</div>
</div>
<div class="localDropdown">
<!-- Dropdown close button -->
<button class="localDropdownCloseButton">
<span class="fa fa-chevron-circle-up"></span>
</button>
<div class="localDropdownPanels">
<!-- Left panel -->
<div class="localDropdownPanel localDropdownPanel--left">
<!-- Title -->
<div class="localDropdownTitle">Left panel</div>
<!-- Help text -->
<div class="localDropdownHelpText">
Here's some help text to explain the purpose of the dropdown.
</div>
</div>
<!-- Right panel -->
<div class="localDropdownPanel localDropdownPanel--left">
<!-- Title -->
<div class="localDropdownTitle">Right panel</div>
<!-- Help text -->
<div class="localDropdownHelpText">
Here's some help text to explain the purpose of the dropdown.
</div>
</div>
</div>
</div>
<div class="localNavRow localNavRow--secondary">
<div class="localSearch">
<input
class="localSearchInput"
type="text"
placeholder="Filter..."
autocomplete="off"
>
<button class="localSearchButton">
<span class="fa fa-search"></span>
</button>
</div>
</div>
</div>

View file

@ -0,0 +1,56 @@
import React from 'react';
import {
createExample,
} from '../../services';
export default createExample([{
title: 'Simple',
description: (
<p>Here's a simple LocalNav with a Title in the top left corner and Menu in the top right.</p>
),
html: require('./local_nav_simple/local_nav_simple.html'),
}, {
title: 'Breadcrumbs',
description: (
<p>You can replace the Title with Breadcrumbs.</p>
),
html: require('./local_nav_breadcrumbs/local_nav_breadcrumbs.html'),
}, {
title: 'Search',
description: (
<p>You can add a Search component for filtering results.</p>
),
html: require('./local_nav_search/local_nav_search.html'),
}, {
title: 'Invalid Search',
html: require('./local_nav_search_error/local_nav_search_error.html'),
}, {
title: 'Selected and disabled Menu Item states',
description: (
<div>
<p>When the user selects a Menu Item, additional content can be displayed inside of a Dropdown.</p>
<p>Menu Items can also be disabled, in which case they become non-interactive.</p>
</div>
),
html: require('./local_nav_menu_item_states/local_nav_menu_item_states.html'),
}, {
title: 'Dropdown',
description: (
<p>Selecting a Menu Item will commonly result in an open Dropdown.</p>
),
html: require('./local_nav_dropdown/local_nav_dropdown.html'),
}, {
title: 'Dropdown panels',
description: (
<p>You can split the Dropdown into side-by-side Panels.</p>
),
html: require('./local_nav_dropdown_panels/local_nav_dropdown_panels.html'),
}, {
title: 'Tabs',
description: (
<p>You can display Tabs for navigating local content.</p>
),
html: require('./local_nav_tabs/local_nav_tabs.html'),
}]);

View file

@ -0,0 +1,55 @@
<div class="localNav">
<div class="localNavRow">
<div class="localNavRow__section">
<div class="localBreadcrumbs">
<div class="localBreadcrumb">
<a class="localBreadcrumb__link" href="#">
Discover
</a>
</div>
<div class="localBreadcrumb">
<a class="localBreadcrumb__link" href="#">
<span class="localBreadcrumb__emphasis">0</span> hits
</a>
</div>
</div>
</div>
<div class="localNavRow__section">
<div class="localMenu">
<div class="localMenuItem localMenuItem-isSelected">
New
</div>
<div class="localMenuItem">
Save
</div>
<div class="localMenuItem localMenuItem-isDisabled">
Open
</div>
<button class="localMenuItem">
<div class="localMenuItem__icon fa fa-clock-o"></div>
Last 5 minutes
</button>
</div>
</div>
</div>
<div class="localNavRow localNavRow--secondary">
<div class="localSearch">
<input
class="localSearchInput"
type="text"
placeholder="Filter..."
autocomplete="off"
>
<button class="localSearchButton">
<span class="fa fa-search"></span>
</button>
</div>
</div>
</div>

View file

@ -0,0 +1,55 @@
<div class="localNav">
<div class="localNavRow">
<div class="localNavRow__section">
<div class="localBreadcrumbs">
<div class="localBreadcrumb">
<a class="localBreadcrumb__link" href="#">
Discover
</a>
</div>
<div class="localBreadcrumb">
<a class="localBreadcrumb__link" href="#">
<span class="localBreadcrumb__emphasis">0</span> hits
</a>
</div>
</div>
</div>
<div class="localNavRow__section">
<div class="localMenu">
<button class="localMenuItem">
New
</button>
<button class="localMenuItem">
Save
</button>
<button class="localMenuItem">
Open
</button>
<button class="localMenuItem">
<div class="localMenuItem__icon fa fa-clock-o"></div>
Last 5 minutes
</button>
</div>
</div>
</div>
<div class="localNavRow localNavRow--secondary">
<div class="localSearch">
<input
class="localSearchInput"
type="text"
placeholder="Filter..."
autocomplete="off"
>
<button class="localSearchButton">
<span class="fa fa-search"></span>
</button>
</div>
</div>
</div>

View file

@ -0,0 +1,55 @@
<div class="localNav">
<div class="localNavRow">
<div class="localNavRow__section">
<div class="localBreadcrumbs">
<div class="localBreadcrumb">
<a class="localBreadcrumb__link" href="#">
Discover
</a>
</div>
<div class="localBreadcrumb">
<a class="localBreadcrumb__link" href="#">
<span class="localBreadcrumb__emphasis">0</span> hits
</a>
</div>
</div>
</div>
<div class="localNavRow__section">
<div class="localMenu">
<button class="localMenuItem">
New
</button>
<button class="localMenuItem">
Save
</button>
<button class="localMenuItem">
Open
</button>
<button class="localMenuItem">
<div class="localMenuItem__icon fa fa-clock-o"></div>
Last 5 minutes
</button>
</div>
</div>
</div>
<div class="localNavRow localNavRow--secondary">
<div class="localSearch">
<input
class="localSearchInput localSearchInput-isInvalid"
type="text"
placeholder="Filter..."
autocomplete="off"
>
<button class="localSearchButton">
<span class="fa fa-search"></span>
</button>
</div>
</div>
</div>

View file

@ -0,0 +1,31 @@
<div class="localNav">
<div class="localNavRow">
<div class="localNavRow__section">
<div class="localTitle">
Untitled Document
</div>
</div>
<div class="localNavRow__section">
<div class="localMenu">
<div class="localMenuItem">
New
</div>
<div class="localMenuItem">
Save
</div>
<div class="localMenuItem">
Open
</div>
<button class="localMenuItem">
<div class="localMenuItem__icon fa fa-clock-o"></div>
Last 5 minutes
</button>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,57 @@
<div class="localNav">
<div class="localNavRow">
<div class="localNavRow__section">
<div class="localBreadcrumbs">
<div class="localBreadcrumb">
<a class="localBreadcrumb__link" href="#">
Discover
</a>
</div>
<div class="localBreadcrumb">
<a class="localBreadcrumb__link" href="#">
<span class="localBreadcrumb__emphasis">0</span> <span>hits</span>
</a>
</div>
</div>
</div>
<div class="localNavRow__section">
<div class="localMenu">
<button class="localMenuItem">
New
</button>
<button class="localMenuItem">
Save
</button>
<button class="localMenuItem">
Open
</button>
<button class="localMenuItem">
<div class="localMenuItem__icon fa fa-clock-o"></div>
Last 5 minutes
</button>
</div>
</div>
</div>
<div class="localNavRow localNavRow--secondary">
<div class="localTabs">
<a class="localTab localTab-isSelected" href="#">
Overview
</a>
<a class="localTab" href="#">
Your Documents
</a>
<a class="localTab" href="#">
Another Tab
</a>
</div>
</div>
</div>

View file

@ -0,0 +1,20 @@
import React, {
Component,
} from 'react';
export default class NotFoundView extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>Page not found.</h1>
</div>
);
}
}

View file

@ -0,0 +1,39 @@
var path = require('path');
module.exports = {
devtool: 'source-map',
entry: {
guide: './src/ui_framework/doc_site/src/index.js'
},
output: {
path: path.resolve(__dirname, 'src/ui_framework/doc_site/build'),
filename: 'bundle.js'
},
resolve: {
root: [
path.resolve(__dirname, 'src/ui_framework/doc_site')
]
},
module: {
loaders: [{
test: /\.jsx?$/,
loader: 'babel',
exclude: /node_modules/
}, {
test: /\.scss$/,
loaders: ['style', 'css', 'sass'],
exclude: /node_modules/
}, {
test: /\.html$/,
loader: 'html',
exclude: /node_modules/
}, {
test: require.resolve('jquery'),
loader: 'expose?jQuery!expose?$'
}]
}
};