Skip to content

Commit d0fa954

Browse files
committed
chore: init repo
0 parents  commit d0fa954

File tree

288 files changed

+33013
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

288 files changed

+33013
-0
lines changed

.dumi/components/SemanticPreview.tsx

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import React from 'react';
2+
import { Col, ConfigProvider, Flex, Row, Tag, theme, Typography } from 'antd';
3+
import { createStyles, css } from 'antd-style';
4+
import classnames from 'classnames';
5+
6+
const MARK_BORDER_SIZE = 2;
7+
8+
const useStyle = createStyles(({ token }, markPos: [number, number, number, number]) => ({
9+
container: css`
10+
position: relative;
11+
`,
12+
colWrap: css`
13+
border-right: 1px solid ${token.colorBorderSecondary};
14+
display: flex;
15+
justify-content: center;
16+
align-items: center;
17+
padding: ${token.paddingMD}px;
18+
overflow: hidden;
19+
`,
20+
listWrap: css`
21+
display: flex;
22+
flex-direction: column;
23+
list-style: none;
24+
margin: 0;
25+
padding: 0;
26+
overflow: hidden;
27+
`,
28+
listItem: css`
29+
cursor: pointer;
30+
padding: ${token.paddingSM}px;
31+
transition: background-color ${token.motionDurationFast} ease;
32+
&:hover {
33+
background-color: ${token.controlItemBgHover};
34+
}
35+
&:not(:first-of-type) {
36+
border-top: 1px solid ${token.colorBorderSecondary};
37+
}
38+
`,
39+
marker: css`
40+
position: absolute;
41+
border: ${MARK_BORDER_SIZE}px solid ${token.colorWarning};
42+
box-sizing: border-box;
43+
z-index: 999999;
44+
box-shadow: 0 0 0 1px #fff;
45+
pointer-events: none;
46+
left: ${markPos[0] - MARK_BORDER_SIZE}px;
47+
top: ${markPos[1] - MARK_BORDER_SIZE}px;
48+
width: ${markPos[2] + MARK_BORDER_SIZE * 2}px;
49+
height: ${markPos[3] + MARK_BORDER_SIZE * 2}px;
50+
`,
51+
markerActive: css`
52+
opacity: 1;
53+
`,
54+
markerNotActive: css`
55+
opacity: 0;
56+
`,
57+
markerMotion: css`
58+
transition:
59+
opacity ${token.motionDurationSlow} ease,
60+
all ${token.motionDurationSlow} ease;
61+
`,
62+
markerNotMotion: css`
63+
transition: opacity ${token.motionDurationSlow} ease;
64+
`,
65+
}));
66+
67+
export interface SemanticPreviewProps {
68+
semantics: { name: string; desc: string; version?: string }[];
69+
children: React.ReactElement;
70+
height?: number;
71+
}
72+
73+
const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
74+
const { semantics = [], children, height } = props;
75+
const { token } = theme.useToken();
76+
77+
// ======================= Semantic =======================
78+
const getMarkClassName = React.useCallback(
79+
(semanticKey: string) => `semantic-mark-${semanticKey}`,
80+
[],
81+
);
82+
83+
const semanticClassNames = React.useMemo<Record<string, string>>(() => {
84+
const classNames: Record<string, string> = {};
85+
86+
semantics.forEach((semantic) => {
87+
classNames[semantic.name] = getMarkClassName(semantic.name);
88+
});
89+
90+
return classNames;
91+
}, [semantics]);
92+
93+
const cloneNode = React.cloneElement(children, {
94+
classNames: semanticClassNames,
95+
});
96+
97+
// ======================== Hover =========================
98+
const containerRef = React.useRef<HTMLDivElement>(null);
99+
100+
const timerRef = React.useRef<ReturnType<typeof setTimeout>>();
101+
102+
const [positionMotion, setPositionMotion] = React.useState<boolean>(false);
103+
const [hoverSemantic, setHoverSemantic] = React.useState<string | null>(null);
104+
const [markPos, setMarkPos] = React.useState<[number, number, number, number]>([0, 0, 0, 0]);
105+
106+
const { styles } = useStyle(markPos);
107+
108+
React.useEffect(() => {
109+
if (hoverSemantic) {
110+
const targetClassName = getMarkClassName(hoverSemantic);
111+
const targetElement = containerRef.current?.querySelector<HTMLElement>(`.${targetClassName}`);
112+
const containerRect = containerRef.current?.getBoundingClientRect();
113+
const targetRect = targetElement?.getBoundingClientRect();
114+
setMarkPos([
115+
(targetRect?.left || 0) - (containerRect?.left || 0),
116+
(targetRect?.top || 0) - (containerRect?.top || 0),
117+
targetRect?.width || 0,
118+
targetRect?.height || 0,
119+
]);
120+
timerRef.current = setTimeout(() => {
121+
setPositionMotion(true);
122+
}, 10);
123+
} else {
124+
timerRef.current = setTimeout(() => {
125+
setPositionMotion(false);
126+
}, 500);
127+
}
128+
return () => {
129+
if (timerRef.current) {
130+
clearTimeout(timerRef.current);
131+
}
132+
};
133+
}, [hoverSemantic]);
134+
135+
// ======================== Render ========================
136+
return (
137+
<div className={classnames(styles.container)} ref={containerRef}>
138+
<Row style={{ minHeight: height }}>
139+
<Col span={16} className={classnames(styles.colWrap)}>
140+
<ConfigProvider theme={{ token: { motion: false } }}>{cloneNode}</ConfigProvider>
141+
</Col>
142+
<Col span={8}>
143+
<ul className={classnames(styles.listWrap)}>
144+
{semantics.map<React.ReactNode>((semantic) => (
145+
<li
146+
key={semantic.name}
147+
className={classnames(styles.listItem)}
148+
onMouseEnter={() => setHoverSemantic(semantic.name)}
149+
onMouseLeave={() => setHoverSemantic(null)}
150+
>
151+
<Flex vertical gap="small">
152+
<Flex gap="small" align="center">
153+
<Typography.Title level={5} style={{ margin: 0 }}>
154+
{semantic.name}
155+
</Typography.Title>
156+
{semantic.version && <Tag color="blue">{semantic.version}</Tag>}
157+
</Flex>
158+
<Typography.Paragraph style={{ margin: 0, fontSize: token.fontSizeSM }}>
159+
{semantic.desc}
160+
</Typography.Paragraph>
161+
</Flex>
162+
</li>
163+
))}
164+
</ul>
165+
</Col>
166+
</Row>
167+
<div
168+
className={classnames(
169+
styles.marker,
170+
hoverSemantic ? styles.markerActive : styles.markerNotActive,
171+
positionMotion ? styles.markerMotion : styles.markerNotMotion,
172+
)}
173+
/>
174+
</div>
175+
);
176+
};
177+
178+
export default SemanticPreview;

.dumi/hooks/use.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
function use<T>(promise: PromiseLike<T>): T {
2+
const internal: PromiseLike<T> & {
3+
status?: 'pending' | 'fulfilled' | 'rejected';
4+
value?: T;
5+
reason?: any;
6+
} = promise;
7+
if (internal.status === 'fulfilled') {
8+
return internal.value as T;
9+
}
10+
if (internal.status === 'rejected') {
11+
throw internal.reason;
12+
} else if (internal.status === 'pending') {
13+
throw internal;
14+
} else {
15+
internal.status = 'pending';
16+
internal.then(
17+
(result) => {
18+
internal.status = 'fulfilled';
19+
internal.value = result;
20+
},
21+
(reason) => {
22+
internal.status = 'rejected';
23+
internal.reason = reason;
24+
},
25+
);
26+
throw internal;
27+
}
28+
}
29+
30+
export default use;

.dumi/hooks/useDark.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React from 'react';
2+
3+
export const DarkContext = React.createContext(false);
4+
5+
export default function useDark() {
6+
return React.useContext(DarkContext);
7+
}

.dumi/hooks/useFetch/cache.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export default class FetchCache {
2+
private cache: Map<string, PromiseLike<any>> = new Map();
3+
4+
get(key: string) {
5+
return this.cache.get(key);
6+
}
7+
8+
set(key: string, value: PromiseLike<any>) {
9+
this.cache.set(key, value);
10+
}
11+
12+
promise<T>(key: string, promiseFn: () => PromiseLike<T>): PromiseLike<T> {
13+
const cached = this.get(key);
14+
if (cached) {
15+
return cached;
16+
}
17+
const promise = promiseFn();
18+
this.set(key, promise);
19+
return promise;
20+
}
21+
}

.dumi/hooks/useFetch/index.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import fetch from 'cross-fetch';
2+
import use from '../use';
3+
import FetchCache from './cache';
4+
5+
const cache = new FetchCache();
6+
7+
const useFetch = <T>(options: string | { request: () => PromiseLike<T>; key: string }) => {
8+
let request;
9+
let key;
10+
if (typeof options === 'string') {
11+
request = () => fetch(options).then((res) => res.json());
12+
key = options;
13+
} else {
14+
request = options.request;
15+
key = options.key;
16+
}
17+
return use(cache.promise<T>(key, request));
18+
};
19+
20+
export default useFetch;

.dumi/hooks/useLayoutState.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { startTransition, useState } from 'react';
2+
3+
const useLayoutState: typeof useState = <S>(
4+
...args: Parameters<typeof useState<S>>
5+
): ReturnType<typeof useState<S>> => {
6+
const [state, setState] = useState<S>(...args);
7+
8+
const setLayoutState: typeof setState = (...setStateArgs) => {
9+
startTransition(() => {
10+
setState(...setStateArgs);
11+
});
12+
};
13+
14+
return [state, setLayoutState];
15+
};
16+
17+
export default useLayoutState;

.dumi/hooks/useLocale.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { useLocale as useDumiLocale } from 'dumi';
2+
3+
export interface LocaleMap<
4+
K extends PropertyKey = PropertyKey,
5+
V extends string | ((...params: any[]) => string) = string,
6+
> {
7+
cn: Record<K, V>;
8+
en: Record<K, V>;
9+
}
10+
11+
const useLocale = <
12+
K extends PropertyKey = PropertyKey,
13+
V extends string | ((...params: any[]) => string) = string,
14+
>(
15+
localeMap?: LocaleMap<K, V>,
16+
): [Record<K, V>, 'cn' | 'en'] => {
17+
const { id } = useDumiLocale();
18+
const localeType = id === 'zh-CN' ? 'cn' : 'en';
19+
return [localeMap?.[localeType]!, localeType] as const;
20+
};
21+
22+
export default useLocale;

.dumi/hooks/useLocation.ts

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { useLocation as useDumiLocation } from 'dumi';
2+
import * as React from 'react';
3+
import useLocale from './useLocale';
4+
5+
function clearPath(path: string) {
6+
return path.replace('-cn', '').replace(/\/$/, '');
7+
}
8+
9+
export default function useLocation() {
10+
const location = useDumiLocation();
11+
const { search } = location;
12+
const [, localeType] = useLocale();
13+
14+
const getLink = React.useCallback(
15+
(path: string, hash?: string | { cn: string; en: string }) => {
16+
let pathname = clearPath(path);
17+
18+
if (localeType === 'cn') {
19+
pathname = `${pathname}-cn`;
20+
}
21+
22+
if (search) {
23+
pathname = `${pathname}${search}`;
24+
}
25+
26+
if (hash) {
27+
let hashStr: string;
28+
if (typeof hash === 'object') {
29+
hashStr = hash[localeType];
30+
} else {
31+
hashStr = hash;
32+
}
33+
34+
pathname = `${pathname}#${hashStr}`;
35+
}
36+
37+
return pathname;
38+
},
39+
[localeType, search],
40+
);
41+
42+
return {
43+
...location,
44+
pathname: clearPath(location.pathname),
45+
getLink,
46+
};
47+
}

0 commit comments

Comments
 (0)