Quick starts
Demo
https://quickstarts.netlify.app/
Install
yarn add @patternfly/quickstarts
Peer dependencies
If your project doesn't already have these installed, you'll need:
yarn add @patternfly/react-core showdown
Stylesheets
In your web-apps entry point, add these stylesheets (these should be imported before modules from @patternfly/quickstarts are imported):
import '@patternfly/react-core/dist/styles/base.css';
import '@patternfly/quickstarts/dist/quickstarts.min.css';
Stylesheets if you use an older version of PatternFly
If you use an older version of @patternfly/react-core (older than "4.115.2"), and you can't upgrade yet, you can pull in the necessary PatternFly styles that @patternfly/quickstarts depends upon.
Ideally @patternfly/quickstarts will use the consumer provided PatternFly styles, only use these stylesheets if really needed.
quickstarts-standalone.min.css
nests the css classes within a .pfext-quick-start__base parent, so that they have higher specificity. patternfly-global.css
includes component styles that we cannot nest with more specificiy (for example Drawer since it can include consumer components that depend on an older PF version).
Note: Only use these stylesheets if necessary!
import '@patternfly/react-core/dist/styles/base.css';
import '@patternfly/quickstarts/dist/patternfly-global.css';
import '@patternfly/quickstarts/dist/quickstarts-standalone.min.css';
Usage example
Note: You can also view this example on codesandbox!
import "./styles.css";
import "@patternfly/react-core/dist/styles/base.css";
import "@patternfly/quickstarts/dist/quickstarts.min.css";
import * as React from "react";
import {
QuickStartContainer,
QuickStartCatalogPage,
QuickStartContext,
useLocalStorage,
setQueryArgument,
removeQueryArgument,
QUICKSTART_ID_FILTER_KEY
} from "@patternfly/quickstarts";
import { Button } from "@patternfly/react-core";
import jsYaml from "js-yaml";
import quickstartOne from "./quick-starts/quickstart-one.yaml";
import quickstartTwo from "./quick-starts/quickstart-two.yaml";
import quickstartThree from "./quick-starts/quickstart-three";
const App = () => {
const [quickStarts, setQuickStarts] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const [activeQuickStartID, setActiveQuickStartID] = useLocalStorage("quickstartId", "");
const [allQuickStartStates, setAllQuickStartStates] = useLocalStorage("quickstarts", {});
React.useEffect(() => {
const load = () => {
const loadedQuickStarts = [
jsYaml.load(quickstartOne),
jsYaml.load(quickstartTwo),
quickstartThree
];
setQuickStarts(loadedQuickStarts);
setLoading(false);
};
setTimeout(() => {
load();
}, 500);
}, []);
const withQueryParams = true;
const drawerProps = {
quickStarts,
activeQuickStartID,
allQuickStartStates,
setActiveQuickStartID,
setAllQuickStartStates,
showCardFooters: false,
useLegacyHeaderColors: false,
loading,
useQueryParams: withQueryParams,
};
const toggleQuickStart = (quickStartId: string) => {
if (activeQuickStartID !== quickStartId) {
setActiveQuickStartID(quickStartId);
withQueryParams && setQueryArgument(QUICKSTART_ID_FILTER_KEY, quickStartId);
} else {
setActiveQuickStartID('');
withQueryParams && removeQueryArgument(QUICKSTART_ID_FILTER_KEY);
}
};
return (
<QuickStartContainer {...drawerProps}>
<Button onClick={() => toggleQuickStart("node-with-s2i")}>
Toggle quick start through prop
</Button>
<SomeNestedComponent />
<QuickStartCatalogPage
showFilter
title="Quick starts"
hint="Learn how to create, import, and run applications with step-by-step instructions and tasks."
/>
</QuickStartContainer>
);
};
const SomeNestedComponent = () => {
const qsContext = React.useContext(QuickStartContext);
return (
<button onClick={() => qsContext.setActiveQuickStart("monitor-sampleapp")}>
Toggle quick start through context
</button>
);
};
export default App;
See above usage of useLegacyHeaderColors
boolean to opt-out of update. Should only be used if new color scheme clashes with the usage context.
Quick starts format
Quick starts are parsed as markdown. To write your own quick start, if you use Typescript you can check out the type definition here.
Here's a yaml template to get you started on writing your own quick starts.
Writing quick starts
Quick starts are typically written in yaml, but we've also seen projects use asciidoc and json. As long as you can pass in an array of quick starts it doesn't really matter in what format your content is sourced.
- We have a yaml starter template here
- The easiest way to preview the content as you're writing it, is to use Visual Studio code with our quickstarts-preview extension.
- Alternatively, you can use github.dev which is basically VS Code on the web, and install the extension there, then edit your yaml content!
- For guidelines on writing a quick start, the fine folks at OpenShift have created this guide
Highlighting elements
You can highlight an element on the page from within a quick start. The element that should be highlightable needs a data-quickstart-id attribute. Example:
<button data-quickstart-id="special-btn">Click me</button>
In the quick start task description, you can add this type of markdown to target this element:
Highlight [special button]{{highlight special-btn}}
Copyable text
You can have inline or block copyable text.
Inline copyable text example
`echo "Donec id est ante"`{{copy}}
Multiline copyable text example
```
First line of text.
Second line of text.
```{{copy}}
Markdown extensions
If your source material content is defined in markdown (yaml + markdown / json + markdown), then you can add your own markdown extensions, example:
const drawerProps: QuickStartContainerProps = {
markdown: {
extensions: [
// variable substitution example
// this replaces the strings [APPLICATION] and [PRODUCT]
{
type: 'output',
filter: function(html: string) {
html = html.replace(/\[APPLICATION\]/g, 'Mercury');
html = html.replace(/\[PRODUCT\]/g, 'Lightning');
return html;
},
},
],
},
};
return <QuickStartContainer {...drawerProps}>My page content</QuickStartContainer>
Localization
We use English as the default language. You can override the default by providing your own key/value pairs to the QuickStartContainer
or QuickStartContextProvider
resourceBundle prop.
Example:
const resourceBundle = i18n.getResourceBundle('cs', 'quickstart');
const resources = {
...resourceBundle,
Start: "Let's go!",
Continue: 'Resume',
Restart: 'Start over',
};
return (
<QuickStartContainer resourceBundle={resources}>
</QuickStartContainer>
)
Use this file as a base for your translations.
Each language is different, especially when it comes to plurals. Try this utility sourced from i18next to determine the suffixes for the right plural format.
For localizing the content of quick starts files, we provide the option to include language
and countryCode
key to your translated file. Based on these keys you can filter out quick starts. We have a demo of this behaviour in our demo app. You can have a look at the code here.
In-App / In Context Help Panel
The quickstarts package is being extended to support a side panel that displays smaller chunks (defined as the HelpTopic
type) of documentation.
Help Topic type definition
type HelpTopicLink = {
href: string;
text?: string;
newTab?: boolean;
isExternal?: boolean;
};
type HelpTopic = {
name: string;
title: string;
tags: string[];
content: string;
links: HelpTopicLink[];
};
Example Help Topic in yaml with markdown support for content and links
- name: auto-deploy
tags:
- page-1
title: Automatic Deployment
content: |-
**An Automatic Deployment** is...
Etiam viverra et tortor et maximus. Aliquam quis scelerisque metus. Proin luctus pretium sodales. Mauris nibh nibh, auctor eu scelerisque et, hendrerit a metus. Vivamus pharetra bibendum finibus. Sed a pulvinar ipsum. Fusce pharetra venenatis porttitor. Praesent justo metus, consectetur quis erat id, congue varius metus. Suspendisse dui est, tempor nec diam quis, facilisis sodales erat. Curabitur viverra convallis ex. Ut egestas condimentum augue, id euismod leo volutpat vitae. Quisque aliquet ac dolor quis pretium. Nunc at nibh quis arcu maximus elementum vel a mi.
links:
- text: 'Creating quick starts (external)'
href: 'https://docs.openshift.com/container-platform/4.9/web_console/creating-quick-start-tutorials.html'
isExternal: true
- text: 'Redhat Console (opens in new tab)'
href: 'https://console.redhat.com'
newTab: true
Usage Example
See the HelpTopicDemo
- Similar to standard Quickstarts usage
- Load yaml defined
HelpTopic
array - Pass
HelpTopicContainerProps
, including loaded HelpTopics
to the <HelpTopicContainer/>
import './App.css';
import { Page } from '@patternfly/react-core';
import { LoadingBox, HelpTopicContainerProps, HelpTopicContainer } from '@patternfly/quickstarts';
import { helpTopics } from './quickstarts-data/quick-start-test-data';
import React from 'react';
import i18n from './i18n/i18n';
import { AppHeader, AppSidebar } from './common/Page';
type AppProps = {
children?: React.ReactNode;
showCardFooters?: boolean;
};
const AppHelpTopicDemo: React.FC<AppProps> = ({ children }) => {
const language = localStorage.getItem('bridge/language') || 'en';
const resourceBundle = i18n.getResourceBundle(language, 'quickstart');
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
const load = async () => {
setLoading(false);
};
setTimeout(() => {
load();
}, 500);
}, []);
const inContextHelpProps: HelpTopicContainerProps = {
helpTopics,
resourceBundle,
language,
loading,
};
return (
<React.Suspense fallback={<LoadingBox />}>
<HelpTopicContainer {...inContextHelpProps}>
<Page header={AppHeader} sidebar={AppSidebar} isManagedSidebar>
{children}
</Page>
</HelpTopicContainer>
</React.Suspense>
);
};
In the example above <HelpTopicContainer />
wraps the <Page/>
element as well as a mock "console" showing how to trigger the side panel and allow navigation to all HelpTopics
available on each console page:
Live preview of code below:
import * as React from 'react';
import {
Banner,
Button,
Divider,
Form,
FormGroup,
PageSection,
Popover,
Split,
SplitItem,
TextInput,
Title,
} from '@patternfly/react-core';
import { HelpTopicContext, HelpTopicContextValues } from '@patternfly/quickstarts';
import HelpIcon from '@patternfly/react-icons/dist/js/icons/help-icon';
import { HelpTopic } from '@patternfly/quickstarts/dist/utils/help-topic-types';
const helpTopicNamesByPage = [
['auto-deploy', 'code-sample', 'manual-deployment'],
['manual-deployment', 'target-port', 'build-configuration'],
['deploy-configuration', 'environment-variables', 'health-checks'],
];
interface FormGroupWithHelpTopicPopoverProps extends React.HTMLProps<HTMLDivElement> {
topic: HelpTopic;
}
const FormGroupWithHelpTopicPopover: React.FC<FormGroupWithHelpTopicPopoverProps> = ({ topic }) => {
const { setActiveHelpTopicByName } = React.useContext<HelpTopicContextValues>(HelpTopicContext);
return (
<FormGroup
label={topic.title}
isRequired
fieldId={topic.name}
key={topic.name}
labelIcon={
<Popover
bodyContent={(hide) => (
<div>
{topic.title} is quite amaizing{' '}
<Button
variant="link"
onClick={() => {
setActiveHelpTopicByName(topic.name);
hide();
}}
>
Learn more
</Button>
</div>
)}
>
<Button variant="plain">
<HelpIcon noVerticalAlign />
</Button>
</Popover>
}
>
<TextInput isRequired type="text" id={topic.name} />
</FormGroup>
);
};
export const MockConsole: React.FC = () => {
const { helpTopics, setFilteredHelpTopics, filteredHelpTopics, setActiveHelpTopicByName } =
React.useContext<HelpTopicContextValues>(HelpTopicContext);
const [consolePageState, setConsolePageState] = React.useState(1);
const handleClickNext = () => {
setActiveHelpTopicByName('');
if (consolePageState === 6) {
setConsolePageState(1);
return;
}
setConsolePageState(consolePageState + 1);
};
const handleClickBack = () => {
setActiveHelpTopicByName('');
if (consolePageState === 6) {
setConsolePageState(4);
return;
}
setConsolePageState(consolePageState - 1);
};
React.useEffect(() => {
if (consolePageState < 4) {
const topics = helpTopics.filter((topic: HelpTopic) => {
const pageTagNumbers = topic.tags.map((tag: string) => {
return Number(tag.slice(-1));
});
return pageTagNumbers.includes(consolePageState);
});
setFilteredHelpTopics(topics);
} else {
setFilteredHelpTopics(
helpTopics.filter((topic) => {
return helpTopicNamesByPage[consolePageState - 4].includes(topic.name);
}),
);
}
}, [consolePageState, helpTopics, setFilteredHelpTopics]);
const formGroupsFromTags = filteredHelpTopics.map((topic: HelpTopic, index) => {
return <FormGroupWithHelpTopicPopover topic={topic} key={index} />;
});
const formGroupsFromTopicNames = (helpTopicNames: string[]) => {
return helpTopicNames.map((topicName: string, index) => {
const topicToRender = helpTopics.find((topic) => {
return topicName === topic.name;
});
if (topicToRender) {
return <FormGroupWithHelpTopicPopover topic={topicToRender} key={index} />;
}
});
};
return (
<>
<PageSection>
<Banner variant="info">
<Title headingLevel="h1">Console Page {consolePageState}</Title>
</Banner>
</PageSection>
<PageSection>
<Title headingLevel="h3">
Example usage of help topics opened via popover{' '}
<b>
{consolePageState < 4
? 'using tags that match the Console Page number'
: 'by defining which help topics should be present on each page'}
</b>
</Title>
<Divider />
<Form>
{consolePageState < 4
? formGroupsFromTags
: formGroupsFromTopicNames(helpTopicNamesByPage[consolePageState - 4])}
</Form>
</PageSection>
<PageSection>
<Split hasGutter>
<SplitItem>
<Button onClick={handleClickBack} variant="tertiary">
Previous
</Button>
</SplitItem>
<SplitItem>
<Button onClick={handleClickNext}>Next</Button>
</SplitItem>
</Split>
</PageSection>
</>
);
};