mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-05 16:11:33 +08:00
Controls: Add conditional args, docs, examples
This commit is contained in:
parent
7bc294d288
commit
4194981765
17
MIGRATION.md
17
MIGRATION.md
@ -2,6 +2,8 @@
|
||||
|
||||
- [From version 6.4.x to 6.5.0](#from-version-64x-to-650)
|
||||
- [CSF3 auto-title redundant filename](#csf3-auto-title-redundant-filename)
|
||||
- [6.5 Deprecations](#65-deprecations)
|
||||
- [Improved args disabling](#improved-args-disabling)
|
||||
- [From version 6.3.x to 6.4.0](#from-version-63x-to-640)
|
||||
- [Automigrate](#automigrate)
|
||||
- [CRA5 upgrade](#cra5-upgrade)
|
||||
@ -207,6 +209,21 @@ Since CSF3 is experimental, we are introducing this technically breaking change
|
||||
export default { title: 'Atoms/Button/Button' };
|
||||
```
|
||||
|
||||
### 6.5 Deprecations
|
||||
|
||||
#### Improved args disabling
|
||||
|
||||
We've simplified disabling arg display in 6.5 by replacing the `table.disable` property with `includeIf`/`excludeIf` property:
|
||||
|
||||
```js
|
||||
// before
|
||||
const argTypes = { foo: { table: { disable: true } } };
|
||||
// after
|
||||
const argTypes = { foo: { includeIf: false } };
|
||||
```
|
||||
|
||||
In addition to being one less level of nesting in the ArgType declaration, `includeIf`/`excludeIf` can also accept the name of another arg (a string) can conditionally include/exclude the arg based on the runtime value of the other arg.
|
||||
|
||||
## From version 6.3.x to 6.4.0
|
||||
|
||||
### Automigrate
|
||||
|
@ -300,6 +300,34 @@ paths={[
|
||||
|
||||
</div>
|
||||
|
||||
### Conditional controls
|
||||
|
||||
In some cases, it's useful to be able to conditionally exclude a control based on the value of another control. Controls supports basic versions of these use cases with the `enableIf` and `disableIf` options, which can take a boolean value, or a string which can refer to the value of another arg.
|
||||
|
||||
Consider a collection of "advanced" settings that are only visible when the user toggles an "advanced" toggle.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/component-story-conditional-controls-toggle.js.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
Or consider a constraint where if the user sets one control value, it doesn't make sense for the user to be able to set another value.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/component-story-conditional-controls-mutual-exclusion.js.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Hide NoControls warning
|
||||
|
||||
If you don't plan to handle the control args inside your Story, you can remove the warning with:
|
||||
@ -348,4 +376,4 @@ Consider the following snippet to force required args first:
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
@ -0,0 +1,16 @@
|
||||
```js
|
||||
// Button.stories.js
|
||||
import { Button } from './Button';
|
||||
export default {
|
||||
component: Button,
|
||||
title: 'Button',
|
||||
argTypes: {
|
||||
// button can be passed a label or an image, not both
|
||||
label: { control: 'text', excludeIf: 'image' },
|
||||
image: {
|
||||
control: { type: 'select', options: ['foo.jpg', 'bar.jpg'] },
|
||||
excludeIf: 'label',
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
@ -0,0 +1,16 @@
|
||||
```js
|
||||
// Button.stories.js
|
||||
import { Button } from './Button';
|
||||
export default {
|
||||
component: Button,
|
||||
title: 'Button',
|
||||
argTypes: {
|
||||
label: { control: 'text' }, // always shows
|
||||
advanced: { control: 'boolean' },
|
||||
// below are only included when advanced is true
|
||||
margin: { control: 'number', includeIf: 'advanced' },
|
||||
padding: { control: 'number', includeIf: 'advanced' },
|
||||
cornerRadius: { control: 'number', includeIf: 'advanced' },
|
||||
},
|
||||
};
|
||||
```
|
@ -5,18 +5,16 @@ import { YourComponent } from './YourComponent';
|
||||
|
||||
export default {
|
||||
/* 👇 The title prop is optional.
|
||||
* See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
|
||||
* to learn how to generate automatic titles
|
||||
*/
|
||||
* See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
|
||||
* to learn how to generate automatic titles
|
||||
*/
|
||||
title: 'YourComponent',
|
||||
component: YourComponent,
|
||||
argTypes: {
|
||||
// foo is the property we want to remove from the UI
|
||||
foo: {
|
||||
table: {
|
||||
disable: true,
|
||||
},
|
||||
excludeIf: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
```
|
||||
|
@ -10,9 +10,7 @@ import { YourComponent } from './YourComponent'
|
||||
component={YourComponent}
|
||||
argTypes={{
|
||||
foo:{
|
||||
table:{
|
||||
disable: true,
|
||||
}
|
||||
excludeIf: true,
|
||||
}
|
||||
}} />
|
||||
```
|
||||
```
|
||||
|
@ -28,6 +28,41 @@ export default {
|
||||
],
|
||||
},
|
||||
},
|
||||
staticDisable: {
|
||||
name: 'Static disabled',
|
||||
excludeIf: true,
|
||||
},
|
||||
mutuallyExclusiveA: { control: 'text', excludeIf: 'mutuallyExclusiveB' },
|
||||
mutuallyExclusiveB: { control: 'text', excludeIf: 'mutuallyExclusiveA' },
|
||||
colorMode: {
|
||||
control: 'boolean',
|
||||
},
|
||||
dynamicText: {
|
||||
excludeIf: 'colorMode',
|
||||
control: 'text',
|
||||
},
|
||||
dynamicColor: {
|
||||
includeIf: 'colorMode',
|
||||
control: 'color',
|
||||
},
|
||||
advanced: {
|
||||
control: 'boolean',
|
||||
},
|
||||
margin: {
|
||||
control: 'number',
|
||||
includeIf: 'advanced',
|
||||
},
|
||||
padding: {
|
||||
control: 'number',
|
||||
includeIf: 'advanced',
|
||||
},
|
||||
cornerRadius: {
|
||||
control: 'number',
|
||||
includeIf: 'advanced',
|
||||
},
|
||||
someText: { control: 'text' },
|
||||
subText: { control: 'text', includeIf: 'someText' },
|
||||
anotherText: { control: 'text', includeIf: 'someText' },
|
||||
},
|
||||
parameters: {
|
||||
chromatic: { disable: true },
|
||||
|
@ -116,6 +116,8 @@ export interface ArgType {
|
||||
name?: string;
|
||||
description?: string;
|
||||
defaultValue?: any;
|
||||
includeIf?: boolean | string;
|
||||
excludeIf?: boolean | string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
import React, { FC } from 'react';
|
||||
import dedent from 'ts-dedent';
|
||||
import deprecate from 'util-deprecate';
|
||||
import pickBy from 'lodash/pickBy';
|
||||
import { styled, ignoreSsrWarning } from '@storybook/theming';
|
||||
import { opacify, transparentize, darken, lighten } from 'polished';
|
||||
import { includeConditionalArg } from '@storybook/csf';
|
||||
import { Icons } from '../../icon/icon';
|
||||
import { ArgRow } from './ArgRow';
|
||||
import { SectionRow } from './SectionRow';
|
||||
@ -10,6 +13,15 @@ import { EmptyBlock } from '../EmptyBlock';
|
||||
import { Link } from '../../typography/link/link';
|
||||
import { ResetWrapper } from '../../typography/ResetWrapper';
|
||||
|
||||
const warnTableDisableDeprecated = deprecate(
|
||||
() => {},
|
||||
dedent`
|
||||
Use 'show' or 'hide' instead of 'table.disable' to disable ArgsTable rows.
|
||||
|
||||
https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#conditional-controls
|
||||
`
|
||||
);
|
||||
|
||||
export const TableWrapper = styled.table<{
|
||||
compact?: boolean;
|
||||
inAddonPanel?: boolean;
|
||||
@ -397,8 +409,12 @@ export const ArgsTable: FC<ArgsTableProps> = (props) => {
|
||||
const isLoading = 'isLoading' in props;
|
||||
const { rows, args } = 'rows' in props ? props : argsTableLoadingData;
|
||||
|
||||
if (Object.values(rows).some((row) => row?.table?.disable)) {
|
||||
warnTableDisableDeprecated();
|
||||
}
|
||||
|
||||
const groups = groupRows(
|
||||
pickBy(rows, (row) => !row?.table?.disable),
|
||||
pickBy(rows, (row) => !row?.table?.disable && includeConditionalArg(row, args)),
|
||||
sort
|
||||
);
|
||||
|
||||
|
@ -32,6 +32,8 @@ export interface ArgType {
|
||||
name?: string;
|
||||
description?: string;
|
||||
defaultValue?: any;
|
||||
includeIf?: boolean | string;
|
||||
excludeIf?: boolean | string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
StoryContext,
|
||||
AnyFramework,
|
||||
StrictArgTypes,
|
||||
includeConditionalArg,
|
||||
} from '@storybook/csf';
|
||||
|
||||
import {
|
||||
@ -165,12 +166,18 @@ export function prepareStory<TFramework extends AnyFramework>(
|
||||
acc[key] = mapping && val in mapping ? mapping[val] : val;
|
||||
return acc;
|
||||
}, {} as Args);
|
||||
const mappedContext = { ...context, args: mappedArgs };
|
||||
|
||||
const includedArgs = Object.entries(mappedArgs).reduce((acc, [key, val]) => {
|
||||
const argType = context.argTypes[key] || {};
|
||||
if (includeConditionalArg(argType, mappedArgs)) acc[key] = val;
|
||||
return acc;
|
||||
}, {} as Args);
|
||||
|
||||
const includedContext = { ...context, args: includedArgs };
|
||||
const { passArgsFirst: renderTimePassArgsFirst = true } = context.parameters;
|
||||
return renderTimePassArgsFirst
|
||||
? (render as ArgsStoryFn<TFramework>)(mappedContext.args, mappedContext)
|
||||
: (render as LegacyStoryFn<TFramework>)(mappedContext);
|
||||
? (render as ArgsStoryFn<TFramework>)(includedContext.args, includedContext)
|
||||
: (render as LegacyStoryFn<TFramework>)(includedContext);
|
||||
};
|
||||
const decoratedStoryFn = applyHooks<TFramework>(applyDecorators)(undecoratedStoryFn, decorators);
|
||||
const unboundStoryFn = (context: StoryContext<TFramework>) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user