Skip to content

Commit 6a63cc7

Browse files
committed
feat(docs): sourceCode support shiki
1 parent 2f9348d commit 6a63cc7

File tree

10 files changed

+1811
-983
lines changed

10 files changed

+1811
-983
lines changed
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.source-code {
2+
&-container {
3+
position: relative;
4+
}
5+
6+
&-content {
7+
max-height: 400px;
8+
padding: 24px 20px;
9+
overflow: auto;
10+
}
11+
12+
&-shadow {
13+
box-shadow: inset 0 6px 6px -6px #00000029;
14+
width: 100%;
15+
height: 3px;
16+
position: absolute;
17+
top: 0;
18+
}
19+
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
import { useScrollWithShadow } from '../../hooks';
3+
import classNames from 'classnames';
4+
import './index.less';
5+
6+
export default function SourceCode({ jsx }: { jsx: string }) {
7+
const [ref, shadow] = useScrollWithShadow();
8+
9+
return (
10+
<div className="source-code-container">
11+
{shadow.top && <div className="source-code-shadow" />}
12+
<div
13+
ref={ref}
14+
className={classNames('source-code-content')}
15+
dangerouslySetInnerHTML={{ __html: jsx }}
16+
/>
17+
</div>
18+
);
19+
}

.dumi/theme/hooks/index.tsx

+38-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,45 @@
1-
import { useState } from 'react';
1+
import { useEffect, useMemo, useState } from 'react';
22

33
const RESPONSIVE_MOBILE = 768;
44

55
export function useMobile() {
66
const [isMobile] = useState<boolean>(window.innerWidth < RESPONSIVE_MOBILE);
77
return isMobile;
88
}
9+
10+
export function useScrollWithShadow<E extends HTMLElement>() {
11+
const [element, ref] = useState<E | null>(null);
12+
13+
const [scrollTop, setScrollTop] = useState(0);
14+
const [scrollHeight, setScrollHeight] = useState(0);
15+
const [clientHeight, setClientHeight] = useState(0);
16+
17+
useEffect(() => {
18+
if (!element) return;
19+
20+
function scrollHandler(event: Event) {
21+
const ele = event.target as E;
22+
setScrollTop(ele.scrollTop);
23+
setScrollHeight(ele.scrollHeight);
24+
setClientHeight(ele.clientHeight);
25+
}
26+
27+
element.addEventListener('scroll', scrollHandler);
28+
29+
return () => {
30+
element.removeEventListener('scroll', scrollHandler);
31+
};
32+
}, [element]);
33+
34+
const shadow = useMemo(() => {
35+
const isBottom = clientHeight === scrollHeight - scrollTop;
36+
const isTop = scrollTop === 0;
37+
const isBetween = scrollTop > 0 && clientHeight < scrollHeight - scrollTop;
38+
return {
39+
top: isBottom || isBetween,
40+
bottom: isTop || isBetween,
41+
};
42+
}, [scrollTop, scrollHeight, clientHeight]);
43+
44+
return [ref, shadow] as const;
45+
}

.dumi/theme/plugin.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { IApi } from 'dumi';
2+
import ReactTechStack from 'dumi/dist/techStacks/react';
3+
import type { ExampleBlockAsset } from 'dumi-assets-types';
4+
5+
class DTReactTech extends ReactTechStack {
6+
async generateMetadata(asset: ExampleBlockAsset) {
7+
// workaround for esm module
8+
// const { transformerTwoslash } = await import('@shikijs/twoslash');
9+
const { codeToHtml } = await import('shiki');
10+
for (const [filename, dep] of Object.entries(asset.dependencies)) {
11+
if (dep.type === 'FILE') {
12+
const html = await codeToHtml(dep.value, {
13+
lang: 'ts',
14+
theme: 'vitesse-light',
15+
// transformers: [transformerTwoslash()],
16+
});
17+
18+
asset.dependencies[filename] = <any>{ ...dep, jsx: html };
19+
}
20+
}
21+
return asset;
22+
}
23+
}
24+
25+
const AssetsPlugin = async (api: IApi) => {
26+
api.registerTechStack(() => new DTReactTech());
27+
};
28+
29+
export default AssetsPlugin;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
@import (reference) '~dumi/theme-default/styles/variables.less';
2+
@import '~dumi/theme-default/styles/utils.less';
3+
4+
.@{prefix}-previewer {
5+
&-actions {
6+
display: flex;
7+
height: 32px;
8+
align-items: center;
9+
justify-content: center;
10+
11+
&:not(:last-child) {
12+
border-bottom: 1px dashed @c-border-light;
13+
14+
@{dark-selector} & {
15+
border-bottom-color: @c-border-less-dark;
16+
}
17+
}
18+
}
19+
20+
&-action-btn {
21+
display: flex;
22+
align-items: center;
23+
justify-content: center;
24+
width: 24px;
25+
height: 24px;
26+
padding: 0;
27+
border: 0;
28+
background: transparent;
29+
cursor: pointer;
30+
31+
> svg {
32+
width: 16px;
33+
fill: darken(@c-border, 20%);
34+
transition: fill 0.2s;
35+
36+
@{dark-selector} & {
37+
fill: lighten(@c-border-dark, 20%);
38+
}
39+
}
40+
41+
&:hover > svg {
42+
fill: darken(@c-border, 30%);
43+
44+
@{dark-selector} & {
45+
fill: lighten(@c-border-dark, 30%);
46+
}
47+
}
48+
49+
&:not(:last-child) {
50+
margin-inline-end: 4px;
51+
}
52+
}
53+
54+
&-action-sketch {
55+
> select {
56+
position: absolute;
57+
appearance: none;
58+
width: 100%;
59+
height: 100%;
60+
opacity: 0;
61+
cursor: pointer;
62+
}
63+
64+
&[data-copied] > svg {
65+
fill: @c-success;
66+
}
67+
}
68+
69+
&-tabs {
70+
position: relative;
71+
padding: 0 12px;
72+
73+
&::after {
74+
content: '';
75+
position: absolute;
76+
left: 0;
77+
bottom: 0;
78+
width: 100%;
79+
height: 0;
80+
border-bottom: 1px dashed @c-border-light;
81+
82+
@{dark-selector} & {
83+
border-bottom-color: @c-border-less-dark;
84+
}
85+
}
86+
}
87+
}
88+
89+
.@{prefix}-tabs {
90+
overflow: hidden;
91+
92+
&-top {
93+
flex-direction: column;
94+
95+
.@{prefix}-tabs-ink-bar {
96+
bottom: 0;
97+
}
98+
}
99+
100+
&-nav {
101+
display: flex;
102+
103+
&-wrap {
104+
display: flex;
105+
white-space: nowrap;
106+
overflow: hidden;
107+
108+
&&-ping-left {
109+
box-shadow: 5px 0 5px -5px rgba(0, 0, 0, 10%) inset;
110+
}
111+
112+
&&-ping-right ~ * > .@{prefix}-tabs-nav-more {
113+
box-shadow: 0 0 5px rgba(0, 0, 0, 10%);
114+
}
115+
}
116+
117+
&-list {
118+
position: relative;
119+
z-index: 1;
120+
display: flex;
121+
transition: transform 0.2s;
122+
}
123+
124+
&-more {
125+
height: 100%;
126+
cursor: pointer;
127+
background: none;
128+
border: 0;
129+
transition: box-shadow 0.2s;
130+
}
131+
}
132+
133+
&-tab {
134+
display: flex;
135+
margin: 0 12px;
136+
137+
&-btn {
138+
padding: 0;
139+
color: @c-text-secondary;
140+
font-size: 14px;
141+
line-height: 36px;
142+
border: 0;
143+
outline: none;
144+
background: transparent;
145+
box-sizing: border-box;
146+
cursor: pointer;
147+
transition: all 0.2s;
148+
149+
@{dark-selector} & {
150+
color: @c-text-secondary-dark;
151+
}
152+
153+
&:hover {
154+
color: @c-text;
155+
156+
@{dark-selector} & {
157+
color: @c-text-dark;
158+
}
159+
}
160+
}
161+
162+
&-active &-btn {
163+
color: @c-text;
164+
165+
@{dark-selector} & {
166+
color: @c-text-dark;
167+
}
168+
}
169+
}
170+
171+
&-ink-bar {
172+
position: absolute;
173+
height: 1px;
174+
background: @c-primary;
175+
transition: left 0.2s, width 0.2s;
176+
pointer-events: none;
177+
178+
@{dark-selector} & {
179+
background: @c-primary-dark;
180+
}
181+
}
182+
183+
&-dropdown {
184+
position: absolute;
185+
background: inherit;
186+
border: 1px solid @c-border;
187+
max-height: 200px;
188+
overflow: auto;
189+
190+
@{dark-selector} & {
191+
border-color: @c-border-dark;
192+
}
193+
194+
> ul {
195+
list-style: none;
196+
margin: 0;
197+
padding: 0;
198+
199+
> li {
200+
padding: 4px 12px;
201+
font-size: 14px;
202+
cursor: pointer;
203+
204+
&:hover {
205+
color: @c-primary;
206+
207+
@{dark-selector} & {
208+
color: @c-primary-dark;
209+
}
210+
}
211+
212+
&:not(:last-child) {
213+
border-bottom: 1px dashed @c-border;
214+
215+
@{dark-selector} & {
216+
border-bottom-color: @c-border-dark;
217+
}
218+
}
219+
}
220+
}
221+
222+
&-hidden {
223+
display: none;
224+
}
225+
}
226+
}

0 commit comments

Comments
 (0)