diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts
index 36c742dc40403..a66661e7ae2ea 100644
--- a/src/dev/storybook/aliases.ts
+++ b/src/dev/storybook/aliases.ts
@@ -21,6 +21,7 @@ export const storybookAliases = {
apm: 'x-pack/plugins/apm/.storybook',
canvas: 'x-pack/plugins/canvas/storybook',
codeeditor: 'src/plugins/kibana_react/public/code_editor/.storybook',
+ dashboard: 'src/plugins/dashboard/.storybook',
dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/.storybook',
data_enhanced: 'x-pack/plugins/data_enhanced/.storybook',
embeddable: 'src/plugins/embeddable/.storybook',
diff --git a/src/plugins/dashboard/.storybook/main.js b/src/plugins/dashboard/.storybook/main.js
new file mode 100644
index 0000000000000..1818aa44a9399
--- /dev/null
+++ b/src/plugins/dashboard/.storybook/main.js
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+module.exports = require('@kbn/storybook').defaultConfig;
diff --git a/src/plugins/dashboard/.storybook/storyshots.test.tsx b/src/plugins/dashboard/.storybook/storyshots.test.tsx
new file mode 100644
index 0000000000000..af8e71c77231a
--- /dev/null
+++ b/src/plugins/dashboard/.storybook/storyshots.test.tsx
@@ -0,0 +1,72 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import fs from 'fs';
+import { ReactChildren } from 'react';
+import path from 'path';
+import moment from 'moment';
+import 'moment-timezone';
+import ReactDOM from 'react-dom';
+
+import initStoryshots, { multiSnapshotWithOptions } from '@storybook/addon-storyshots';
+import styleSheetSerializer from 'jest-styled-components/src/styleSheetSerializer';
+import { addSerializer } from 'jest-specific-snapshot';
+
+// Set our default timezone to UTC for tests so we can generate predictable snapshots
+moment.tz.setDefault('UTC');
+
+// Freeze time for the tests for predictable snapshots
+const testTime = new Date(Date.UTC(2019, 5, 1)); // June 1 2019
+Date.now = jest.fn(() => testTime.getTime());
+
+// Mock React Portal for components that use modals, tooltips, etc
+// @ts-expect-error Portal mocks are notoriously difficult to type
+ReactDOM.createPortal = jest.fn((element) => element);
+
+// Mock EUI generated ids to be consistently predictable for snapshots.
+jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`);
+
+// Mock react-datepicker dep used by eui to avoid rendering the entire large component
+jest.mock('@elastic/eui/packages/react-datepicker', () => {
+ return {
+ __esModule: true,
+ default: 'ReactDatePicker',
+ };
+});
+
+// Mock the EUI HTML ID Generator so elements have a predictable ID in snapshots
+jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => {
+ return {
+ htmlIdGenerator: () => () => `generated-id`,
+ };
+});
+
+// To be resolved by EUI team.
+// https://github.com/elastic/eui/issues/3712
+jest.mock('@elastic/eui/lib/components/overlay_mask/overlay_mask', () => {
+ return {
+ EuiOverlayMask: ({ children }: { children: ReactChildren }) => children,
+ };
+});
+
+import { EuiObserver } from '@elastic/eui/test-env/components/observer/observer';
+jest.mock('@elastic/eui/test-env/components/observer/observer');
+EuiObserver.mockImplementation(() => 'EuiObserver');
+
+// Some of the code requires that this directory exists, but the tests don't actually require any css to be present
+const cssDir = path.resolve(__dirname, '../../../../built_assets/css');
+if (!fs.existsSync(cssDir)) {
+ fs.mkdirSync(cssDir, { recursive: true });
+}
+
+addSerializer(styleSheetSerializer);
+
+// Initialize Storyshots and build the Jest Snapshots
+initStoryshots({
+ configPath: path.resolve(__dirname, './../.storybook'),
+ framework: 'react',
+ test: multiSnapshotWithOptions({}),
+});
diff --git a/src/plugins/dashboard/public/application/_dashboard_app.scss b/src/plugins/dashboard/public/application/_dashboard_app.scss
index e3447b0a86c2d..f969f936ddebc 100644
--- a/src/plugins/dashboard/public/application/_dashboard_app.scss
+++ b/src/plugins/dashboard/public/application/_dashboard_app.scss
@@ -24,6 +24,7 @@
}
.dshEmptyWidget {
+ background-color: $euiColorLightestShade;
border: $euiBorderThin;
border-style: dashed;
border-radius: $euiBorderRadius;
diff --git a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap
index a1ecd178cd52d..bdfb1c45f351a 100644
--- a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap
+++ b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`DashboardEmptyScreen renders correctly with readonly mode 1`] = `
+exports[`DashboardEmptyScreen renders correctly with edit mode 1`] = `
-
-
-
-
+
+
+
+
+
+
+ Add your first panel
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
- This dashboard is empty.
-
-
-
-
-
-
-
- You need additional privileges to edit this dashboard.
-
-
-
-
-
-
-
-
-
-
-
+
+ Create content that tells a story about your data.
+
+
+`;
diff --git a/src/plugins/dashboard/public/application/top_nav/panel_toolbar/index.ts b/src/plugins/dashboard/public/application/top_nav/panel_toolbar/index.ts
new file mode 100644
index 0000000000000..31b25955b4b48
--- /dev/null
+++ b/src/plugins/dashboard/public/application/top_nav/panel_toolbar/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { PanelToolbar } from './panel_toolbar';
diff --git a/src/plugins/dashboard/public/application/top_nav/panel_toolbar/panel_toolbar.scss b/src/plugins/dashboard/public/application/top_nav/panel_toolbar/panel_toolbar.scss
new file mode 100644
index 0000000000000..196ae68e3ed17
--- /dev/null
+++ b/src/plugins/dashboard/public/application/top_nav/panel_toolbar/panel_toolbar.scss
@@ -0,0 +1,12 @@
+.panelToolbar {
+ padding: 0 $euiSizeS $euiSizeS;
+ flex-grow: 0;
+}
+
+.panelToolbarButton {
+ line-height: $euiButtonHeight; // Keeps alignment of text and chart icon
+ background-color: $euiColorEmptyShade;
+
+ // Lighten the border color for all states
+ border-color: $euiBorderColor !important; // sass-lint:disable-line no-important
+}
diff --git a/src/plugins/dashboard/public/application/top_nav/panel_toolbar/panel_toolbar.stories.tsx b/src/plugins/dashboard/public/application/top_nav/panel_toolbar/panel_toolbar.stories.tsx
new file mode 100644
index 0000000000000..0884dfde57d4a
--- /dev/null
+++ b/src/plugins/dashboard/public/application/top_nav/panel_toolbar/panel_toolbar.stories.tsx
@@ -0,0 +1,30 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { storiesOf } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+import React from 'react';
+import { PanelToolbar } from './panel_toolbar';
+
+storiesOf('components/PanelToolbar', module).add('default', () => (
+
+));
diff --git a/src/plugins/dashboard/public/application/top_nav/panel_toolbar/panel_toolbar.tsx b/src/plugins/dashboard/public/application/top_nav/panel_toolbar/panel_toolbar.tsx
new file mode 100644
index 0000000000000..91a7e0ab64a90
--- /dev/null
+++ b/src/plugins/dashboard/public/application/top_nav/panel_toolbar/panel_toolbar.tsx
@@ -0,0 +1,61 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import './panel_toolbar.scss';
+import React, { FC } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+
+interface Props {
+ /** The click handler for the Add Panel button for creating new panels */
+ onAddPanelClick: () => void;
+ /** The click handler for the Library button for adding existing visualizations/embeddables */
+ onLibraryClick: () => void;
+}
+
+export const PanelToolbar: FC = ({ onAddPanelClick, onLibraryClick }) => (
+
+
+
+ {i18n.translate('dashboard.panelToolbar.addPanelButtonLabel', {
+ defaultMessage: 'Create panel',
+ })}
+
+
+
+
+ {i18n.translate('dashboard.panelToolbar.libraryButtonLabel', {
+ defaultMessage: 'Add from library',
+ })}
+
+
+
+);
diff --git a/src/plugins/dashboard/public/dashboard_strings.ts b/src/plugins/dashboard/public/dashboard_strings.ts
index 239846638d3aa..4aa552893ab9b 100644
--- a/src/plugins/dashboard/public/dashboard_strings.ts
+++ b/src/plugins/dashboard/public/dashboard_strings.ts
@@ -285,25 +285,13 @@ export const emptyScreenStrings = {
i18n.translate('dashboard.howToStartWorkingOnNewDashboardEditLinkAriaLabel', {
defaultMessage: 'Edit dashboard',
}),
- getAddExistingVisualizationLinkText: () =>
- i18n.translate('dashboard.addExistingVisualizationLinkText', {
- defaultMessage: 'Add an existing',
+ getEmptyWidgetTitle: () =>
+ i18n.translate('dashboard.emptyWidget.addPanelTitle', {
+ defaultMessage: 'Add your first panel',
}),
- getAddExistingVisualizationLinkAriaLabel: () =>
- i18n.translate('dashboard.addVisualizationLinkAriaLabel', {
- defaultMessage: 'Add an existing visualization',
- }),
- getAddNewVisualizationDescription: () =>
- i18n.translate('dashboard.addNewVisualizationText', {
- defaultMessage: 'or new object to this dashboard',
- }),
- getCreateNewVisualizationButton: () =>
- i18n.translate('dashboard.createNewVisualizationButton', {
- defaultMessage: 'Create new',
- }),
- getCreateNewVisualizationButtonAriaLabel: () =>
- i18n.translate('dashboard.createNewVisualizationButtonAriaLabel', {
- defaultMessage: 'Create new visualization button',
+ getEmptyWidgetDescription: () =>
+ i18n.translate('dashboard.emptyWidget.addPanelDescription', {
+ defaultMessage: 'Create content that tells a story about your data.',
}),
};
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx
index d3e100366f2df..3203b3affb2c1 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx
@@ -153,7 +153,10 @@ export class AddPanelFlyout extends React.Component {