-
-
Notifications
You must be signed in to change notification settings - Fork 189
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 72d7a7a
Showing
1,007 changed files
with
38,255 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
import { XProvider } from '@ant-design/x'; | ||
import { Col, Flex, Row, Tag, Typography, theme } from 'antd'; | ||
import { createStyles, css } from 'antd-style'; | ||
import classnames from 'classnames'; | ||
import React from 'react'; | ||
|
||
const MARK_BORDER_SIZE = 2; | ||
|
||
const useStyle = createStyles(({ token }, markPos: [number, number, number, number]) => ({ | ||
container: css` | ||
position: relative; | ||
`, | ||
colWrap: css` | ||
border-right: 1px solid ${token.colorBorderSecondary}; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
padding: ${token.paddingMD}px; | ||
overflow: hidden; | ||
`, | ||
listWrap: css` | ||
display: flex; | ||
flex-direction: column; | ||
list-style: none; | ||
margin: 0; | ||
padding: 0; | ||
overflow: hidden; | ||
`, | ||
listItem: css` | ||
cursor: pointer; | ||
padding: ${token.paddingSM}px; | ||
transition: background-color ${token.motionDurationFast} ease; | ||
&:hover { | ||
background-color: ${token.controlItemBgHover}; | ||
} | ||
&:not(:first-of-type) { | ||
border-top: 1px solid ${token.colorBorderSecondary}; | ||
} | ||
`, | ||
marker: css` | ||
position: absolute; | ||
border: ${MARK_BORDER_SIZE}px solid ${token.colorWarning}; | ||
box-sizing: border-box; | ||
z-index: 999999; | ||
box-shadow: 0 0 0 1px #fff; | ||
pointer-events: none; | ||
inset-inline-start: ${markPos[0] - MARK_BORDER_SIZE}px; | ||
top: ${markPos[1] - MARK_BORDER_SIZE}px; | ||
width: ${markPos[2] + MARK_BORDER_SIZE * 2}px; | ||
height: ${markPos[3] + MARK_BORDER_SIZE * 2}px; | ||
`, | ||
markerActive: css` | ||
opacity: 1; | ||
`, | ||
markerNotActive: css` | ||
opacity: 0; | ||
`, | ||
markerMotion: css` | ||
transition: | ||
opacity ${token.motionDurationSlow} ease, | ||
all ${token.motionDurationSlow} ease; | ||
`, | ||
markerNotMotion: css` | ||
transition: opacity ${token.motionDurationSlow} ease; | ||
`, | ||
})); | ||
|
||
export interface SemanticPreviewProps { | ||
semantics: { name: string; desc: string; version?: string }[]; | ||
children: React.ReactElement | ((injectProps: any) => React.ReactElement); | ||
height?: number; | ||
} | ||
|
||
const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => { | ||
const { semantics = [], children, height } = props; | ||
const { token } = theme.useToken(); | ||
|
||
// ======================= Semantic ======================= | ||
const getMarkClassName = React.useCallback( | ||
(semanticKey: string) => `semantic-mark-${semanticKey}`, | ||
[], | ||
); | ||
|
||
const semanticClassNames = React.useMemo<Record<string, string>>(() => { | ||
const classNames: Record<string, string> = {}; | ||
|
||
semantics.forEach((semantic) => { | ||
classNames[semantic.name] = getMarkClassName(semantic.name); | ||
}); | ||
|
||
return classNames; | ||
}, [semantics]); | ||
|
||
const injectProps = { | ||
classNames: semanticClassNames, | ||
}; | ||
|
||
const cloneNode = | ||
typeof children === 'function' | ||
? children(injectProps) | ||
: React.cloneElement(children, injectProps); | ||
|
||
// ======================== Hover ========================= | ||
const containerRef = React.useRef<HTMLDivElement>(null); | ||
|
||
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(); | ||
|
||
const [positionMotion, setPositionMotion] = React.useState<boolean>(false); | ||
const [hoverSemantic, setHoverSemantic] = React.useState<string | null>(null); | ||
const [markPos, setMarkPos] = React.useState<[number, number, number, number]>([0, 0, 0, 0]); | ||
|
||
const { styles } = useStyle(markPos); | ||
|
||
React.useEffect(() => { | ||
if (hoverSemantic) { | ||
const targetClassName = getMarkClassName(hoverSemantic); | ||
const targetElement = containerRef.current?.querySelector<HTMLElement>(`.${targetClassName}`); | ||
const containerRect = containerRef.current?.getBoundingClientRect(); | ||
const targetRect = targetElement?.getBoundingClientRect(); | ||
setMarkPos([ | ||
(targetRect?.left || 0) - (containerRect?.left || 0), | ||
(targetRect?.top || 0) - (containerRect?.top || 0), | ||
targetRect?.width || 0, | ||
targetRect?.height || 0, | ||
]); | ||
timerRef.current = setTimeout(() => { | ||
setPositionMotion(true); | ||
}, 10); | ||
} else { | ||
timerRef.current = setTimeout(() => { | ||
setPositionMotion(false); | ||
}, 500); | ||
} | ||
return () => { | ||
if (timerRef.current) { | ||
clearTimeout(timerRef.current); | ||
} | ||
}; | ||
}, [hoverSemantic]); | ||
|
||
// ======================== Render ======================== | ||
return ( | ||
<div className={classnames(styles.container)} ref={containerRef}> | ||
<Row style={{ minHeight: height }}> | ||
<Col span={16} className={classnames(styles.colWrap)}> | ||
<XProvider theme={{ token: { motion: false } }}>{cloneNode}</XProvider> | ||
</Col> | ||
<Col span={8}> | ||
<ul className={classnames(styles.listWrap)}> | ||
{semantics.map<React.ReactNode>((semantic) => ( | ||
<li | ||
key={semantic.name} | ||
className={classnames(styles.listItem)} | ||
onMouseEnter={() => setHoverSemantic(semantic.name)} | ||
onMouseLeave={() => setHoverSemantic(null)} | ||
> | ||
<Flex vertical gap="small"> | ||
<Flex gap="small" align="center"> | ||
<Typography.Title level={5} style={{ margin: 0 }}> | ||
{semantic.name} | ||
</Typography.Title> | ||
{semantic.version && <Tag color="blue">{semantic.version}</Tag>} | ||
</Flex> | ||
<Typography.Paragraph style={{ margin: 0, fontSize: token.fontSizeSM }}> | ||
{semantic.desc} | ||
</Typography.Paragraph> | ||
</Flex> | ||
</li> | ||
))} | ||
</ul> | ||
</Col> | ||
</Row> | ||
<div | ||
className={classnames( | ||
styles.marker, | ||
hoverSemantic ? styles.markerActive : styles.markerNotActive, | ||
positionMotion ? styles.markerMotion : styles.markerNotMotion, | ||
)} | ||
/> | ||
</div> | ||
); | ||
}; | ||
|
||
export default SemanticPreview; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
.demo-logo { | ||
width: 120px; | ||
min-width: 120px; | ||
height: 32px; | ||
background: rgba(255, 255, 255, 0.2); | ||
border-radius: 6px; | ||
margin-inline-end: 24px; | ||
} | ||
|
||
.demo-logo-vertical { | ||
height: 32px; | ||
margin: 16px; | ||
background: rgba(255, 255, 255, 0.2); | ||
border-radius: 6px; | ||
} | ||
|
||
html { | ||
scrollbar-width: thin; | ||
scrollbar-color: unset; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
function use<T>(promise: PromiseLike<T>): T { | ||
const internal: PromiseLike<T> & { | ||
status?: 'pending' | 'fulfilled' | 'rejected'; | ||
value?: T; | ||
reason?: any; | ||
} = promise; | ||
if (internal.status === 'fulfilled') { | ||
return internal.value as T; | ||
} | ||
if (internal.status === 'rejected') { | ||
throw internal.reason; | ||
} | ||
if (internal.status === 'pending') { | ||
throw internal; | ||
} | ||
internal.status = 'pending'; | ||
internal.then( | ||
(result) => { | ||
internal.status = 'fulfilled'; | ||
internal.value = result; | ||
}, | ||
(reason) => { | ||
internal.status = 'rejected'; | ||
internal.reason = reason; | ||
}, | ||
); | ||
throw internal; | ||
} | ||
|
||
export default use; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import React from 'react'; | ||
|
||
export const DarkContext = React.createContext(false); | ||
|
||
export default function useDark() { | ||
return React.useContext(DarkContext); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
export default class FetchCache { | ||
private cache: Map<string, PromiseLike<any>> = new Map(); | ||
|
||
get(key: string) { | ||
return this.cache.get(key); | ||
} | ||
|
||
set(key: string, value: PromiseLike<any>) { | ||
this.cache.set(key, value); | ||
} | ||
|
||
promise<T>(key: string, promiseFn: () => PromiseLike<T>): PromiseLike<T> { | ||
const cached = this.get(key); | ||
if (cached) { | ||
return cached; | ||
} | ||
const promise = promiseFn(); | ||
this.set(key, promise); | ||
return promise; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import fetch from 'cross-fetch'; | ||
|
||
import use from '../use'; | ||
import FetchCache from './cache'; | ||
|
||
const cache = new FetchCache(); | ||
|
||
const useFetch = <T>(options: string | { request: () => PromiseLike<T>; key: string }) => { | ||
let request; | ||
let key; | ||
if (typeof options === 'string') { | ||
request = () => fetch(options).then((res) => res.json()); | ||
key = options; | ||
} else { | ||
request = options.request; | ||
key = options.key; | ||
} | ||
return use(cache.promise<T>(key, request)); | ||
}; | ||
|
||
export default useFetch; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { startTransition, useState } from 'react'; | ||
|
||
const useLayoutState: typeof useState = <S>( | ||
...args: Parameters<typeof useState<S>> | ||
): ReturnType<typeof useState<S>> => { | ||
const [state, setState] = useState<S>(...args); | ||
|
||
const setLayoutState: typeof setState = (...setStateArgs) => { | ||
startTransition(() => { | ||
setState(...setStateArgs); | ||
}); | ||
}; | ||
|
||
return [state, setLayoutState]; | ||
}; | ||
|
||
export default useLayoutState; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { useLocale as useDumiLocale } from 'dumi'; | ||
|
||
export interface LocaleMap< | ||
K extends PropertyKey = PropertyKey, | ||
V extends string | ((...params: any[]) => string) = string, | ||
> { | ||
cn: Record<K, V>; | ||
en: Record<K, V>; | ||
} | ||
|
||
const useLocale = < | ||
K extends PropertyKey = PropertyKey, | ||
V extends string | ((...params: any[]) => string) = string, | ||
>( | ||
localeMap?: LocaleMap<K, V>, | ||
): [Record<K, V>, 'cn' | 'en'] => { | ||
const { id } = useDumiLocale(); | ||
const localeType = id === 'zh-CN' ? 'cn' : 'en'; | ||
return [localeMap?.[localeType]!, localeType] as const; | ||
}; | ||
|
||
export default useLocale; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { useLocation as useDumiLocation } from 'dumi'; | ||
import * as React from 'react'; | ||
|
||
import useLocale from './useLocale'; | ||
|
||
function clearPath(path: string) { | ||
return path.replace('-cn', '').replace(/\/$/, ''); | ||
} | ||
|
||
export default function useLocation() { | ||
const location = useDumiLocation(); | ||
const { search } = location; | ||
const [, localeType] = useLocale(); | ||
|
||
const getLink = React.useCallback( | ||
(path: string, hash?: string | { cn: string; en: string }) => { | ||
let pathname = clearPath(path); | ||
|
||
if (localeType === 'cn') { | ||
pathname = `${pathname}-cn`; | ||
} | ||
|
||
if (search) { | ||
pathname = `${pathname}${search}`; | ||
} | ||
|
||
if (hash) { | ||
let hashStr: string; | ||
if (typeof hash === 'object') { | ||
hashStr = hash[localeType]; | ||
} else { | ||
hashStr = hash; | ||
} | ||
|
||
pathname = `${pathname}#${hashStr}`; | ||
} | ||
|
||
return pathname; | ||
}, | ||
[localeType, search], | ||
); | ||
|
||
return { | ||
...location, | ||
pathname: clearPath(location.pathname), | ||
getLink, | ||
}; | ||
} |
Oops, something went wrong.