1 | export { Link } from 'gatsby';
|
2 | import React, { createContext, useContext, Component, useMemo, createElement, useEffect, useRef, useState, memo } from 'react';
|
3 | import _merge from 'lodash/fp/merge';
|
4 | import equal from 'fast-deep-equal';
|
5 | import _kebabCase from 'lodash/fp/kebabCase';
|
6 | import _mapValues from 'lodash/fp/mapValues';
|
7 | import _get from 'lodash/fp/get';
|
8 | import _first from 'lodash/fp/first';
|
9 | import _assoc from 'lodash/fp/assoc';
|
10 | import { pascalCase } from 'pascal-case';
|
11 | import marksy from 'marksy';
|
12 | import sort from 'array-sort';
|
13 | import _unionBy from 'lodash/fp/unionBy';
|
14 | import _flattenDepth from 'lodash/fp/flattenDepth';
|
15 | import _omit from 'lodash/fp/omit';
|
16 | import _pipe from 'lodash/fp/pipe';
|
17 | import { ulid } from 'ulid';
|
18 | import match from 'match-sorter';
|
19 | import _throttle from 'lodash/fp/throttle';
|
20 | import { __rest } from 'tslib';
|
21 | import capitalize from 'capitalize';
|
22 |
|
23 | const DefNotFound = () => React.createElement(React.Fragment, null, "Not found");
|
24 |
|
25 | const DefLayout = ({
|
26 | children
|
27 | }) => React.createElement(React.Fragment, null, children);
|
28 |
|
29 | const DefPlayground = ({
|
30 | component,
|
31 | code
|
32 | }) => React.createElement("div", null, component, React.createElement("pre", null, code));
|
33 |
|
34 | const defaultComponents = {
|
35 | layout: DefLayout,
|
36 | notFound: DefNotFound,
|
37 | playground: DefPlayground
|
38 | };
|
39 | const ctx = createContext(defaultComponents);
|
40 | const ComponentsProvider = ({
|
41 | components: themeComponents = {},
|
42 | children
|
43 | }) => React.createElement(ctx.Provider, {
|
44 | value: Object.assign(Object.assign({}, defaultComponents), themeComponents)
|
45 | }, children);
|
46 | const useComponents = () => {
|
47 | return useContext(ctx);
|
48 | };
|
49 |
|
50 | function create(initial) {
|
51 | var _a;
|
52 |
|
53 | const ctx = createContext(initial);
|
54 | const listeners = new Set();
|
55 |
|
56 | const dispatch = fn => {
|
57 | listeners.forEach(listener => listener(fn));
|
58 | };
|
59 |
|
60 | return {
|
61 | context: ctx,
|
62 | set: fn => dispatch(fn),
|
63 | Provider: (_a = class Provider extends Component {
|
64 | constructor() {
|
65 | super(...arguments);
|
66 | this.state = this.props.initial || initial || {};
|
67 | }
|
68 |
|
69 | static getDerivedStateFromProps(props, state) {
|
70 | if (!equal(props.initial, state)) return props.initial;
|
71 | return null;
|
72 | }
|
73 |
|
74 | componentDidMount() {
|
75 | listeners.add(fn => this.setState(fn));
|
76 | }
|
77 |
|
78 | componentWillUnmount() {
|
79 | listeners.clear();
|
80 | }
|
81 |
|
82 | render() {
|
83 | return React.createElement(ctx.Provider, {
|
84 | value: this.state
|
85 | }, this.props.children);
|
86 | }
|
87 |
|
88 | }, _a.displayName = 'DoczStateProvider', _a)
|
89 | };
|
90 | }
|
91 |
|
92 | const doczState = create({});
|
93 |
|
94 | const useConfig = () => {
|
95 | const state = useContext(doczState.context);
|
96 | const {
|
97 | transform,
|
98 | config,
|
99 | themeConfig = {}
|
100 | } = state;
|
101 |
|
102 | const newConfig = _merge(themeConfig, config ? config.themeConfig : {});
|
103 |
|
104 | const transformed = transform ? transform(newConfig) : newConfig;
|
105 | return Object.assign(Object.assign({}, config), {
|
106 | themeConfig: transformed
|
107 | });
|
108 | };
|
109 |
|
110 | const useComponentProps = ({
|
111 | componentName,
|
112 | fileName
|
113 | }) => {
|
114 | const components = useComponents();
|
115 | const {
|
116 | props: stateProps
|
117 | } = useContext(doczState.context);
|
118 |
|
119 | const componentMatcher = (componentName, item) => {
|
120 | const matchingPatterns = [fileName, `/${componentName}.`, `/${_kebabCase(componentName)}.`, `/${pascalCase(componentName)}.`];
|
121 | return !!matchingPatterns.find(pattern => item.key.includes(pattern));
|
122 | };
|
123 |
|
124 | const found = stateProps && stateProps.length > 0 && stateProps.find(item => componentMatcher(componentName, item));
|
125 | const value = _get('value', found) || [];
|
126 |
|
127 | const firstDefinition = _first(value);
|
128 |
|
129 | const definition = value.find(i => i.displayName === componentName);
|
130 | const compile = useMemo(() => marksy({
|
131 | createElement,
|
132 | elements: components
|
133 | }), [components]);
|
134 | const props = useMemo(() => {
|
135 | const props = _get('props', definition || firstDefinition);
|
136 |
|
137 | const parseDescs = _mapValues(prop => {
|
138 | const desc = _get('description', prop);
|
139 |
|
140 | return !desc ? prop : _assoc('description', compile(desc).tree, prop);
|
141 | });
|
142 |
|
143 | return parseDescs(props);
|
144 | }, [compile, definition || firstDefinition]);
|
145 | return props;
|
146 | };
|
147 |
|
148 | const useCurrentDoc = () => {
|
149 | const state = useContext(doczState.context);
|
150 | return _get('currentEntry.value', state);
|
151 | };
|
152 |
|
153 | const updateState = ev => {
|
154 | const {
|
155 | type,
|
156 | payload
|
157 | } = JSON.parse(ev.data);
|
158 | const prop = type.startsWith('state.') && type.split('.')[1];
|
159 |
|
160 | if (prop) {
|
161 | doczState.set(state => Object.assign(Object.assign({}, state), {
|
162 | [prop]: payload
|
163 | }));
|
164 | }
|
165 | };
|
166 |
|
167 | const useDataServer = url => {
|
168 | useEffect(() => {
|
169 | if (!url) return;
|
170 | const socket = new WebSocket(url);
|
171 | socket.onmessage = updateState;
|
172 | return () => socket.close();
|
173 | }, []);
|
174 | };
|
175 |
|
176 | function flatArrFromObject(arr, prop) {
|
177 | const reducer = (arr, obj) => {
|
178 | const value = _get(prop)(obj);
|
179 |
|
180 | return value ? arr.concat([value]) : arr;
|
181 | };
|
182 |
|
183 | return Array.from(new Set(arr.reduce(reducer, [])));
|
184 | }
|
185 | function compare(a, b, reverse) {
|
186 | if (a < b) return reverse ? 1 : -1;
|
187 | if (a > b) return reverse ? -1 : 1;
|
188 | return 0;
|
189 | }
|
190 |
|
191 | const useDocs = () => {
|
192 | const {
|
193 | entries = []
|
194 | } = useContext(doczState.context);
|
195 | const arr = entries.map(({
|
196 | value
|
197 | }) => value);
|
198 | return sort(arr, (a, b) => compare(a.name, b.name));
|
199 | };
|
200 |
|
201 | const noMenu = entry => !entry.menu;
|
202 |
|
203 | const fromMenu = menu => entry => entry.menu === menu;
|
204 |
|
205 | const entriesOfMenu = (menu, entries) => entries.filter(fromMenu(menu));
|
206 |
|
207 | const parseMenu = entries => name => ({
|
208 | name,
|
209 | menu: entriesOfMenu(name, entries)
|
210 | });
|
211 |
|
212 | const menusFromEntries = entries => {
|
213 | const entriesWithoutMenu = entries.filter(noMenu);
|
214 | const menus = flatArrFromObject(entries, 'menu').map(parseMenu(entries));
|
215 | return _unionBy('name', menus, entriesWithoutMenu);
|
216 | };
|
217 |
|
218 | const parseItemStr = item => typeof item === 'string' ? {
|
219 | name: item
|
220 | } : item;
|
221 |
|
222 | const normalize = item => {
|
223 | const selected = parseItemStr(item);
|
224 | return Object.assign(Object.assign({}, selected), {
|
225 | id: selected.id || ulid(),
|
226 | parent: _get('parent', selected) || _get('parent', item),
|
227 | menu: Array.isArray(selected.menu) ? selected.menu.map(normalize) : selected.menu
|
228 | });
|
229 | };
|
230 |
|
231 | const clean = item => item.href || item.route ? _omit('menu', item) : item;
|
232 |
|
233 | const normalizeAndClean = _pipe(normalize, clean);
|
234 |
|
235 | const mergeMenus = (entriesMenu, configMenu) => {
|
236 | const first = entriesMenu.map(normalizeAndClean);
|
237 | const second = configMenu.map(normalizeAndClean);
|
238 |
|
239 | const merged = _unionBy('name', first, second);
|
240 |
|
241 | return merged.map(item => {
|
242 | if (!item.menu) return item;
|
243 | const found = second.find(i => i.name === item.name);
|
244 | const foundMenu = found && found.menu;
|
245 | return Object.assign(Object.assign({}, item), {
|
246 | menu: foundMenu ? mergeMenus(item.menu, foundMenu) : item.menu || found.menu
|
247 | });
|
248 | });
|
249 | };
|
250 |
|
251 | const UNKNOWN_POS = Infinity;
|
252 |
|
253 | const findPos = (item, orderedList = []) => {
|
254 | const name = typeof item !== 'string' ? _get('name', item) : item;
|
255 | const pos = orderedList.findIndex(item => item === name);
|
256 | return pos !== -1 ? pos : UNKNOWN_POS;
|
257 | };
|
258 |
|
259 | const compareWithMenu = (to = []) => (a, b) => {
|
260 | const list = to.map(i => i.name || i);
|
261 | return compare(findPos(a, list), findPos(b, list));
|
262 | };
|
263 |
|
264 | const sortByName = (a, b) => {
|
265 | return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
266 | };
|
267 |
|
268 | const sortMenus = (first, second = []) => {
|
269 | const sorted = sort(first, compareWithMenu(second), sortByName);
|
270 | return sorted.map(item => {
|
271 | if (!item.menu) return item;
|
272 | const found = second.find(menu => menu.name === item.name);
|
273 | const foundMenu = found && found.menu;
|
274 | return Object.assign(Object.assign({}, item), {
|
275 | menu: foundMenu ? sortMenus(item.menu, foundMenu) : sort(item.menu, sortByName)
|
276 | });
|
277 | });
|
278 | };
|
279 |
|
280 | const search = (val, menu) => {
|
281 | const items = menu.map(item => [item].concat(item.menu || []));
|
282 |
|
283 | const flattened = _flattenDepth(2, items);
|
284 |
|
285 | const flattenedDeduplicated = Array.from(new Set(flattened));
|
286 | return match(flattenedDeduplicated, val, {
|
287 | keys: ['name']
|
288 | });
|
289 | };
|
290 |
|
291 | const filterMenus = (items, filter) => {
|
292 | if (!filter) return items;
|
293 | return items.filter(filter).map(item => {
|
294 | if (!item.menu) return item;
|
295 | return Object.assign(Object.assign({}, item), {
|
296 | menu: item.menu.filter(filter)
|
297 | });
|
298 | });
|
299 | };
|
300 |
|
301 | const useMenus = opts => {
|
302 | const {
|
303 | query = ''
|
304 | } = opts || {};
|
305 | const {
|
306 | entries,
|
307 | config
|
308 | } = useContext(doczState.context);
|
309 | if (!entries) return null;
|
310 | const arr = entries.map(({
|
311 | value
|
312 | }) => value);
|
313 | const entriesMenu = menusFromEntries(arr);
|
314 | const sorted = useMemo(() => {
|
315 | const merged = mergeMenus(entriesMenu, config.menu);
|
316 | const result = sortMenus(merged, config.menu);
|
317 | return filterMenus(result, opts && opts.filter);
|
318 | }, [entries, config]);
|
319 | return query && query.length > 0 ? search(query, sorted) : sorted;
|
320 | };
|
321 |
|
322 | const usePrevious = (value, defaultValue) => {
|
323 | const ref = useRef(defaultValue);
|
324 | useEffect(() => {
|
325 | ref.current = value;
|
326 | });
|
327 | return ref.current;
|
328 | };
|
329 |
|
330 | const isClient = typeof window === 'object';
|
331 |
|
332 | const getSize = (initialWidth, initialHeight) => ({
|
333 | innerHeight: isClient ? window.innerHeight : initialHeight,
|
334 | innerWidth: isClient ? window.innerWidth : initialWidth,
|
335 | outerHeight: isClient ? window.outerHeight : initialHeight,
|
336 | outerWidth: isClient ? window.outerWidth : initialWidth
|
337 | });
|
338 |
|
339 | const useWindowSize = (throttleMs = 300, _initialWidth = Infinity, initialHeight = Infinity) => {
|
340 | const [windowSize, setWindowSize] = useState(getSize(initialHeight, initialHeight));
|
341 |
|
342 | const tSetWindowResize = _throttle(throttleMs, () => setWindowSize(getSize(initialHeight, initialHeight)));
|
343 |
|
344 | useEffect(() => {
|
345 | window.addEventListener('resize', tSetWindowResize);
|
346 | return () => void window.removeEventListener('resize', tSetWindowResize);
|
347 | }, []);
|
348 | return windowSize;
|
349 | };
|
350 |
|
351 | const Playground = ({
|
352 | className,
|
353 | children,
|
354 | style,
|
355 | wrapper,
|
356 | __scope,
|
357 | __position,
|
358 | __code,
|
359 | language,
|
360 | useScoping
|
361 | }) => {
|
362 | const components = useComponents();
|
363 | const PlaygroundComponent = components.playground;
|
364 | if (!PlaygroundComponent) return null;
|
365 | return React.createElement(PlaygroundComponent, {
|
366 | components: components,
|
367 | component: children,
|
368 | className: className,
|
369 | style: style,
|
370 | wrapper: wrapper,
|
371 | scope: __scope,
|
372 | position: __position,
|
373 | code: __code,
|
374 | language: language,
|
375 | useScoping: useScoping
|
376 | });
|
377 | };
|
378 |
|
379 | const RE_OBJECTOF = /(?:React\.)?(?:PropTypes\.)?objectOf\((?:React\.)?(?:PropTypes\.)?(\w+)\)/;
|
380 |
|
381 | const getTypeStr = type => {
|
382 | switch (type.name.toLowerCase()) {
|
383 | case 'instanceof':
|
384 | return `Class(${type.value})`;
|
385 |
|
386 | case 'enum':
|
387 | if (type.computed) return type.value;
|
388 | return type.value ? type.value.map(v => `${v.value}`).join(' │ ') : type.raw;
|
389 |
|
390 | case 'union':
|
391 | return type.value ? type.value.map(t => `${getTypeStr(t)}`).join(' │ ') : type.raw;
|
392 |
|
393 | case 'array':
|
394 | return type.raw;
|
395 |
|
396 | case 'arrayof':
|
397 | return `Array<${getTypeStr(type.value)}>`;
|
398 |
|
399 | case 'custom':
|
400 | if (type.raw.indexOf('function') !== -1 || type.raw.indexOf('=>') !== -1) return 'Custom(Function)';else if (type.raw.toLowerCase().indexOf('objectof') !== -1) {
|
401 | const m = type.raw.match(RE_OBJECTOF);
|
402 | if (m && m[1]) return `ObjectOf(${capitalize(m[1])})`;
|
403 | return 'ObjectOf';
|
404 | }
|
405 | return 'Custom';
|
406 |
|
407 | case 'bool':
|
408 | return 'Boolean';
|
409 |
|
410 | case 'func':
|
411 | return 'Function';
|
412 |
|
413 | case 'shape':
|
414 | const shape = type.value;
|
415 | const rst = {};
|
416 | Object.keys(shape).forEach(key => {
|
417 | rst[key] = getTypeStr(shape[key]);
|
418 | });
|
419 | return JSON.stringify(rst, null, 2);
|
420 |
|
421 | default:
|
422 | return type.name;
|
423 | }
|
424 | };
|
425 |
|
426 | const humanize = type => getTypeStr(type);
|
427 |
|
428 | const getPropType = prop => {
|
429 | const propName = _get('name', prop.flowType || prop.type);
|
430 |
|
431 | if (!propName) return null;
|
432 | const isEnum = propName.startsWith('"') || propName === 'enum';
|
433 | const name = isEnum ? 'enum' : propName;
|
434 |
|
435 | const value = _get('type.value', prop);
|
436 |
|
437 | if (!name) return null;
|
438 |
|
439 | if (isEnum && typeof value === 'string' || !prop.flowType && !isEnum && !value || prop.flowType && !prop.flowType.elements) {
|
440 | return name;
|
441 | }
|
442 |
|
443 | return prop.flowType ? humanize(prop.flowType) : humanize(prop.type);
|
444 | };
|
445 | const Props = _a => {
|
446 | var {
|
447 | title,
|
448 | isToggle,
|
449 | isRaw,
|
450 | of: component
|
451 | } = _a,
|
452 | rest = __rest(_a, ["title", "isToggle", "isRaw", "of"]);
|
453 |
|
454 | const components = useComponents();
|
455 | const PropsComponent = components.props;
|
456 |
|
457 | const fileName = _get('__filemeta.filename', component);
|
458 |
|
459 | const filemetaName = _get('__filemeta.name', component);
|
460 |
|
461 | const componentName = filemetaName || _get('displayName', component) || _get('name', component);
|
462 |
|
463 | const props = useComponentProps({
|
464 | componentName,
|
465 | fileName
|
466 | });
|
467 | if (!PropsComponent) return null;
|
468 | return React.createElement(PropsComponent, Object.assign({
|
469 | title: title,
|
470 | isRaw: isRaw,
|
471 | isToggle: isToggle,
|
472 | props: props,
|
473 | getPropType: getPropType,
|
474 | of: component
|
475 | }, rest));
|
476 | };
|
477 |
|
478 | function theme(themeConfig, transform = c => c) {
|
479 | return WrappedComponent => {
|
480 | const Theme = memo(props => {
|
481 | const {
|
482 | db,
|
483 | currentEntry,
|
484 | children
|
485 | } = props;
|
486 | const initial = Object.assign(Object.assign({}, db), {
|
487 | currentEntry,
|
488 | themeConfig,
|
489 | transform
|
490 | });
|
491 | return React.createElement(doczState.Provider, {
|
492 | initial: initial
|
493 | }, React.createElement(WrappedComponent, null, children));
|
494 | });
|
495 | Theme.displayName = WrappedComponent.displayName || 'DoczTheme';
|
496 | return Theme;
|
497 | };
|
498 | }
|
499 |
|
500 | export { ComponentsProvider, Playground, Props, doczState, theme, useComponentProps, useComponents, useConfig, useCurrentDoc, useDataServer, useDocs, useMenus, usePrevious, useWindowSize };
|