Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Storybook: Support proper extraction of TypeScript prop types #38842

Merged
merged 25 commits into from
Mar 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f60feaf
Export WordPressComponent types properly
mirka Feb 15, 2022
a6ea38b
Expand code by default in Docs tab
mirka Feb 15, 2022
9195e71
Heading: Use unconnected component for extraction
mirka Feb 15, 2022
17cd970
Heading: Tweak story for Controls
mirka Feb 15, 2022
ddc9814
Divider: Extract types for stories
mirka Feb 15, 2022
9c46706
Divider: Tweak story for "all Controls" view
mirka Feb 15, 2022
64d5f73
Convert contextConnect to TS
mirka Feb 16, 2022
e7eca9f
Heading: Simply named export connected component
mirka Feb 16, 2022
d92ce1b
Heading: Export with correct name
mirka Feb 16, 2022
1f094fa
Heading: Fix doc comment
mirka Feb 16, 2022
bb1443c
Divider: Rejigger exports
mirka Feb 16, 2022
dbbb7a5
Storybook: Allow tsx stories
mirka Feb 15, 2022
3e53470
Heading: Convert story to TS
mirka Feb 17, 2022
37799c4
Divider: Convert story to TS
mirka Feb 17, 2022
b51f400
Replace Emotion with inline style
mirka Feb 18, 2022
a8dfc1a
Move source expansion setting to per-component
mirka Feb 18, 2022
0c8c661
Remove `unstable_system` prop from types
mirka Feb 18, 2022
f4a448c
Text: Remove extraneous type alias
mirka Feb 22, 2022
cfa4891
Enforce TS checks on ComponentMeta argTypes
mirka Feb 22, 2022
3c0a4e2
Don't exclude tsx stories from type check
mirka Feb 22, 2022
f95f2cd
Improve prop sorting
mirka Feb 22, 2022
ba3ca92
Add more control type hints
mirka Feb 22, 2022
f98d14d
Add JSDoc description for `as` prop
mirka Feb 22, 2022
354c730
Change `Ref` to `ForwardedRef`
mirka Feb 22, 2022
f75562e
Clarify `as` prop comment
mirka Mar 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/components/src/divider/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { DividerView } from './styles';
import type { Props } from './types';

function Divider(
function UnconnectedDivider(
props: WordPressComponentProps< Props, 'hr', false >,
forwardedRef: ForwardedRef< any >
) {
Expand Down Expand Up @@ -53,6 +53,6 @@ function Divider(
* }
* ```
*/
const ConnectedDivider = contextConnect( Divider, 'Divider' );
export const Divider = contextConnect( UnconnectedDivider, 'Divider' );
mirka marked this conversation as resolved.
Show resolved Hide resolved

export default ConnectedDivider;
export default Divider;
64 changes: 0 additions & 64 deletions packages/components/src/divider/stories/index.js

This file was deleted.

70 changes: 70 additions & 0 deletions packages/components/src/divider/stories/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* External dependencies
*/
import type { ComponentMeta, ComponentStory } from '@storybook/react';

/**
* Internal dependencies
*/
import { Text } from '../../text';
import { Divider } from '..';

const meta: ComponentMeta< typeof Divider > = {
component: Divider,
title: 'Components (Experimental)/Divider',
argTypes: {
margin: {
control: { type: 'number' },
},
marginStart: {
control: { type: 'number' },
},
marginEnd: {
control: { type: 'number' },
},
},
parameters: {
controls: { expanded: true },
docs: { source: { state: 'open' } },
},
};
export default meta;

const HorizontalTemplate: ComponentStory< typeof Divider > = ( args ) => (
<div>
<Text>Some text before the divider</Text>
<Divider { ...args } />
<Text>Some text after the divider</Text>
</div>
);

const VerticalTemplate: ComponentStory< typeof Divider > = ( args ) => {
const styles = {
display: 'flex',
alignItems: 'stretch',
justifyContent: 'start',
};

return (
<div style={ styles }>
<Text>Some text before the divider</Text>
<Divider { ...args } />
<Text>Some text after the divider</Text>
</div>
);
};

export const Horizontal: ComponentStory<
typeof Divider
> = HorizontalTemplate.bind( {} );
Horizontal.args = {
margin: 2,
};

export const Vertical: ComponentStory< typeof Divider > = VerticalTemplate.bind(
{}
);
Vertical.args = {
...Horizontal.args,
orientation: 'vertical',
};
4 changes: 3 additions & 1 deletion packages/components/src/divider/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ export interface OwnProps {
marginEnd?: SpaceInput;
}

export interface Props extends Omit< SeparatorProps, 'children' >, OwnProps {}
export interface Props
extends Omit< SeparatorProps, 'children' | 'unstable_system' >,
OwnProps {}
8 changes: 4 additions & 4 deletions packages/components/src/heading/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { contextConnect, WordPressComponentProps } from '../ui/context';
import { View } from '../view';
import { useHeading, HeadingProps } from './hook';

function Heading(
function UnconnectedHeading(
props: WordPressComponentProps< HeadingProps, 'h1' >,
forwardedRef: ForwardedRef< any >
) {
Expand All @@ -24,13 +24,13 @@ function Heading(
*
* @example
* ```jsx
* import { Heading } from `@wordpress/components`
* import { __experimentalHeading as Heading } from "@wordpress/components";
*
* function Example() {
* return <Heading>Code is Poetry</Heading>;
* }
* ```
*/
const ConnectedHeading = contextConnect( Heading, 'Heading' );
export const Heading = contextConnect( UnconnectedHeading, 'Heading' );

export default ConnectedHeading;
export default Heading;
24 changes: 0 additions & 24 deletions packages/components/src/heading/stories/index.js

This file was deleted.

37 changes: 37 additions & 0 deletions packages/components/src/heading/stories/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* External dependencies
*/
import type { ComponentMeta, ComponentStory } from '@storybook/react';

/**
* Internal dependencies
*/
import { Heading } from '..';

const meta: ComponentMeta< typeof Heading > = {
component: Heading,
title: 'Components (Experimental)/Heading',
argTypes: {
adjustLineHeightForInnerControls: { control: { type: 'text' } },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adjustLineHeightForInnerControls can also be a boolean — should we add true and false to the list of possible values, maybe in a dropdown-like control?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm yeah, this one’s a bit overcomplicated too. Based on the code, true gives the same result as 'medium', and false gives the same result as undefined. Before we graduate <Text> from experimental status, I'd prefer removing the boolean types from this prop API. (Kind of similar to your UnitControl cleanup!)

I'll note this in the TS refactor tracking issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to close the circle, this was finally done in #54953 !

as: { control: { type: 'text' } },
color: { control: { type: 'color' } },
display: { control: { type: 'text' } },
letterSpacing: { control: { type: 'text' } },
lineHeight: { control: { type: 'text' } },
Comment on lines +19 to +20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These values can be both strings or numbers, is there a way to achieve that with controls?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The object control can strictly cover both, since that's basically a JSON string input so it differentiates between 1 and "1". Though in the vast majority of cases I think a normal text control would be sufficient for tinkering and better for UX (because people will have trouble recognizing that they need to quote their strings in a object control).

optimizeReadabilityFor: { control: { type: 'color' } },
variant: { control: { type: 'radio' }, options: [ 'muted' ] },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: using a radio with only one option, it's not possible to "deselect" that option, once selected

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind of like the "reset" problem in the editor sidebar! I actually think this is consistent with all controls (especially auto-generated ones), in that you can't really go back to the undefined state once you set a value. We could of course add an undefined option, but I think the overall design intent is to use the "Reset controls" button ↩️ in the top right.

I'm not sure how I feel about this yet! On one hand I don't want to establish a pattern of adding a lot of manual tweaks to auto-generated controls, because it increases maintenance burden. But in some cases it may be annoying to have to hit Reset each time. So... maybe worth it for heavily trafficked props like variant on Button?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Let's leave it as-is (and rely on the reset button for now). I believe we are amongst the heavy users of this Storybook anyway, so we can make this change if we ever feel like it's really needed.

weight: { control: { type: 'text' } },
},
parameters: {
controls: { expanded: true },
docs: { source: { state: 'open' } },
ciampo marked this conversation as resolved.
Show resolved Hide resolved
},
};
export default meta;

export const Default: ComponentStory< typeof Heading > = ( props ) => (
<Heading { ...props } />
);
Default.args = {
children: 'Heading',
};
14 changes: 6 additions & 8 deletions packages/components/src/text/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,6 @@ import type { Props as TruncateProps } from '../truncate/types';
*/
import type { CSSProperties } from 'react';

type TextAdjustLineHeightForInnerControls =
| boolean
| 'large'
| 'medium'
| 'small'
| 'xSmall';

export type TextSize =
| 'body'
| 'caption'
Expand All @@ -35,7 +28,12 @@ export interface Props extends TruncateProps {
/**
* Automatically calculate the appropriate line-height value for contents that render text and Control elements (e.g. `TextInput`).
*/
adjustLineHeightForInnerControls?: TextAdjustLineHeightForInnerControls;
adjustLineHeightForInnerControls?:
| boolean
| 'large'
| 'medium'
| 'small'
| 'xSmall';
Comment on lines +31 to +36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the "limit" for expanding type aliases is if the types that are unioned are all of the same general type or not?

E.g. a union of just strings could be "easier" to expand for the docgen, while a union of strings and boolean may be kept un-expanded?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know 🤷

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me neither, but good to keep at the back of our minds for future work :)

/**
* Adjusts the text color.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* External dependencies
*/
import { uniq } from 'lodash';
import type { ForwardedRef, ReactChild, ReactNode } from 'react';

/**
* WordPress dependencies
Expand All @@ -14,8 +15,13 @@ import warn from '@wordpress/warning';
*/
import { CONNECT_STATIC_NAMESPACE } from './constants';
import { getStyledClassNameFromKey } from './get-styled-class-name-from-key';
import type { WordPressComponentFromProps } from '.';

type ContextConnectOptions = {
/** Defaults to `false`. */
memo?: boolean;
};

/* eslint-disable jsdoc/valid-types */
/**
* Forwards ref (React.ForwardRef) and "Connects" (or registers) a component
* within the Context system under a specified namespace.
Expand All @@ -24,15 +30,16 @@ import { getStyledClassNameFromKey } from './get-styled-class-name-from-key';
* The hope is that we can improve render performance by removing functional
* component wrappers.
*
* @template {import('./wordpress-component').WordPressComponentProps<{}, any, any>} P
* @param {(props: P, ref: import('react').Ref<any>) => JSX.Element | null} Component The component to register into the Context system.
* @param {string} namespace The namespace to register the component under.
* @param {Object} options
* @param {boolean} [options.memo=false]
* @return {import('./wordpress-component').WordPressComponentFromProps<P>} The connected WordPressComponent
* @param Component The component to register into the Context system.
* @param namespace The namespace to register the component under.
* @param options
* @return The connected WordPressComponent
*/
export function contextConnect( Component, namespace, options = {} ) {
/* eslint-enable jsdoc/valid-types */
export function contextConnect< P >(
Component: ( props: P, ref: ForwardedRef< any > ) => JSX.Element | null,
namespace: string,
options: ContextConnectOptions = {}
): WordPressComponentFromProps< P > {
const { memo: memoProp = false } = options;

let WrappedComponent = forwardRef( Component );
Expand Down Expand Up @@ -75,10 +82,12 @@ export function contextConnect( Component, namespace, options = {} ) {
/**
* Attempts to retrieve the connected namespace from a component.
*
* @param {import('react').ReactChild | undefined | {}} Component The component to retrieve a namespace from.
* @return {Array<string>} The connected namespaces.
* @param Component The component to retrieve a namespace from.
* @return The connected namespaces.
*/
export function getConnectNamespace( Component ) {
export function getConnectNamespace(
Component: ReactChild | undefined | {}
): string[] {
if ( ! Component ) return [];

let namespaces = [];
Expand All @@ -101,11 +110,13 @@ export function getConnectNamespace( Component ) {
/**
* Checks to see if a component is connected within the Context system.
*
* @param {import('react').ReactNode} Component The component to retrieve a namespace from.
* @param {Array<string>|string} match The namespace to check.
* @return {boolean} The result.
* @param Component The component to retrieve a namespace from.
* @param match The namespace to check.
*/
export function hasConnectNamespace( Component, match ) {
export function hasConnectNamespace(
Component: ReactNode,
match: string[] | string
): boolean {
if ( ! Component ) return false;

if ( typeof match === 'string' ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type WordPressComponentProps<
Omit< React.ComponentPropsWithRef< T >, 'as' | keyof P | 'children' > &
( IsPolymorphic extends true
? {
/** The HTML element or React component to render the component as. */
as?: T | keyof JSX.IntrinsicElements;
}
: {} );
Expand Down
2 changes: 1 addition & 1 deletion packages/components/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
"src/**/*.ios.js",
"src/**/*.native.js",
"src/**/react-native-*",
"src/**/stories",
"src/**/stories/**.js", // only exclude js files, tsx files should be checked
ciampo marked this conversation as resolved.
Show resolved Hide resolved
"src/**/test",
"src/ui/__storybook-utils",
"src/ui/font-size-control"
Expand Down
8 changes: 4 additions & 4 deletions storybook/main.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const stories = [
process.env.NODE_ENV !== 'test' && './stories/**/*.@(js|mdx)',
'../packages/block-editor/src/**/stories/*.js',
'../packages/components/src/**/stories/*.js',
'../packages/icons/src/**/stories/*.js',
process.env.NODE_ENV !== 'test' && './stories/**/*.@(js|tsx|mdx)',
'../packages/block-editor/src/**/stories/*.@(js|tsx|mdx)',
'../packages/components/src/**/stories/*.@(js|tsx|mdx)',
'../packages/icons/src/**/stories/*.@(js|tsx|mdx)',
].filter( Boolean );

const customEnvVariables = {};
Expand Down
Loading