mirror of https://github.com/grafana/grafana
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 globpull/107872/head
parent
d27d8f02b6
commit
a9e70d4a1d
@ -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', |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}); |
@ -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; |
@ -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; |
@ -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; |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue