Skip to content

Commit 95680f9

Browse files
Merge branch 'feature' into master
2 parents 88659b3 + 3c297da commit 95680f9

296 files changed

Lines changed: 13116 additions & 13471 deletions

File tree

Some content is hidden

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

.dumi/layer-import.less

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@layer antd {
2+
@import '~../components/style/antd.css';
3+
}

.dumi/pages/index/components/BannerRecommends.tsx

Lines changed: 91 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import raf from '@rc-component/util/lib/raf';
23
import { Alert, Badge, Carousel, Flex, Skeleton, Typography } from 'antd';
34
import { createStyles } from 'antd-style';
45
import { clsx } from 'clsx';
@@ -18,12 +19,46 @@ const useStyle = createStyles(({ cssVar, css, cx }) => {
1819
align-items: stretch;
1920
text-decoration: none;
2021
background: ${cssVar.colorBgContainer};
22+
background: color-mix(in srgb, ${cssVar.colorBgContainer} 30%, transparent);
23+
backdrop-filter: blur(8px);
2124
border: ${cssVar.lineWidth} solid ${cssVar.colorBorderSecondary};
2225
border-radius: ${cssVar.borderRadiusLG};
2326
transition: all ${cssVar.motionDurationSlow};
2427
padding-block: ${cssVar.paddingMD};
2528
padding-inline: ${cssVar.paddingLG};
2629
box-sizing: border-box;
30+
position: relative;
31+
32+
&:before {
33+
content: '';
34+
inset: calc(${cssVar.lineWidth} * -1);
35+
position: absolute;
36+
37+
background: radial-gradient(
38+
circle 150px at var(--mouse-x, 0) var(--mouse-y, 0),
39+
${cssVar.colorPrimaryBorderHover},
40+
${cssVar.colorBorderSecondary}
41+
);
42+
opacity: 0;
43+
transition: all 0.3s ease;
44+
mask:
45+
linear-gradient(#fff 0 0) content-box,
46+
linear-gradient(#fff 0 0);
47+
48+
mask-composite: subtract;
49+
-webkit-mask-composite: xor;
50+
padding: 1px;
51+
border-radius: inherit;
52+
}
53+
54+
&:hover {
55+
backdrop-filter: blur(0px);
56+
background: color-mix(in srgb, ${cssVar.colorBgContainer} 90%, transparent);
57+
58+
&:before {
59+
opacity: 1;
60+
}
61+
}
2762
`;
2863

2964
return {
@@ -33,12 +68,6 @@ const useStyle = createStyles(({ cssVar, css, cx }) => {
3368
height: 100%;
3469
}
3570
`,
36-
cardItem: css`
37-
&:hover {
38-
box-shadow: ${cssVar.boxShadowCard};
39-
border-color: transparent;
40-
}
41-
`,
4271
sliderItem: css`
4372
margin: 0 ${cssVar.margin};
4473
text-align: start;
@@ -64,6 +93,9 @@ const useStyle = createStyles(({ cssVar, css, cx }) => {
6493
};
6594
});
6695

96+
// ======================================================================
97+
// == Item ==
98+
// ======================================================================
6799
interface RecommendItemProps {
68100
extra: Extra;
69101
index: number;
@@ -73,9 +105,47 @@ interface RecommendItemProps {
73105

74106
const RecommendItem: React.FC<RecommendItemProps> = (props) => {
75107
const { extra, index, icons, className } = props;
108+
const cardRef = React.useRef<HTMLAnchorElement>(null);
76109

77110
const { styles } = useStyle();
78111

112+
// ====================== MousePos ======================
113+
const [mousePosition, setMousePosition] = React.useState<[number, number]>([0, 0]);
114+
const [transMousePosition, setTransMousePosition] = React.useState<[number, number]>([0, 0]);
115+
116+
const onMouseMove = (e: React.MouseEvent<HTMLAnchorElement>) => {
117+
if (!cardRef.current) return;
118+
119+
const rect = cardRef.current.getBoundingClientRect();
120+
const x = e.clientX - rect.left;
121+
const y = e.clientY - rect.top;
122+
123+
setMousePosition([x, y]);
124+
};
125+
126+
// Transition mouse position
127+
React.useEffect(() => {
128+
const [targetX, targetY] = mousePosition;
129+
const [currentX, currentY] = transMousePosition;
130+
131+
if (Math.abs(targetX - currentX) < 0.5 && Math.abs(targetY - currentY) < 0.5) {
132+
return;
133+
}
134+
135+
const rafId = raf(() => {
136+
setTransMousePosition((ori) => {
137+
const [curX, curY] = ori;
138+
const deltaX = (targetX - curX) * 0.1;
139+
const deltaY = (targetY - curY) * 0.1;
140+
141+
return [curX + deltaX, curY + deltaY];
142+
});
143+
});
144+
145+
return () => raf.cancel(rafId);
146+
}, [mousePosition, transMousePosition]);
147+
148+
// ======================= Render =======================
79149
if (!extra) {
80150
return <Skeleton key={index} />;
81151
}
@@ -84,11 +154,19 @@ const RecommendItem: React.FC<RecommendItemProps> = (props) => {
84154

85155
const card = (
86156
<a
157+
ref={cardRef}
87158
key={extra?.title}
88159
href={extra.href}
89160
target="_blank"
90161
className={clsx(styles.itemBase, className)}
162+
style={
163+
{
164+
'--mouse-x': `${transMousePosition[0]}px`,
165+
'--mouse-y': `${transMousePosition[1]}px`,
166+
} as React.CSSProperties
167+
}
91168
rel="noreferrer"
169+
onMouseMove={onMouseMove}
92170
>
93171
<Typography.Title level={5}>{extra?.title}</Typography.Title>
94172
<Typography.Paragraph type="secondary" style={{ flex: 'auto' }}>
@@ -114,6 +192,9 @@ const RecommendItem: React.FC<RecommendItemProps> = (props) => {
114192
return card;
115193
};
116194

195+
// ======================================================================
196+
// == Fallback ==
197+
// ======================================================================
117198
export const BannerRecommendsFallback: React.FC = () => {
118199
const { isMobile } = React.use(SiteContext);
119200

@@ -140,6 +221,9 @@ export const BannerRecommendsFallback: React.FC = () => {
140221
);
141222
};
142223

224+
// ======================================================================
225+
// == Recommends ==
226+
// ======================================================================
143227
const BannerRecommends: React.FC = () => {
144228
const { styles } = useStyle();
145229
const [, lang] = useLocale();
@@ -186,13 +270,7 @@ const BannerRecommends: React.FC = () => {
186270
return (
187271
<div className={styles.container}>
188272
{mergedExtras.map((extra, index) => (
189-
<RecommendItem
190-
key={`desktop-${index}`}
191-
extra={extra}
192-
index={index}
193-
icons={data?.icons}
194-
className={styles.cardItem}
195-
/>
273+
<RecommendItem key={`desktop-${index}`} extra={extra} index={index} icons={data?.icons} />
196274
))}
197275
</div>
198276
);

.dumi/pages/index/components/Group.tsx

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
1010
box: css`
1111
position: relative;
1212
transition: all ${cssVar.motionDurationSlow};
13+
background-size: cover;
14+
background-position: 50% 0%;
15+
background-repeat: no-repeat;
1316
`,
1417
container: css`
1518
position: absolute;
@@ -41,14 +44,46 @@ export interface GroupProps {
4144
/** 是否不使用两侧 margin */
4245
collapse?: boolean;
4346
decoration?: React.ReactNode;
47+
/** 预加载的背景图片列表 */
48+
backgroundPrefetchList?: string[];
4449
}
4550

4651
const Group: React.FC<React.PropsWithChildren<GroupProps>> = (props) => {
47-
const { id, title, titleColor, description, children, decoration, background, collapse } = props;
52+
const {
53+
id,
54+
title,
55+
titleColor,
56+
description,
57+
children,
58+
decoration,
59+
background,
60+
collapse,
61+
backgroundPrefetchList,
62+
} = props;
63+
64+
// 预加载背景图片
65+
React.useEffect(() => {
66+
if (backgroundPrefetchList && backgroundPrefetchList.length > 0) {
67+
backgroundPrefetchList.forEach((url) => {
68+
if (url && url.startsWith('https')) {
69+
const img = new Image();
70+
img.src = url;
71+
}
72+
});
73+
}
74+
}, [backgroundPrefetchList]);
75+
4876
const token = useTheme();
4977
const { isMobile } = React.use(SiteContext);
5078
return (
51-
<div style={{ backgroundColor: background }} className={styles.box}>
79+
<div
80+
style={
81+
background?.startsWith('https')
82+
? { backgroundImage: `url(${background})` }
83+
: { backgroundColor: background }
84+
}
85+
className={styles.box}
86+
>
5287
<div className={styles.container}>{decoration}</div>
5388
<GroupMaskLayer style={{ paddingBlock: token.marginFarSM }}>
5489
<div className={styles.typographyWrapper}>
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { useEvent } from '@rc-component/util';
3+
import { createStyles } from 'antd-style';
4+
5+
import { DarkContext } from '../../../../hooks/useDark';
6+
7+
interface BubbleProps {
8+
size: number | string;
9+
left?: number | string;
10+
top?: number | string;
11+
color: string;
12+
offsetXMultiple?: number;
13+
offsetYMultiple?: number;
14+
defaultOpacity?: number;
15+
}
16+
17+
const MAX_OFFSET = 200;
18+
19+
const Bubble = ({
20+
size,
21+
left,
22+
top,
23+
color,
24+
offsetXMultiple = 1,
25+
offsetYMultiple = 1,
26+
defaultOpacity = 0.1,
27+
}: BubbleProps) => {
28+
const [offset, setOffset] = useState([0, 0]);
29+
const [opacity, setOpacity] = useState(defaultOpacity);
30+
const [sizeOffset, setSizeOffset] = useState(1);
31+
32+
const isDark = React.use(DarkContext);
33+
34+
const randomPos = useEvent(() => {
35+
const baseOffsetX = (Math.random() - 0.5) * MAX_OFFSET * 2 * offsetXMultiple;
36+
const baseOffsetY = (Math.random() - 0.5) * MAX_OFFSET * 2 * offsetYMultiple;
37+
setOffset([baseOffsetX, baseOffsetY]);
38+
39+
setOpacity(isDark ? 0.1 + Math.random() * 0.2 : 0.1 + Math.random() * 0.05);
40+
setSizeOffset(1 + Math.random() * 1);
41+
});
42+
43+
useEffect(() => {
44+
randomPos();
45+
}, []);
46+
47+
useEffect(() => {
48+
const randomTimeout = Math.random() * 2000 + 3000;
49+
const id = setTimeout(randomPos, randomTimeout);
50+
51+
return () => clearTimeout(id);
52+
}, [offset]);
53+
54+
return (
55+
<div
56+
aria-hidden="true"
57+
data-desc="luminous-bubble"
58+
style={{
59+
opacity,
60+
width: size,
61+
height: size,
62+
borderRadius: '50%',
63+
background: color,
64+
filter: 'blur(100px)',
65+
left,
66+
top,
67+
transform: `translate(-50%, -50%) translate(${offset[0]}px, ${offset[1]}px) scale(${sizeOffset})`,
68+
transition: 'all 5s ease-in-out',
69+
position: 'absolute',
70+
}}
71+
/>
72+
);
73+
};
74+
75+
const useStyles = createStyles(({ css, cssVar }) => ({
76+
container: css`
77+
position: absolute;
78+
inset: 0;
79+
overflow: hidden;
80+
background: ${cssVar.colorBgContainer};
81+
`,
82+
}));
83+
84+
interface LuminousBgProps {
85+
className?: string;
86+
}
87+
88+
export default function LuminousBg({ className }: LuminousBgProps) {
89+
const { styles, cx } = useStyles();
90+
91+
return (
92+
<div className={cx(styles.container, className)}>
93+
{/* Left + Top */}
94+
<Bubble
95+
size={300}
96+
color="#ee35f1"
97+
left="0vw"
98+
top="0vh"
99+
offsetXMultiple={2}
100+
defaultOpacity={0.2}
101+
/>
102+
{/* Left + Bottom */}
103+
<Bubble size={300} color="#5939dc" left="30vw" top="80vh" defaultOpacity={0.1} />
104+
{/* Right + Middle */}
105+
<Bubble
106+
size={300}
107+
color="#00D6FF"
108+
left="100vw"
109+
top="50vh"
110+
offsetYMultiple={2}
111+
defaultOpacity={0.2}
112+
/>
113+
</div>
114+
);
115+
}

0 commit comments

Comments
 (0)