Skip to content

Commit

Permalink
[Storybook] Add stories for more components (letter P) - Part 1 (#7648)
Browse files Browse the repository at this point in the history
Co-authored-by: Cee Chen <constance.chen@elastic.co>
  • Loading branch information
mgadewoll and cee-chen authored Apr 11, 2024
1 parent 9b0e1da commit 5cc91b2
Show file tree
Hide file tree
Showing 12 changed files with 744 additions and 138 deletions.
71 changes: 69 additions & 2 deletions .storybook/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
* Side Public License, v 1.
*/

import {
import * as utils from './utils';

const {
hideStorybookControls,
disableStorybookControls,
moveStorybookControlsToCategory,
} from './utils';
enableFunctionToggleControls,
} = utils;

describe('hideStorybookControls', () => {
it('updates the provided config with the expected `argTypes` object when passed prop name strings', () => {
Expand Down Expand Up @@ -198,3 +201,67 @@ describe('moveStorybookControlsToCategory', () => {
]);
});
});

describe('enableFunctionToggleControls', () => {
it('updates the provided config with the expected `argTypes` object when passed function prop name strings', () => {
expect(enableFunctionToggleControls({ argTypes: {} }, ['onClick'])).toEqual(
{
args: {
onClick: true,
},
argTypes: {
onClick: {
control: 'boolean',
mapping: { false: undefined, true: expect.any(Function) },
},
},
parameters: { actions: { argTypesRegex: null } },
}
);
});

it('merges existing and new `argTypes` objects correctly', () => {
type TestProps = { hello: boolean; onHello: () => {} };

expect(
enableFunctionToggleControls<TestProps>(
{
args: { hello: true },
argTypes: {
isDisabled: { control: { type: 'boolean' } },
},
},
['onHello']
)
).toEqual({
args: {
hello: true,
onHello: true,
},
argTypes: {
isDisabled: { control: { type: 'boolean' } },
onHello: {
control: 'boolean',
mapping: { false: undefined, true: expect.any(Function) },
},
},
parameters: { actions: { argTypesRegex: null } },
});
});

it('throws a typescript error if a generic is passed and the prop names do not match', () => {
type TestProps = { hello: boolean; onHello: () => {} };

// No typescript error
enableFunctionToggleControls<TestProps>({ argTypes: {} }, [
'hello',
'onHello',
]);
enableFunctionToggleControls<TestProps>({ argTypes: {} }, [
'hello',
'onHello',
// @ts-expect-error - will fail `yarn lint` if a TS error is *not* produced
'error',
]);
});
});
136 changes: 107 additions & 29 deletions .storybook/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

import type { Args, ArgTypes, Meta, Preview, StoryObj } from '@storybook/react';
import { action } from '@storybook/addon-actions';

type StorybookConfig<T> = Meta<T> | StoryObj<T> | Preview;

Expand All @@ -27,10 +28,12 @@ export const hideStorybookControls = <Props>(
config: StorybookConfig<Props>,
propNames: Array<keyof Props>
): StorybookConfig<Props> => {
const updatedConfig = _updateArgTypes(config, propNames, {
key: 'table',
value: { disable: true },
});
const updatedConfig = _updateArgTypes(config, propNames, [
{
key: 'table',
value: { disable: true },
},
]);

return updatedConfig;
};
Expand All @@ -49,10 +52,12 @@ export const disableStorybookControls = <Props>(
config: StorybookConfig<Props>,
propNames: Array<keyof Props>
): StorybookConfig<Props> => {
const updatedConfig = _updateArgTypes(config, propNames, {
key: 'control',
value: false,
});
const updatedConfig = _updateArgTypes(config, propNames, [
{
key: 'control',
value: false,
},
]);

return updatedConfig;
};
Expand All @@ -72,11 +77,70 @@ export const moveStorybookControlsToCategory = <Props>(
propNames: Array<keyof Props>,
category = 'Additional'
): StorybookConfig<Props> => {
const updatedConfig = _updateArgTypes(config, propNames, {
key: 'table',
value: { category },
const updatedConfig = _updateArgTypes(config, propNames, [
{
key: 'table',
value: { category },
},
]);

return updatedConfig;
};

/**
* Configures passed argTypes to be setup as toggle control
* which fires a Storybook action when enabled.
* Should be used for function props only.
*
* Can be used for preview (Preview), component (Meta) or story (Story)
* context by passing the config object for either. Use after defining
* the specific config to be able to pass the config to this util.
*
* @returns the mutated config
*/
export const enableFunctionToggleControls = <Props>(
config: StorybookConfig<Props>,
propNames: Array<keyof Props>
) => {
const setAction = (propName: string | number) => ({
true: action(propName.toString()),
false: undefined,
});

/* Sets the default value for the passed function prop.
This is needed to ensure the coolean control is set and
to prevent additional clicks.
NOTE: This has to happen before the argTypes are updated */
config.args = propNames.reduce(
(acc, propName) => ({
...acc,
[propName]: true,
}),
config.args
);

let updatedConfig = _updateArgTypes(config, propNames, [
{ key: 'control', value: 'boolean' },
{
key: 'mapping',
value: setAction,
},
]);

updatedConfig = {
...updatedConfig,
/* Overwrites global parameters.actions setting in preview.tsx which enables
actions on function props starting with "on[Name]" by default. This is needed
to ensure the default "false" state is actually false. */
parameters: {
...updatedConfig.parameters,
actions: {
...updatedConfig.parameters?.actions,
argTypesRegex: null,
},
},
};

return updatedConfig;
};

Expand Down Expand Up @@ -112,29 +176,43 @@ export const hidePanel = {
const _updateArgTypes = <Props>(
config: StorybookConfig<Props>,
propNames: Array<keyof Props>,
{
key,
value,
}: { key: string; value: Record<string, string | boolean> | boolean }
controls: Array<{
key: string;
value:
| Record<string, any>
| boolean
| string
| ((propName: any) => Record<string, any>);
}>
): StorybookConfig<Props> => {
const currentArgTypes = config.argTypes as Partial<ArgTypes<Props>>;
const newArgTypes = { ...currentArgTypes };

for (const propName of propNames) {
const currentArgTypeValue = newArgTypes?.[propName] ?? ({} as Args);
const currentControlValue = currentArgTypeValue.hasOwnProperty(key)
? currentArgTypeValue[key]
: ({} as Record<string, any>);

const newValue =
typeof value === 'object' && typeof currentArgTypeValue[key] === 'object'
? { ...currentControlValue, ...value }
: value;

newArgTypes[propName] = {
...currentArgTypeValue,
[key]: newValue,
};
for (const { key, value } of controls) {
const currentArgTypeValue = newArgTypes?.[propName] ?? ({} as Args);
const currentControlValue = currentArgTypeValue.hasOwnProperty(key)
? currentArgTypeValue[key]
: ({} as Record<string, any>);

let newValue = value;

if (typeof value === 'function') {
newValue = value(propName);
}

if (
typeof value === 'object' &&
typeof currentArgTypeValue[key] === 'object'
) {
newValue = { ...currentControlValue, ...value };
}

newArgTypes[propName] = {
...currentArgTypeValue,
[key]: newValue,
};
}
}

config.argTypes = newArgTypes;
Expand Down
4 changes: 4 additions & 0 deletions changelogs/upcoming/7648.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
**Bug fixes**

- Fixed an `EuiPageTemplate` bug where prop updates would not cascade down to child sections
- To cascade props down to the sidebar, `EuiPageTemplate` now explicitly requires using the `EuiPageTemplate.Sidebar` rather than `EuiPageSidebar`
76 changes: 53 additions & 23 deletions src/components/page/page_header/page_header.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';

import { moveStorybookControlsToCategory } from '../../../../.storybook/utils';
import { EuiButton } from '../../button';
import { EuiPageHeader, EuiPageHeaderProps } from '../page_header';

Expand All @@ -23,6 +24,10 @@ const meta: Meta<EuiPageHeaderProps> = {
pageTitleProps: { control: 'object' },
breadcrumbProps: { control: 'object' },
tabsProps: { control: 'object' },
restrictWidth: {
control: 'select',
options: [true, false, 500, 900, 1800, '25%', '50%', '75%'],
},
},
args: {
// Component defaults
Expand All @@ -33,9 +38,55 @@ const meta: Meta<EuiPageHeaderProps> = {
},
};

moveStorybookControlsToCategory(
meta,
[
'pageTitle',
'pageTitleProps',
'iconType',
'iconProps',
'breadcrumbs',
'breadcrumbProps',
'tabs',
'tabsProps',
'description',
'responsive',
'alignItems',
'rightSideItems',
'rightSideGroupProps',
'children',
],
'EuiPageHeaderContent props'
);

export default meta;
type Story = StoryObj<EuiPageHeaderProps>;

const tabs = [
{
label: 'Tab 1',
isSelected: true,
},
{
label: 'Tab 2',
},
];

const breadcrumbs = [
{
text: 'Breadcrumb 1',
href: '#',
},
{
text: 'Breadcrumb 2',
href: '#',
},
{
text: 'Current',
href: '#',
},
];

export const Playground: Story = {
args: {
pageTitle: 'Page title',
Expand All @@ -46,28 +97,7 @@ export const Playground: Story = {
<EuiButton fill>Add something</EuiButton>,
<EuiButton>Do something</EuiButton>,
],
tabs: [
{
label: 'Tab 1',
isSelected: true,
},
{
label: 'Tab 2',
},
],
breadcrumbs: [
{
text: 'Breadcrumb 1',
href: '#',
},
{
text: 'Breadcrumb 2',
href: '#',
},
{
text: 'Current',
href: '#',
},
],
tabs,
breadcrumbs,
},
};
Loading

0 comments on commit 5cc91b2

Please sign in to comment.