Storybook: Rearrange and tidy stories (#107270)

* Tidy up storybook a little bit

* change sort order, delete some stories

* More tidy up of actions

* More tidy up of actions

* tweak story sorting, again

* Make all internal stories public

* fix sort

* Add ESLint rule to enforce storybook titles

* update verify storybook test

* simplify glob
pull/107872/head
Josh Hunt 2 weeks ago committed by GitHub
parent d27d8f02b6
commit a9e70d4a1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 42
      .betterer.results
  2. 2
      e2e/storybook/verify.spec.ts
  3. 7
      eslint.config.js
  4. 27
      packages/grafana-eslint-rules/README.md
  5. 2
      packages/grafana-eslint-rules/index.cjs
  6. 106
      packages/grafana-eslint-rules/rules/consistent-story-titles.cjs
  7. 117
      packages/grafana-eslint-rules/tests/consistent-stories.test.js
  8. 8
      packages/grafana-ui/.storybook/main.ts
  9. 82
      packages/grafana-ui/.storybook/preview.ts
  10. 13
      packages/grafana-ui/src/components/Alert/Alert.story.tsx
  11. 126
      packages/grafana-ui/src/components/Alert/Toast.story.tsx
  12. 2
      packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.story.tsx
  13. 2
      packages/grafana-ui/src/components/Badge/Badge.story.tsx
  14. 2
      packages/grafana-ui/src/components/BarGauge/BarGauge.story.tsx
  15. 2
      packages/grafana-ui/src/components/BigValue/BigValue.story.tsx
  16. 2
      packages/grafana-ui/src/components/Button/Button.story.tsx
  17. 2
      packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.story.tsx
  18. 2
      packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.story.tsx
  19. 1
      packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.tsx
  20. 5
      packages/grafana-ui/src/components/Card/Card.story.tsx
  21. 2
      packages/grafana-ui/src/components/Carousel/Carousel.story.tsx
  22. 2
      packages/grafana-ui/src/components/Cascader/Cascader.story.tsx
  23. 2
      packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.story.tsx
  24. 2
      packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.story.tsx
  25. 38
      packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx
  26. 32
      packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.story.tsx
  27. 52
      packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx
  28. 43
      packages/grafana-ui/src/components/ColorPicker/Palettes.story.tsx
  29. 39
      packages/grafana-ui/src/components/ColorPicker/SeriesColorPicker.story.tsx
  30. 2
      packages/grafana-ui/src/components/Combobox/Combobox.story.tsx
  31. 2
      packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx
  32. 2
      packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.story.tsx
  33. 2
      packages/grafana-ui/src/components/ContextMenu/ContextMenu.mdx
  34. 2
      packages/grafana-ui/src/components/ContextMenu/ContextMenu.story.tsx
  35. 2
      packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.story.tsx
  36. 2
      packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.story.tsx
  37. 2
      packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.story.tsx
  38. 2
      packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.story.tsx
  39. 2
      packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.story.tsx
  40. 2
      packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.story.tsx
  41. 2
      packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.story.tsx
  42. 2
      packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.story.tsx
  43. 2
      packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.story.tsx
  44. 2
      packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.story.tsx
  45. 2
      packages/grafana-ui/src/components/Divider/Divider.story.tsx
  46. 2
      packages/grafana-ui/src/components/Dropdown/ButtonSelect.story.tsx
  47. 3
      packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx
  48. 2
      packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.story.tsx
  49. 1
      packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.tsx
  50. 2
      packages/grafana-ui/src/components/EmptyState/EmptyState.story.tsx
  51. 2
      packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.story.tsx
  52. 2
      packages/grafana-ui/src/components/FeatureBadge/FeatureBadge.story.tsx
  53. 2
      packages/grafana-ui/src/components/FileDropzone/FileDropzone.story.tsx
  54. 2
      packages/grafana-ui/src/components/FileDropzone/FileListItem.story.tsx
  55. 2
      packages/grafana-ui/src/components/FileUpload/FileUpload.story.tsx
  56. 2
      packages/grafana-ui/src/components/FilterPill/FilterPill.story.tsx
  57. 2
      packages/grafana-ui/src/components/FormField/FormField.story.tsx
  58. 2
      packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.story.tsx
  59. 2
      packages/grafana-ui/src/components/Forms/Checkbox.story.tsx
  60. 58
      packages/grafana-ui/src/components/Forms/Legacy/Input/Input.internal.story.tsx
  61. 113
      packages/grafana-ui/src/components/Forms/Legacy/Select/Select.internal.story.tsx
  62. 27
      packages/grafana-ui/src/components/Forms/Legacy/Switch/Switch.internal.story.tsx
  63. 2
      packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.story.tsx
  64. 2
      packages/grafana-ui/src/components/Forms/RadioButtonList/RadioButtonList.story.tsx
  65. 2
      packages/grafana-ui/src/components/Icon/Icon.story.tsx
  66. 2
      packages/grafana-ui/src/components/IconButton/IconButton.story.tsx
  67. 2
      packages/grafana-ui/src/components/InfoBox/InfoBox.story.tsx
  68. 2
      packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.story.tsx
  69. 1
      packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx
  70. 3
      packages/grafana-ui/src/components/InlineToast/InlineToast.story.tsx
  71. 2
      packages/grafana-ui/src/components/Input/AutoSizeInput.story.tsx
  72. 2
      packages/grafana-ui/src/components/Input/Input.story.tsx
  73. 2
      packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.story.tsx
  74. 2
      packages/grafana-ui/src/components/Layout/Box/Box.story.tsx
  75. 2
      packages/grafana-ui/src/components/Layout/Grid/Grid.story.tsx
  76. 6
      packages/grafana-ui/src/components/Layout/Layout.story.tsx
  77. 2
      packages/grafana-ui/src/components/Layout/Space.story.tsx
  78. 20
      packages/grafana-ui/src/components/Layout/Stack/Stack.story.tsx
  79. 2
      packages/grafana-ui/src/components/Link/TextLink.story.tsx
  80. 1
      packages/grafana-ui/src/components/List/InlineList.tsx
  81. 2
      packages/grafana-ui/src/components/List/List.story.tsx
  82. 1
      packages/grafana-ui/src/components/List/List.tsx
  83. 2
      packages/grafana-ui/src/components/LoadingBar/LoadingBar.story.tsx
  84. 2
      packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.story.tsx
  85. 2
      packages/grafana-ui/src/components/Menu/Menu.mdx
  86. 2
      packages/grafana-ui/src/components/Menu/Menu.story.tsx
  87. 2
      packages/grafana-ui/src/components/Monaco/CodeEditor.story.tsx
  88. 2
      packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx
  89. 2
      packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx
  90. 2
      packages/grafana-ui/src/components/Pagination/Pagination.story.tsx
  91. 2
      packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx
  92. 2
      packages/grafana-ui/src/components/PanelContainer/PanelContainer.story.tsx
  93. 2
      packages/grafana-ui/src/components/PanelContainer/PanelContainer.tsx
  94. 2
      packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.story.tsx
  95. 2
      packages/grafana-ui/src/components/QueryField/QueryField.story.tsx
  96. 2
      packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.story.tsx
  97. 2
      packages/grafana-ui/src/components/RenderUserContentAsHTML/RenderUserContentAsHTML.story.tsx
  98. 2
      packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.story.tsx
  99. 2
      packages/grafana-ui/src/components/SecretFormField/SecretFormField.story.tsx
  100. 2
      packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

@ -4128,6 +4128,15 @@ exports[`no undocumented stories`] = {
"packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.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/CallToActionCard/CallToActionCard.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/ColorPicker/ColorPickerInput.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/ColorPicker/SeriesColorPicker.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/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.story.tsx:5381": [
[0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"]
],
@ -4143,12 +4152,27 @@ exports[`no undocumented stories`] = {
"packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.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/Dropdown/ButtonSelect.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/FormField/FormField.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/InfoTooltip/InfoTooltip.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/List/List.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/PageLayout/PageToolbar.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/QueryField/QueryField.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/SecretFormField/SecretFormField.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/SecretTextArea/SecretTextArea.story.tsx:5381": [
[0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"]
],
@ -4161,6 +4185,9 @@ exports[`no undocumented stories`] = {
"packages/grafana-ui/src/components/Segment/SegmentInput.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/Select/SelectPerf.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/Slider/RangeSlider.story.tsx:5381": [
[0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"]
],
@ -4170,9 +4197,21 @@ exports[`no undocumented stories`] = {
"packages/grafana-ui/src/components/StatsPicker/StatsPicker.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/TableInputCSV/TableInputCSV.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/ThemeDemos/BorderRadius.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/ThemeDemos/EmotionPerfTest.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/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/ThemeDemos/Typography.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"]
],
@ -4184,6 +4223,9 @@ exports[`no undocumented stories`] = {
],
"packages/grafana-ui/src/components/VizTooltip/SeriesTable.story.tsx:5381": [
[0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"]
],
"packages/grafana-ui/src/utils/useDelayedSwitch.story.tsx:5381": [
[0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"]
]
}`
};

@ -3,7 +3,7 @@
// NOTE: storybook must already be running (`yarn storybook`) for this test to work
describe('Verify storybook', () => {
it('Loads the button story correctly', () => {
cy.visit('?path=/story/buttons-button--basic');
cy.visit('?path=/story/inputs-button--basic');
getIframeBody().find('button:contains("Example button")').should('be.visible');
});
});

@ -176,6 +176,13 @@ module.exports = [
'react/react-in-jsx-scope': 'off',
},
},
{
name: 'grafana/story-rules',
files: ['packages/grafana-ui/src/**/*.story.tsx'],
rules: {
'@grafana/consistent-story-titles': 'error',
},
},
{
name: 'grafana/public-dashboards-overrides',
files: ['public/dashboards/scripted*.js'],

@ -113,3 +113,30 @@ const getStyles = (theme: GrafanaTheme2) => ({
### `theme-token-usage`
Used to find all instances of `theme` tokens being used in the codebase and emit the counts as metrics. Should **not** be used as an actual lint rule!
### `consistent-story-titles`
Enforce consistent Storybook titles in `.story.tsx` files.
Storybook titles should not contain more than one `/` for sections (resulting in maximum 2 parts), unless one of the sections is 'Deprecated'. This helps maintain a clean and organized Storybook structure.
#### Examples
```tsx
// Bad ❌
export default { title: 'Components/Forms/Button' };
// Good ✅
export default { title: 'Components/Button' };
// Good ✅ - Deprecated allows any number of sections
export default { title: 'Components/Deprecated/Forms/Button/Extra' };
// Good ✅ - Variable assignment pattern
const storyConfig = { title: 'Components/Button' };
export default storyConfig;
// Bad ❌ - Variable assignment with too many sections
const storyConfig = { title: 'Components/Forms/Button' };
export default storyConfig;
```

@ -3,6 +3,7 @@ const noBorderRadiusLiteral = require('./rules/no-border-radius-literal.cjs');
const noUnreducedMotion = require('./rules/no-unreduced-motion.cjs');
const themeTokenUsage = require('./rules/theme-token-usage.cjs');
const noRestrictedImgSrcs = require('./rules/no-restricted-img-srcs.cjs');
const consistentStoryTitles = require('./rules/consistent-story-titles.cjs');
module.exports = {
rules: {
@ -11,5 +12,6 @@ module.exports = {
'no-border-radius-literal': noBorderRadiusLiteral,
'theme-token-usage': themeTokenUsage,
'no-restricted-img-srcs': noRestrictedImgSrcs,
'consistent-story-titles': consistentStoryTitles,
},
};

@ -0,0 +1,106 @@
// @ts-check
const { ESLintUtils, AST_NODE_TYPES } = require('@typescript-eslint/utils');
const createRule = ESLintUtils.RuleCreator(
(name) => `https://github.com/grafana/grafana/blob/main/packages/grafana-eslint-rules/README.md#${name}`
);
/**
* @param {string} title
* @returns {boolean}
*/
const isValidStorybookTitle = (title) => {
if (typeof title !== 'string') {
return true; // Skip non-string titles
}
const sections = title.split('/');
// Allow up to 3 sections if one of them is 'Deprecated'
if (sections.some((section) => section.trim() === 'Deprecated')) {
return sections.length <= 3;
}
// Otherwise, limit to maximum 2 sections (1 slash)
return sections.length <= 2;
};
/**
* @param {import('@typescript-eslint/utils').TSESTree.ObjectExpression} objectNode
* @param {import('@typescript-eslint/utils/ts-eslint').RuleContext<'invalidTitle', []>} context
*/
const checkObjectForTitle = (objectNode, context) => {
const titleProperty = objectNode.properties.find(
(prop) =>
prop.type === AST_NODE_TYPES.Property && prop.key.type === AST_NODE_TYPES.Identifier && prop.key.name === 'title'
);
if (
titleProperty &&
titleProperty.type === AST_NODE_TYPES.Property &&
titleProperty.value.type === AST_NODE_TYPES.Literal
) {
const titleValue = titleProperty.value.value;
if (typeof titleValue === 'string' && !isValidStorybookTitle(titleValue)) {
context.report({
node: titleProperty.value,
messageId: 'invalidTitle',
data: {
title: titleValue,
},
});
}
}
};
const consistentStoryTitlesRule = createRule({
create(context) {
return {
ExportDefaultDeclaration(node) {
// Only check .story.tsx files
const filename = context.filename;
if (!filename || !filename.endsWith('.story.tsx')) {
return;
}
if (node.declaration.type === AST_NODE_TYPES.ObjectExpression) {
// Handle direct object export: export default { title: '...' }
checkObjectForTitle(node.declaration, context);
} else if (node.declaration.type === AST_NODE_TYPES.Identifier) {
// Handle variable reference export: export default storyConfig
const variableName = node.declaration.name;
const scope = context.sourceCode.getScope(node);
const variable = scope.set.get(variableName);
if (variable) {
// Find the variable declaration
const declaration = variable.defs.find((def) => def.type === 'Variable');
if (
declaration &&
declaration.node.init &&
declaration.node.init.type === AST_NODE_TYPES.ObjectExpression
) {
checkObjectForTitle(declaration.node.init, context);
}
}
}
},
};
},
name: 'consistent-story-titles',
meta: {
type: 'problem',
docs: {
description: 'Enforce consistent Storybook titles with maximum two sections (1 slash) unless one is "Deprecated"',
},
messages: {
invalidTitle:
'Storybook title "{{ title }}" has too many sections. Use maximum 2 sections (1 slash) unless one section is "Deprecated".',
},
schema: [],
},
defaultOptions: [],
});
module.exports = consistentStoryTitlesRule;

@ -0,0 +1,117 @@
import { RuleTester } from 'eslint';
import consistentStories from '../rules/consistent-story-titles.cjs';
RuleTester.setDefaultConfig({
languageOptions: {
ecmaVersion: 2018,
sourceType: 'module',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
});
const ruleTester = new RuleTester();
ruleTester.run('eslint consistent-stories', consistentStories, {
valid: [
{
name: 'simple title',
code: `export default { title: 'Button' };`,
filename: 'Button.story.tsx',
},
{
name: 'one section',
code: `export default { title: 'Components/Button' };`,
filename: 'Button.story.tsx',
},
{
name: 'deprecated can have 3 sections',
code: `export default { title: 'Components/Deprecated/Button' };`,
filename: 'Button.story.tsx',
},
{
name: 'not a story file',
code: `export default { title: 'Components/Forms/Button/Extra/Section' };`,
filename: 'Button.tsx',
},
{
name: 'non-string title',
code: `export default { title: 123 };`,
filename: 'Button.story.tsx',
},
{
name: 'no title property',
code: `export default { component: Button };`,
filename: 'Button.story.tsx',
},
{
name: 'variable assignment - simple title',
code: `
const storyConfig = { title: 'Button' };
export default storyConfig;`,
filename: 'Button.story.tsx',
},
{
name: 'variable assignment - one section',
code: `
const storyConfig = { title: 'Components/Button' };
export default storyConfig;`,
filename: 'Button.story.tsx',
},
{
name: 'variable assignment - with Deprecated',
code: `
const storyConfig = { title: 'Components/Deprecated/Button' };
export default storyConfig;`,
filename: 'Button.story.tsx',
},
],
invalid: [
{
name: 'too many sections without Deprecated',
code: `export default { title: 'Components/Forms/Button' };`,
filename: 'Button.story.tsx',
errors: [
{
messageId: 'invalidTitle',
},
],
},
{
name: 'too many sections without Deprecated',
code: `export default { title: 'Components/Forms/Button/Extra' };`,
filename: 'Button.story.tsx',
errors: [
{
messageId: 'invalidTitle',
},
],
},
{
name: 'with spaces around sections',
code: `export default { title: 'Components / Forms / Button' };`,
filename: 'Button.story.tsx',
errors: [
{
messageId: 'invalidTitle',
},
],
},
{
name: 'variable assignment - too many sections',
code: `
const storyConfig = { title: 'Components/Forms/Button' };
export default storyConfig;`,
filename: 'Button.story.tsx',
errors: [
{
messageId: 'invalidTitle',
},
],
},
],
});

@ -2,13 +2,7 @@ import path, { dirname, join } from 'node:path';
import type { StorybookConfig } from '@storybook/react-webpack5';
import { copyAssetsSync } from './copyAssets';
// Internal stories should only be visible during development
const coreComponentsGlobs: StorybookConfig['stories'] = [
'../src/Intro.mdx',
process.env.NODE_ENV === 'production'
? '../src/components/**/!(*.internal).story.tsx'
: '../src/components/**/*.story.tsx',
];
const coreComponentsGlobs: StorybookConfig['stories'] = ['../src/Intro.mdx', '../src/**/*.story.tsx'];
const alertingComponentsGlobs: StorybookConfig['stories'] = [
{

@ -71,20 +71,84 @@ const preview: Preview = {
// 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
// https://github.com/storybookjs/storybook/issues/22470
// Story sorting is weird - All stories are sorted as a single 1D list, but then grouped in the UI.
// Story titles are generally in the format of [Category]/[Component]/[Story]. However, some categories
// will have an additional `Deprecated` sub folder before the [Component]
//
// We want to have multi-level sorting where:
// - The top level category has an explicit order
// - Components are sorted alphabetically within their category
// - Except the Deprecated folder, which is sorted to the bottom
// - Stories per component use the default file sort order
storySort: (a, b) => {
// Skip sorting for stories with nosort tag
if (a.tags.includes('nosort') || b.tags.includes('nosort')) {
return 0;
const CATEGORY_ORDER = [
// Should all be lowercase
'docs overview',
'foundations',
'iconography',
'layout',
'forms',
'inputs',
'pickers',
'date time pickers',
'information',
'overlays',
'utilities',
'navigation',
'plugins',
'alerting',
'developers',
];
const aTitle = a.title.toLowerCase();
const bTitle = b.title.toLowerCase();
const [aCategory, aComponent] = aTitle.split('/');
const [bCategory, bComponent] = bTitle.split('/');
//
// Sort by category order first
const aCategoryIndex = CATEGORY_ORDER.indexOf(aCategory);
const bCategoryIndex = CATEGORY_ORDER.indexOf(bCategory);
if (aCategoryIndex === -1 || bCategoryIndex === -1) {
const category = aCategoryIndex === -1 ? aCategory : bCategory;
throw new Error(
`Category ${category} not found in CATEGORY_ORDER. Prefer reusing the existing categories, or add to CATEGORY_ORDER.`
);
}
if (aCategoryIndex !== bCategoryIndex) {
return aCategoryIndex - bCategoryIndex;
}
if (a.title.startsWith('Docs Overview')) {
if (b.title.startsWith('Docs Overview')) {
return 0;
}
//
// Sort 'Deprecated' subfolders to the bottom
if (aTitle.includes('deprecated') && !bTitle.includes('deprecated')) {
return 1;
} else if (bTitle.includes('deprecated') && !aTitle.includes('deprecated')) {
return -1;
}
//
// Sort Docs to the top
if (a.type === 'docs' && b.type !== 'docs') {
return -1;
} else if (b.title.startsWith('Docs Overview')) {
} else if (a.type !== 'docs' && b.type === 'docs') {
return 1;
}
return a.id === b.id ? 0 : a.id.localeCompare(b.id, undefined, { numeric: true });
//
// If sorting different components, sort alphabetically
if (aComponent !== bComponent) {
return aComponent.localeCompare(bComponent, undefined, { numeric: true });
}
// Otherwise, sort stories within componmments according to source order
return 0;
},
},
},

@ -10,7 +10,7 @@ import mdx from './Alert.mdx';
const severities: AlertVariant[] = ['error', 'warning', 'info', 'success'];
const meta: Meta = {
title: 'Overlays/Alert/InlineBanner',
title: 'Information/Alert',
component: Alert,
parameters: {
docs: {
@ -78,4 +78,15 @@ export const Examples: StoryFn<typeof Alert> = () => {
);
};
export const Toast: StoryFn<typeof Alert> = (args) => {
return <Alert {...args}>To use as a toast, set the elevated and onRemove props.</Alert>;
};
Toast.args = {
title: 'Toast',
severity: 'error',
onRemove: action('Remove button clicked'),
elevated: true,
};
export default meta;

@ -1,126 +0,0 @@
import { action } from '@storybook/addon-actions';
import { StoryFn, Meta } from '@storybook/react';
import { StoryExample } from '../../utils/storybook/StoryExample';
import { Stack } from '../Layout/Stack/Stack';
import { Alert, AlertVariant } from './Alert';
import mdx from './Alert.mdx';
const severities: AlertVariant[] = ['error', 'warning', 'info', 'success'];
const meta: Meta<typeof Alert> = {
title: 'Overlays/Alert/Toast',
component: Alert,
parameters: {
docs: {
page: mdx,
},
controls: { exclude: ['onRemove'] },
},
argTypes: {
severity: { control: { type: 'select', options: severities } },
},
args: {
title: 'Toast',
severity: 'error',
onRemove: action('Remove button clicked'),
},
};
export const Basic: StoryFn<typeof Alert> = (args) => {
return (
<Alert {...args} elevated>
Child content that includes some alert details, like maybe what actually happened.
</Alert>
);
};
export function Examples() {
return (
<Stack direction="column">
<StoryExample name="Severities">
<Stack direction="column">
{severities.map((severity) => (
<Alert
title={`Severity: ${severity}`}
severity={severity}
key={severity}
onRemove={action('Remove button clicked')}
elevated={true}
/>
))}
</Stack>
</StoryExample>
<StoryExample name="With huge payload">
<Alert title="Alert with huge payload" severity="error" elevated={true}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam metus urna, aliquam eu scelerisque non,
facilisis eget est. Morbi eleifend egestas massa id vulputate. Fusce dignissim magna lacus, ut molestie odio
feugiat sed. Cras fringilla justo sit amet turpis scelerisque, a volutpat purus iaculis. Nunc sagittis
molestie faucibus. Curabitur at neque luctus, pellentesque urna eget, posuere urna. Nunc malesuada elit in
ipsum dictum egestas. Praesent convallis mauris massa, porta mattis ex gravida ut. Proin consectetur ultrices
tortor sit amet efficitur. Suspendisse nec turpis dapibus mauris venenatis maximus quis eget orci. Ut semper
enim magna, ullamcorper elementum sapien pharetra vitae. Vivamus at nulla ut metus bibendum ornare et ut leo.
Proin ante turpis, ornare a malesuada et, rutrum nec lorem. Maecenas vestibulum orci vel nibh convallis
eleifend. Quisque vitae consectetur massa, vitae elementum mauris. Pellentesque sit amet ligula lorem. Fusce
sit amet lorem non augue rutrum varius. Donec sed imperdiet libero, eget venenatis elit. Fusce porttitor
dapibus urna. Duis fringilla ante vel tempor tincidunt. In euismod vestibulum odio sit amet iaculis. Donec vel
dapibus libero. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi lacinia commodo lectus. Aenean
in magna eget lectus luctus suscipit et vitae erat. Pellentesque quis ligula id lorem egestas sollicitudin sit
amet sed sem. Nullam et nibh a odio rhoncus efficitur sed nec est. Sed commodo lacus vitae sem congue,
accumsan dignissim metus iaculis. Praesent in dignissim nisl. Aliquam facilisis, sapien eget porttitor
ultrices, massa libero bibendum odio, at ornare diam arcu ac massa. Vestibulum egestas leo eget lorem congue
condimentum. Praesent egestas, neque id gravida vehicula, augue ex scelerisque lectus, finibus pellentesque
enim dolor vel ante. Cras convallis, sem at malesuada tincidunt, diam urna auctor leo, sed laoreet est ex in
libero. Ut condimentum ante eget ex gravida, id tempus metus ultricies. Pellentesque placerat, massa id
laoreet molestie, justo nisl varius metus, maximus vehicula erat libero vitae nulla. Mauris rhoncus ligula
vitae volutpat auctor. Suspendisse potenti. Quisque quis orci faucibus, ullamcorper dolor eget, mollis massa.
Etiam eu molestie ipsum. Sed laoreet diam metus, luctus maximus erat viverra quis. Ut eu felis dictum,
tincidunt erat sit amet, scelerisque neque. Orci varius natoque penatibus et magnis dis parturient montes,
nascetur ridiculus mus. Phasellus sit amet est tristique, fermentum massa ut, viverra metus. Interdum et
malesuada fames ac ante ipsum primis in faucibus. Nunc iaculis nunc elit, ut feugiat ipsum egestas eget.
Vestibulum pulvinar ligula mi, quis lacinia diam suscipit eget. Etiam consectetur vel nunc at hendrerit.
Pellentesque blandit eleifend aliquam. Etiam et malesuada purus, et bibendum sapien. Phasellus tincidunt
consequat eros consequat sodales. Vestibulum quis viverra neque. Integer sit amet lacinia nunc. Ut cursus,
elit id faucibus elementum, elit nunc dapibus tellus, non ornare nisi sapien et eros. Nunc sit amet suscipit
arcu. Nulla ut nunc tempor, auctor massa sed, consectetur orci. Pellentesque erat ante, placerat eget dictum
elementum, dapibus et ipsum. Nunc sit amet nulla gravida, finibus felis vel, tempus sem. In urna purus,
accumsan quis aliquam et, condimentum ac urna. Nullam volutpat ullamcorper sapien, quis ultricies purus
dignissim aliquam. Mauris quis enim ante. Etiam vulputate faucibus placerat. Ut pellentesque, purus vitae
euismod cursus, lacus enim vulputate sapien, in porttitor erat dui eu lectus. Duis eleifend, massa vel
vehicula gravida, magna urna rutrum ligula, vitae mollis ipsum neque id enim. Donec varius tristique nisi, et
vestibulum dolor efficitur eget. Cras mauris leo, bibendum eget pretium a, tincidunt faucibus massa.
Vestibulum hendrerit arcu magna, vel consequat est euismod nec. Vestibulum non lacus porttitor, congue tortor
ut, venenatis elit. Duis at lectus arcu. Nunc quis sapien eu ipsum rutrum accumsan. Orci varius natoque
penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus quis sapien luctus, volutpat nulla
eget, gravida nunc. Aenean placerat a felis quis imperdiet. Sed sapien tellus, ultrices non ipsum eget,
pretium rhoncus quam. Aliquam erat volutpat. Maecenas at interdum turpis, eu mattis ligula. Class aptent
taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In lobortis felis a leo
ultricies, venenatis mollis felis lobortis. Suspendisse placerat vel ante vel euismod. Aenean sit amet
ullamcorper mauris, id consectetur est. Ut ultricies enim non quam condimentum, et congue arcu commodo.
Praesent convallis eleifend turpis, vitae feugiat turpis imperdiet sit amet. Class aptent taciti sociosqu ad
litora torquent per conubia nostra, per inceptos himenaeos. Quisque vulputate porttitor mattis. Pellentesque
sed ullamcorper lectus. Suspendisse velit tortor, viverra eget facilisis condimentum, accumsan sit amet felis.
Cras lobortis mi fermentum ligula consectetur, vitae tincidunt mauris scelerisque. Aenean ac condimentum erat,
quis lacinia lacus. Ut magna nibh, tempor et ligula suscipit, placerat laoreet ipsum. In semper semper nisl.
Donec risus lorem, tempor sed sollicitudin vitae, fringilla et mi. Vivamus pulvinar quam nisl, et tincidunt
justo tempus quis. Duis semper magna nunc, vitae faucibus lectus facilisis sed. Phasellus consequat arcu vel
interdum fermentum. In condimentum euismod neque, sed aliquet mauris posuere nec. Etiam metus eros,
pellentesque eget scelerisque id, porttitor at ligula. Curabitur eget nibh maximus enim lobortis sodales.
Etiam vulputate ligula lobortis vestibulum pulvinar. Curabitur eros justo, accumsan sed elit ac, mattis
lacinia nisi. Suspendisse ullamcorper lectus sit amet tellus condimentum porttitor. Duis cursus, neque et
aliquam congue, odio lectus porta elit, id lacinia dolor justo non leo. Aliquam vehicula at tellus ullamcorper
tincidunt. Phasellus neque nibh, convallis sit amet arcu sit amet, convallis egestas tortor. Etiam sit amet
vehicula quam. Praesent id consequat lacus, ac facilisis quam. Integer tristique lorem eros, id consequat
lorem lobortis vitae. Aliquam luctus purus eget sem molestie iaculis. Duis nisl risus, sodales sit amet nunc
vitae, volutpat cursus augue. Pellentesque congue massa eu metus pellentesque consectetur at vel neque. Donec
bibendum hendrerit erat, vitae dictum enim lobortis a. Quisque ac dapibus tellus, sit amet facilisis orci.
Cras pretium tortor non condimentum semper. Phasellus mollis condimentum blandit. Pellentesque at arcu risus.
Vivamus sit amet dui semper, suscipit est nec, elementum arcu. Praesent ante turpis, convallis ac leo eget,
</Alert>
</StoryExample>
</Stack>
);
}
export default meta;

@ -12,7 +12,7 @@ import { AutoSaveField } from './AutoSaveField';
import mdx from './AutoSaveField.mdx';
const meta: Meta = {
title: 'Forms/AutoSaveField',
title: 'Inputs/AutoSaveField',
component: AutoSaveField,
parameters: {
docs: {

@ -6,7 +6,7 @@ import { Badge } from './Badge';
import mdx from './Badge.mdx';
const meta: Meta<typeof Badge> = {
title: 'Data Display/Badge',
title: 'Information/Badge',
component: Badge,
parameters: {
docs: { page: mdx },

@ -9,7 +9,7 @@ import { BarGauge, Props } from './BarGauge';
import mdx from './BarGauge.mdx';
const meta: Meta = {
title: 'Visualizations/BarGauge',
title: 'Plugins/BarGauge',
component: BarGauge,
parameters: {
docs: {

@ -15,7 +15,7 @@ import {
import mdx from './BigValue.mdx';
const meta: Meta = {
title: 'Visualizations/BigValue',
title: 'Plugins/BigValue',
component: BigValue,
parameters: {
docs: {

@ -12,7 +12,7 @@ import { ButtonGroup } from './ButtonGroup';
const sizes: ComponentSize[] = ['lg', 'md', 'sm'];
export default {
title: 'Buttons/Button',
title: 'Inputs/Button',
component: Button,
parameters: {
docs: {

@ -3,7 +3,7 @@ import { StoryFn, Meta } from '@storybook/react';
import { ButtonCascader } from './ButtonCascader';
const meta: Meta<typeof ButtonCascader> = {
title: 'Forms/Cascader/ButtonCascader',
title: 'Inputs/ButtonCascader',
component: ButtonCascader,
parameters: {
controls: {

@ -6,7 +6,7 @@ import { Button } from '../Button/Button';
import { CallToActionCard, CallToActionCardProps } from './CallToActionCard';
const meta: Meta = {
title: 'Layout/CallToActionCard',
title: 'Information/Deprecated/CallToActionCard',
component: CallToActionCard,
parameters: {
controls: {

@ -11,6 +11,7 @@ export interface CallToActionCardProps {
className?: string;
}
/** @deprecated Use <EmptyState variant="call-to-action" /> instead */
export const CallToActionCard = ({ message, callToActionElement, footer, className }: CallToActionCardProps) => {
const css = useStyles2(getStyles);

@ -9,10 +9,9 @@ import { Card } from './Card';
const logo = 'https://grafana.com/static/assets/img/apple-touch-icon.png';
const meta: Meta<typeof Card> = {
title: 'General/Card',
title: 'Layout/Card',
component: Card,
// nosort is a custom tag used so the stories shown in docs keep the order they are defined in the file
tags: ['autodocs', 'nosort'],
tags: ['autodocs'],
parameters: {
controls: {
exclude: ['onClick', 'href', 'heading', 'description', 'className', 'noMargin'],

@ -15,7 +15,7 @@ const sampleImages = [
];
const meta: Meta<typeof Carousel> = {
title: 'Data Display/Carousel',
title: 'Overlays/Carousel',
component: Carousel,
parameters: {
docs: { page: mdx },

@ -31,7 +31,7 @@ const options = [
];
const meta: Meta<typeof Cascader> = {
title: 'Forms/Cascader',
title: 'Inputs/Cascader',
component: Cascader,
parameters: {
docs: {

@ -5,7 +5,7 @@ import { ClickOutsideWrapper } from './ClickOutsideWrapper';
import mdx from './ClickOutsideWrapper.mdx';
const meta: Meta<typeof ClickOutsideWrapper> = {
title: 'Layout/ClickOutsideWrapper',
title: 'Utilities/ClickOutsideWrapper',
component: ClickOutsideWrapper,
parameters: {
docs: {

@ -7,7 +7,7 @@ import { ClipboardButton as ClipboardButtonImpl, Props } from './ClipboardButton
import mdx from './ClipboardButton.mdx';
const meta: Meta = {
title: 'Buttons/ClipboardButton',
title: 'Inputs/ClipboardButton',
component: ClipboardButtonImpl,
parameters: {
docs: {

@ -5,12 +5,11 @@ import { Meta, StoryFn } from '@storybook/react';
import { useStyles2 } from '../../themes/ThemeContext';
import { clearButtonStyles } from '../Button/Button';
import { ColorPicker, SeriesColorPicker } from './ColorPicker';
import { ColorPicker } from './ColorPicker';
import mdx from './ColorPicker.mdx';
import { ColorPickerInput } from './ColorPickerInput';
const meta: Meta<typeof ColorPicker> = {
title: 'Pickers and Editors/ColorPicker',
title: 'Pickers/ColorPicker',
component: ColorPicker,
parameters: {
docs: {
@ -43,24 +42,6 @@ export const Basic: StoryFn<typeof ColorPicker> = ({ color, enableNamedColors })
);
};
export const SeriesPicker: StoryFn<typeof SeriesColorPicker> = ({ color, enableNamedColors }) => {
const [, updateArgs] = useArgs();
return (
<div style={{ display: 'flex', alignItems: 'flex-start' }}>
<SeriesColorPicker
enableNamedColors={enableNamedColors}
yaxis={1}
onToggleAxis={() => {}}
color={color}
onChange={(color) => {
action('Color changed')(color);
updateArgs({ color });
}}
/>
</div>
);
};
export const CustomTrigger: StoryFn<typeof ColorPicker> = ({ color, enableNamedColors }) => {
const [, updateArgs] = useArgs();
const clearButton = useStyles2(clearButtonStyles);
@ -89,19 +70,4 @@ export const CustomTrigger: StoryFn<typeof ColorPicker> = ({ color, enableNamedC
);
};
export const Input: StoryFn<typeof ColorPickerInput> = ({ color }) => {
const [, updateArgs] = useArgs();
return (
<div style={{ minHeight: '100dvh', display: 'grid', placeContent: 'center' }}>
<ColorPickerInput
value={color}
onChange={(color) => {
action('Color changed')(color);
updateArgs({ color });
}}
/>
</div>
);
};
export default meta;

@ -0,0 +1,32 @@
import { action } from '@storybook/addon-actions';
import { useArgs } from '@storybook/preview-api';
import { Meta, StoryFn } from '@storybook/react';
import { ColorPickerInput } from './ColorPickerInput';
const meta: Meta<typeof ColorPickerInput> = {
title: 'Pickers/ColorPickerInput',
component: ColorPickerInput,
parameters: {
controls: {
exclude: ['onChange', 'onColorChange'],
},
},
};
export const Basic: StoryFn<typeof ColorPickerInput> = ({ color }) => {
const [, updateArgs] = useArgs();
return (
<div style={{ minHeight: '100dvh', display: 'grid', placeContent: 'center' }}>
<ColorPickerInput
value={color}
onChange={(color) => {
action('Color changed')(color);
updateArgs({ color });
}}
/>
</div>
);
};
export default meta;

@ -1,52 +0,0 @@
import { Meta } from '@storybook/react';
import { useState } from 'react';
import { useTheme2 } from '../../themes/ThemeContext';
import mdx from './ColorPicker.mdx';
import { ColorPickerPopover } from './ColorPickerPopover';
import { SeriesColorPickerPopover } from './SeriesColorPickerPopover';
const meta: Meta<typeof ColorPickerPopover> = {
title: 'Pickers and Editors/ColorPicker/Popovers',
component: ColorPickerPopover,
parameters: {
docs: {
page: mdx,
},
},
};
export const Basic = () => {
return (
<div style={{ position: 'absolute' }}>
<ColorPickerPopover
color="#BC67E6"
onChange={(color: string) => {
console.log(color);
}}
/>
</div>
);
};
export const SeriesColorPickerPopoverExample = () => {
const theme = useTheme2();
const [yAxis, setYAxis] = useState(0);
return (
<div style={{ position: 'absolute' }}>
<SeriesColorPickerPopover
theme={theme}
yaxis={yAxis}
onToggleAxis={() => (yAxis ? setYAxis(0) : setYAxis(2))}
color="#BC67E6"
onChange={(color: string) => {
console.log(color);
}}
/>
</div>
);
};
export default meta;

@ -1,43 +0,0 @@
import { action } from '@storybook/addon-actions';
import { useArgs } from '@storybook/preview-api';
import { Meta, StoryFn } from '@storybook/react';
import { useState } from 'react';
import mdx from './ColorPicker.mdx';
import { NamedColorsPalette } from './NamedColorsPalette';
import SpectrumPalette from './SpectrumPalette';
const meta: Meta = {
title: 'Pickers and Editors/ColorPicker/Palettes',
parameters: {
docs: {
page: mdx,
},
controls: {
exclude: ['theme', 'color'],
},
},
args: {
color: 'green',
},
};
export const NamedColors: StoryFn<typeof NamedColorsPalette> = ({ color }) => {
const [colorVal, setColor] = useState(color);
return <NamedColorsPalette color={colorVal} onChange={setColor} />;
};
export const Spectrum: StoryFn<typeof SpectrumPalette> = ({ color }) => {
const [, updateArgs] = useArgs();
return (
<SpectrumPalette
color={color}
onChange={(color: string) => {
action('Color changed')(color);
updateArgs({ color });
}}
/>
);
};
export default meta;

@ -0,0 +1,39 @@
import { action } from '@storybook/addon-actions';
import { useArgs } from '@storybook/preview-api';
import { Meta, StoryFn } from '@storybook/react';
import { SeriesColorPicker } from './ColorPicker';
const meta: Meta<typeof SeriesColorPicker> = {
title: 'Pickers/SeriesColorPicker',
component: SeriesColorPicker,
parameters: {
controls: {
exclude: ['onChange', 'onColorChange'],
},
},
args: {
enableNamedColors: false,
color: '#00ff00',
},
};
export const Basic: StoryFn<typeof SeriesColorPicker> = ({ color, enableNamedColors }) => {
const [, updateArgs] = useArgs();
return (
<div style={{ display: 'flex', alignItems: 'flex-start' }}>
<SeriesColorPicker
enableNamedColors={enableNamedColors}
yaxis={1}
onToggleAxis={() => {}}
color={color}
onChange={(color) => {
action('Color changed')(color);
updateArgs({ color });
}}
/>
</div>
);
};
export default meta;

@ -16,7 +16,7 @@ type PropsAndCustomArgs<T extends string | number = string> = ComboboxProps<T> &
type Story<T extends string | number = string> = StoryObj<PropsAndCustomArgs<T>>;
const meta: Meta<PropsAndCustomArgs> = {
title: 'Forms/Combobox',
title: 'Inputs/Combobox',
component: Combobox,
parameters: {
docs: {

@ -11,7 +11,7 @@ import { generateOptions, fakeSearchAPI, generateGroupingOptions } from './story
import { ComboboxOption } from './types';
const meta: Meta<typeof MultiCombobox> = {
title: 'Forms/MultiCombobox',
title: 'Inputs/MultiCombobox',
component: MultiCombobox,
parameters: {
docs: {

@ -8,7 +8,7 @@ import mdx from './ConfirmButton.mdx';
import { DeleteButton } from './DeleteButton';
const meta: Meta = {
title: 'Buttons/ConfirmButton',
title: 'Inputs/ConfirmButton',
component: ConfirmButton,
// SB7 has broken subcomponent types due to dropping support for the feature
// https://github.com/storybookjs/storybook/issues/20782

@ -6,6 +6,8 @@ import { WithContextMenu } from './WithContextMenu';
A menu displaying additional options when it's not possible to show them at all times due to a space constraint.
`ContextMenu` wraps `Menu` to supply options as a list, and display at absolute coordinates.
### Usage
There are controlled and uncontrolled versions of the component available. With the controlled component (`ContextMenu`) the open/close logic needs to be handled separately. Uncontrolled component (`WithContextMenu`) handles this logic internally.

@ -10,7 +10,7 @@ import { renderMenuItems } from './ContextMenuStoryHelper';
import { WithContextMenu, WithContextMenuProps } from './WithContextMenu';
const meta: Meta<typeof ContextMenu> = {
title: 'General/ContextMenu',
title: 'Overlays/ContextMenu',
component: ContextMenu,
parameters: {
docs: {

@ -36,7 +36,7 @@ const settingsMock: HttpSettingsProps['dataSourceConfig'] = {
};
const meta: Meta<typeof DataSourceHttpSettings> = {
title: 'Data Source/DataSourceHttpSettings',
title: 'Plugins/DataSourceHttpSettings',
component: DataSourceHttpSettings,
parameters: {
controls: {

@ -7,7 +7,7 @@ import { DatePicker, DatePickerProps } from './DatePicker';
import mdx from './DatePicker.mdx';
const meta: Meta<typeof DatePicker> = {
title: 'Pickers and Editors/TimePickers/Pickers And Editors/DatePicker',
title: 'Date time pickers/DatePicker',
component: DatePicker,
argTypes: {
minDate: { control: 'date' },

@ -13,7 +13,7 @@ const minimumDate = new Date();
minimumDate.setMonth(minimumDate.getMonth() - 1);
const meta: Meta<typeof DatePickerWithInput> = {
title: 'Pickers and Editors/TimePickers/DatePickerWithInput',
title: 'Date time pickers/DatePickerWithInput',
component: DatePickerWithInput,
parameters: {
docs: {

@ -15,7 +15,7 @@ const minimumDate = new Date();
minimumDate.setDate(minimumDate.getDate() - 7);
const meta: Meta<typeof DateTimePicker> = {
title: 'Pickers and Editors/TimePickers/DateTimePicker',
title: 'Date time pickers/DateTimePicker',
component: DateTimePicker,
argTypes: {
date: {

@ -5,7 +5,7 @@ import { Meta, StoryFn } from '@storybook/react';
import { RelativeTimeRangePicker } from './RelativeTimeRangePicker';
const meta: Meta<typeof RelativeTimeRangePicker> = {
title: 'Pickers and Editors/TimePickers/RelativeTimeRangePicker',
title: 'Date time pickers/RelativeTimeRangePicker',
component: RelativeTimeRangePicker,
parameters: {
controls: {

@ -7,7 +7,7 @@ import { dateTime } from '@grafana/data';
import { TimeOfDayPicker } from './TimeOfDayPicker';
const meta: Meta<typeof TimeOfDayPicker> = {
title: 'Pickers and Editors/TimePickers/TimeOfDayPicker',
title: 'Date time pickers/TimeOfDayPicker',
component: TimeOfDayPicker,
parameters: {
controls: {

@ -30,7 +30,7 @@ const nullRange = {
};
const meta: Meta<typeof TimeRangeInput> = {
title: 'Pickers and Editors/TimePickers/TimeRangeInput',
title: 'Date time pickers/TimeRangeInput',
component: TimeRangeInput,
parameters: {
controls: {

@ -10,7 +10,7 @@ const to = dateTime();
const from = to.subtract(6, 'h');
const meta: Meta<typeof TimeRangePicker> = {
title: 'Pickers and Editors/TimePickers/TimeRangePicker',
title: 'Date time pickers/TimeRangePicker',
component: TimeRangePicker,
args: {
value: {

@ -5,7 +5,7 @@ import { Meta, StoryFn } from '@storybook/react';
import { TimeZonePicker } from './TimeZonePicker';
const meta: Meta<typeof TimeZonePicker> = {
title: 'Pickers and Editors/TimePickers/TimeZonePicker',
title: 'Date time pickers/TimeZonePicker',
component: TimeZonePicker,
parameters: {
controls: {

@ -5,7 +5,7 @@ import { Meta, StoryFn } from '@storybook/react';
import { WeekStartPicker } from './WeekStartPicker';
const meta: Meta<typeof WeekStartPicker> = {
title: 'Pickers and Editors/TimePickers/WeekStartPicker',
title: 'Date time pickers/WeekStartPicker',
component: WeekStartPicker,
parameters: {
controls: {

@ -4,7 +4,7 @@ import { Divider } from './Divider';
import mdx from './Divider.mdx';
const meta: Meta<typeof Divider> = {
title: 'General/Divider',
title: 'Layout/Divider',
component: Divider,
parameters: {
docs: {

@ -5,7 +5,7 @@ import { Meta, StoryFn } from '@storybook/react';
import { ButtonSelect } from './ButtonSelect';
const meta: Meta<typeof ButtonSelect> = {
title: 'Forms/Select/ButtonSelect',
title: 'Inputs/Deprecated/ButtonSelect',
component: ButtonSelect,
parameters: {
controls: {

@ -33,8 +33,7 @@ export interface Props<T> extends HTMLAttributes<HTMLButtonElement> {
}
/**
* @internal
* A temporary component until we have a proper dropdown component
* @deprecated Use Combobox or Dropdown instead
*/
const ButtonSelectComponent = <T,>(props: Props<T>) => {
const { className, options, value, onChange, narrow, variant, ...restProps } = props;

@ -4,7 +4,7 @@ import { EmptySearchResult } from './EmptySearchResult';
import mdx from './EmptySearchResult.mdx';
const meta: Meta<typeof EmptySearchResult> = {
title: 'Visualizations/EmptySearchResult',
title: 'Information/Deprecated/EmptySearchResult',
component: EmptySearchResult,
parameters: {
docs: {

@ -8,6 +8,7 @@ export interface Props {
children: JSX.Element | string;
}
/** @deprecated Use <EmptyState variant="not-found" /> instead */
const EmptySearchResult = ({ children }: Props) => {
const styles = useStyles2(getStyles);
return <div className={styles.container}>{children}</div>;

@ -6,7 +6,7 @@ import { EmptyState } from './EmptyState';
import mdx from './EmptyState.mdx';
const meta: Meta<typeof EmptyState> = {
title: 'General/EmptyState',
title: 'Information/EmptyState',
component: EmptyState,
parameters: {
docs: {

@ -9,7 +9,7 @@ import mdx from './ErrorBoundary.mdx';
import { ErrorWithStack } from './ErrorWithStack';
const meta: Meta<typeof ErrorBoundary> = {
title: 'General/ErrorBoundary',
title: 'Utilities/ErrorBoundary',
component: ErrorBoundary,
parameters: {
docs: {

@ -6,7 +6,7 @@ import { FeatureBadge } from './FeatureBadge';
import mdx from './FeatureBadge.mdx';
const meta: Meta<typeof FeatureBadge> = {
title: 'Data Display/FeatureBadge',
title: 'Information/FeatureBadge',
component: FeatureBadge,
parameters: {
docs: { page: mdx },

@ -4,7 +4,7 @@ import { FileDropzone } from './FileDropzone';
import mdx from './FileDropzone.mdx';
const meta: Meta<typeof FileDropzone> = {
title: 'Forms/FileDropzone',
title: 'Inputs/FileDropzone',
component: FileDropzone,
parameters: {
docs: {

@ -4,7 +4,7 @@ import { FileListItem as FileListItemComponent, FileListItemProps } from './File
import mdx from './FileListItem.mdx';
const meta: Meta = {
title: 'Forms/FileListItem',
title: 'Inputs/FileListItem',
component: FileListItemComponent,
parameters: {
docs: {

@ -4,7 +4,7 @@ import { FileUpload } from './FileUpload';
import mdx from './FileUpload.mdx';
const meta: Meta<typeof FileUpload> = {
title: 'Forms/FileUpload',
title: 'Inputs/FileUpload',
component: FileUpload,
parameters: {
docs: {

@ -8,7 +8,7 @@ import { FilterPill } from './FilterPill';
import mdx from './FilterPill.mdx';
const meta: Meta<typeof FilterPill> = {
title: 'General/FilterPill',
title: 'Inputs/FilterPill',
component: FilterPill,
argTypes: {
icon: { control: { type: 'select', options: getAvailableIcons() } },

@ -3,7 +3,7 @@ import { Meta, StoryFn } from '@storybook/react';
import { FormField } from './FormField';
const meta: Meta<typeof FormField> = {
title: 'Forms/Legacy/FormField',
title: 'Forms/Deprecated/FormField',
component: FormField,
parameters: {
controls: {

@ -4,7 +4,7 @@ import { FormattedValueDisplay } from './FormattedValueDisplay';
import mdx from './FormattedValueDisplay.mdx';
const meta: Meta<typeof FormattedValueDisplay> = {
title: 'Visualizations/FormattedValueDisplay',
title: 'Plugins/FormattedValueDisplay',
component: FormattedValueDisplay,
parameters: {
docs: {

@ -9,7 +9,7 @@ import mdx from './Checkbox.mdx';
import { Field } from './Field';
const meta: Meta<typeof Checkbox> = {
title: 'Forms/Checkbox',
title: 'Inputs/Checkbox',
component: Checkbox,
parameters: {
docs: {

@ -1,58 +0,0 @@
import { Meta, StoryFn } from '@storybook/react';
import { zip, fromPairs } from 'lodash';
import { useState } from 'react';
import { EventsWithValidation } from '../../../../utils/validate';
import { Input } from './Input';
const meta: Meta = {
title: 'Forms/Legacy/Input',
component: Input,
parameters: {
controls: {
exclude: ['inputRef', 'onBlur', 'onFocus', 'onChange'],
},
},
argTypes: {
validationEvents: {
control: {
type: 'select',
options: fromPairs(zip(Object.keys(EventsWithValidation), Object.values(EventsWithValidation))),
},
},
validation: { name: 'Validation regex (will do a partial match if you do not anchor it)' },
},
};
const Wrapper: StoryFn = (args) => {
const [value, setValue] = useState('');
const validations = {
[args.validationEvents]: [
{
rule: (value: string) => {
return !!value.match(args.validation);
},
errorMessage: args.validationErrorMessage,
},
],
};
return (
<Input
value={value}
onChange={(e) => setValue(e.currentTarget.value)}
validationEvents={validations}
hideErrorMessage={args.hideErrorMessage}
/>
);
};
export const Basic = Wrapper.bind({});
Basic.args = {
validation: '',
validationErrorMessage: 'Input not valid',
validationEvents: EventsWithValidation.onBlur,
hideErrorMessage: false,
};
export default meta;

@ -1,113 +0,0 @@
import { action } from '@storybook/addon-actions';
import { useArgs } from '@storybook/preview-api';
import { Meta, StoryFn } from '@storybook/react';
import { useCallback } from 'react';
import { SelectableValue } from '@grafana/data';
import { Select, AsyncSelect as AsyncSelectComponent } from './Select';
const meta: Meta<typeof Select> = {
title: 'Forms/Legacy/Select',
component: Select,
parameters: {
controls: {
exclude: [
'className',
'menuPlacement',
'menuPosition',
'maxMenuHeight',
'minMenuHeight',
'maxVisibleValues',
'prefix',
'renderControl',
'value',
'tooltipContent',
'components',
'inputValue',
'id',
'inputId',
'defaultValue',
'aria-label',
'noOptionsMessage',
'onChange',
'onBlur',
'onKeyDown',
'filterOption',
'formatCreateLabel',
'getOptionLabel',
'getOptionValue',
'onCloseMenu',
'onCreateOption',
'onInputChange',
'onOpenMenu',
'isOptionDisabled',
],
},
},
argTypes: {
width: { control: { type: 'range', min: 5, max: 30 } },
},
};
const initialValue: SelectableValue<string> = { label: 'A label', value: 'A value' };
const options = [
initialValue,
{ label: 'Another label', value: 'Another value 1' },
{ label: 'Another label', value: 'Another value 2' },
{ label: 'Another label', value: 'Another value 3' },
{ label: 'Another label', value: 'Another value 4' },
{ label: 'Another label', value: 'Another value 5' },
{ label: 'Another label', value: 'Another value ' },
];
export const Basic: StoryFn<typeof Select> = (args) => {
const [, updateArgs] = useArgs();
return (
<Select
{...args}
onChange={(value) => {
action('onChange fired')(value);
updateArgs({ value });
}}
/>
);
};
Basic.args = {
placeholder: 'Choose...',
options: options,
width: 20,
};
export const AsyncSelect: StoryFn<typeof AsyncSelectComponent> = (args) => {
const [, updateArgs] = useArgs();
const loadAsyncOptions = useCallback(
(inputValue: string) => {
return new Promise<Array<SelectableValue<string>>>((resolve) => {
setTimeout(() => {
updateArgs({ isLoading: false });
resolve(options.filter((option) => option.label && option.label.includes(inputValue)));
}, 1000);
});
},
[updateArgs]
);
return (
<AsyncSelectComponent
{...args}
loadOptions={loadAsyncOptions}
onChange={(value) => {
action('onChange')(value);
updateArgs({ value });
}}
/>
);
};
AsyncSelect.args = {
isLoading: true,
defaultOptions: true,
width: 20,
};
export default meta;

@ -1,27 +0,0 @@
import { Meta, StoryFn } from '@storybook/react';
import { useState } from 'react';
import { Switch } from './Switch';
const meta: Meta<typeof Switch> = {
title: 'Forms/Legacy/Switch',
component: Switch,
parameters: {
controls: {
exclude: ['className', 'labelClass', 'switchClass', 'onChange'],
},
},
};
const SwitchWrapper: StoryFn<typeof Switch> = ({ label, ...args }) => {
const [checked, setChecked] = useState(false);
return <Switch {...args} label={label} checked={checked} onChange={() => setChecked(!checked)} />;
};
export const Basic = SwitchWrapper.bind({});
Basic.args = {
label: 'Label',
tooltip: '',
};
export default meta;

@ -5,7 +5,7 @@ import { RadioButtonGroup } from './RadioButtonGroup';
import mdx from './RadioButtonGroup.mdx';
const meta: Meta = {
title: 'Forms/RadioButtonGroup',
title: 'Inputs/RadioButtonGroup',
component: RadioButtonGroup,
parameters: {
docs: {

@ -17,7 +17,7 @@ const defaultOptions: Array<SelectableValue<string>> = [
];
const meta: Meta<typeof RadioButtonList> = {
title: 'Forms/RadioButtonList',
title: 'Inputs/RadioButtonList',
component: RadioButtonList,
parameters: {
controls: {

@ -13,7 +13,7 @@ import { Icon } from './Icon';
import mdx from './Icon.mdx';
const meta: Meta<typeof Icon> = {
title: 'Docs overview/Icon',
title: 'Iconography/Icon',
component: Icon,
parameters: {
options: {

@ -16,7 +16,7 @@ const defaultExcludes = ['ariaLabel', 'aria-label'];
const additionalExcludes = ['size', 'name', 'variant', 'iconType'];
const meta: Meta<typeof IconButton> = {
title: 'Buttons/IconButton',
title: 'Inputs/IconButton',
component: IconButton,
parameters: {
docs: {

@ -9,7 +9,7 @@ import { InfoBox } from './InfoBox';
import mdx from './InfoBox.mdx';
const meta: Meta = {
title: 'Layout/InfoBox',
title: 'Information/Deprecated/InfoBox',
component: InfoBox,
decorators: [],
parameters: {

@ -3,7 +3,7 @@ import { Meta, StoryFn } from '@storybook/react';
import { InfoTooltip } from './InfoTooltip';
const meta: Meta<typeof InfoTooltip> = {
title: 'Overlays/TooltipInternal',
title: 'Overlays/Deprecated/InfoTooltip',
component: InfoTooltip,
};

@ -6,6 +6,7 @@ interface InfoTooltipProps extends Omit<TooltipProps, 'children' | 'content'> {
children: PopoverContent;
}
/** @deprecated Use <IconButton name="info-circle" tooltip={children} /> instead */
export const InfoTooltip = ({ children, ...restProps }: InfoTooltipProps) => {
return <IconButton name="info-circle" tooltip={children} {...restProps} />;
};

@ -8,7 +8,7 @@ import { InlineToast as InlineToastImpl, InlineToastProps } from './InlineToast'
import mdx from './InlineToast.mdx';
const story: Meta = {
title: 'InlineToast',
title: 'Information/InlineToast',
component: InlineToastImpl,
parameters: {
docs: {
@ -16,7 +16,6 @@ const story: Meta = {
},
},
argTypes: {
// foo is the property we want to remove from the UI
referenceElement: {
table: {
disable: true,

@ -18,7 +18,7 @@ const prefixSuffixOpts = {
};
const meta: Meta = {
title: 'Forms/Input/AutoSizeInput',
title: 'Inputs/AutoSizeInput',
component: AutoSizeInput,
parameters: {
docs: {

@ -22,7 +22,7 @@ const prefixSuffixOpts = {
};
const meta: Meta = {
title: 'Forms/Input',
title: 'Inputs/Input',
component: Input,
parameters: {
docs: {

@ -103,7 +103,7 @@ const pageableData: CarData[] = [
];
const meta: Meta<typeof InteractiveTable<CarData>> = {
title: 'Experimental/InteractiveTable',
title: 'Layout/InteractiveTable',
component: InteractiveTable,
parameters: {
docs: {

@ -23,7 +23,7 @@ const borderRadiusOptions: BorderRadius[] = ['default', 'pill', 'circle'];
const boxShadowOptions: BoxShadow[] = ['z1', 'z2', 'z3'];
const meta: Meta<typeof Box> = {
title: 'General/Layout/Box',
title: 'Layout/Box',
component: Box,
parameters: {
docs: {

@ -11,7 +11,7 @@ const dimensions = Array.from({ length: 9 }).map(() => ({
}));
const meta: Meta<typeof Grid> = {
title: 'General/Layout/Grid',
title: 'Layout/Grid',
component: Grid,
parameters: {
docs: {

@ -7,13 +7,9 @@ import { HorizontalGroup, Layout, LayoutProps, VerticalGroup } from './Layout';
import mdx from './Layout.mdx';
const meta: Meta = {
title: 'Layout/Groups',
title: 'Layout/Deprecated/Groups',
component: Layout,
decorators: [withStoryContainer],
// SB7 has broken subcomponent types due to dropping support for the feature
// https://github.com/storybookjs/storybook/issues/20782
// @ts-ignore
subcomponents: { HorizontalGroup, VerticalGroup },
parameters: {
docs: {
page: mdx,

@ -7,7 +7,7 @@ import { Space } from './Space';
import mdx from './Space.mdx';
const meta: Meta<typeof Space> = {
title: 'General/Layout/Space',
title: 'Layout/Space',
component: Space,
parameters: {
docs: {

@ -9,6 +9,16 @@ import { JustifyContent, Wrap, Direction } from '../types';
import { Stack } from './Stack';
import mdx from './Stack.mdx';
const meta: Meta<typeof Stack> = {
title: 'Layout/Stack',
component: Stack,
parameters: {
docs: {
page: mdx,
},
},
};
const Item = ({ color, text, height }: { color: string; text?: string | number; height?: string }) => {
return (
<div
@ -26,16 +36,6 @@ const Item = ({ color, text, height }: { color: string; text?: string | number;
);
};
const meta: Meta<typeof Stack> = {
title: 'General/Layout/Stack',
component: Stack,
parameters: {
docs: {
page: mdx,
},
},
};
export const Basic: StoryFn<typeof Stack> = (args) => {
const theme = useTheme2();
return (

@ -8,7 +8,7 @@ import { TextLink } from './TextLink';
import mdx from './TextLink.mdx';
const meta: Meta = {
title: 'General/TextLink',
title: 'Foundations/TextLink',
component: TextLink,
parameters: {
docs: {

@ -2,6 +2,7 @@ import { PureComponent } from 'react';
import { ListProps, AbstractList } from './AbstractList';
/** @deprecated Use ul/li/arr.map directly instead */
export class InlineList<T> extends PureComponent<ListProps<T>> {
render() {
return <AbstractList inline {...this.props} />;

@ -6,7 +6,7 @@ import { InlineList } from './InlineList';
import { List } from './List';
const meta: Meta = {
title: 'Layout/List',
title: 'Layout/Deprecated/List',
component: List,
parameters: {
controls: {

@ -2,6 +2,7 @@ import { PureComponent } from 'react';
import { ListProps, AbstractList } from './AbstractList';
/** @deprecated Use ul/li/arr.map directly instead */
export class List<T> extends PureComponent<ListProps<T>> {
render() {
return <AbstractList {...this.props} />;

@ -10,7 +10,7 @@ import { LoadingBar, LoadingBarProps } from './LoadingBar';
import mdx from './LoadingBar.mdx';
const meta: Meta<typeof LoadingBar> = {
title: 'General/LoadingBar',
title: 'Information/LoadingBar',
component: LoadingBar,
parameters: {
controls: {},

@ -4,7 +4,7 @@ import { LoadingPlaceholder, LoadingPlaceholderProps } from './LoadingPlaceholde
import mdx from './LoadingPlaceholder.mdx';
const meta: Meta<typeof LoadingPlaceholder> = {
title: 'General/LoadingPlaceholder',
title: 'Information/LoadingPlaceholder',
component: LoadingPlaceholder,
parameters: {
docs: {

@ -11,7 +11,7 @@ A simple menu component.
### When to use
When you need to display a list of actions or navigation options in a dropdown.
When you need to display a list of actions or navigation options, often used in combination with Dropdown.
### Usage

@ -8,7 +8,7 @@ import { Menu } from './Menu';
import mdx from './Menu.mdx';
const meta: Meta<typeof Menu> = {
title: 'General/Menu',
title: 'Overlays/Menu',
component: Menu,
argTypes: {},
parameters: {

@ -5,7 +5,7 @@ import { CodeEditor } from './CodeEditor';
import mdx from './CodeEditor.mdx';
const meta: Meta<typeof CodeEditor> = {
title: 'CodeEditor',
title: 'Inputs/CodeEditor',
component: CodeEditor,
parameters: {
docs: {

@ -9,7 +9,7 @@ import { ToolbarButton } from '../ToolbarButton/ToolbarButton';
import { PageToolbar } from './PageToolbar';
const meta: Meta<typeof PageToolbar> = {
title: 'Layout/PageToolbar',
title: 'Navigation/Deprecated/PageToolbar',
component: PageToolbar,
parameters: {},
};

@ -34,7 +34,7 @@ export interface Props {
forceShowLeftItems?: boolean;
}
/** @alpha */
/** @deprecated Use Page instead */
export const PageToolbar = memo(
({
title,

@ -5,7 +5,7 @@ import { Pagination } from './Pagination';
import mdx from './Pagination.mdx';
const meta: Meta<typeof Pagination> = {
title: 'Buttons/Pagination',
title: 'Navigation/Pagination',
component: Pagination,
parameters: {
docs: {

@ -22,7 +22,7 @@ const PANEL_WIDTH = 400;
const PANEL_HEIGHT = 150;
const meta: Meta<typeof PanelChrome> = {
title: 'Visualizations/PanelChrome',
title: 'Plugins/PanelChrome',
component: PanelChrome,
parameters: {
controls: {

@ -4,7 +4,7 @@ import { PanelContainer } from './PanelContainer';
import mdx from './PanelContainer.mdx';
const meta: Meta<typeof PanelContainer> = {
title: 'General/PanelContainer',
title: 'Layout/Deprecated/PanelContainer',
component: PanelContainer,
parameters: {
docs: {

@ -7,6 +7,8 @@ import { useStyles2 } from '../../themes/ThemeContext';
type Props = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
// TODO: Reimplement this with Box
/** @deprecated Use Box instead */
export const PanelContainer = ({ children, className, ...props }: Props) => {
const styles = useStyles2(getStyles);
return (

@ -6,7 +6,7 @@ import { PluginSignatureBadge } from './PluginSignatureBadge';
import mdx from './PluginSignatureBadge.mdx';
const meta: Meta<typeof PluginSignatureBadge> = {
title: 'Data Display/PluginSignatureBadge',
title: 'Information/PluginSignatureBadge',
component: PluginSignatureBadge,
argTypes: {
status: {

@ -5,7 +5,7 @@ import { TypeaheadInput } from '../../types/completion';
import { QueryField, QueryFieldProps } from './QueryField';
const meta: Meta<typeof QueryField> = {
title: 'Data Source/QueryField',
title: 'Inputs/Deprecated/QueryField',
component: QueryField,
parameters: {
controls: {

@ -6,7 +6,7 @@ import { RefreshPicker } from './RefreshPicker';
import mdx from './RefreshPicker.mdx';
const meta: Meta<typeof RefreshPicker> = {
title: 'Pickers and Editors/RefreshPicker',
title: 'Pickers/RefreshPicker',
component: RefreshPicker,
parameters: {
docs: {

@ -4,7 +4,7 @@ import { RenderUserContentAsHTML } from './RenderUserContentAsHTML';
import mdx from './RenderUserContentAsHTML.mdx';
const meta: Meta<typeof RenderUserContentAsHTML> = {
title: 'General/RenderUserContentAsHTML',
title: 'Utilities/RenderUserContentAsHTML',
component: RenderUserContentAsHTML,
parameters: {
docs: {

@ -5,7 +5,7 @@ import { ScrollContainer } from './ScrollContainer';
import mdx from './ScrollContainer.mdx';
const meta: Meta<typeof ScrollContainer> = {
title: 'General/Layout/ScrollContainer',
title: 'Layout/ScrollContainer',
component: ScrollContainer,
parameters: {
controls: {

@ -5,7 +5,7 @@ import { Meta, StoryFn } from '@storybook/react';
import { SecretFormField } from './SecretFormField';
const meta: Meta<typeof SecretFormField> = {
title: 'Forms/SecretFormField',
title: 'Forms/Deprecated/SecretFormField',
component: SecretFormField,
parameters: {
controls: {

@ -5,7 +5,7 @@ import { SecretInput } from './SecretInput';
import mdx from './SecretInput.mdx';
const meta: Meta<typeof SecretInput> = {
title: 'Forms/SecretInput',
title: 'Inputs/SecretInput',
component: SecretInput,
parameters: {
docs: {

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save