Skip to content

Commit 9ce1e84

Browse files
authored
refactor: forward refs in Skeleton (#1450)
1 parent 7c3060a commit 9ce1e84

File tree

2 files changed

+82
-23
lines changed

2 files changed

+82
-23
lines changed

src/components/ui/Skeleton/Skeleton.tsx

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,52 @@
22
import React from 'react';
33
import { customClassSwitcher } from '~/core';
44
import { clsx } from 'clsx';
5+
56
const COMPONENT_NAME = 'Skeleton';
67

7-
export type SkeletonProps = {
8-
loading:boolean;
9-
className?:string;
10-
customRootClass?:string;
11-
children:React.ReactNode;
12-
height:string;
13-
width:string;
14-
radius?:string;
8+
export type SkeletonProps = React.ComponentPropsWithoutRef<'div'> & {
9+
loading: boolean;
10+
customRootClass?: string;
11+
height: string;
12+
width: string;
13+
radius?: string;
14+
};
1515

16-
}
16+
const Skeleton = React.forwardRef<React.ElementRef<'div'>, SkeletonProps>(
17+
(
18+
{
19+
loading = true,
20+
className = '',
21+
customRootClass = '',
22+
children,
23+
height,
24+
width,
25+
radius,
26+
style,
27+
...props
28+
},
29+
ref
30+
) => {
31+
if (!loading) return <>{children}</>;
1732

18-
const Skeleton = ({ loading = true, className = '', customRootClass = '', children, height, width, radius, ...props }:SkeletonProps) => {
19-
// If loading is false, return the children
20-
if (!loading) return children;
33+
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
2134

22-
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
23-
return <div
24-
className={clsx(rootClass, className)} {...props} style={{
25-
26-
['--skeleton-height' as any]: height,
27-
['--skeleton-width' as any]: width,
28-
['--skeleton-radius' as any]: radius
29-
}}
30-
>
31-
</div>;
32-
};
35+
return (
36+
<div
37+
ref={ref}
38+
className={clsx(rootClass, className)}
39+
style={{
40+
...style,
41+
['--skeleton-height' as any]: height,
42+
['--skeleton-width' as any]: width,
43+
['--skeleton-radius' as any]: radius
44+
}}
45+
{...props}
46+
/>
47+
);
48+
}
49+
);
3350

3451
Skeleton.displayName = COMPONENT_NAME;
52+
3553
export default Skeleton;

src/components/ui/Skeleton/tests/Skeleton.test.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,45 @@ describe('Skeleton', () => {
6363
expect(div.style.getPropertyValue('--skeleton-width')).toBe('200px');
6464
expect(div.style.getPropertyValue('--skeleton-radius')).toBe('10px');
6565
});
66+
67+
it('merges custom style with skeleton variables', () => {
68+
const { container } = render(
69+
<Skeleton
70+
{...defaultProps}
71+
loading={true}
72+
style={{ marginTop: '4px' }}
73+
>
74+
<div>Child</div>
75+
</Skeleton>
76+
);
77+
78+
const div = container.querySelector('div')!;
79+
expect(div.style.getPropertyValue('--skeleton-height')).toBe('100px');
80+
expect(div.style.marginTop).toBe('4px');
81+
});
82+
83+
it('forwards refs to the underlying element', () => {
84+
const ref = React.createRef<HTMLDivElement>();
85+
render(
86+
<Skeleton {...defaultProps} loading ref={ref}>
87+
<p>Hidden content</p>
88+
</Skeleton>
89+
);
90+
expect(ref.current).not.toBeNull();
91+
expect(ref.current?.tagName).toBe('DIV');
92+
});
93+
94+
it('renders without console errors', () => {
95+
const consoleError = jest
96+
.spyOn(console, 'error')
97+
.mockImplementation(() => {});
98+
99+
render(
100+
<Skeleton {...defaultProps} loading>
101+
<p>Hidden content</p>
102+
</Skeleton>
103+
);
104+
expect(consoleError).not.toHaveBeenCalled();
105+
consoleError.mockRestore();
106+
});
66107
});

0 commit comments

Comments
 (0)