forked from mantinedev/mantine
/
Image.tsx
133 lines (114 loc) · 3.56 KB
/
Image.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import React, { useState, forwardRef } from 'react';
import {
DefaultProps,
MantineNumberSize,
Selectors,
useComponentDefaultProps,
} from '@mantine/styles';
import { useDidUpdate } from '@mantine/hooks';
import { Text } from '../Text';
import { Box } from '../Box';
import { ImageIcon } from './ImageIcon';
import useStyles, { ImageStylesParams } from './Image.styles';
export type ImageStylesNames = Selectors<typeof useStyles>;
export interface ImageProps
extends DefaultProps<ImageStylesNames, ImageStylesParams>,
Omit<React.ComponentPropsWithoutRef<'div'>, 'placeholder'> {
/** Image src */
src?: string;
/** Image alt text, used as title for placeholder if image was not loaded */
alt?: string;
/** Image object-fit property */
fit?: React.CSSProperties['objectFit'];
/** Image width, defaults to 100%, cannot exceed 100% */
width?: number | string;
/** Image height, defaults to original image height adjusted to given width */
height?: number | string;
/** Predefined border-radius value from theme.radius or number for border-radius in px */
radius?: MantineNumberSize;
/** Enable placeholder when image is loading and when image fails to load */
withPlaceholder?: boolean;
/** Customize placeholder content */
placeholder?: React.ReactNode;
/** Props spread to img element */
imageProps?: React.ComponentPropsWithoutRef<'img'>;
/** Get image element ref */
imageRef?: React.ForwardedRef<HTMLImageElement>;
/** Image figcaption, displayed below image */
caption?: React.ReactNode;
}
const defaultProps: Partial<ImageProps> = {
fit: 'cover',
width: '100%',
height: 'auto',
radius: 0,
};
export const Image = forwardRef<HTMLDivElement, ImageProps>((props: ImageProps, ref) => {
const {
className,
alt,
src,
fit,
width,
height,
radius,
imageProps,
withPlaceholder,
placeholder,
imageRef,
classNames,
styles,
caption,
unstyled,
style,
...others
} = useComponentDefaultProps('Image', defaultProps, props);
const { classes, cx } = useStyles({ radius }, { classNames, styles, unstyled, name: 'Image' });
const [loaded, setLoaded] = useState(false);
const [error, setError] = useState(!src);
const isPlaceholder = withPlaceholder && (!loaded || error);
useDidUpdate(() => {
setLoaded(false);
setError(false);
}, [src]);
return (
<Box
className={cx(classes.root, className)}
ref={ref}
style={{ width, height, ...style }}
{...others}
>
<figure className={classes.figure}>
<div className={classes.imageWrapper}>
<img
className={classes.image}
src={src}
alt={alt}
style={{ objectFit: fit, width, height }}
ref={imageRef}
onLoad={(event) => {
setLoaded(true);
typeof imageProps?.onLoad === 'function' && imageProps.onLoad(event);
}}
onError={(event) => {
setError(true);
typeof imageProps?.onError === 'function' && imageProps.onError(event);
}}
{...imageProps}
/>
{isPlaceholder && (
<div className={classes.placeholder} title={alt}>
{placeholder || <ImageIcon style={{ width: 40, height: 40 }} />}
</div>
)}
</div>
{!!caption && (
<Text component="figcaption" size="sm" align="center" className={classes.caption}>
{caption}
</Text>
)}
</figure>
</Box>
);
});
Image.displayName = '@mantine/core/Image';