Chore: Bump Storybook to 8.4.x (#96128)

* feat(storybook): upgrade to version 8.4

* chore(grafana-ui): replace all usage of preview with canvas

* chore(grafana-ui): add fs-extra as dev dependency

* feat(storybook): copy required assets to temp static directory due to 8.4 not supporting file paths

* chore(yarn): fix up lock file so swc-loader doesnt break for decoupled plugins

* Add ExampleFrame component to render grafana-ui examples

* Prevent Storybook from styling in ExampleFrame

* Use global styles in Storybook docs

* Update mdx docs to use ExampleFrame or correct Canvas usage

* update AutoSizeInput

* Update Index mdx

* remove the gfm mdx package

* silence sass warnings

* fix(storybook): add missing imports to fix failed rendering of stories/docs

* remove empty docs

---------

Co-authored-by: joshhunt <josh@trtr.co>
pull/97170/head
Jack Westbrook 7 months ago committed by GitHub
parent 10eec83478
commit 25da5f0806
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      .betterer.results
  2. 2
      .github/renovate.json5
  3. 1
      .gitignore
  4. 134
      .yarn/patches/@storybook-blocks-npm-8.1.6-892f57a6d7.patch
  5. 1
      package.json
  6. 62
      packages/grafana-ui/.storybook/copyAssets.ts
  7. 54
      packages/grafana-ui/.storybook/main.ts
  8. 2
      packages/grafana-ui/.storybook/preview.ts
  9. 33
      packages/grafana-ui/package.json
  10. 8
      packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.mdx
  11. 133
      packages/grafana-ui/src/components/Button/Button.mdx
  12. 6
      packages/grafana-ui/src/components/Combobox/MultiCombobox.internal.story.tsx
  13. 0
      packages/grafana-ui/src/components/Combobox/MultiCombobox.mdx
  14. 14
      packages/grafana-ui/src/components/Input/AutoSizeInput.mdx
  15. 17
      packages/grafana-ui/src/components/Input/Input.mdx
  16. 76
      packages/grafana-ui/src/components/Layout/Layout.mdx
  17. 6
      packages/grafana-ui/src/components/Layout/Layout.story.tsx
  18. 37
      packages/grafana-ui/src/components/Link/TextLink.mdx
  19. 25
      packages/grafana-ui/src/components/Link/TextLink.story.tsx
  20. 15
      packages/grafana-ui/src/components/LoadingBar/LoadingBar.mdx
  21. 116
      packages/grafana-ui/src/components/PanelChrome/PanelChrome.mdx
  22. 23
      packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.mdx
  23. 9
      packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.mdx
  24. 2
      packages/grafana-ui/src/components/RenderUserContentAsHTML/RenderUserContentAsHTML.mdx
  25. 10
      packages/grafana-ui/src/components/Spinner/Spinner.mdx
  26. 17
      packages/grafana-ui/src/components/Tags/Tag.mdx
  27. 16
      packages/grafana-ui/src/components/Tags/TagList.mdx
  28. 21
      packages/grafana-ui/src/components/Text/Text.mdx
  29. 18
      packages/grafana-ui/src/components/TextArea/TextArea.mdx
  30. 2
      packages/grafana-ui/src/components/ThemeDemos/EmotionPerfTest.tsx
  31. 6
      packages/grafana-ui/src/components/UnitPicker/UnitPicker.mdx
  32. 2
      packages/grafana-ui/src/components/UnitPicker/UnitPicker.story.tsx
  33. 39
      packages/grafana-ui/src/utils/storybook/ExampleFrame.tsx
  34. 2
      packages/grafana-ui/src/utils/storybook/ThemedDocsContainer.tsx
  35. 2959
      yarn.lock

@ -5400,6 +5400,9 @@ exports[`no undocumented stories`] = {
"packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.story.tsx:5381": [
[0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"]
],
"packages/grafana-ui/src/components/UnitPicker/UnitPicker.story.tsx:5381": [
[0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"]
],
"packages/grafana-ui/src/components/VizLayout/VizLayout.story.tsx:5381": [
[0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"]
],

@ -13,7 +13,7 @@
"slate-react", // we don't want to continue using this on the long run, use Monaco editor instead of Slate
"@types/slate-react", // we don't want to continue using this on the long run, use Monaco editor instead of Slate
"@types/slate", // we don't want to continue using this on the long run, use Monaco editor instead of Slate
"storybook-dark-mode", // 4.0.2 causes storybook 8.4 to break with react hooks errors
// Temporarily pause updating lerna and nx until we resolve build issues
"lerna",
"nx"

1
.gitignore vendored

@ -141,6 +141,7 @@ pkg/services/quota/quotaimpl/storage/storage.json
# Ignoring frontend packages specifics
/packages/grafana-ui/.yarn/.cache
/packages/grafana-ui/.storybook/static
/packages/**/dist
/packages/**/compiled
/packages/**/.rpt2_cache

File diff suppressed because one or more lines are too long

@ -429,7 +429,6 @@
"react-split-pane@0.1.92": "patch:react-split-pane@npm:0.1.92#.yarn/patches/react-split-pane-npm-0.1.92-93dbf51dff.patch",
"history@4.10.1": "patch:history@npm%3A4.10.1#./.yarn/patches/history-npm-4.10.1-ee217563ae.patch",
"redux": "^5.0.0",
"@storybook/blocks@npm:8.1.6": "patch:@storybook/blocks@npm%3A8.1.6#~/.yarn/patches/@storybook-blocks-npm-8.1.6-892f57a6d7.patch",
"react-grid-layout": "patch:react-grid-layout@npm%3A1.4.4#~/.yarn/patches/react-grid-layout-npm-1.4.4-4024c5395b.patch",
"@grafana/plugin-e2e/@grafana/e2e-selectors": "workspace:*",
"@grafana/scenes/@grafana/e2e-selectors": "workspace:*",

@ -0,0 +1,62 @@
// This script is used to copy assets from the public folder to a temporary static folder within
// the .storybook directory.
// We selectively limit assets that are uploaded to the Storybook bucket to prevent rate limiting
// when publishing new storybook.
// Note: Storybook has a static copying feature but it copies entire directories which can contain thousands of icons.
import { copySync, emptyDirSync, lstatSync } from 'fs-extra';
import { resolve } from 'node:path';
// avoid importing from @grafana/data to prevent error: window is not defined
import { availableIconsIndex, IconName } from '../../grafana-data/src/types/icon';
import { getIconSubDir } from '../src/components/Icon/utils';
// doesn't require uploading 1000s of unused assets.
const iconPaths = Object.keys(availableIconsIndex)
.filter((iconName) => !iconName.startsWith('fa '))
.map((iconName) => {
const subDir = getIconSubDir(iconName as IconName, 'default');
return {
from: `../../../public/img/icons/${subDir}/${iconName}.svg`,
to: `./static/public/img/icons/${subDir}/${iconName}.svg`,
};
});
export function copyAssetsSync() {
const assets = [
{
from: '../../../public/fonts',
to: './static/public/fonts',
},
{
from: '../../../public/img/grafana_text_logo-dark.svg',
to: './static/public/img/grafana_text_logo-dark.svg',
},
{
from: '../../../public/img/grafana_text_logo-light.svg',
to: './static/public/img/grafana_text_logo-light.svg',
},
{
from: '../../../public/img/fav32.png',
to: './static/public/img/fav32.png',
},
{
from: '../../../public/lib',
to: './static/public/lib',
},
...iconPaths,
];
const staticDir = resolve(__dirname, 'static', 'public');
emptyDirSync(staticDir);
for (const asset of assets) {
const fromPath = resolve(__dirname, asset.from);
const toPath = resolve(__dirname, asset.to);
copySync(fromPath, toPath, {
filter: (src) => !lstatSync(src).isSymbolicLink(),
});
}
}

@ -1,8 +1,6 @@
import path, { dirname, join } from 'path';
import path, { dirname, join } from 'node:path';
import type { StorybookConfig } from '@storybook/react-webpack5';
// avoid importing from @grafana/data to prevent node error: ERR_REQUIRE_ESM
import { availableIconsIndex, IconName } from '../../grafana-data/src/types/icon';
import { getIconSubDir } from '../src/components/Icon/utils';
import { copyAssetsSync } from './copyAssets';
// Internal stories should only be visible during development
const storyGlob =
@ -12,17 +10,8 @@ const storyGlob =
const stories = ['../src/Intro.mdx', storyGlob];
// We limit icon paths to only the available icons so publishing
// doesn't require uploading 1000s of unused assets.
const iconPaths = Object.keys(availableIconsIndex)
.filter((iconName) => !iconName.startsWith('fa '))
.map((iconName) => {
const subDir = getIconSubDir(iconName as IconName, 'default');
return {
from: `../../../public/img/icons/${subDir}/${iconName}.svg`,
to: `/public/img/icons/${subDir}/${iconName}.svg`,
};
});
// Copy the assets required by storybook before starting the storybook server.
copyAssetsSync();
const mainConfig: StorybookConfig = {
stories,
@ -45,17 +34,18 @@ const mainConfig: StorybookConfig = {
url: false,
importLoaders: 2,
},
sassLoaderOptions: {
sassOptions: {
// silencing these warnings since we're planning to remove sass when angular is gone
silenceDeprecations: ['import', 'global-builtin'],
},
},
},
},
getAbsolutePath('@storybook/addon-storysource'),
getAbsolutePath('storybook-dark-mode'),
getAbsolutePath('@storybook/addon-mdx-gfm'),
getAbsolutePath('@storybook/addon-webpack5-compiler-swc'),
],
core: {},
docs: {
autodocs: true,
},
framework: {
name: getAbsolutePath('@storybook/react-webpack5'),
options: {
@ -66,29 +56,7 @@ const mainConfig: StorybookConfig = {
},
},
logLevel: 'debug',
staticDirs: [
{
from: '../../../public/fonts',
to: '/public/fonts',
},
{
from: '../../../public/img/grafana_text_logo-dark.svg',
to: '/public/img/grafana_text_logo-dark.svg',
},
{
from: '../../../public/img/grafana_text_logo-light.svg',
to: '/public/img/grafana_text_logo-light.svg',
},
{
from: '../../../public/img/fav32.png',
to: '/public/img/fav32.png',
},
{
from: '../../../public/lib',
to: '/public/lib',
},
...iconPaths,
],
staticDirs: ['static'],
typescript: {
check: true,
reactDocgen: 'react-docgen-typescript',

@ -46,7 +46,6 @@ const preview: Preview = {
knobs: {
disable: true,
},
layout: 'fullscreen',
options: {
// Sort stories first by Docs Overview, then alphabetically
// We should be able to use the builtin alphabetical sort, but is broken in SB 7.0
@ -83,6 +82,7 @@ const preview: Preview = {
},
},
},
tags: ['autodocs'],
};
export default preview;

@ -118,22 +118,22 @@
"@faker-js/faker": "^9.0.0",
"@grafana/tsconfig": "^2.0.0",
"@rollup/plugin-node-resolve": "15.3.0",
"@storybook/addon-a11y": "^8.1.6",
"@storybook/addon-actions": "^8.1.6",
"@storybook/addon-docs": "^8.1.6",
"@storybook/addon-essentials": "^8.1.6",
"@storybook/addon-mdx-gfm": "^8.1.6",
"@storybook/addon-storysource": "^8.1.6",
"@storybook/addon-webpack5-compiler-swc": "^1.0.2",
"@storybook/blocks": "patch:@storybook/blocks@npm%3A8.1.6#~/.yarn/patches/@storybook-blocks-npm-8.1.6-892f57a6d7.patch",
"@storybook/components": "^8.1.6",
"@storybook/core-events": "^8.1.6",
"@storybook/addon-a11y": "^8.4.2",
"@storybook/addon-actions": "^8.4.2",
"@storybook/addon-docs": "^8.4.2",
"@storybook/addon-essentials": "^8.4.2",
"@storybook/addon-storysource": "^8.4.2",
"@storybook/addon-webpack5-compiler-swc": "^1.0.5",
"@storybook/blocks": "^8.4.2",
"@storybook/components": "^8.4.2",
"@storybook/core-events": "^8.4.2",
"@storybook/manager-api": "^8.4.2",
"@storybook/mdx2-csf": "1.1.0",
"@storybook/preset-scss": "1.0.3",
"@storybook/preview-api": "^8.1.6",
"@storybook/react": "^8.1.6",
"@storybook/react-webpack5": "^8.1.6",
"@storybook/theming": "^8.1.6",
"@storybook/preview-api": "^8.4.2",
"@storybook/react": "^8.4.2",
"@storybook/react-webpack5": "^8.4.2",
"@storybook/theming": "^8.4.2",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.0.1",
@ -166,6 +166,7 @@
"csstype": "3.1.3",
"esbuild": "0.24.0",
"expose-loader": "5.0.0",
"fs-extra": "^11.2.0",
"mock-raf": "1.0.1",
"process": "^0.11.10",
"react": "18.2.0",
@ -180,8 +181,8 @@
"rollup-plugin-node-externals": "^7.1.3",
"rollup-plugin-svg-import": "3.0.0",
"sass-loader": "16.0.3",
"storybook": "^8.1.6",
"storybook-dark-mode": "^4.0.1",
"storybook": "^8.4.2",
"storybook-dark-mode": "4.0.1",
"style-loader": "4.0.0",
"typescript": "5.5.4",
"webpack": "5.95.0"

@ -1,6 +1,6 @@
import { Preview, ArgTypes } from '@storybook/blocks';
import { Canvas, ArgTypes } from '@storybook/blocks';
import { AutoSaveField } from './AutoSaveField';
import { Basic } from './AutoSaveField.story.tsx';
import * as AutoSaveFieldStories from './AutoSaveField.story';
# AutoSaveField
@ -217,9 +217,7 @@ The `AutoSaveField`component is comprised of:
{' '}
<Preview>
<Basic />
</Preview>
<Canvas of={AutoSaveFieldStories.Basic} />
<br />
### Placement

@ -1,15 +1,12 @@
import { Meta, Preview, ArgTypes } from '@storybook/blocks';
import { Meta, Canvas, ArgTypes, Unstyled } from '@storybook/blocks';
import { Button, LinkButton } from './Button';
import { Alert } from '../Alert/Alert';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
<Meta title="MDX|Button" component={Button} />
# Button
<Alert severity="warning" title={'Please note:'}>
After reviewing this component we are asking you to use the IconButton when you require a button with only an icon.
</Alert>
When using a button please always follow a11y rules (e.g. W3C Recommendation [3.3.2 Labels or instructions](https://www.w3.org/TR/WCAG21/#labels-or-instructions)) and make sure the context in which the button is located is also communicated by screen readers.
## Primary
@ -18,19 +15,17 @@ Used for "call to action", i.e. triggering the main action. There should never b
If there is no primary action, all `Button` components should be secondary.
<Preview>
<div>
<Button variant="primary" size="sm" style={{ margin: '5px' }}>
{'Small'}
</Button>
<Button variant="primary" size="md" style={{ margin: '5px' }}>
{'Medium'}
</Button>
<Button variant="primary" size="lg" style={{ margin: '5px' }}>
{'Large'}
</Button>
</div>
</Preview>
<ExampleFrame>
<Button variant="primary" size="sm" style={{ margin: '5px' }}>
{'Small'}
</Button>
<Button variant="primary" size="md" style={{ margin: '5px' }}>
{'Medium'}
</Button>
<Button variant="primary" size="lg" style={{ margin: '5px' }}>
{'Large'}
</Button>
</ExampleFrame>
## Secondary
@ -39,70 +34,64 @@ The secondary `Button` is the default button style and can trigger various actio
1. When next to the primary `Button`, the Secondary style can for example be used for "Cancel" or "Abort" actions.
2. When there is no main important action on a given page, all `Button` components should use the secondary style.
<Preview>
<div>
<Button variant="secondary" size="sm" style={{ margin: '5px' }}>
{'Small'}
</Button>
<Button variant="secondary" size="md" style={{ margin: '5px' }}>
{'Medium'}
</Button>
<Button variant="secondary" size="lg" style={{ margin: '5px' }}>
{'Large'}
</Button>
</div>
</Preview>
<ExampleFrame>
<Button variant="secondary" size="sm" style={{ margin: '5px' }}>
{'Small'}
</Button>
<Button variant="secondary" size="md" style={{ margin: '5px' }}>
{'Medium'}
</Button>
<Button variant="secondary" size="lg" style={{ margin: '5px' }}>
{'Large'}
</Button>
</ExampleFrame>
## Destructive
Used for triggering a removing or deleting action. Because of its dominant coloring, it should be used sparingly. If you need multiple Destructive `Button` components in one view, we recommend using a secondary `Button` or Link variant instead and only use the Destructive variant to double confirm.
<Preview>
<div>
<Button variant="destructive" size="sm" style={{ margin: '5px' }}>
{'Small'}
</Button>
<Button variant="destructive" size="md" style={{ margin: '5px' }}>
{'Medium'}
</Button>
<Button variant="destructive" size="lg" style={{ margin: '5px' }}>
{'Large'}
</Button>
</div>
</Preview>
<ExampleFrame>
<Button variant="destructive" size="sm" style={{ margin: '5px' }}>
{'Small'}
</Button>
<Button variant="destructive" size="md" style={{ margin: '5px' }}>
{'Medium'}
</Button>
<Button variant="destructive" size="lg" style={{ margin: '5px' }}>
{'Large'}
</Button>
</ExampleFrame>
## Text
<Preview>
<div>
<Button fill="text" size="sm" style={{ margin: '5px' }}>
{'Small'}
</Button>
<Button fill="text" size="md" style={{ margin: '5px' }}>
{'Medium'}
</Button>
<Button fill="text" size="lg" style={{ margin: '5px' }}>
{'Large'}
</Button>
</div>
</Preview>
<ArgTypes of={Button} />
<ExampleFrame>
<Button fill="text" size="sm" style={{ margin: '5px' }}>
{'Small'}
</Button>
<Button fill="text" size="md" style={{ margin: '5px' }}>
{'Medium'}
</Button>
<Button fill="text" size="lg" style={{ margin: '5px' }}>
{'Large'}
</Button>
</ExampleFrame>
## Links
To add an anchor that looks like a button use the `<LinkButton>` component and pass a href prop.
<Preview>
<div>
<LinkButton href="/" size="sm" style={{ margin: '5px' }}>
{'Small'}
</LinkButton>
<LinkButton href="/" size="md" style={{ margin: '5px' }}>
{'Medium'}
</LinkButton>
<LinkButton href="/" size="lg" style={{ margin: '5px' }}>
{'Large'}
</LinkButton>
</div>
</Preview>
<ExampleFrame>
<LinkButton href="/" size="sm" style={{ margin: '5px' }}>
{'Small'}
</LinkButton>
<LinkButton href="/" size="md" style={{ margin: '5px' }}>
{'Medium'}
</LinkButton>
<LinkButton href="/" size="lg" style={{ margin: '5px' }}>
{'Large'}
</LinkButton>
</ExampleFrame>
## Props
<ArgTypes of={Button} />

@ -1,16 +1,10 @@
import type { Meta, StoryObj } from '@storybook/react';
import { MultiCombobox } from './MultiCombobox';
import mdx from './MultiCombobox.mdx';
const meta: Meta<typeof MultiCombobox> = {
title: 'Forms/MultiCombobox',
component: MultiCombobox,
parameters: {
docs: {
page: mdx,
},
},
};
export default meta;

@ -1,7 +1,8 @@
import { ArgTypes, Preview } from '@storybook/blocks';
import { ArgTypes } from '@storybook/blocks';
import { AutoSizeInput } from './AutoSizeInput';
import { Field } from '../Forms/Field';
import { Icon } from '../Icon/Icon';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
# AutoSizeInput
@ -15,9 +16,9 @@ To add more context to the input, you can add either text or an icon before or a
<AutoSizeInput prefix={<Icon name="search" />} />
```
<Preview>
<ExampleFrame>
<AutoSizeInput prefix={<Icon name="search" />} />
</Preview>
</ExampleFrame>
## Usage in forms with Field
@ -29,9 +30,12 @@ Use `AutoSizeInput`with the`Field`component to get labels and descriptions. Also
</Field>
```
<Preview>
<ExampleFrame>
<Field label="Important information!" description="Please enter the relevant information.">
<AutoSizeInput name="importantInput" required />
</Field>
</Preview>
</ExampleFrame>
## Props
<ArgTypes of={AutoSizeInput} />

@ -1,12 +1,16 @@
import { ArgTypes, Preview } from '@storybook/blocks';
import { ArgTypes, Canvas } from '@storybook/blocks';
import { Input } from './Input';
import { Field } from '../Forms/Field';
import { Icon } from '../Icon/Icon';
import * as InputStories from './Input.story';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
# Input
Used for regular text input. For an array of data or tree-structured data, consider using `Select` or `Cascader` respectively.
<Canvas of={InputStories.Simple} />
## Prefix and suffix
To add more context to the input you can add either text or an icon before or after the input. You can use the `prefix` and `suffix` props for this. Try some examples in the Preview!
@ -15,9 +19,9 @@ To add more context to the input you can add either text or an icon before or af
<Input prefix={<Icon name="search" />} />
```
<Preview>
<ExampleFrame>
<Input prefix={<Icon name="search" />} />
</Preview>
</ExampleFrame>
## Usage in forms with Field
@ -29,12 +33,15 @@ To add more context to the input you can add either text or an icon before or af
</Field>
```
<Preview>
<ExampleFrame>
<Field
label="Important information"
description="This information is very important, so you really need to fill it in"
>
<Input name="importantInput" required />
</Field>
</Preview>
</ExampleFrame>
## Props
<ArgTypes of={Input} />

@ -1,7 +1,9 @@
import { Preview, ArgTypes } from '@storybook/blocks';
import { Canvas, ArgTypes } from '@storybook/blocks';
import { Layout, HorizontalGroup, VerticalGroup } from './Layout';
import { Button } from '../Button';
import { Select } from '../index';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
import * as LayoutStories from './Layout.story';
# Layout
@ -19,78 +21,10 @@ Expects multiple elements as `children` prop. This is the base for more speciali
Used for horizontally aligning several elements (e.g. Button, Select) with a predefined spacing between them.
<Preview>
<HorizontalGroup>
<Button>Button 1</Button>
<Button>Button 2</Button>
<Button>Button 3</Button>
</HorizontalGroup>
</Preview>
<Preview>
<HorizontalGroup>
<Select
width={25}
onChange={() => {}}
options={[
{ value: 1, label: 'Option 1' },
{ value: 2, label: 'Option 2' },
]}
/>
<Select
width={25}
onChange={() => {}}
options={[
{ value: 1, label: 'Option 1' },
{ value: 2, label: 'Option 2' },
]}
/>
<Select
width={25}
onChange={() => {}}
options={[
{ value: 1, label: 'Option 1' },
{ value: 2, label: 'Option 2' },
]}
/>
</HorizontalGroup>
</Preview>
<Canvas of={LayoutStories.Horizontal} />
## Vertical
Used for vertically aligning several elements (e.g. Button, Select) with a predefined spacing between them.
<Preview>
<VerticalGroup>
<Button>Button 1</Button>
<Button>Button 2</Button>
<Button>Button 3</Button>
</VerticalGroup>
</Preview>
<Preview>
<VerticalGroup justify="center">
<Select
width={25}
onChange={() => {}}
options={[
{ value: 1, label: 'Option 1' },
{ value: 2, label: 'Option 2' },
]}
/>
<Select
width={25}
onChange={() => {}}
options={[
{ value: 1, label: 'Option 1' },
{ value: 2, label: 'Option 2' },
]}
/>
<Select
width={25}
onChange={() => {}}
options={[
{ value: 1, label: 'Option 1' },
{ value: 2, label: 'Option 2' },
]}
/>
</VerticalGroup>
</Preview>
<Canvas of={LayoutStories.Vertical} />

@ -62,8 +62,9 @@ export default meta;
export const Horizontal: StoryFn<LayoutProps> = (args) => {
return (
<HorizontalGroup {...args}>
<Button variant="secondary">Cancel</Button>
<Button variant="destructive">Delete</Button>
<Button>Save</Button>
<Button>Cancel</Button>
</HorizontalGroup>
);
};
@ -71,8 +72,9 @@ export const Horizontal: StoryFn<LayoutProps> = (args) => {
export const Vertical: StoryFn<LayoutProps> = (args) => {
return (
<VerticalGroup {...args}>
<Button variant="secondary">Cancel</Button>
<Button variant="destructive">Delete</Button>
<Button>Save</Button>
<Button>Cancel</Button>
</VerticalGroup>
);
};

@ -1,6 +1,7 @@
import { Meta, Preview, ArgTypes } from '@storybook/blocks';
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
import { TextLink } from './TextLink';
import { Basic } from './TextLink.story.tsx';
import * as TextLinkStories from './TextLink.story.tsx';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
<Meta title="MDX|TextLink" component={TextLink} />
@ -81,30 +82,26 @@ The following is the default behaviour and so, it will be applied according to i
### <a name="inline-behaviour"/>**Inline**:<br/>
<Preview>
<TextLink href="https://google.es" external inline>
{'This an inline link example'}
</TextLink>
</Preview>
- _Initial appearence_: Blue and underlined at first. - _On hover_: blue and not underlined
- _Initial appearence_: Blue and underlined at first.
- _On hover_: blue and not underlined
<Canvas of={TextLinkStories.Inline} />
### <a name="standalone-behaviour"/>**Standalone**:
<Preview>
<TextLink href="https://google.es" inline={false} external>
{'This an standalone link example'}
</TextLink>
</Preview>
- _Initial appearence_: Blue and not underlined at first. - _On hover_: blue and underlined.
- _Initial appearence_: Blue and not underlined at first.
- _On hover_: blue and underlined.
<Canvas of={TextLinkStories.Standalone} />
_Example_: We should take into account that through the color prop the user will be able to change this value,
but the rest will be kept. So, in this example, if the user renders an external and standalone _text link_ setting the color value to 'primary' the result will be a text link 'white' in dark theme or 'black' in light theme, as set by the user, and not underlined at first while blue and underlined when onhover, as it inherits from the standalone default behaviour.
<Preview>
<ExampleFrame>
<TextLink href="https://google.es" color="primary" inline={false} external>
{'This an external standalone link example'}
</TextLink>
</Preview>
</ExampleFrame>
```jsx
<TextLink href="https://google.es" color="primary" inline={false} external>
@ -116,11 +113,11 @@ but the rest will be kept. So, in this example, if the user renders an external
By default, **external _text links_** will show an `external-alt-icon`. If by design reasons, the user needs to set another icon it can be done through the correspondent prop.
<Preview>
<ExampleFrame>
<TextLink href="https://google.es" external>
{'This an external with the default icon'}
</TextLink>
</Preview>
</ExampleFrame>
```jsx
<TextLink href="https://google.es" external>
@ -130,11 +127,11 @@ By default, **external _text links_** will show an `external-alt-icon`. If by de
Both, **external** and **internal** links can show a specific icon using the `icon` prop.
<Preview>
<ExampleFrame>
<TextLink href="https://google.es" icon="google" external>
{'This an external with a specific icon'}
</TextLink>
</Preview>
</ExampleFrame>
```jsx
<TextLink href="https://google.es" icon="google" external>
This an external with a specific icon

@ -32,7 +32,7 @@ const meta: Meta = {
inline: { control: 'boolean' },
},
args: {
href: 'https://www.google.com',
href: 'https://www.grafana.com',
external: true,
icon: 'external-link-alt',
},
@ -67,14 +67,33 @@ Example.parameters = {
controls: { exclude: ['href', 'external', 'variant', 'weight', 'color', 'inline', 'icon'] },
};
export const Basic: StoryFn = (args) => {
export const Inline: StoryFn = (args) => {
return (
<div>
For more information{' '}
<TextLink href={args.href} {...args}>
Go to Google
see Grafana.com
</TextLink>
</div>
);
};
Inline.args = {
inline: true,
};
export const Standalone: StoryFn = (args) => {
return (
<div>
<TextLink href={args.href} {...args}>
Go to Grafana.com
</TextLink>
</div>
);
};
Standalone.args = {
inline: false,
};
export default meta;

@ -1,5 +1,6 @@
import { Meta, Preview, ArgTypes } from '@storybook/blocks';
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
import { LoadingBar } from './LoadingBar';
import * as LoadingBarStories from './LoadingBar.story';
<Meta title="MDX|LoadingBar" component={LoadingBar} />
@ -7,16 +8,6 @@ import { LoadingBar } from './LoadingBar';
The LoadingBar is used as a simple loading slider animation in the top of its container.
<Preview>
<div style={{ height: '200px', width: '400px' }}>
<LoadingBar containerWidth={400} height={2} width={128} />
</div>
</Preview>
```jsx
<div style={{ height: '200px', width: '400px' }}>
<LoadingBar containerWidth={400} height={2} width={128} />
</div>
```
<Canvas of={LoadingBarStories.Basic} />
<ArgTypes of={LoadingBar} />

@ -1,5 +1,5 @@
import { css } from '@emotion/css';
import { Meta, Preview } from '@storybook/blocks';
import { Meta, Canvas } from '@storybook/blocks';
import { PanelChrome } from './PanelChrome';
import { action } from '@storybook/addon-actions';
@ -8,6 +8,8 @@ import { Button } from '../Button';
import { Menu } from '../Menu/Menu';
import { Stack } from '../Layout/Stack/Stack';
import { IconButton } from '../IconButton/IconButton';
import * as PanelChromeStories from './PanelChrome.story';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
<Meta title="MDX|PanelChrome" component={PanelChrome} />
@ -17,97 +19,11 @@ Component used for rendering content wrapped in the same style as grafana panels
### Basic Usage: Title, Description and Content
```tsx
<PanelChrome
title="My awesome panel title"
description="Here I will put a description that explains a bit more this panel"
width={400}
height={200}
>
{(innerwidth, innerheight) => {
return (
<div
style={{
width: innerwidth,
height: innerheight,
background: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
New panel with old API
</div>
);
}}
</PanelChrome>
```
<Preview>
<PanelChrome
title="My awesome panel title"
description="Here I will put a description that explains a bit more this panel"
width={400}
height={200}
>
{(innerwidth, innerheight) => {
return (
<div
style={{
width: innerwidth,
height: innerheight,
background: 'rgba(230,0,0,0.05)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
Content
</div>
);
}}
</PanelChrome>
</Preview>
<Canvas of={PanelChromeStories.Basic} />
### Menu: Standard & Hover
```tsx
<PanelChrome
title="My awesome panel title"
hoverHeader={<true || false>}
menu={() => (
<Menu>
<Menu.Item label="View" icon="eye" />
<Menu.Item label="Edit" icon="edit" />
<Menu.Item label="Share" icon="share-alt" />
<Menu.Divider />
<Menu.Item label="Remove" icon="trash-alt" />
</Menu>
)}
description="Here I will put a description that explains a bit more this panel"
width={400}
height={200}
>
{(innerwidth, innerheight) => {
return (
<div
style={{
width: innerwidth,
height: innerheight,
background: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
New panel with old API
</div>
);
}}
</PanelChrome>
```
<Preview>
<ExampleFrame>
<Stack gap={2} alignItems="flex-start" wrap="wrap">
<PanelChrome
@ -183,7 +99,7 @@ Component used for rendering content wrapped in the same style as grafana panels
</PanelChrome>
</Stack>
</Preview>
</ExampleFrame>
### States: Loading , Streaming, Error
@ -215,7 +131,7 @@ Component used for rendering content wrapped in the same style as grafana panels
</PanelChrome>
```
<Preview>
<ExampleFrame>
<Stack gap={2} alignItems="flex-start" wrap="wrap">
<PanelChrome
@ -298,7 +214,7 @@ Component used for rendering content wrapped in the same style as grafana panels
</PanelChrome>
</Stack>
</Preview>
</ExampleFrame>
### Extra options? Title items and actions
@ -345,7 +261,7 @@ Component used for rendering content wrapped in the same style as grafana panels
</PanelChrome>
```
<Preview>
<ExampleFrame>
<PanelChrome
title="My awesome panel title"
titleItems={
@ -386,7 +302,7 @@ Component used for rendering content wrapped in the same style as grafana panels
);
}}
</PanelChrome>
</Preview>
</ExampleFrame>
### Migration from old PanelChrome (before v9.4.0)
@ -418,7 +334,7 @@ Component used for rendering content wrapped in the same style as grafana panels
</PanelChrome>
```
<Preview>
<ExampleFrame>
<PanelChrome
title="My awesome panel title"
leftItems={[
@ -448,7 +364,7 @@ Component used for rendering content wrapped in the same style as grafana panels
);
}}
</PanelChrome>
</Preview>
</ExampleFrame>
#### After
```tsx
@ -478,7 +394,7 @@ Component used for rendering content wrapped in the same style as grafana panels
</PanelChrome>
```
<Preview>
<ExampleFrame>
<PanelChrome
title="My awesome panel title"
loadingState={LoadingState.Loading}
@ -503,7 +419,7 @@ Component used for rendering content wrapped in the same style as grafana panels
);
}}
</PanelChrome>
</Preview>
</ExampleFrame>
### Collapsible
@ -545,7 +461,7 @@ function Container() {
}
```
<Preview>
<ExampleFrame>
<PanelChrome title="My awesome panel title" width={400} height={200} collapsible={true}>
{(innerwidth, innerheight) => {
return (
@ -564,4 +480,4 @@ function Container() {
);
}}
</PanelChrome>
</Preview>
</ExampleFrame>

@ -1,5 +1,6 @@
import { Meta, Preview, ArgTypes } from '@storybook/blocks';
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
import { PluginSignatureBadge } from './PluginSignatureBadge';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
<Meta title="MDX|PluginSignatureBadge" component={PluginSignatureBadge} />
@ -12,32 +13,32 @@ This is a security measure to make sure plugins haven't been tampered with. Upon
1.- Core: this badge indicates that the plugin has been built into Grafana.
<Preview>
<ExampleFrame>
<PluginSignatureBadge status="internal" />
</Preview>
</ExampleFrame>
2.- Signed: the plugin's signature was successfully verified.
<Preview>
<ExampleFrame>
<PluginSignatureBadge status="valid" />
</Preview>
</ExampleFrame>
3.- Invalid signature: this plugin has a invalid signature.
<Preview>
<ExampleFrame>
<PluginSignatureBadge status="invalid" />
</Preview>
</ExampleFrame>
4.- Modified signature: this plugin has changed since it was signed. This may indicate malicious intent.
<Preview>
<ExampleFrame>
<PluginSignatureBadge status="modified" />
</Preview>
</ExampleFrame>
5.- Unsigned: the plugin is not signed. In this case, as upon loading Grafana checks the plugins signature, Grafana will not load or start it. Furthermore, it will write an error message to the server log.
<Preview>
<ExampleFrame>
<PluginSignatureBadge status="unsigned" />
</Preview>
</ExampleFrame>
<ArgTypes of={PluginSignatureBadge} />

@ -1,5 +1,6 @@
import { Meta, Preview, ArgTypes } from '@storybook/blocks';
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
import { RefreshPicker } from './RefreshPicker';
import * as RefreshPickerStories from './RefreshPicker.story';
<Meta title="MDX|RefreshPicker" component={RefreshPicker} />
@ -11,10 +12,6 @@ This component is used on dashboards to refresh visualizations. Grafana does not
**The down arrow:** will display a list of refresh intervals. If one of them is selected the dashboard will regularly refresh according to that schedule.
<Preview>
<RefreshPicker tooltip="Run query" />
<RefreshPicker text="Run query" tooltip="Run query" />
<RefreshPicker text="Run query" isLoading={true} tooltip="Run query" value="1h" />
</Preview>
<Canvas of={RefreshPickerStories.Examples} />
<ArgTypes of={RefreshPicker} />

@ -1,4 +1,4 @@
import { ArgTypes } from '@storybook/blocks';
import { Meta, ArgTypes } from '@storybook/blocks';
import { RenderUserContentAsHTML } from './RenderUserContentAsHTML';
<Meta title="MDX|RenderUserContentAsHTML" component={RenderUserContentAsHTML} />

@ -1,5 +1,6 @@
import { Meta, Preview, ArgTypes } from '@storybook/blocks';
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
import { Spinner } from './Spinner';
import * as SpinnerStories from './Spinner.story';
<Meta title="MDX|Spinner" component={Spinner} />
@ -7,11 +8,6 @@ import { Spinner } from './Spinner';
Spinner is `fa-spinner` icon animated. It is used to alert a user to wait for an activity to complete.
<Preview>
<div>
<Spinner />
<Spinner className="my-spin-div" iconClassName="my-spinner-classname" size={24} />
</div>
</Preview>
<Canvas of={SpinnerStories.Basic} />
<ArgTypes of={Spinner} />

@ -1,5 +1,6 @@
import { Meta, Preview, ArgTypes } from '@storybook/blocks';
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
import { Tag } from './Tag';
import * as TagStories from './Tag.story';
<Meta title="MDX|Tag" component={Tag} />
@ -7,19 +8,7 @@ import { Tag } from './Tag';
Used for displaying metadata, for example to add more details to search results. Background and border colors are generated from the tag name.
<Preview>
<div>
<Tag name="Tag" onClick={(name) => console.log(name)} />
</div>
</Preview>
### Usage
```jsx
import { Tag } from '@grafana/ui';
<Tag name="Tag" onClick={(name) => console.log(name)} />;
```
<Canvas of={TagStories.Single} />
### Props

@ -1,22 +1,12 @@
import { Preview, ArgTypes } from '@storybook/blocks';
import { Canvas, ArgTypes } from '@storybook/blocks';
import { TagList } from './TagList';
import * as TagListStories from './TagList.story';
# TagList
List of tags with predefined margins and positioning.
<Preview>
<TagList tags={['datasource-test', 'gdev', 'mysql', 'mssql']} />
</Preview>
### Usage
```jsx
import { TagList } from '@grafana/ui';
const tags = ['datasource-test', 'gdev', 'mysql', 'mssql'];
<TagList tags={tags} onClick={(tag) => console.log(tag)} />;
```
<Canvas of={TagListStories.List} />
### Props

@ -1,8 +1,9 @@
import { Meta, Preview, ArgTypes } from '@storybook/blocks';
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
import { Text } from './Text';
import { TextLink } from '../Link/TextLink.tsx';
import { Basic } from './Text.story.tsx';
import { Tooltip } from '../Tooltip';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
<Meta title="MDX|Text" component={Text} />
@ -65,14 +66,14 @@ The following is the default behaviour and so, it will be applied according to i
The Text component is mainly comprised by itself. In occasions, the Text component can have another Text or TextLink component as a child.
<Preview>
<ExampleFrame>
<Text color="primary" element="p">
{'If you need more help of how to write in Grafana you can go to our '}
<TextLink href="https://grafana.com/docs/writers-toolkit/" external>
{'Writer’s Toolkit'}
</TextLink>
</Text>
</Preview>
</ExampleFrame>
```jsx
<Text color="primary" element="p">
@ -83,12 +84,12 @@ The Text component is mainly comprised by itself. In occasions, the Text compone
</Text>
```
<Preview>
<ExampleFrame>
<Text color="primary" element="p">
{'And Forrest Gump said: '}
<Text italic>{"Life is like a box of chocolates. You never know what you're gonna get."}</Text>
</Text>
</Preview>
</ExampleFrame>
```jsx
<Text color="primary" element="p">
@ -104,23 +105,23 @@ The Text component can be truncated. However, the Text component element rendere
1. The parent element is a Text component: the user just has to set the element prop to another value and the truncate prop to true.
As a result, the Text will be truncated but when the user hovers over it the full text will be seen on a tooltip.
<Preview>
<ExampleFrame>
<Text color="primary" element="p" truncate>
{'And Forrest Gump said: '}
<Text italic>{'Life is like a box of chocolates. You never know what you are gonna get.'}</Text>
</Text>
</Preview>
</ExampleFrame>
```jsx
<Text color="primary" element="p" truncate>
And Forrest Gump said:
<Text italic>{'Life is like a box of chocolates. You never know what you are gonna get.'}</Text>
<Text italic>Life is like a box of chocolates. You never know what you are gonna get.</Text>
</Text>
```
2. The parent element is not a Text component: the user has to add `overflow: hidden`, `text-overflow: ellipsis` and `whiteSpace: 'nowrap'` to it. In this case, the user should wrap up this container with a Tooltip, so when the text is truncated its content can still be seen hovering on the text.
<Preview>
<ExampleFrame>
<Tooltip content="This is a example of a span element truncated by its parent container">
<div style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
<Text color="primary" variant="body">
@ -128,7 +129,7 @@ The Text component can be truncated. However, the Text component element rendere
</Text>
</div>
</Tooltip>
</Preview>
</ExampleFrame>
```jsx
<Tooltip content="This is a example of a span element truncated by its parent container">
<div style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>

@ -1,6 +1,8 @@
import { ArgTypes, Preview } from '@storybook/blocks';
import { ArgTypes, Canvas } from '@storybook/blocks';
import { Field } from '../Forms/Field';
import { TextArea } from './TextArea';
import * as TextAreaStories from './TextArea.story';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
# TextArea
@ -8,27 +10,19 @@ Use for multi line inputs like descriptions.
### Usage
```jsx
<TextArea invalid={invalid} placeholder={placeholder} cols={cols} disabled={disabled} />
```
<Canvas of={TextAreaStories.Basic} />
### Usage in forms with Field
`TextArea` should be used with the `Field` component to get labels and descriptions. It can also be used for validation by using the `required` attribute. See the `Field` component for more information.
```jsx
<Field label="Important information" description="This information is very important, so you really need to fill it in">
<TextArea name="importantTextarea" required />
</Field>
```
<Preview>
<ExampleFrame>
<Field
label="Important information"
description="This information is very important, so you really need to fill it in"
>
<TextArea name="importantTextarea" required />
</Field>
</Preview>
</ExampleFrame>
<ArgTypes of={TextArea} />

@ -3,7 +3,7 @@
/** @jsxRuntime classic */
import { css, cx } from '@emotion/css';
import classnames from 'classnames';
import { Profiler, ProfilerOnRenderCallback, useState, FC } from 'react';
import React, { Profiler, ProfilerOnRenderCallback, useState, FC } from 'react';
import { GrafanaTheme2 } from '@grafana/data';

@ -1,6 +0,0 @@
import { ArgTypes } from '@storybook/blocks';
import { UnitPicker } from './UnitPicker';
# UnitPicker
<ArgTypes of={UnitPicker} />{' '}

@ -2,7 +2,6 @@ import { action } from '@storybook/addon-actions';
import { Meta, StoryFn } from '@storybook/react';
import { UnitPicker, UnitPickerProps } from './UnitPicker';
import mdx from './UnitPicker.mdx';
const meta: Meta<typeof UnitPicker> = {
title: 'Pickers and Editors/UnitPicker',
@ -11,7 +10,6 @@ const meta: Meta<typeof UnitPicker> = {
controls: {
exclude: ['onChange', 'value'],
},
docs: mdx,
},
};

@ -0,0 +1,39 @@
import { css } from '@emotion/css';
import { Unstyled } from '@storybook/blocks';
import { ReactNode } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '../../themes';
interface ExampleFrameProps {
children: ReactNode;
}
/**
* Wraps children with a border for nicer presentation in Storybook docs.
*
* This is intended to be used temporarily to move away from our <Preview> patch until
* we have something more long term.
*/
export function ExampleFrame(props: ExampleFrameProps) {
const { children } = props;
const styles = useStyles2(getStyles);
return (
<Unstyled>
<div className={styles.container}>{children}</div>
</Unstyled>
);
}
const getStyles = (theme: GrafanaTheme2) => {
return {
container: css({
border: `1px solid ${theme.colors.border.medium}`,
borderRadius: theme.shape.radius.default,
padding: theme.spacing(2),
marginBottom: theme.spacing(4),
}),
};
};

@ -4,6 +4,7 @@ import * as React from 'react';
import { useDarkMode } from 'storybook-dark-mode';
import { GrafanaLight, GrafanaDark } from '../../../.storybook/storybookTheme';
import { GlobalStyles } from '../../themes';
type Props = {
context: DocsContextProps;
@ -15,6 +16,7 @@ export const ThemedDocsContainer = ({ children, context }: Props) => {
return (
<DocsContainer theme={dark ? GrafanaDark : GrafanaLight} context={context}>
<GlobalStyles />
{children}
</DocsContainer>
);

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save