forked from storybookjs/storybook
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Props.tsx
187 lines (168 loc) · 5.59 KB
/
Props.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import React, { FunctionComponent, useContext } from 'react';
import { isNil } from 'lodash';
import {
PropsTable,
PropsTableError,
PropsTableProps,
PropsTableSectionsProps,
PropRowsProps,
PropDef,
TabsState,
} from '@storybook/components';
import { DocsContext, DocsContextProps } from './DocsContext';
import { Component, PropsSlot, CURRENT_SELECTION } from './shared';
import { getComponentName } from './utils';
import { PropsExtractor } from '../lib/docgen/types';
import { extractProps as reactExtractProps } from '../frameworks/react/extractProps';
import { extractProps as vueExtractProps } from '../frameworks/vue/extractProps';
interface PropsProps {
exclude?: string[];
of?: '.' | Component;
components?: {
[label: string]: Component;
};
slot?: PropsSlot;
}
// FIXME: remove in SB6.0 & require config
const inferPropsExtractor = (framework: string): PropsExtractor | null => {
switch (framework) {
case 'react':
return reactExtractProps;
case 'vue':
return vueExtractProps;
default:
return null;
}
};
const filterRows = (rows: PropDef[], exclude: string[]) =>
rows && rows.filter((row: PropDef) => !exclude.includes(row.name));
export const getComponentProps = (
component: Component,
{ exclude }: PropsProps,
{ parameters }: DocsContextProps
): PropsTableProps | PropsTableSectionsProps => {
if (!component) {
return null;
}
try {
const params = parameters || {};
const { framework = null } = params;
const { extractProps = inferPropsExtractor(framework) }: { extractProps: PropsExtractor } =
params.docs || {};
if (!extractProps) {
throw new Error(PropsTableError.PROPS_UNSUPPORTED);
}
let props: PropsTableProps | PropsTableSectionsProps = extractProps(component);
if (!isNil(exclude)) {
const { rows } = props as PropRowsProps;
const { sections } = props as PropsTableSectionsProps;
if (rows) {
props = { rows: filterRows(rows, exclude) };
} else if (sections) {
Object.keys(sections).forEach(section => {
sections[section] = filterRows(sections[section], exclude);
});
}
}
if ((props as PropRowsProps).rows) {
const propSections = (props as PropRowsProps).rows.reduce(
(acc: { [key: string]: PropDef[] }, prop: PropDef) => {
if (prop.parent && prop.parent.name) {
if (!acc[prop.parent.name]) {
return { ...acc, [prop.parent.name]: [prop] };
}
return { ...acc, [prop.parent.name]: [...acc[prop.parent.name], prop] };
}
return acc;
},
{}
);
const propSectionsArray = Object.keys(propSections);
if (propSectionsArray.length > 1) {
// the props are with sections (inherited interfaces in typescript)
// find out what section is the components own props
// by default just use the first listed interface of props
let expanded: string[] = [propSectionsArray[0]];
// if any section names contain the componnet name, expand them by default
if (component.displayName) {
// find all sections that contain the component name
const nameMatch = propSectionsArray.filter(
section => section.indexOf(component.displayName) >= 0
);
//
if (nameMatch.length) {
expanded = nameMatch;
}
// if any section have only 1 prop, expand them by default
propSectionsArray.forEach(section => {
if (propSections[section].length === 1 && expanded.indexOf(section) < 0) {
expanded.push(section);
}
});
}
props = { sections: propSections, expanded };
}
}
return props;
} catch (err) {
return { error: err.message };
}
};
export const getComponent = (props: PropsProps = {}, context: DocsContextProps): Component => {
const { of } = props;
const { parameters = {} } = context;
const { component } = parameters;
const target = of === CURRENT_SELECTION ? component : of;
if (!target) {
if (of === CURRENT_SELECTION) {
return null;
}
throw new Error(PropsTableError.NO_COMPONENT);
}
return target;
};
const PropsContainer: FunctionComponent<PropsProps> = props => {
const context = useContext(DocsContext);
const { slot, components } = props;
const {
parameters: { subcomponents },
} = context;
let allComponents = components;
if (!allComponents) {
const main = getComponent(props, context);
const mainLabel = getComponentName(main);
const mainProps = slot ? slot(context, main) : getComponentProps(main, props, context);
if (!subcomponents || typeof subcomponents !== 'object') {
return mainProps && <PropsTable {...mainProps} />;
}
allComponents = { [mainLabel]: main, ...subcomponents };
}
const tabs: { label: string; table: PropsTableProps }[] = [];
Object.entries(allComponents).forEach(([label, component]) => {
tabs.push({
label,
table: slot ? slot(context, component) : getComponentProps(component, props, context),
});
});
return (
<TabsState>
{tabs.map(({ label, table }) => {
if (!table) {
return null;
}
const id = `prop_table_div_${label}`;
return (
<div key={id} id={id} title={label}>
{({ active }: { active: boolean }) =>
active ? <PropsTable key={`prop_table_${label}`} {...table} /> : null
}
</div>
);
})}
</TabsState>
);
};
PropsContainer.defaultProps = {
of: '.',
};
export { PropsContainer as Props };