Skip to content

Commit 2fa9a50

Browse files
authored
Image, Avatar, AvatarGroup 공통 컴포넌트 구현 (#28)
* feat: Image 공통 컴포넌트 구현 * feat: Avatar 공통 컴포넌트 구현 * feat: AvatarGroup 공통 컴포넌트 구현 * feat: Avatar 컴포넌트가 img element props를 받는 기능
1 parent 97bcf77 commit 2fa9a50

File tree

9 files changed

+156
-0
lines changed

9 files changed

+156
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import styled from '@emotion/styled';
2+
3+
import { Image } from '@components/shared/Image';
4+
5+
import { AvatarProps } from './Avatar';
6+
7+
export const StyledImage = styled(Image)<AvatarProps>`
8+
border: ${({ border }) => border};
9+
border-radius: ${({ radius }) => radius};
10+
`;
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { HTMLAttributes } from 'react';
2+
3+
import { StyledImage } from './Avatar.styles';
4+
5+
export type AvatarProps = {
6+
src: string;
7+
size?: number;
8+
radius?: string;
9+
border?: string;
10+
} & HTMLAttributes<HTMLImageElement>;
11+
12+
export const Avatar = ({
13+
src,
14+
size = 30,
15+
radius = '50%',
16+
border,
17+
...props
18+
}: AvatarProps) => {
19+
return (
20+
<StyledImage
21+
src={src}
22+
alt="avatar"
23+
width={size}
24+
border={border}
25+
radius={radius}
26+
{...props}
27+
/>
28+
);
29+
};

src/components/shared/Avatar/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './Avatar';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import styled from '@emotion/styled';
2+
3+
import { Avatar, AvatarProps } from '@components/shared/Avatar';
4+
5+
import { AvatarGroupProps } from './AvatarGroup';
6+
7+
type AvatarGroupWrapperProps = Required<Pick<AvatarGroupProps, 'overlap'>>;
8+
9+
export const AvatarGroupWrapper = styled.div<AvatarGroupWrapperProps>`
10+
padding-left: ${({ overlap }) => `${overlap / 16}rem`};
11+
`;
12+
13+
export const OverlapedAvatar = styled(Avatar)<
14+
AvatarProps & { overlap: number }
15+
>`
16+
margin-left: ${({ overlap }) => `-${overlap / 16}rem`};
17+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React, { HTMLAttributes } from 'react';
2+
3+
import { Avatar, AvatarProps } from '../Avatar';
4+
import { AvatarGroupWrapper, OverlapedAvatar } from './AvatarGroup.styles';
5+
6+
export type AvatarGroupProps = {
7+
children: React.ReactNode;
8+
overlap?: number;
9+
} & Omit<AvatarProps, 'src'> &
10+
HTMLAttributes<HTMLDivElement>;
11+
12+
export const AvatarGroup = ({
13+
children,
14+
size = 30,
15+
radius = '50%',
16+
border,
17+
overlap = 10,
18+
...props
19+
}: AvatarGroupProps) => {
20+
const avatars = React.Children.toArray(children)
21+
.filter((element): element is React.ReactElement<AvatarProps> => {
22+
if (!React.isValidElement(element)) {
23+
return false;
24+
}
25+
if (element.type !== Avatar) {
26+
return false;
27+
}
28+
return true;
29+
})
30+
.map(({ props }) => (
31+
<OverlapedAvatar
32+
{...props}
33+
size={size}
34+
border={border}
35+
radius={radius}
36+
overlap={overlap}
37+
/>
38+
));
39+
40+
return (
41+
<AvatarGroupWrapper overlap={overlap} {...props}>
42+
{avatars}
43+
</AvatarGroupWrapper>
44+
);
45+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './AvatarGroup';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import styled from '@emotion/styled';
2+
3+
import { ImageCustomProps } from './Image';
4+
5+
type ImgProps = Required<ImageCustomProps>;
6+
7+
export const Img = styled.img<ImgProps>`
8+
display: ${({ block }) => (block ? 'block' : 'inline')};
9+
width: ${({ width }) => width};
10+
height: ${({ height }) => height};
11+
object-fit: ${({ mode }) => mode};
12+
`;

src/components/shared/Image/Image.tsx

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { CSSProperties, HTMLAttributes } from 'react';
2+
3+
import { Img } from './Image.styles';
4+
5+
export type ImageCustomProps = {
6+
src: string;
7+
block?: boolean;
8+
width: number | string;
9+
height?: number | string;
10+
alt: string;
11+
mode?: CSSProperties['objectFit'];
12+
};
13+
type ImageProps = ImageCustomProps & HTMLAttributes<HTMLImageElement>;
14+
15+
export const Image = ({
16+
src,
17+
block = false,
18+
width,
19+
height = width,
20+
alt,
21+
mode = 'cover',
22+
...props
23+
}: ImageProps) => {
24+
const stringifiedWidth =
25+
typeof width === 'number' ? `${width / 16}rem` : width;
26+
const stringifiedHeight =
27+
typeof height === 'number' ? `${height / 16}rem` : height;
28+
29+
return (
30+
<Img
31+
src={src}
32+
alt={alt}
33+
width={stringifiedWidth}
34+
height={stringifiedHeight}
35+
block={block}
36+
mode={mode}
37+
{...props}
38+
/>
39+
);
40+
};

src/components/shared/Image/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './Image';

0 commit comments

Comments
 (0)