Skip to content

Commit 2cf88a0

Browse files
Generic layout renderer (#210)
* refactor hybrid layout + docs * docs(changeset): Adds a generic layout renderer which can be used to create bespoke prop tables. Also refactors the internals of the hybrid layout to utilise it
1 parent a73f916 commit 2cf88a0

File tree

11 files changed

+380
-228
lines changed

11 files changed

+380
-228
lines changed

.changeset/sour-houses-share.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'pretty-proptypes': minor
3+
---
4+
5+
Adds a generic layout renderer which can be used to create bespoke prop tables. Also refactors the internals of the hybrid layout to utilise it

packages/pretty-proptypes/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,28 @@ ${<Props
9696

9797
While you can pass style `components` directly to `Prop`, we recommend passing
9898
style components in the top level Props, and letting them flow down.
99+
100+
## Custom layouts
101+
102+
In cases where a completely bespoke layout is required, use the `LayoutRenderer`. This component allows you to define a completely custom layout and substitute in your own UI.
103+
104+
The `renderTypes` prop is called for every prop found on a given component and allows you to specify how that type should be rendered.
105+
106+
If you don't want to override the default components, you can use the `components` property. Or import them directly from `pretty-proptypes`.
107+
108+
```js
109+
import { LayoutRenderer } from 'pretty-proptypes';
110+
111+
<LayoutRenderer
112+
props={require('!!extract-react-types-loader!../MyCoolComponent')}
113+
renderTypes={({ typeValue, defaultValue, description, required, name, type, components }) => {
114+
<div>
115+
<h2>{name}</h2>
116+
<components.Description>{description}</components.Description>
117+
{required && <components.Required>Required</components.Required>}
118+
<components.Type>{type}</components.Type>
119+
<components.PropType typeValue={typeValue} />
120+
</div>;
121+
}}
122+
/>;
123+
```

packages/pretty-proptypes/src/HybridLayout/PropEntry.js

Lines changed: 0 additions & 127 deletions
This file was deleted.

packages/pretty-proptypes/src/HybridLayout/index.js

Lines changed: 133 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -4,93 +4,151 @@
44

55
/** @jsx jsx */
66
import { jsx, css } from '@emotion/core';
7-
import { Component, type ComponentType } from 'react';
7+
import { Component, type Node } from 'react';
88

9-
import type { Components } from '../components';
10-
import type { CommonProps } from '../types';
9+
import md from 'react-markings';
1110
import PropsWrapper from '../Props/Wrapper';
12-
import getPropTypes from '../getPropTypes';
13-
import renderPropType from '../PropType';
14-
import PropEntry from './PropEntry';
11+
import LayoutRenderer, { type LayoutRendererProps } from '../LayoutRenderer';
12+
import { HeadingType, HeadingDefault, Heading, HeadingRequired } from '../Prop/Heading';
13+
import { colors } from '../components/constants';
1514

16-
type Obj = {
17-
kind: 'object',
18-
members: Array<any>
19-
};
20-
21-
type Gen = {
22-
kind: 'generic',
23-
value: any
24-
};
25-
26-
type Inter = {
27-
kind: 'intersection',
28-
types: Array<Obj | Gen>
29-
};
30-
31-
type DynamicPropsProps = {
32-
components?: Components,
15+
type DynamicPropsProps = LayoutRendererProps & {
3316
heading?: string,
34-
shouldCollapseProps?: boolean,
35-
overrides?: {
36-
[string]: ComponentType<CommonProps>
37-
},
38-
props?: {
39-
component?: Obj | Inter
40-
},
41-
component?: ComponentType<any>
17+
shouldCollapseProps?: boolean
4218
};
4319

44-
const getProps = props => {
45-
if (props && props.component) {
46-
return getPropTypes(props.component);
47-
}
48-
return null;
49-
};
20+
const Description = ({ children }: { children: Node }) => (
21+
<div
22+
css={css`
23+
p:first-of-type {
24+
margin-top: 0px;
25+
}
26+
p:last-of-type {
27+
margin-bottom: 0px;
28+
}
29+
`}
30+
>
31+
{children}
32+
</div>
33+
);
5034

5135
export default class HybridLayout extends Component<DynamicPropsProps> {
5236
render() {
53-
let { props, heading, component, components, ...rest } = this.props;
54-
if (component) {
55-
/* $FlowFixMe the component prop is typed as a component because
56-
that's what people pass to Props and the ___types property shouldn't
57-
exist in the components types so we're just going to ignore this error */
58-
if (component.___types) {
59-
props = { type: 'program', component: component.___types };
60-
} else {
61-
/* eslint-disable-next-line no-console */
62-
console.error(
63-
'A component was passed to <Props> but it does not have types attached.\n' +
64-
'babel-plugin-extract-react-types may not be correctly installed.\n' +
65-
'<Props> will fallback to the props prop to display types.'
66-
);
67-
}
68-
}
69-
70-
if (!components || !components.Description) {
71-
components = components || {};
72-
components.Description = ({ children }) => (
73-
<div
74-
css={css`
75-
p:first-of-type {
76-
margin-top: 0px;
77-
}
78-
p:last-of-type {
79-
margin-bottom: 0px;
80-
}
81-
`}
82-
>
83-
{children}
84-
</div>
85-
);
86-
}
87-
88-
let propTypes = getProps(props);
89-
if (!propTypes) return null;
37+
const { props, heading, component, shouldCollapseProps } = this.props;
9038

9139
return (
9240
<PropsWrapper heading={heading}>
93-
{propTypes.map(propType => renderPropType(propType, { ...rest, components }, PropEntry))}
41+
<LayoutRenderer
42+
component={component}
43+
props={props}
44+
renderType={({
45+
typeValue,
46+
defaultValue,
47+
description,
48+
required,
49+
name,
50+
type,
51+
components: Comp
52+
}) => (
53+
<table
54+
css={css`
55+
width: 100%;
56+
border-collapse: collapse;
57+
margin-top: 40px;
58+
59+
th {
60+
text-align: left;
61+
padding: 4px 16px 4px 8px;
62+
white-space: nowrap;
63+
vertical-align: top;
64+
}
65+
66+
td {
67+
padding: 4px 0 4px 8px;
68+
width: 100%;
69+
}
70+
71+
tbody {
72+
border-bottom: none;
73+
}
74+
`}
75+
>
76+
<caption
77+
css={css`
78+
text-align: left;
79+
margin: 0;
80+
font-size: 1em;
81+
`}
82+
>
83+
<Heading
84+
css={css`
85+
font-size: 1em;
86+
padding-bottom: 8px;
87+
border-bottom: 1px solid ${colors.N30};
88+
margin-bottom: 4px;
89+
`}
90+
>
91+
<code
92+
css={css`
93+
background-color: ${colors.N20};
94+
color: ${colors.N800};
95+
border-radius: 3px;
96+
padding: 4px 8px;
97+
line-height: 20px;
98+
display: inline-block;
99+
`}
100+
>
101+
{name}
102+
</code>
103+
{required && defaultValue === undefined && (
104+
<HeadingRequired
105+
css={css`
106+
margin-left: 1em;
107+
color: ${colors.R400};
108+
`}
109+
>
110+
required
111+
</HeadingRequired>
112+
)}
113+
</Heading>
114+
</caption>
115+
<tbody>
116+
<tr>
117+
<th scope="row">Description</th>
118+
<td>{description && <Description>{md([description])}</Description>}</td>
119+
</tr>
120+
{defaultValue !== undefined && (
121+
<tr>
122+
<th scope="row">Default</th>
123+
<td>
124+
<HeadingDefault>{defaultValue}</HeadingDefault>
125+
</td>
126+
</tr>
127+
)}
128+
<tr>
129+
<th scope="row">Type</th>
130+
<td
131+
css={css`
132+
display: flex;
133+
flex-direction: column;
134+
`}
135+
>
136+
<span>
137+
<HeadingType>{type}</HeadingType>
138+
</span>
139+
<span>
140+
<Comp.PropType
141+
typeValue={typeValue}
142+
components={Comp}
143+
shouldCollapse={shouldCollapseProps}
144+
/>
145+
</span>
146+
</td>
147+
</tr>
148+
</tbody>
149+
</table>
150+
)}
151+
/>
94152
</PropsWrapper>
95153
);
96154
}

0 commit comments

Comments
 (0)