返回 reveal.js
fragment.tsx
根目录 / react / src / components / fragment.tsx
1 import {
2 Children,
3 Fragment as ReactFragment,
4 cloneElement,
5 isValidElement,
6 type CSSProperties,
7 type ReactElement,
8 } from 'react';
9 import type { FragmentProps } from '../types';
10
11 type FragmentChildProps = {
12 className?: string;
13 style?: CSSProperties;
14 'data-fragment-index'?: number;
15 };
16
17 function mergeClassNames(...classNames: Array<string | undefined>) {
18 return classNames.filter(Boolean).join(' ');
19 }
20
21 function mergeStyles(
22 childStyle: CSSProperties | undefined,
23 style: CSSProperties | undefined
24 ) {
25 if (!childStyle) return style;
26 if (!style) return childStyle;
27
28 return {
29 ...childStyle,
30 ...style,
31 };
32 }
33
34 export function Fragment({
35 animation,
36 index,
37 as,
38 asChild,
39 className,
40 style,
41 children,
42 }: FragmentProps) {
43 const classes = mergeClassNames('fragment', animation, className);
44
45 if (asChild) {
46 let child: ReactElement<FragmentChildProps>;
47 try {
48 child = Children.only(children) as ReactElement<FragmentChildProps>;
49 } catch {
50 throw new Error('Fragment with asChild expects exactly one React element child.');
51 }
52
53 if (!isValidElement(child) || child.type === ReactFragment) {
54 throw new Error('Fragment with asChild expects exactly one non-Fragment React element child.');
55 }
56
57 const fragmentChildProps: FragmentChildProps = {
58 className: mergeClassNames(child.props.className, classes),
59 style: mergeStyles(child.props.style, style),
60 };
61
62 if (index !== undefined) {
63 fragmentChildProps['data-fragment-index'] = index;
64 }
65
66 return cloneElement(child, fragmentChildProps);
67 }
68
69 const Tag = as ?? 'span';
70
71 return (
72 <Tag className={classes} style={style} data-fragment-index={index}>
73 {children}
74 </Tag>
75 );
76 }
77
77 lines Plain Text