UNPKG

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