Skip to content

Commit b5c1a5d

Browse files
authored
Add "sortProps" and "requiredPropsFirst" props to LayoutRenderer (#228)
* Add "sortProps" and "requiredPropsFirst" props to LayoutRenderer * docs(changeset): Adds "sortProps" and "requiredPropsFirst" props to LayoutRenderer and HybridLayout Co-authored-by: Andrew Campbell <[email protected]>
1 parent 12b36c8 commit b5c1a5d

File tree

6 files changed

+157
-6
lines changed

6 files changed

+157
-6
lines changed

.changeset/twenty-sheep-tie.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 "sortProps" and "requiredPropsFirst" props to LayoutRenderer and HybridLayout

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,22 @@ const Description = ({ children }: { children: Node }) => (
4040

4141
export default class HybridLayout extends Component<DynamicPropsProps> {
4242
render() {
43-
const { props, heading, component, shouldCollapseProps } = this.props;
43+
const {
44+
props,
45+
heading,
46+
component,
47+
shouldCollapseProps,
48+
requiredPropsFirst,
49+
sortProps
50+
} = this.props;
4451

4552
return (
4653
<PropsWrapper heading={heading}>
4754
<LayoutRenderer
4855
component={component}
4956
props={props}
57+
requiredPropsFirst={requiredPropsFirst}
58+
sortProps={sortProps}
5059
renderType={({
5160
typeValue,
5261
defaultValue,

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

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,53 @@ type Inter = {
2626
types: Array<Obj | Gen>
2727
};
2828

29+
type Prop = {
30+
kind: string,
31+
optional: boolean,
32+
key: { kind: string, name: string },
33+
value: { kind: string }
34+
};
35+
2936
export type LayoutRendererProps = {
37+
/**
38+
* Types to render, passed directly from the output of extract-react-types-loader
39+
*/
3040
props?: {
3141
component?: Obj | Inter
3242
},
43+
/** React Component to render props for. */
3344
component?: ComponentType<any>,
45+
/** Custom components to render to end-users */
3446
components?: Components,
35-
renderType: CommonProps => ComponentType<CommonProps>
47+
/**
48+
* Function to render each prop in the props list.
49+
* Render props contain the prop's information, including name, description etc.
50+
*/
51+
renderType: CommonProps => ComponentType<CommonProps>,
52+
/**
53+
* If true, required props will be placed first in the props array
54+
*/
55+
requiredPropsFirst?: boolean,
56+
/**
57+
* Sorting function applied to props list.
58+
* Given two prop objects, returns:
59+
* - 0 to maintain order
60+
* - < 0 if propA should render before propB
61+
* - > 0 if propB should render before propA
62+
*/
63+
sortProps?: (propA: Prop, propB: Prop) => number
3664
};
3765

3866
const getProps = props => (props && props.component ? getPropTypes(props.component) : []);
3967

40-
const LayoutRenderer: FC<LayoutRendererProps> = ({ props, component, components, ...rest }) => {
68+
const LayoutRenderer: FC<LayoutRendererProps> = ({
69+
props,
70+
component,
71+
components,
72+
requiredPropsFirst,
73+
sortProps,
74+
...rest
75+
}) => {
4176
let resolvedProps = props;
4277
if (component) {
4378
/* $FlowFixMe the component prop is typed as a component because
@@ -65,7 +100,19 @@ const LayoutRenderer: FC<LayoutRendererProps> = ({ props, component, components,
65100
};
66101
}
67102

68-
return getProps(resolvedProps).map(propType =>
103+
// Sort prop list
104+
let finalProps = getProps(resolvedProps);
105+
if (sortProps) finalProps.sort(sortProps);
106+
if (requiredPropsFirst) {
107+
finalProps.sort((propA, propB) => {
108+
const propARequired = !(propA.optional || propA.default);
109+
const propBRequired = !(propB.optional || propB.default);
110+
if (propARequired === propBRequired) return 0;
111+
return propARequired ? -1 : 1;
112+
});
113+
}
114+
115+
return finalProps.map(propType =>
69116
renderPropType(
70117
propType,
71118
{ ...renderProps, components: { ...components, PropType: PrettyPropType } },
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// @flow
2+
3+
import { mount, configure } from 'enzyme';
4+
import React from 'react';
5+
import Adapter from 'enzyme-adapter-react-16';
6+
import { extractReactTypes } from 'extract-react-types';
7+
import LayoutRenderer from './index';
8+
9+
configure({ adapter: new Adapter() });
10+
11+
const assembleComponent = (propTypes, defaultProps, type = 'typescript') => {
12+
let file = `
13+
class Component extends React.Component<${propTypes}> {
14+
defaultProps = ${defaultProps}
15+
}`;
16+
return extractReactTypes(file, type);
17+
};
18+
19+
test('requiredPropsFirst should place required props in front of other props', () => {
20+
const propTypes = assembleComponent(
21+
`{
22+
/**
23+
* Required, but with a default value. Should be rendered second
24+
*/
25+
a: number,
26+
/**
27+
* Optional, should be rendered third
28+
*/
29+
b?: string,
30+
/**
31+
* Required, no default. Should be rendered first
32+
*/
33+
c: boolean,
34+
}`,
35+
`{a: 1}`
36+
);
37+
38+
const order = [];
39+
40+
// $FlowFixMe - deliberately no children
41+
mount(
42+
<LayoutRenderer
43+
props={propTypes}
44+
requiredPropsFirst
45+
renderType={props => {
46+
order.push(props.name);
47+
return <div />;
48+
}}
49+
/>
50+
);
51+
52+
expect(order[0]).toEqual('c');
53+
});
54+
55+
test('sortProps should run sort function before applying requiredPropsFirst', () => {
56+
const propTypes = assembleComponent(
57+
`{
58+
c?: number,
59+
b?: string,
60+
a?: boolean,
61+
e: string,
62+
d: string,
63+
}`,
64+
`{a: 1}`
65+
);
66+
67+
const order = [];
68+
69+
// $FlowFixMe - deliberately no children
70+
mount(
71+
<LayoutRenderer
72+
props={propTypes}
73+
sortProps={(propA, propB) => propA.key.name.localeCompare(propB.key.name)}
74+
requiredPropsFirst
75+
renderType={props => {
76+
order.push(props.name);
77+
return <div />;
78+
}}
79+
/>
80+
);
81+
82+
expect(order[0]).toEqual('d');
83+
});

stories/layout/Hybrid.stories.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,14 @@ const Template = args => args.component;
1313

1414
export const Base = Template.bind({});
1515

16+
const props = {
17+
component: TypeScriptComponent,
18+
requiredPropsFirst: true,
19+
heading: 'Primitive types'
20+
};
21+
1622
Base.args = {
17-
component: <HybridLayout component={TypeScriptComponent} heading="Primitive types" />
23+
component: <HybridLayout {...props} />
1824
};
1925

2026
export const WithReset = Template.bind({});
@@ -27,7 +33,7 @@ WithReset.args = {
2733
@import 'https://unpkg.com/@atlaskit/[email protected]/dist/bundle.css';
2834
`}
2935
/>
30-
<HybridLayout component={TypeScriptComponent} heading="Primitive types" />
36+
<HybridLayout {...props} />
3137
</>
3238
)
3339
};

stories/layout/LayoutRenderer.stories.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Base.args = {
8282
<h2>Primitive types</h2>
8383
<LayoutRenderer
8484
component={TypeScriptComponent}
85+
requiredPropsFirst
8586
renderType={({
8687
typeValue,
8788
defaultValue,

0 commit comments

Comments
 (0)