UNPKG

7.74 kBJavaScriptView Raw
1import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
2
3/*
4 * The MIT License (MIT)
5 *
6 * Copyright (c) 2015 - present Instructure, Inc.
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in all
16 * copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * SOFTWARE.
25 */
26import { nanoid } from 'nanoid';
27import { generatePropCombinations } from './generatePropCombinations';
28import React from 'react';
29
30/**
31 * Generates examples for the given component based on the given configuration.
32 * @param Component A React component
33 * @param config A configuration object (stored in xy.examples.jsx files in InstUI)
34 * @returns Array of examples broken into sections and pages if configured to do so.
35 * @module generateComponentExamples
36 * @private
37 *
38 */
39export function generateComponentExamples(Component, config) {
40 const sectionProp = config.sectionProp,
41 excludeProps = config.excludeProps,
42 filter = config.filter;
43 const PROPS_CACHE = [];
44 const sections = [];
45 const maxExamples = config.maxExamples ? config.maxExamples : 500;
46 let exampleCount = 0;
47 let propValues = {};
48
49 const getParameters = page => {
50 const examples = page.examples;
51 const index = page.index;
52 let parameters = {};
53
54 if (typeof config.getParameters === 'function') {
55 parameters = { ...config.getParameters({
56 examples,
57 index
58 })
59 };
60 }
61
62 return parameters;
63 };
64 /**
65 * Merges the auto-generated props with ones in the examples files specified
66 * by the `getComponentProps()` method; props from the example files have
67 * priority
68 */
69
70
71 const mergeComponentPropsFromConfig = props => {
72 let componentProps = props; // TODO this code is so complicated because getComponentProps(props) can return
73 // different values based on its props parameter.
74 // If it would always return the same thing then we could reduce the
75 // number of combinations generated by generatePropCombinations() by
76 // getComponentProps() reducing some to 1 value, it would also remove the
77 // need of PROPS_CACHE and duplicate checks.
78 // InstUI is not using the 'props' param of getComponentProps(), but others are
79
80 if (typeof config.getComponentProps === 'function') {
81 componentProps = { ...componentProps,
82 ...config.getComponentProps(props)
83 };
84 }
85
86 return componentProps;
87 };
88
89 const getExampleProps = props => {
90 let exampleProps = {};
91
92 if (typeof config.getExampleProps === 'function') {
93 exampleProps = { ...config.getExampleProps(props)
94 };
95 }
96
97 return exampleProps;
98 };
99
100 const addPage = section => {
101 const page = {
102 examples: [],
103 index: section.pages.length
104 };
105 section.pages.push(page);
106 return page;
107 };
108
109 const addExample = (sectionName, example) => {
110 let section = sections.find(section => section.sectionName === sectionName);
111
112 if (!section) {
113 section = {
114 sectionName: sectionName,
115 propName: sectionProp,
116 propValue: sectionName,
117 pages: []
118 };
119 sections.push(section);
120 }
121
122 let page = section.pages[section.pages.length - 1];
123 let maxExamplesPerPage = config.maxExamplesPerPage;
124
125 if (typeof maxExamplesPerPage === 'function') {
126 maxExamplesPerPage = maxExamplesPerPage(sectionName);
127 }
128
129 if (!page) {
130 page = addPage(section);
131 } else if (maxExamplesPerPage && page.examples.length % maxExamplesPerPage === 0 && page.examples.length > 0) {
132 page = addPage(section);
133 }
134
135 page.examples.push(example);
136 }; // Serializes the given recursively, faster than JSON.stringify()
137
138
139 const fastSerialize = props => {
140 const strArr = [];
141 objToString(props, strArr);
142 return strArr.join('');
143 };
144
145 const objToString = (currObject, currString) => {
146 if (!currObject) {
147 return;
148 }
149
150 if ( /*#__PURE__*/React.isValidElement(currObject)) {
151 currString.push(JSON.stringify(currObject));
152 } else if (typeof currObject === 'object') {
153 for (const _ref of Object.entries(currObject)) {
154 var _ref2 = _slicedToArray(_ref, 2);
155
156 const key = _ref2[0];
157 const value = _ref2[1];
158 currString.push(key);
159 objToString(value, currString);
160 }
161 } else {
162 currString.push(currObject);
163 }
164 };
165
166 const maybeAddExample = props => {
167 const componentProps = mergeComponentPropsFromConfig(props);
168 const ignore = typeof filter === 'function' ? filter(componentProps) : false;
169
170 if (ignore) {
171 return;
172 }
173
174 const propsString = fastSerialize(componentProps);
175
176 if (!PROPS_CACHE.includes(propsString)) {
177 const key = nanoid();
178 const exampleProps = getExampleProps(props);
179 exampleCount++;
180
181 if (exampleCount < maxExamples) {
182 PROPS_CACHE.push(propsString);
183 let sectionName = 'Examples';
184
185 if (sectionProp && componentProps[sectionProp]) {
186 sectionName = componentProps[sectionProp];
187 }
188
189 addExample(sectionName, {
190 Component,
191 componentProps,
192 exampleProps,
193 key
194 });
195 }
196 }
197 };
198
199 if (isEmpty(config.propValues)) {
200 maybeAddExample({});
201 } else {
202 if (Array.isArray(excludeProps)) {
203 ;
204 Object.keys(config.propValues).forEach(propName => {
205 if (!excludeProps.includes(propName)) {
206 propValues[propName] = config.propValues[propName];
207 }
208 });
209 } else {
210 propValues = config.propValues;
211 } // eslint-disable-next-line no-console
212
213
214 console.info(`Generating examples for ${Component.displayName} (${Object.keys(propValues).length} props):`, propValues); // TODO reconcile the differences between these files
215 // generatePropCombinations should call getComponentProps and not do anything?
216
217 const combos = generatePropCombinations(propValues).filter(Boolean);
218 let index = 0;
219
220 while (index < combos.length && exampleCount < maxExamples) {
221 const combo = combos[index];
222
223 if (combo) {
224 maybeAddExample(combo);
225 index++;
226 }
227 }
228 }
229
230 if (exampleCount >= maxExamples) {
231 console.error(`Too many examples for ${Component.displayName}! Add a filter to the config.`);
232 } // eslint-disable-next-line no-console
233
234
235 console.info(`Generated ${exampleCount} examples for ${Component.displayName}`);
236 sections.forEach(_ref3 => {
237 let pages = _ref3.pages;
238 pages.forEach(page => {
239 // eslint-disable-next-line no-param-reassign
240 page.parameters = getParameters(page);
241 });
242 });
243 return sections;
244}
245
246function isEmpty(obj) {
247 if (typeof obj !== 'object') return true;
248
249 for (const key in obj) {
250 if (Object.hasOwnProperty.call(obj, key)) return false;
251 }
252
253 return true;
254}
255
256export default generateComponentExamples;
\No newline at end of file