Skip to content

Commit

Permalink
Storybook: Support proper extraction of TypeScript prop types (#38842)
Browse files Browse the repository at this point in the history
* Export WordPressComponent types properly

* Expand code by default in Docs tab

* Heading: Use unconnected component for extraction

Export unconnected `Heading`

* Heading: Tweak story for Controls

* Divider: Extract types for stories

* Divider: Tweak story for "all Controls" view

* Convert contextConnect to TS

* Heading: Simply named export connected component

* Heading: Export with correct name

This is required for the code snippets to show the correct component name.

* Heading: Fix doc comment

* Divider: Rejigger exports

* Storybook: Allow tsx stories

* Heading: Convert story to TS

* Divider: Convert story to TS

* Replace Emotion with inline style

* Move source expansion setting to per-component

* Remove `unstable_system` prop from types

* Text: Remove extraneous type alias

* Enforce TS checks on ComponentMeta argTypes

* Don't exclude tsx stories from type check

* Improve prop sorting

`requiredFirst` seems to also sort the rest of the props alphabetically 🎉

* Add more control type hints

* Add JSDoc description for `as` prop

* Change `Ref` to `ForwardedRef`

* Clarify `as` prop comment
  • Loading branch information
mirka authored Mar 1, 2022
1 parent 562c60f commit 82c0ef4
Show file tree
Hide file tree
Showing 14 changed files with 159 additions and 125 deletions.
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' );

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' } },
as: { control: { type: 'text' } },
color: { control: { type: 'color' } },
display: { control: { type: 'text' } },
letterSpacing: { control: { type: 'text' } },
lineHeight: { control: { type: 'text' } },
optimizeReadabilityFor: { control: { type: 'color' } },
variant: { control: { type: 'radio' }, options: [ 'muted' ] },
weight: { control: { type: 'text' } },
},
parameters: {
controls: { expanded: true },
docs: { source: { state: 'open' } },
},
};
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';
/**
* 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
File renamed without changes.
1 change: 1 addition & 0 deletions packages/components/src/ui/context/wordpress-component.ts
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
"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

0 comments on commit 82c0ef4

Please sign in to comment.