Onboarding: New getting started panel (#23826)

* image and card component

* change height of getting started panel

* progress

* setup basic step

* advanced steps

* step forward and backward

* do checks

* fix button size

* minor styling on butttons

* add correct links

* save tutorial click in localstorage

* types and gradients

* fix gradients

* use spacing variable

* lots of responsiveness

* add links to help

* Getting started work

* redo according to split panel design

* minor touch ups

* new background images

* split up docs card to different hrefs

* welcome bar touch ups

* hide icon on small screens

* transparent false on welcome banner

* fix urls

* source tag in welcome urls

* move images to panel dir, removed unused images

* Nicer loading message

* make the cards look nicer on wide screens

* append utm tag on render instead

* replace width with margin

* new background image for light

* remove target on a element

* removing buttonselect, add tag to href

* more polishing

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
pull/24605/head
Peter Holmberg 6 years ago committed by GitHub
parent cab066f8ce
commit 36fa54a288
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      packages/grafana-data/src/types/theme.ts
  2. 1
      packages/grafana-ui/src/themes/default.ts
  3. 2
      pkg/api/dashboard.go
  4. 298
      public/app/plugins/panel/gettingstarted/GettingStarted.tsx
  5. 61
      public/app/plugins/panel/gettingstarted/components/DocsCard.tsx
  6. 68
      public/app/plugins/panel/gettingstarted/components/Step.tsx
  7. 72
      public/app/plugins/panel/gettingstarted/components/TutorialCard.tsx
  8. 49
      public/app/plugins/panel/gettingstarted/components/sharedStyles.ts
  9. 1
      public/app/plugins/panel/gettingstarted/img/Onboarding_Panel_dark.svg
  10. 1
      public/app/plugins/panel/gettingstarted/img/Onboarding_Panel_light.svg
  11. 2
      public/app/plugins/panel/gettingstarted/module.ts
  12. 107
      public/app/plugins/panel/gettingstarted/steps.ts
  13. 26
      public/app/plugins/panel/gettingstarted/types.ts
  14. 65
      public/app/plugins/panel/welcome/Welcome.tsx
  15. 78
      public/app/plugins/panel/welcome/img/background_light.svg
  16. 2
      public/dashboards/home.json
  17. 1
      public/img/onboarding_art_dark.svg
  18. 1
      public/img/onboarding_art_light.svg

@ -12,6 +12,7 @@ export interface GrafanaThemeCommons {
md: string;
lg: string;
xl: string;
xxl: string;
};
typography: {
fontFamily: {

@ -72,6 +72,7 @@ const theme: GrafanaThemeCommons = {
md: '769px', // 1 more than regular ipad in portrait
lg: '992px',
xl: '1200px',
xxl: '1440px',
},
spacing: {
insetSquishMd: '4px 8px',

@ -366,7 +366,7 @@ func addGettingStartedPanelToHomeDashboard(dash *simplejson.Json) {
"x": 0,
"y": 3,
"w": 24,
"h": 4,
"h": 9,
},
})

@ -1,125 +1,69 @@
// Libraries
import React, { PureComponent } from 'react';
import { PanelProps } from '@grafana/data';
import { Icon, IconName } from '@grafana/ui';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { backendSrv } from 'app/core/services/backend_srv';
import { Button, Spinner, stylesFactory } from '@grafana/ui';
import { config } from '@grafana/runtime';
import { css, cx } from 'emotion';
import { contextSrv } from 'app/core/core';
import { backendSrv } from 'app/core/services/backend_srv';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
interface Step {
title: string;
cta?: string;
icon: IconName;
href: string;
target?: string;
note?: string;
check: () => Promise<boolean>;
done?: boolean;
}
import { Step } from './components/Step';
import imageDark from './img/Onboarding_Panel_dark.svg';
import imageLight from './img/Onboarding_Panel_light.svg';
import { getSteps } from './steps';
import { Card, SetupStep } from './types';
interface State {
checksDone: boolean;
currentStep: number;
steps: SetupStep[];
}
export class GettingStarted extends PureComponent<PanelProps, State> {
stepIndex = 0;
readonly steps: Step[];
constructor(props: PanelProps) {
super(props);
this.state = {
checksDone: false,
};
this.steps = [
{
title: 'Install Grafana',
icon: 'check',
href: 'http://docs.grafana.org/',
target: '_blank',
note: 'Review the installation docs',
check: () => Promise.resolve(true),
},
{
title: 'Create a data source',
cta: 'Add data source',
icon: 'database',
href: 'datasources/new?gettingstarted',
check: () => {
return new Promise(resolve => {
resolve(
getDatasourceSrv()
.getMetricSources()
.filter(item => {
return item.meta.builtIn !== true;
}).length > 0
);
});
},
},
{
title: 'Build a dashboard',
cta: 'New dashboard',
icon: 'apps',
href: 'dashboard/new?gettingstarted',
check: () => {
return backendSrv.search({ limit: 1 }).then(result => {
return result.length > 0;
});
},
},
{
title: 'Invite your team',
cta: 'Add Users',
icon: 'users-alt',
href: 'org/users?gettingstarted',
check: () => {
return backendSrv.get('/api/org/users/lookup').then((res: any) => {
/* return res.length > 1; */
return false;
});
},
},
{
title: 'Install apps & plugins',
cta: 'Explore plugin repository',
icon: 'plug',
href: 'https://grafana.com/plugins?utm_source=grafana_getting_started',
check: () => {
return backendSrv.get('/api/plugins', { embedded: 0, core: 0 }).then((plugins: any[]) => {
return plugins.length > 0;
});
},
},
];
}
state = {
checksDone: false,
currentStep: 0,
steps: getSteps(),
};
async componentDidMount() {
const { steps } = this.state;
componentDidMount() {
this.stepIndex = -1;
return this.nextStep().then((res: any) => {
this.setState({ checksDone: true });
const checkedStepsPromises: Array<Promise<SetupStep>> = steps.map(async (step: SetupStep) => {
const checkedCardsPromises: Array<Promise<Card>> = step.cards.map((card: Card) => {
return card.check().then(passed => {
return { ...card, done: passed };
});
});
const checkedCards = await Promise.all(checkedCardsPromises);
return {
...step,
done: checkedCards.every(c => c.done),
cards: checkedCards,
};
});
}
nextStep(): any {
if (this.stepIndex === this.steps.length - 1) {
return Promise.resolve();
}
this.stepIndex += 1;
const currentStep = this.steps[this.stepIndex];
return currentStep.check().then(passed => {
if (passed) {
currentStep.done = true;
return this.nextStep();
}
return Promise.resolve();
const checkedSteps = await Promise.all(checkedStepsPromises);
this.setState({
currentStep: !checkedSteps[0].done ? 0 : 1,
steps: checkedSteps,
checksDone: true,
});
}
onForwardClick = () => {
this.setState(prevState => ({
currentStep: prevState.currentStep + 1,
}));
};
onPreviousClick = () => {
this.setState(prevState => ({
currentStep: prevState.currentStep - 1,
}));
};
dismiss = () => {
const { id } = this.props;
const dashboard = getDashboardSrv().getCurrent();
@ -137,34 +81,130 @@ export class GettingStarted extends PureComponent<PanelProps, State> {
};
render() {
const { checksDone } = this.state;
if (!checksDone) {
return <div>checking...</div>;
}
const { checksDone, currentStep, steps } = this.state;
const styles = getStyles();
const step = steps[currentStep];
return (
<div className="progress-tracker-container">
<button className="progress-tracker-close-btn" onClick={this.dismiss}>
<Icon name="times" />
</button>
<div className="progress-tracker">
{this.steps.map((step, index) => {
return (
<div key={index} className={step.done ? 'progress-step completed' : 'progress-step active'}>
<a className="progress-link" href={step.href} target={step.target} title={step.note}>
<span className="progress-marker">
<Icon name={step.icon} size="xxl" />
</span>
<span className="progress-text">{step.title}</span>
</a>
<a className="btn-small progress-step-cta" href={step.href} target={step.target}>
{step.cta}
</a>
<div className={styles.container}>
{!checksDone ? (
<div className={styles.loading}>
<div className={styles.loadingText}>Checking completed setup steps</div>
<Spinner size={24} inline />
</div>
) : (
<>
<div className={styles.dismiss}>
<div onClick={this.dismiss}>Remove this panel</div>
</div>
{currentStep === steps.length - 1 && (
<div className={cx(styles.backForwardButtons, styles.previous)} onClick={this.onPreviousClick}>
<Button icon="angle-left" variant="secondary" />
</div>
);
})}
</div>
)}
<div className={styles.content}>
<Step step={step} />
</div>
{currentStep < steps.length - 1 && (
<div className={cx(styles.backForwardButtons, styles.forward)} onClick={this.onForwardClick}>
<Button icon="angle-right" variant="secondary" />
</div>
)}
</>
)}
</div>
);
}
}
const getStyles = stylesFactory(() => {
const { theme } = config;
const backgroundImage = theme.isDark ? imageDark : imageLight;
return {
container: css`
display: flex;
flex-direction: column;
height: 100%;
background: url(${backgroundImage}) no-repeat;
background-size: cover;
padding: ${theme.spacing.xl} ${theme.spacing.md} 0;
`,
content: css`
label: content;
display: flex;
justify-content: center;
@media only screen and (max-width: ${theme.breakpoints.xxl}) {
margin-left: ${theme.spacing.lg};
justify-content: flex-start;
}
`,
header: css`
label: header;
margin-bottom: ${theme.spacing.lg};
display: flex;
flex-direction: column;
@media only screen and (min-width: ${theme.breakpoints.lg}) {
flex-direction: row;
}
`,
headerLogo: css`
height: 58px;
padding-right: ${theme.spacing.md};
display: none;
@media only screen and (min-width: ${theme.breakpoints.md}) {
display: block;
}
`,
heading: css`
label: heading;
margin-right: ${theme.spacing.lg};
margin-bottom: ${theme.spacing.lg};
flex-grow: 1;
display: flex;
@media only screen and (min-width: ${theme.breakpoints.md}) {
margin-bottom: 0;
}
`,
backForwardButtons: css`
position: absolute;
bottom: 50%;
top: 50%;
height: 50px;
`,
previous: css`
left: 10px;
@media only screen and (max-width: ${theme.breakpoints.md}) {
left: 0;
}
`,
forward: css`
right: 10px;
@media only screen and (max-width: ${theme.breakpoints.md}) {
right: 0;
}
`,
dismiss: css`
display: flex;
justify-content: flex-end;
cursor: pointer;
text-decoration: underline;
margin-right: ${theme.spacing.md};
margin-bottom: ${theme.spacing.sm};
`,
loading: css`
display: flex;
justify-content: center;
align-items: center;
height: 100%;
`,
loadingText: css`
margin-right: ${theme.spacing.sm};
`,
};
});

@ -0,0 +1,61 @@
import React, { FC } from 'react';
import { Card } from '../types';
import { Icon, stylesFactory, useTheme } from '@grafana/ui';
import { GrafanaTheme } from '@grafana/data';
import { css } from 'emotion';
import { cardContent, cardStyle, iconStyle } from './sharedStyles';
interface Props {
card: Card;
}
export const DocsCard: FC<Props> = ({ card }) => {
const theme = useTheme();
const styles = getStyles(theme, card.done);
return (
<div className={styles.card}>
<div className={cardContent}>
<a href={`${card.href}?utm_source=grafana_gettingstarted`}>
<div className={styles.heading}>{card.done ? 'complete' : card.heading}</div>
<h4 className={styles.title}>{card.title}</h4>
<div>
<Icon className={iconStyle(theme, card.done)} name={card.icon} size="xxl" />
</div>
</a>
</div>
<a href={`${card.learnHref}?utm_source=grafana_gettingstarted`} className={styles.url} target="_blank">
Learn how in the docs <Icon name="external-link-alt" />
</a>
</div>
);
};
const getStyles = stylesFactory((theme: GrafanaTheme, complete: boolean) => {
return {
card: css`
${cardStyle(theme, complete)}
min-width: 230px;
@media only screen and (max-width: ${theme.breakpoints.md}) {
min-width: 192px;
}
`,
heading: css`
text-transform: uppercase;
color: ${complete ? theme.palette.blue95 : '#FFB357'};
margin-bottom: ${theme.spacing.md};
`,
title: css`
margin-bottom: 48px;
`,
url: css`
border-top: 1px solid ${theme.colors.border1};
position: absolute;
bottom: 0;
padding: 8px 16px;
width: 100%;
`,
};
});

@ -0,0 +1,68 @@
import React, { FC } from 'react';
import { css } from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import { stylesFactory, useTheme } from '@grafana/ui';
import { TutorialCard } from './TutorialCard';
import { Card, SetupStep } from '../types';
import { DocsCard } from './DocsCard';
interface Props {
step: SetupStep;
}
export const Step: FC<Props> = ({ step }) => {
const theme = useTheme();
const styles = getStyles(theme);
return (
<div className={styles.setup}>
<div className={styles.info}>
<h2 className={styles.title}>{step.title}</h2>
<p>{step.info}</p>
</div>
<div className={styles.cards}>
{step.cards.map((card: Card, index: number) => {
const key = `${card.title}-${index}`;
if (card.type === 'tutorial') {
return <TutorialCard key={key} card={card} />;
}
return <DocsCard key={key} card={card} />;
})}
</div>
</div>
);
};
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
setup: css`
display: flex;
width: 95%;
`,
info: css`
width: 172px;
margin-right: 5%;
@media only screen and (max-width: ${theme.breakpoints.xxl}) {
margin-right: ${theme.spacing.xl};
}
@media only screen and (max-width: ${theme.breakpoints.sm}) {
display: none;
}
`,
title: css`
color: ${theme.palette.blue95};
`,
cards: css`
overflow-x: scroll;
overflow-y: hidden;
width: 100%;
display: flex;
justify-content: center;
@media only screen and (max-width: ${theme.breakpoints.xxl}) {
justify-content: flex-start;
}
`,
};
});

@ -0,0 +1,72 @@
import React, { FC, MouseEvent } from 'react';
import { GrafanaTheme } from '@grafana/data';
import { Icon, stylesFactory, useTheme } from '@grafana/ui';
import { css } from 'emotion';
import store from 'app/core/store';
import { cardContent, cardStyle, iconStyle } from './sharedStyles';
import { Card } from '../types';
interface Props {
card: Card;
}
export const TutorialCard: FC<Props> = ({ card }) => {
const theme = useTheme();
const styles = getStyles(theme, card.done);
return (
<a className={styles.card} onClick={(event: MouseEvent<HTMLAnchorElement>) => handleTutorialClick(event, card)}>
<div className={cardContent}>
<div className={styles.type}>{card.type}</div>
<div className={styles.heading}>{card.done ? 'complete' : card.heading}</div>
<h4>{card.title}</h4>
<div className={styles.info}>{card.info}</div>
<Icon className={iconStyle(theme, card.done)} name={card.icon} size="xxl" />
</div>
</a>
);
};
const handleTutorialClick = (event: MouseEvent<HTMLAnchorElement>, card: Card) => {
event.preventDefault();
const isSet = store.get(card.key);
if (!isSet) {
store.set(card.key, true);
}
window.open(`${card.href}?utm_source=grafana_gettingstarted`, '_blank');
};
const getStyles = stylesFactory((theme: GrafanaTheme, complete: boolean) => {
const textColor = `${complete ? theme.palette.blue95 : '#FFB357'}`;
return {
card: css`
${cardStyle(theme, complete)}
width: 460px;
min-width: 460px;
@media only screen and (max-width: ${theme.breakpoints.xl}) {
min-width: 368px;
}
@media only screen and (max-width: ${theme.breakpoints.lg}) {
min-width: 272px;
}
`,
type: css`
color: ${textColor};
text-transform: uppercase;
`,
heading: css`
text-transform: uppercase;
color: ${textColor};
margin-bottom: ${theme.spacing.sm};
`,
info: css`
margin-bottom: ${theme.spacing.md};
`,
status: css`
display: flex;
justify-content: flex-end;
`,
};
});

@ -0,0 +1,49 @@
import { GrafanaTheme } from '@grafana/data';
import { css } from 'emotion';
import { stylesFactory } from '@grafana/ui';
export const cardStyle = stylesFactory((theme: GrafanaTheme, complete: boolean) => {
const completeGradient = 'linear-gradient(to right, #5182CC 0%, #245BAF 100%)';
const darkThemeGradients = complete ? completeGradient : 'linear-gradient(to right, #f05a28 0%, #fbca0a 100%)';
const lightThemeGradients = complete ? completeGradient : 'linear-gradient(to right, #FBCA0A 0%, #F05A28 100%)';
const borderGradient = theme.isDark ? darkThemeGradients : lightThemeGradients;
return `
background-color: ${theme.colors.bg1};
margin-right: ${theme.spacing.xl};
border: 1px solid ${theme.colors.border1};
border-bottom-left-radius: ${theme.border.radius.md};
border-bottom-right-radius: ${theme.border.radius.md};
position: relative;
max-height: 230px;
@media only screen and (max-width: ${theme.breakpoints.xxl}) {
margin-right: ${theme.spacing.md};
}
&::before {
display: block;
content: ' ';
position: absolute;
left: 0;
right: 0;
height: 2px;
top: 0;
background-image: ${borderGradient};
}
`;
});
export const iconStyle = stylesFactory(
(theme: GrafanaTheme, complete: boolean) => css`
color: ${complete ? theme.palette.blue95 : theme.colors.textWeak};
@media only screen and (max-width: ${theme.breakpoints.sm}) {
display: none;
}
`
);
export const cardContent = css`
padding: 24px 16px;
`;

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

@ -2,4 +2,4 @@ import { PanelPlugin } from '@grafana/data';
import { GettingStarted } from './GettingStarted';
// Simplest possible panel plugin
export const plugin = new PanelPlugin(GettingStarted);
export const plugin = new PanelPlugin(GettingStarted).setNoPadding();

@ -0,0 +1,107 @@
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { getBackendSrv } from 'app/core/services/backend_srv';
import store from 'app/core/store';
import { SetupStep } from './types';
const step1TutorialTitle = 'Grafana fundamentals';
const step2TutorialTitle = 'Create users and teams';
const keyPrefix = 'getting.started.';
const step1Key = `${keyPrefix}${step1TutorialTitle
.replace(' ', '-')
.trim()
.toLowerCase()}`;
const step2Key = `${keyPrefix}${step2TutorialTitle
.replace(' ', '-')
.trim()
.toLowerCase()}`;
export const getSteps = (): SetupStep[] => [
{
heading: 'Welcome to Grafana',
subheading: 'The steps below will guide you to quickly finish setting up your Grafana installation.',
title: 'Basic',
info: 'The steps below will guide you to quickly finish setting up your Grafana installation.',
done: false,
cards: [
{
type: 'tutorial',
heading: 'Data source and dashboards',
title: step1TutorialTitle,
info:
'Set up and understand Grafana if you have no prior experience. This tutorial guides you through the entire process and covers the “Data source” and “Dashboards” steps to the right.',
href: 'https://grafana.com/tutorials/grafana-fundamentals',
icon: 'grafana',
check: () => Promise.resolve(store.get(step1Key)),
key: step1Key,
done: false,
},
{
type: 'docs',
title: 'Add your first data source',
heading: 'data sources',
icon: 'database',
learnHref: 'https://grafana.com/docs/grafana/latest/features/datasources/add-a-data-source',
href: 'datasources/new',
check: () => {
return new Promise(resolve => {
resolve(
getDatasourceSrv()
.getMetricSources()
.filter(item => {
return item.meta.builtIn !== true;
}).length > 0
);
});
},
done: false,
},
{
type: 'docs',
heading: 'dashboards',
title: 'Create your first dashboard',
icon: 'apps',
href: 'dashboard/new',
learnHref: 'https://grafana.com/docs/grafana/latest/guides/getting_started/#create-a-dashboard',
check: async () => {
const result = await getBackendSrv().search({ limit: 1 });
return result.length > 0;
},
done: false,
},
],
},
{
heading: 'Setup complete!',
subheading:
'All necessary steps to use Grafana are done. Now tackle advanced steps or make the best use of this home dashboard – it is, after all, a fully customizable dashboard – and remove this panel.',
title: 'Advanced',
info: ' Manage your users and teams and add plugins. These steps are optional',
done: false,
cards: [
{
type: 'tutorial',
heading: 'Users',
title: 'Create users and teams',
info: 'Learn to organize your users in teams and manage resource access and roles.',
href: 'https://grafana.com/tutorials/create-users-and-teams',
icon: 'users-alt',
key: step2Key,
check: () => Promise.resolve(store.get(step2Key)),
done: false,
},
{
type: 'docs',
heading: 'plugins',
title: 'Find and install plugins',
learnHref: 'https://grafana.com/docs/grafana/latest/plugins/installation',
href: 'plugins',
icon: 'plug',
check: async () => {
const plugins = await getBackendSrv().get('/api/plugins', { embedded: 0, core: 0 });
return Promise.resolve(plugins.length > 0);
},
done: false,
},
],
},
];

@ -0,0 +1,26 @@
import { IconName } from '@grafana/ui';
export type CardType = 'tutorial' | 'docs' | 'other';
export interface Card {
title: string;
type: CardType;
icon: IconName;
href: string;
check: () => Promise<boolean>;
done: boolean;
heading: string;
info?: string;
// For local storage
key?: string;
learnHref?: string;
}
export interface SetupStep {
heading: string;
subheading: string;
title: string;
info: string;
cards: Card[];
done: boolean;
}

@ -1,13 +1,14 @@
import React, { FC } from 'react';
import { css } from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import { ButtonSelect, stylesFactory, useTheme } from '@grafana/ui';
import { stylesFactory, useTheme } from '@grafana/ui';
import lightBackground from './img/background_light.svg';
const helpOptions = [
{ value: 0, label: 'Documentation', href: 'https://grafana.com/docs/grafana/latest/' },
{ value: 1, label: 'Tutorials', href: 'https://grafana.com/tutorials/' },
{ value: 2, label: 'Community', href: 'https://community.grafana.com/' },
{ value: 3, label: 'Public Slack', href: '' },
{ value: 0, label: 'Documentation', href: 'https://grafana.com/docs/grafana/latest' },
{ value: 1, label: 'Tutorials', href: 'https://grafana.com/tutorials' },
{ value: 2, label: 'Community', href: 'https://community.grafana.com' },
{ value: 3, label: 'Public Slack', href: 'http://slack.grafana.com' },
];
export const WelcomeBanner: FC = () => {
@ -18,19 +19,14 @@ export const WelcomeBanner: FC = () => {
<h1 className={styles.title}>Welcome to Grafana</h1>
<div className={styles.help}>
<h3 className={styles.helpText}>Need help?</h3>
<div className={styles.smallScreenHelp}>
<ButtonSelect
defaultValue={helpOptions[0]}
variant="secondary"
size="sm"
onChange={onHelpLinkClick}
options={helpOptions}
/>
</div>
<div className={styles.helpLinks}>
{helpOptions.map((option, index) => {
return (
<a key={`${option.label}-${index}`} className={styles.helpLink} href={option.href}>
<a
key={`${option.label}-${index}`}
className={styles.helpLink}
href={`${option.href}?utm_source=grafana_gettingstarted`}
>
{option.label}
</a>
);
@ -41,14 +37,8 @@ export const WelcomeBanner: FC = () => {
);
};
const onHelpLinkClick = (option: { label: string; href: string }) => {
window.open(option.href, '_blank');
};
const getStyles = stylesFactory((theme: GrafanaTheme) => {
const backgroundImage = theme.isDark
? 'public/img/login_background_dark.svg'
: 'public/img/login_background_light.svg';
const backgroundImage = theme.isDark ? 'public/img/login_background_dark.svg' : lightBackground;
return {
container: css`
@ -59,31 +49,28 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
align-items: center;
padding: 0 16px;
justify-content: space-between;
@media only screen and (max-width: ${theme.breakpoints.xl}) {
padding: 0 30px 0 100px;
}
padding: 0 ${theme.spacing.lg};
@media only screen and (max-width: ${theme.breakpoints.lg}) {
padding: 0 24px 0 44px;
background-position: 0px;
flex-direction: column;
align-items: flex-start;
justify-content: center;
}
@media only screen and (max-width: ${theme.breakpoints.sm}) {
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
padding: ${theme.spacing.md} ${theme.spacing.md} ${theme.spacing.md} 48px;
padding: 0 ${theme.spacing.sm};
}
`,
title: css`
margin-bottom: 0;
@media only screen and (max-width: ${theme.breakpoints.lg}) {
margin-bottom: ${theme.spacing.sm};
}
@media only screen and (max-width: ${theme.breakpoints.md}) {
font-size: ${theme.typography.heading.h2};
margin-bottom: ${theme.spacing.sm};
}
@media only screen and (max-width: ${theme.breakpoints.sm}) {
font-size: ${theme.typography.heading.h3};
@ -105,14 +92,18 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
display: none;
}
`,
helpLinks: css``,
helpLinks: css`
display: flex;
flex-wrap: wrap;
`,
helpLink: css`
margin-right: 8px;
margin-right: ${theme.spacing.md};
text-decoration: underline;
text-wrap: no-wrap;
`,
smallScreenHelp: css`
display: none;
@media only screen and (max-width: ${theme.breakpoints.sm}) {
margin-right: 8px;
}
`,
};
});

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

@ -11,7 +11,7 @@
},
"id": 1,
"title": "",
"transparent": true,
"transparent": false,
"type": "welcome"
},
{

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 85 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

Loading…
Cancel
Save