Skip to content

Commit 1561a0a

Browse files
committed
✨ feat: 新增Features组件
1 parent 4de74ee commit 1561a0a

8 files changed

Lines changed: 358 additions & 1 deletion

File tree

src/DraggablePanel/DraggablePanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { revesePlacement } from './utils';
1515
const DEFAULT_HEIGHT = 180;
1616
const DEFAULT_WIDTH = 280;
1717

18-
const Center = ({ children, ...rest }: FlexProps) => (
18+
export const Center = ({ children, ...rest }: FlexProps) => (
1919
<Flex justify="center" align="center" {...rest}>
2020
{children}
2121
</Flex>

src/Features/Features.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { CSSProperties, memo } from 'react';
2+
3+
import SpotlightCard, { SpotlightCardProps } from '@/SpotlightCard';
4+
import { DivProps } from '@/types';
5+
6+
import { default as Item, type FeatureItem } from './Item';
7+
8+
export type { FeatureItem } from './Item';
9+
10+
export interface FeaturesProps extends DivProps {
11+
columns?: SpotlightCardProps['columns'];
12+
gap?: SpotlightCardProps['gap'];
13+
/**
14+
* @description The class name of the item
15+
*/
16+
itemClassName?: string;
17+
/**
18+
* @description The style of the item
19+
*/
20+
itemStyle?: CSSProperties;
21+
/**
22+
* @description The array of feature items
23+
*/
24+
items: FeatureItem[];
25+
maxWidth?: number;
26+
}
27+
28+
const Features = memo<FeaturesProps>(
29+
({ items, className, itemClassName, itemStyle, maxWidth = 960, style, ...rest }) => {
30+
if (!items?.length) return;
31+
32+
return (
33+
<SpotlightCard
34+
className={className}
35+
items={items}
36+
renderItem={(item: any) => (
37+
<Item className={itemClassName} key={item.title} style={itemStyle} {...item} />
38+
)}
39+
style={{ maxWidth, ...style }}
40+
{...rest}
41+
/>
42+
);
43+
},
44+
);
45+
46+
export default Features;

src/Features/Item.tsx

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { Flex } from 'antd';
2+
import { CSSProperties, memo } from 'react';
3+
4+
import Icon, { IconProps } from '@/Icon';
5+
import Img from '@/Img';
6+
import type { DivProps } from '@/types';
7+
8+
import { Center } from '@/DraggablePanel/DraggablePanel';
9+
import { useStyles } from './style';
10+
11+
export interface FeatureItem {
12+
/**
13+
* @description The number of columns the item spans.
14+
*/
15+
column?: number;
16+
/**
17+
* @description The description of the feature item.
18+
*/
19+
description?: string;
20+
/**
21+
* @description Whether this item is a hero item.
22+
*/
23+
hero?: boolean;
24+
/**
25+
* @description The name of the icon to display on the feature item.
26+
*/
27+
icon?: IconProps['icon'];
28+
/**
29+
* @description The URL of the image to display on the feature item.
30+
*/
31+
image?: string;
32+
/**
33+
* @description The CSS style of the image to display on the feature item.
34+
*/
35+
imageStyle?: CSSProperties;
36+
/**
37+
* @description The type of the image to display on the feature item.
38+
* @default 'normal'
39+
*/
40+
imageType?: 'light' | 'primary' | 'soon';
41+
/**
42+
* @description The link to navigate to when clicking on the feature item.
43+
*/
44+
link?: string;
45+
/**
46+
* @description Whether to open the link in a new tab when clicking on the feature item.
47+
* @default false
48+
*/
49+
openExternal?: boolean;
50+
/**
51+
* @description The number of rows the item spans.
52+
*/
53+
row?: number;
54+
/**
55+
* @description The title of the feature item.
56+
*/
57+
title: string;
58+
}
59+
60+
// @ts-ignore
61+
export interface FeatureItemProps extends FeatureItem, DivProps {}
62+
63+
const Image = memo<{ className?: string; image: string; style?: CSSProperties; title: string }>(
64+
({ image, className, title, style }) => {
65+
return image.startsWith('http') ? (
66+
<Img alt={title} className={className} src={image} style={style} />
67+
) : (
68+
<Center className={className} style={style}>
69+
{image}
70+
</Center>
71+
);
72+
},
73+
);
74+
75+
const Item = memo<FeatureItemProps>(
76+
({
77+
style,
78+
className,
79+
row,
80+
column,
81+
description,
82+
image,
83+
title,
84+
link,
85+
icon,
86+
imageStyle,
87+
openExternal,
88+
...rest
89+
}) => {
90+
const rowNumber = row || 7;
91+
const { styles, cx } = useStyles({ hasLink: Boolean(link), rowNum: rowNumber });
92+
93+
return (
94+
<div
95+
className={cx(styles.container, className)}
96+
style={{
97+
gridColumn: `span ${column || 1}`,
98+
gridRow: `span ${rowNumber}`,
99+
...style,
100+
}}
101+
{...rest}
102+
>
103+
<div className={styles.cell}>
104+
{image ||
105+
(icon && (
106+
<Center className={styles.imgContainer} style={imageStyle}>
107+
{icon && <Icon className={styles.img} icon={icon} />}
108+
{image && <Image className={styles.img} image={image} title={title} />}
109+
</Center>
110+
))}
111+
{title && (
112+
<Flex component="h3" align={'center'} className={styles.title} gap={8}>
113+
{title}
114+
</Flex>
115+
)}
116+
{description && (
117+
<p className={styles.desc} dangerouslySetInnerHTML={{ __html: description }} />
118+
)}
119+
{link && (
120+
<div className={styles.link}>
121+
<a href={link} rel="noreferrer" target={openExternal ? '_blank' : undefined}>
122+
Read More
123+
</a>
124+
</div>
125+
)}
126+
</div>
127+
</div>
128+
);
129+
},
130+
);
131+
132+
export default Item;

src/Features/demos/index.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Features, FeaturesProps } from '@datoou/components';
2+
import { MoonStar, Palette, Zap } from 'lucide-react';
3+
4+
const items: FeaturesProps['items'] = [
5+
{
6+
description:
7+
'Provides a simple way to customize default themes, you can change the colors, fonts, breakpoints and everything you need.',
8+
icon: Palette,
9+
title: 'Themeable',
10+
},
11+
{
12+
description:
13+
'voids unnecessary styles props at runtime, making it more performant than other UI libraries.',
14+
icon: Zap,
15+
title: 'Fast',
16+
},
17+
{
18+
description:
19+
'Automatic dark mode recognition, NextUI automatically changes the theme when detects HTML theme prop changes.',
20+
icon: MoonStar,
21+
title: 'Light & Dark UI',
22+
},
23+
];
24+
25+
export default () => {
26+
return <Features items={items} />;
27+
};

src/Features/index.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
title: Features 功能
3+
description: Features 组件 用于显示一系列功能项。它接收一个功能项数组,并使用 Item 组件进行渲染。它还允许自定义内容的最大宽度,每个项的类名和样式。
4+
group:
5+
title: 站点
6+
order: 2
7+
nav:
8+
title: 站点
9+
order: 2
10+
---
11+
12+
## Default
13+
14+
<code src="./demos/index.tsx" ></code>
15+
16+
## APIs
17+
18+
<API id='Features'></API>

src/Features/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Features from './Features';
2+
3+
export { default as Features } from './Features';
4+
export type { FeaturesProps } from './Features';
5+
export default Features;

src/Features/style.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { createStyles } from 'antd-style';
2+
3+
export const useStyles = createStyles(
4+
({ token, prefixCls, css, cx }, { rowNum, hasLink }: { hasLink?: boolean; rowNum: number }) => {
5+
const prefix = `${prefixCls}-features`;
6+
const coverCls = `${prefix}-cover`;
7+
const descCls = `${prefix}-description`;
8+
const titleCls = `${prefix}-title`;
9+
const imgCls = `${prefix}-img`;
10+
11+
const scaleUnit = 20;
12+
13+
const genSize = (size: number) => css`
14+
width: ${size}px;
15+
height: ${size}px;
16+
font-size: ${size * (22 / 24)}px;
17+
`;
18+
19+
const withTransition = css`
20+
transition: all ${token.motionDurationSlow} ${token.motionEaseInOutCirc};
21+
`;
22+
23+
return {
24+
cell: css`
25+
overflow: hidden;
26+
`,
27+
28+
container: css`
29+
${withTransition};
30+
position: relative;
31+
z-index: 1;
32+
33+
padding: 24px;
34+
height: 228px;
35+
max-height: 228px;
36+
37+
overflow: hidden;
38+
39+
p {
40+
font-size: 16px;
41+
line-height: 1.2;
42+
text-align: left;
43+
word-break: break-word;
44+
}
45+
46+
&:hover {
47+
.${coverCls} {
48+
width: 100%;
49+
height: ${scaleUnit * rowNum}px;
50+
padding: 0;
51+
background: ${token.colorFillContent};
52+
}
53+
54+
.${imgCls} {
55+
${genSize(100)};
56+
}
57+
58+
.${descCls} {
59+
position: absolute;
60+
visibility: hidden;
61+
opacity: 0;
62+
}
63+
64+
.${titleCls} {
65+
font-size: ${hasLink ? 14 : 20}px;
66+
}
67+
}
68+
`,
69+
desc: cx(
70+
descCls,
71+
withTransition,
72+
css`
73+
pointer-events: none;
74+
color: ${token.colorTextSecondary};
75+
76+
quotient {
77+
position: relative;
78+
79+
display: block;
80+
81+
margin: 12px 0;
82+
padding-left: 12px;
83+
84+
color: ${token.colorTextDescription};
85+
}
86+
`,
87+
),
88+
img: cx(
89+
imgCls,
90+
withTransition,
91+
css`
92+
${genSize(20)};
93+
color: ${token.colorText};
94+
`,
95+
),
96+
97+
imgContainer: cx(
98+
coverCls,
99+
withTransition,
100+
css`
101+
${genSize(24)};
102+
padding: 4px;
103+
opacity: 0.8;
104+
border-radius: ${token.borderRadius}px;
105+
`,
106+
),
107+
108+
link: css`
109+
${withTransition};
110+
margin-top: 24px;
111+
`,
112+
113+
title: cx(
114+
titleCls,
115+
withTransition,
116+
css`
117+
pointer-events: none;
118+
119+
margin: 16px 0;
120+
121+
font-size: 20px;
122+
line-height: ${token.lineHeightHeading3};
123+
color: ${token.colorText};
124+
`,
125+
),
126+
};
127+
},
128+
);

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export { default as ActionIcon, type ActionIconProps, type ActionIconSize } from
22
export * from './ConfigProvider';
33
export { default as CopyButton, type CopyButtonProps } from './CopyButton';
44
export { default as DraggablePanel, type DraggablePanelProps } from './DraggablePanel';
5+
export { default as Features, type FeaturesProps } from './Features';
56
export { default as FluentEmoji, type FluentEmojiProps } from './FluentEmoji';
67
export { default as Icon, type IconProps, type IconSize } from './Icon';
78
export { default as Spotlight, type SpotlightProps } from './Spotlight';

0 commit comments

Comments
 (0)