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>
This commit is contained in:
Peter Holmberg 2020-05-13 08:00:40 +02:00 committed by GitHub
parent cab066f8ce
commit 36fa54a288
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 662 additions and 168 deletions

View File

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

View File

@ -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',

View File

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

View File

@ -1,124 +1,68 @@
// 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[];
state = {
checksDone: false,
currentStep: 0,
steps: getSteps(),
};
constructor(props: PanelProps) {
super(props);
async componentDidMount() {
const { steps } = this.state;
this.state = {
checksDone: false,
};
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,
};
});
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;
});
},
},
];
}
const checkedSteps = await Promise.all(checkedStepsPromises);
componentDidMount() {
this.stepIndex = -1;
return this.nextStep().then((res: any) => {
this.setState({ checksDone: true });
this.setState({
currentStep: !checkedSteps[0].done ? 0 : 1,
steps: checkedSteps,
checksDone: true,
});
}
nextStep(): any {
if (this.stepIndex === this.steps.length - 1) {
return Promise.resolve();
}
onForwardClick = () => {
this.setState(prevState => ({
currentStep: prevState.currentStep + 1,
}));
};
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();
});
}
onPreviousClick = () => {
this.setState(prevState => ({
currentStep: prevState.currentStep - 1,
}));
};
dismiss = () => {
const { id } = this.props;
@ -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};
`,
};
});

View File

@ -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%;
`,
};
});

View File

@ -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;
}
`,
};
});

View File

@ -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;
`,
};
});

View File

@ -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

View File

@ -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();

View File

@ -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,
},
],
},
];

View File

@ -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;
}

View File

@ -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

View File

@ -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