@antv/dumi-theme-antv 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +9 -0
  2. package/es/antv/404/index.js +20 -0
  3. package/es/antv/Banner/Banner.module.less +412 -0
  4. package/es/antv/Banner/Notification.js +44 -0
  5. package/es/antv/Banner/Notification.module.less +108 -0
  6. package/es/antv/Banner/index.js +115 -0
  7. package/es/antv/Cases/Cases.js +124 -0
  8. package/es/antv/Cases/Cases.module.less +203 -0
  9. package/es/antv/Features/FeatureCard.js +25 -0
  10. package/es/antv/Features/FeatureCard.module.less +51 -0
  11. package/es/antv/Features/Features.module.less +169 -0
  12. package/es/antv/Features/index.js +102 -0
  13. package/es/antv/Footer/Footer.module.less +36 -0
  14. package/es/antv/Footer/index.js +232 -0
  15. package/es/antv/Header/Logo.js +130 -0
  16. package/es/antv/Products/Product.js +61 -0
  17. package/es/antv/Products/Product.module.less +146 -0
  18. package/es/antv/Products/getNewProducts.js +41 -0
  19. package/es/antv/Products/getProducts.js +466 -0
  20. package/es/antv/Products/index.js +81 -0
  21. package/es/antv/hooks.js +81 -0
  22. package/es/antv/mixins.less +21 -0
  23. package/es/antv/utils.js +49 -0
  24. package/es/builtins/API.js +37 -0
  25. package/es/builtins/Alert.js +9 -0
  26. package/es/builtins/Alert.less +62 -0
  27. package/es/builtins/Badge.js +9 -0
  28. package/es/builtins/Badge.less +31 -0
  29. package/es/builtins/Example.js +48 -0
  30. package/es/builtins/Example.less +47 -0
  31. package/es/builtins/Previewer.js +225 -0
  32. package/es/builtins/Previewer.less +406 -0
  33. package/es/builtins/SourceCode.js +72 -0
  34. package/es/builtins/SourceCode.less +103 -0
  35. package/es/builtins/Table.js +56 -0
  36. package/es/builtins/Table.less +43 -0
  37. package/es/builtins/Tree.js +219 -0
  38. package/es/builtins/Tree.less +159 -0
  39. package/es/components/Dark.js +125 -0
  40. package/es/components/Dark.less +121 -0
  41. package/es/components/LocaleSelect.js +53 -0
  42. package/es/components/LocaleSelect.less +59 -0
  43. package/es/components/Navbar.js +155 -0
  44. package/es/components/Navbar.less +180 -0
  45. package/es/components/SearchBar.js +83 -0
  46. package/es/components/SearchBar.less +165 -0
  47. package/es/components/SideMenu.js +99 -0
  48. package/es/components/SideMenu.less +379 -0
  49. package/es/components/SlugList.js +33 -0
  50. package/es/components/SlugList.less +18 -0
  51. package/es/layout.js +276 -0
  52. package/es/style/layout.less +402 -0
  53. package/es/style/markdown.less +240 -0
  54. package/es/style/variables.less +37 -0
  55. package/package.json +58 -0
  56. package/src/antv/404/index.tsx +25 -0
  57. package/src/antv/Banner/Banner.module.less +412 -0
  58. package/src/antv/Banner/Notification.module.less +108 -0
  59. package/src/antv/Banner/Notification.tsx +45 -0
  60. package/src/antv/Banner/index.tsx +121 -0
  61. package/src/antv/Cases/Cases.module.less +203 -0
  62. package/src/antv/Cases/Cases.tsx +116 -0
  63. package/src/antv/Features/FeatureCard.module.less +51 -0
  64. package/src/antv/Features/FeatureCard.tsx +24 -0
  65. package/src/antv/Features/Features.module.less +169 -0
  66. package/src/antv/Features/index.tsx +86 -0
  67. package/src/antv/Footer/Footer.module.less +36 -0
  68. package/src/antv/Footer/index.tsx +272 -0
  69. package/src/antv/Header/Logo.tsx +85 -0
  70. package/src/antv/Products/Product.module.less +146 -0
  71. package/src/antv/Products/Product.tsx +80 -0
  72. package/src/antv/Products/getNewProducts.tsx +53 -0
  73. package/src/antv/Products/getProducts.tsx +626 -0
  74. package/src/antv/Products/index.tsx +70 -0
  75. package/src/antv/hooks.ts +87 -0
  76. package/src/antv/mixins.less +21 -0
  77. package/src/antv/utils.ts +44 -0
  78. package/src/builtins/API.tsx +57 -0
  79. package/src/builtins/Alert.less +62 -0
  80. package/src/builtins/Alert.tsx +4 -0
  81. package/src/builtins/Badge.less +31 -0
  82. package/src/builtins/Badge.tsx +4 -0
  83. package/src/builtins/Example.less +47 -0
  84. package/src/builtins/Example.tsx +34 -0
  85. package/src/builtins/Previewer.less +406 -0
  86. package/src/builtins/Previewer.tsx +264 -0
  87. package/src/builtins/SourceCode.less +103 -0
  88. package/src/builtins/SourceCode.tsx +55 -0
  89. package/src/builtins/Table.less +43 -0
  90. package/src/builtins/Table.tsx +42 -0
  91. package/src/builtins/Tree.less +159 -0
  92. package/src/builtins/Tree.tsx +130 -0
  93. package/src/components/Dark.less +121 -0
  94. package/src/components/Dark.tsx +78 -0
  95. package/src/components/LocaleSelect.less +59 -0
  96. package/src/components/LocaleSelect.tsx +53 -0
  97. package/src/components/Navbar.less +180 -0
  98. package/src/components/Navbar.tsx +152 -0
  99. package/src/components/SearchBar.less +165 -0
  100. package/src/components/SearchBar.tsx +68 -0
  101. package/src/components/SideMenu.less +379 -0
  102. package/src/components/SideMenu.tsx +148 -0
  103. package/src/components/SlugList.less +18 -0
  104. package/src/components/SlugList.tsx +20 -0
  105. package/src/layout.tsx +225 -0
  106. package/src/style/layout.less +402 -0
  107. package/src/style/markdown.less +240 -0
  108. package/src/style/variables.less +37 -0
  109. package/src/test/SearchBar.test.ts +32 -0
  110. package/src/test/Table.test.tsx +41 -0
  111. package/src/test/index.test.tsx +377 -0
@@ -0,0 +1,406 @@
1
+ @import (reference) '../style/variables.less';
2
+
3
+ .@{prefix}-previewer {
4
+ background-color: #fff;
5
+ border: 1px solid @c-border;
6
+ border-radius: 1px;
7
+
8
+ [data-prefers-color=dark] & {
9
+ background-color: @c-bg-dark;
10
+ border-color: @c-border-dark;
11
+ }
12
+
13
+ &[data-debug] {
14
+ margin-top: 32px;
15
+ border-color: #ffcb00;
16
+
17
+ &::before {
18
+ content: 'DEV ONLY';
19
+ float: left;
20
+ margin-left: -1px;
21
+ margin-top: -18px;
22
+ padding: 3px 6px;
23
+ font-size: 12px;
24
+ line-height: 1;
25
+ background-color: #ffcb00;
26
+ color: #735600;
27
+ text-shadow: 0.5px 0.5px 0 rgba(255, 255, 255, 0.5);
28
+ border-top-left-radius: 1px;
29
+ border-top-right-radius: 1px;
30
+ }
31
+ }
32
+
33
+ &[data-iframe] {
34
+ .@{prefix}-previewer-browser-nav {
35
+ padding: 2px 6px;
36
+ background-color: @c-border;
37
+
38
+ &::before {
39
+ @s-btn: 12px;
40
+ @s-btn-gap: 8px;
41
+
42
+ content: '';
43
+ display: inline-block;
44
+ width: @s-btn;
45
+ height: @s-btn;
46
+ border-radius: 50%;
47
+ background-color: #fd6458;
48
+ box-shadow: (@s-btn + @s-btn-gap) 0 0 #ffbf2b, (@s-btn + @s-btn-gap) * 2 0 0 #24cc3d;
49
+ }
50
+ }
51
+
52
+ .@{prefix}-previewer-demo > iframe {
53
+ border: 0;
54
+ width: 100%;
55
+ height: 300px;
56
+ }
57
+ }
58
+
59
+ + .@{prefix}-previewer {
60
+ margin-top: 32px;
61
+ }
62
+
63
+ &-demo {
64
+ padding: 40px 24px;
65
+ }
66
+
67
+ &-target {
68
+ border-color: fade(@c-primary, 50%);
69
+ box-shadow: 0 0 0 5px fade(@c-primary, 5%);
70
+
71
+ [data-prefers-color=dark] & {
72
+ box-shadow: 0 0 0 5px fade(@c-primary, 30%);
73
+ }
74
+ }
75
+
76
+ &-desc {
77
+ > div:last-child {
78
+ padding: 1.2em 1em 1em;
79
+ color: @c-text;
80
+ border-top: 1px solid @c-border;
81
+
82
+ [data-prefers-color=dark] & {
83
+ border-color: @c-border-dark;
84
+ }
85
+ }
86
+
87
+ .markdown > p:first-child {
88
+ margin-top: 0;
89
+ }
90
+
91
+ .markdown > p:last-child {
92
+ margin-bottom: 0;
93
+ }
94
+
95
+ &[data-title] {
96
+ position: relative;
97
+
98
+ > a:first-child {
99
+ position: absolute;
100
+ top: 0;
101
+ left: 1em;
102
+ margin-left: -4px;
103
+ padding: 0 4px;
104
+ color: @c-heading;
105
+ font-size: inherit;
106
+ font-weight: 500;
107
+ background: linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 1) 50%, rgba(255, 255, 255, 0)) 100%;
108
+ transform: translateY(-50%);
109
+ pointer-events: auto;
110
+ cursor: pointer;
111
+
112
+ [data-prefers-color=dark] & {
113
+ color: @c-heading-dark;
114
+ background: linear-gradient(to top, @c-bg-dark, @c-bg-dark 50%, rgba(255, 255, 255, 0)) 100%;
115
+ }
116
+ }
117
+
118
+ &:empty {
119
+ padding-top: 0;
120
+
121
+ // modify action area style when only has title field
122
+ + .@{prefix}-previewer-actions {
123
+ height: 46px;
124
+ border-top-style: solid;
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+ &-actions {
131
+ display: flex;
132
+ height: 40px;
133
+ padding: 0 1em;
134
+ align-items: center;
135
+ border-top: 1px dashed @c-border;
136
+
137
+ [data-prefers-color=dark] & {
138
+ border-color: @c-border-dark;
139
+ }
140
+
141
+ > a:not(:last-child),
142
+ > button:not(:last-child) {
143
+ margin-right: 8px;
144
+ }
145
+
146
+ > a {
147
+ display: flex;
148
+ }
149
+
150
+ button {
151
+ position: relative;
152
+ display: inline-block;
153
+ width: 16px;
154
+ height: 16px;
155
+ padding: 0;
156
+ border: 0;
157
+ box-sizing: border-box;
158
+ cursor: pointer;
159
+ opacity: 0.6;
160
+ outline: none;
161
+ transition: opacity 0.2s, background 0.2s;
162
+
163
+ [data-prefers-color=dark] & {
164
+ opacity: 1;
165
+ }
166
+
167
+ // expand click area
168
+ &::after {
169
+ content: '';
170
+ position: absolute;
171
+ top: -8px;
172
+ left: -8px;
173
+ right: -8px;
174
+ bottom: -8px;
175
+ }
176
+
177
+ &:hover {
178
+ opacity: 0.8;
179
+ }
180
+
181
+ &:active {
182
+ opacity: 0.9;
183
+ }
184
+
185
+ &:disabled {
186
+ opacity: 0.2;
187
+ cursor: not-allowed;
188
+ }
189
+
190
+ &[role='codesandbox'] {
191
+ background-position: -18px 0;
192
+ }
193
+
194
+ &[role='codepen'] {
195
+ background-position: -36px 0;
196
+ }
197
+
198
+ &[role='source'] {
199
+ background-position: -72px 0;
200
+ }
201
+
202
+ &[role='change-jsx'] {
203
+ background-position: -90px 0;
204
+ }
205
+
206
+ &[role='change-tsx'] {
207
+ background-position: -108px 0;
208
+ }
209
+
210
+ &[role='open-demo'] {
211
+ background-position: -126px 0;
212
+ }
213
+
214
+ &[role='motions'] {
215
+ background-position: -162px 0;
216
+ }
217
+
218
+ &[role='sketch-component'] {
219
+ background-position: -182px 0;
220
+ }
221
+
222
+ &[role='sketch-group'] {
223
+ background-position: -200px 0;
224
+ }
225
+
226
+ &[role='copy'][data-status='ready'] {
227
+ background-position: -54px 0;
228
+ }
229
+
230
+ &[role='copy'][data-status='copied'] {
231
+ pointer-events: none;
232
+ background-position: -54px -16px;
233
+ }
234
+
235
+ &[role='refresh'] {
236
+ background-position-x: -144px;
237
+ }
238
+ }
239
+
240
+ // split action buttons by a blank node
241
+ > span {
242
+ flex: 1;
243
+ display: inline-block;
244
+ }
245
+ }
246
+
247
+ &-source {
248
+ border-top: 1px dashed @c-border;
249
+
250
+ [data-prefers-color=dark] & {
251
+ border-color: @c-border-dark;
252
+ }
253
+
254
+ &-tab {
255
+ border-top: 1px dashed @c-border;
256
+
257
+ [data-prefers-color=dark] & {
258
+ border-color: @c-border-dark;
259
+ }
260
+
261
+ .@{prefix}-tabs-tab-btn {
262
+ position: relative;
263
+ padding-left: 32px;
264
+
265
+ &::before,
266
+ &::after {
267
+ content: '';
268
+ position: absolute;
269
+ margin-right: 4px;
270
+ display: inline-block;
271
+ box-sizing: border-box;
272
+ }
273
+
274
+ &::before {
275
+ left: 16px;
276
+ top: 50%;
277
+ margin-top: -6px;
278
+ width: 10px;
279
+ height: 12px;
280
+ border: 1px solid @c-secondary;
281
+ }
282
+
283
+ &::after {
284
+ top: 50%;
285
+ left: 23px;
286
+ margin-top: -7px;
287
+ width: 4px;
288
+ height: 4px;
289
+ background: #fff;
290
+ border-bottom: 1px solid @c-secondary;
291
+ transform: rotate(45deg);
292
+ [data-prefers-color=dark] & {
293
+ background: @c-bg-dark;
294
+ }
295
+ }
296
+ }
297
+ }
298
+ }
299
+ }
300
+
301
+ .@{prefix}-tabs {
302
+ overflow: hidden;
303
+
304
+ &.@{prefix}-tabs-top {
305
+ flex-direction: column;
306
+
307
+ .@{prefix}-tabs-ink-bar {
308
+ bottom: 0;
309
+ }
310
+ }
311
+
312
+ &-nav {
313
+ display: flex;
314
+
315
+ &-wrap {
316
+ display: flex;
317
+ white-space: nowrap;
318
+ overflow: hidden;
319
+
320
+ &&-ping-left {
321
+ box-shadow: 5px 0 5px -5px rgba(0, 0, 0, 0.1) inset;
322
+ }
323
+
324
+ &&-ping-right ~ * > .@{prefix}-tabs-nav-more {
325
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
326
+ }
327
+ }
328
+
329
+ &-list {
330
+ position: relative;
331
+ display: flex;
332
+ transition: transform 0.2s;
333
+ }
334
+
335
+ &-more {
336
+ height: 100%;
337
+ cursor: pointer;
338
+ background: none;
339
+ border: 0;
340
+ transition: box-shadow 0.2s;
341
+ }
342
+ }
343
+
344
+ &-tab {
345
+ display: flex;
346
+
347
+ &-btn {
348
+ padding: 0 16px;
349
+ font-size: 14px;
350
+ line-height: 36px;
351
+ border: 0;
352
+ outline: none;
353
+ background: transparent;
354
+ box-sizing: border-box;
355
+ cursor: pointer;
356
+
357
+ &:hover {
358
+ color: @c-primary;
359
+ }
360
+ }
361
+ }
362
+
363
+ &-ink-bar {
364
+ position: absolute;
365
+ height: 2px;
366
+ background: @c-primary;
367
+ transition: left 0.2s, width 0.2s;
368
+ pointer-events: none;
369
+
370
+ [data-prefers-color=dark] & {
371
+ background: @c-primary-dark;
372
+ }
373
+ }
374
+
375
+ &-dropdown {
376
+ position: absolute;
377
+ background: #fff;
378
+ border: 1px solid @c-border;
379
+ max-height: 200px;
380
+ overflow: auto;
381
+
382
+ > ul {
383
+ list-style: none;
384
+ margin: 0;
385
+ padding: 0;
386
+
387
+ > li {
388
+ padding: 4px 12px;
389
+ font-size: 14px;
390
+ cursor: pointer;
391
+
392
+ &:hover {
393
+ color: @c-primary;
394
+ }
395
+
396
+ &:not(:last-child) {
397
+ border-bottom: 1px dashed @c-border;
398
+ }
399
+ }
400
+ }
401
+
402
+ &-hidden {
403
+ display: none;
404
+ }
405
+ }
406
+ }
@@ -0,0 +1,264 @@
1
+ import React, { useState, useContext, useRef, useEffect } from 'react';
2
+ import Tabs, { TabPane } from 'rc-tabs';
3
+ // @ts-ignore
4
+ import { history } from 'dumi';
5
+ import type { IPreviewerComponentProps } from 'dumi/theme';
6
+ import {
7
+ context,
8
+ useCodeSandbox,
9
+ useRiddle,
10
+ useMotions,
11
+ useCopy,
12
+ useLocaleProps,
13
+ useDemoUrl,
14
+ useTSPlaygroundUrl,
15
+ Link,
16
+ AnchorLink,
17
+ usePrefersColor,
18
+ } from 'dumi/theme';
19
+ import type { ICodeBlockProps } from './SourceCode';
20
+ import SourceCode from './SourceCode';
21
+ import './Previewer.less';
22
+
23
+ export interface IPreviewerProps extends IPreviewerComponentProps {
24
+ /**
25
+ * enable transform to change CSS containing block for demo
26
+ */
27
+ transform?: boolean;
28
+ /**
29
+ * modify background for demo area
30
+ */
31
+ background?: string;
32
+ /**
33
+ * collapse padding of demo area
34
+ */
35
+ compact?: boolean;
36
+ /**
37
+ * configurations for action button
38
+ */
39
+ hideActions?: ('CSB' | 'EXTERNAL' | 'RIDDLE')[];
40
+ /**
41
+ * show source code by default
42
+ */
43
+ defaultShowCode?: boolean;
44
+ /**
45
+ * use iframe mode for this demo
46
+ */
47
+ iframe?: true | number;
48
+ /**
49
+ * replace builtin demo url
50
+ */
51
+ demoUrl?: string;
52
+ /**
53
+ * control action bar render
54
+ */
55
+ actionBarRender?: (actionBarNode: React.ReactNode) => React.ReactNode;
56
+ }
57
+
58
+ /**
59
+ * get source code type for file
60
+ * @param file file path
61
+ * @param source file source object
62
+ */
63
+ function getSourceType(file: string, source: IPreviewerComponentProps['sources']['_']) {
64
+ // use file extension as source type first
65
+ let type = file.match(/\.(\w+)$/)?.[1];
66
+
67
+ if (!type) {
68
+ type = source.tsx ? 'tsx' : 'jsx';
69
+ }
70
+
71
+ return type as ICodeBlockProps['lang'];
72
+ }
73
+
74
+ const Previewer: React.FC<IPreviewerProps> = oProps => {
75
+ const demoRef = useRef();
76
+ const { locale } = useContext(context);
77
+ const props = useLocaleProps<IPreviewerProps>(locale, oProps);
78
+ const builtinDemoUrl = useDemoUrl(props.identifier);
79
+ const demoUrl = props.demoUrl || builtinDemoUrl;
80
+ const isActive = history?.location.hash === `#${props.identifier}`;
81
+ const isSingleFile = Object.keys(props.sources).length === 1;
82
+ const openCSB = useCodeSandbox(props.hideActions?.includes('CSB') ? null : props);
83
+ const openRiddle = useRiddle(props.hideActions?.includes('RIDDLE') ? null : props);
84
+ const [execMotions, isMotionRunning] = useMotions(props.motions || [], demoRef.current);
85
+ const [copyCode, copyStatus] = useCopy();
86
+ const [currentFile, setCurrentFile] = useState(() =>
87
+ props.sources._ ? '_' : Object.keys(props.sources)[0],
88
+ );
89
+ const [sourceType, setSourceType] = useState(
90
+ getSourceType(currentFile, props.sources[currentFile]),
91
+ );
92
+ const [showSource, setShowSource] = useState(Boolean(props.defaultShowCode));
93
+ const [iframeKey, setIframeKey] = useState(Math.random());
94
+ const currentFileCode =
95
+ props.sources[currentFile][sourceType] || props.sources[currentFile].content;
96
+ const playgroundUrl = useTSPlaygroundUrl(locale, currentFileCode);
97
+ const iframeRef = useRef<HTMLIFrameElement>();
98
+ const [color] = usePrefersColor();
99
+ const { actionBarRender = o => o } = props;
100
+
101
+ // re-render iframe if prefers color changed
102
+ useEffect(() => {
103
+ setIframeKey(Math.random());
104
+ }, [color]);
105
+
106
+ function handleFileChange(filename: string) {
107
+ setCurrentFile(filename);
108
+ setSourceType(getSourceType(filename, props.sources[filename]));
109
+ }
110
+
111
+ return (
112
+ <div
113
+ style={props.style}
114
+ className={[
115
+ props.className,
116
+ '__dumi-default-previewer',
117
+ isActive ? '__dumi-default-previewer-target' : '',
118
+ ]
119
+ .filter(Boolean)
120
+ .join(' ')}
121
+ id={props.identifier}
122
+ data-debug={props.debug || undefined}
123
+ data-iframe={props.iframe || undefined}
124
+ >
125
+ {props.iframe && <div className="__dumi-default-previewer-browser-nav" />}
126
+ <div
127
+ ref={demoRef}
128
+ className="__dumi-default-previewer-demo"
129
+ style={{
130
+ transform: props.transform ? 'translate(0, 0)' : undefined,
131
+ padding: props.compact || (props.iframe && props.compact !== false) ? '0' : undefined,
132
+ background: props.background,
133
+ }}
134
+ >
135
+ {props.iframe ? (
136
+ <iframe
137
+ title="dumi-previewer"
138
+ style={{
139
+ // both compatible with unit or non-unit, such as 100, 100px, 100vh
140
+ height: String(props.iframe).replace(/(\d)$/, '$1px'),
141
+ }}
142
+ key={iframeKey}
143
+ src={demoUrl}
144
+ ref={iframeRef}
145
+ />
146
+ ) : (
147
+ props.children
148
+ )}
149
+ </div>
150
+ <div className="__dumi-default-previewer-desc" data-title={props.title}>
151
+ {props.title && <AnchorLink to={`#${props.identifier}`}>{props.title}</AnchorLink>}
152
+ {props.description && (
153
+ <div
154
+ // eslint-disable-next-line
155
+ dangerouslySetInnerHTML={{ __html: props.description }}
156
+ />
157
+ )}
158
+ </div>
159
+ <div className="__dumi-default-previewer-actions">
160
+ {actionBarRender(
161
+ <>
162
+ {openCSB && (
163
+ <button
164
+ title="Open demo on CodeSandbox.io"
165
+ className="__dumi-default-icon"
166
+ role="codesandbox"
167
+ onClick={openCSB}
168
+ />
169
+ )}
170
+ {openRiddle && (
171
+ <button
172
+ title="Open demo on Riddle"
173
+ className="__dumi-default-icon"
174
+ role="riddle"
175
+ onClick={openRiddle}
176
+ />
177
+ )}
178
+ {props.motions && (
179
+ <button
180
+ title="Execute motions"
181
+ className="__dumi-default-icon"
182
+ role="motions"
183
+ disabled={isMotionRunning}
184
+ onClick={() => execMotions()}
185
+ />
186
+ )}
187
+ {props.iframe && (
188
+ <button
189
+ title="Reload demo iframe page"
190
+ className="__dumi-default-icon"
191
+ role="refresh"
192
+ onClick={() => setIframeKey(Math.random())}
193
+ />
194
+ )}
195
+ {!props.hideActions?.includes('EXTERNAL') && (
196
+ <Link target="_blank" to={demoUrl}>
197
+ <button
198
+ title="Open demo in new tab"
199
+ className="__dumi-default-icon"
200
+ role="open-demo"
201
+ type="button"
202
+ />
203
+ </Link>
204
+ )}
205
+ <span />
206
+ <button
207
+ title="Copy source code"
208
+ className="__dumi-default-icon"
209
+ role="copy"
210
+ data-status={copyStatus}
211
+ onClick={() => copyCode(currentFileCode)}
212
+ />
213
+ {sourceType === 'tsx' && showSource && (
214
+ <Link target="_blank" to={playgroundUrl}>
215
+ <button
216
+ title="Get JSX via TypeScript Playground"
217
+ className="__dumi-default-icon"
218
+ role="change-tsx"
219
+ type="button"
220
+ />
221
+ </Link>
222
+ )}
223
+ <button
224
+ title="Toggle source code panel"
225
+ className={`__dumi-default-icon${showSource ? ' __dumi-default-btn-expand' : ''}`}
226
+ role="source"
227
+ type="button"
228
+ onClick={() => setShowSource(!showSource)}
229
+ />
230
+ </>,
231
+ )}
232
+ </div>
233
+ {showSource && (
234
+ <div className="__dumi-default-previewer-source-wrapper">
235
+ {!isSingleFile && (
236
+ <Tabs
237
+ className="__dumi-default-previewer-source-tab"
238
+ prefixCls="__dumi-default-tabs"
239
+ moreIcon="···"
240
+ defaultActiveKey={currentFile}
241
+ onChange={handleFileChange}
242
+ >
243
+ {Object.keys(props.sources).map(filename => (
244
+ <TabPane
245
+ tab={
246
+ filename === '_'
247
+ ? `index.${getSourceType(filename, props.sources[filename])}`
248
+ : filename
249
+ }
250
+ key={filename}
251
+ />
252
+ ))}
253
+ </Tabs>
254
+ )}
255
+ <div className="__dumi-default-previewer-source">
256
+ <SourceCode code={currentFileCode} lang={sourceType} showCopy={false} />
257
+ </div>
258
+ </div>
259
+ )}
260
+ </div>
261
+ );
262
+ };
263
+
264
+ export default Previewer;