Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#55] - Icon 공통 컴포넌트 #62

Merged
merged 5 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions src/assets/icons/greenPin.svg

This file was deleted.

File renamed without changes
10 changes: 10 additions & 0 deletions src/common/components/icon/icon-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const icons = import.meta.glob<{
default: (props: React.SVGProps<SVGSVGElement>) => JSX.Element;
}>('@assets/icons/*.svg', { query: '?react', eager: true });

export const iconMap = Object.fromEntries(
Object.entries(icons).map(([path, { default: SvgComponent }]) => [
path.split('/').pop()?.replace('.svg', ''),
SvgComponent,
]),
) as Record<string, (props: React.SVGProps<SVGSVGElement>) => JSX.Element>;
39 changes: 39 additions & 0 deletions src/common/components/icon/icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { css } from '@emotion/react';

import { iconMap } from './icon-map';
import iconPalette from './iconPalette.json';

type IconColor = keyof typeof iconPalette;

interface IconProps {
name: string;
width?: number;
color?: IconColor;
customStyle?: ReturnType<typeof css>;
onClick?: React.MouseEventHandler<SVGSVGElement>;
}

export default function Icon({ name, width, color, customStyle, onClick }: IconProps) {
const IconComponent = iconMap[name];

// 만약 width : width 로 그냥 넣어주게 되면 width를 설정하지 않을 경우
// undefined로 들어가서 기본으로 설정된 크기가 아닌 엄청 크게 보이는 문제가 발생해서
// width가 있는 경우에만 prop으로 넣는 방식을 채택했습니다.
// height같은 경우 viewBox 속성으로 인해 자동으로 비율이 조정되므로 undefined로 넣었습니다.
return (
<IconComponent
{...(width ? { width: width, height: undefined } : {})}
onClick={onClick}
css={[
css`
cursor: ${onClick ? 'pointer' : 'default'};

path {
fill: ${color ? iconPalette[color] : 'currentColor'};
}
`,
customStyle,
]}
/>
);
}
16 changes: 16 additions & 0 deletions src/common/components/icon/iconPalette.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"red": "#ff4d4f",
"blue": "#1890ff",
"green": "#52c41a",
"yellow": "#fadb14",
"orange": "#fa8c16",
"purple": "#722ed1",
"pink": "#eb2f96",
"teal": "#13c2c2",
"cyan": "#36cfc9",
"brown": "#a52a2a",
"gray": "#808080",
"black": "#000000",
"white": "#ffffff"
}

62 changes: 62 additions & 0 deletions src/common/stories/icon.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { css } from '@emotion/react';
import { Meta, StoryObj } from '@storybook/react';

import Icon from '@/common/components/icon/icon';

const meta = {
title: 'Components/Icon',
component: Icon,
argTypes: {
name: {
control: 'text',
description: '아이콘 이름',
},
width: {
control: { type: 'number', min: 1, max: 100, step: 1 },
description: '아이콘 크기 (width)',
},
color: {
control: 'select',
options: ['red', 'green', 'blue', 'black'],
description: '아이콘 색상',
},
customStyle: {
control: 'object',
description: 'Emotion 스타일 적용',
},
onClick: {
action: 'clicked',
description: '아이콘 클릭 이벤트',
},
},
} satisfies Meta<typeof Icon>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
name: 'checkIcon',
width: 40,
color: 'black',
},
};

export const Clickable: Story = {
args: {
name: 'pin',
width: 18,
color: 'red',
customStyle: css`
transition:
transform 0.2s ease-in-out,
filter 0.2s ease-in-out;

&:hover {
transform: scale(1.2);
filter: brightness(1.2);
}
`,
onClick: () => console.log('Icon clicked!'),
},
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import CheckIcon from '@assets/icons/checkIcon.svg?react';
import Icon from '@common/components/icon/icon';

import * as styles from './improvement-title.styles';

Expand All @@ -9,7 +9,7 @@ interface ImprovementTitleProps {
export default function ImprovementTitle({ improvementTitle }: ImprovementTitleProps) {
return (
<div css={styles.improvementTitleWrapper}>
<CheckIcon />
<Icon name="checkIcon" />
<span css={styles.improvementTitle}>{improvementTitle}</span>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import RedPin from '@assets/icons/redPin.svg?react';
import Icon from '@/common/components/icon/icon';

import * as styles from './logical-error.styles';

Expand All @@ -14,7 +14,7 @@ export default function LogicalError({ label, logicalErrorList }: LogicalErrorPr
<div css={styles.logicalErrorTextWrapper}>
<span css={styles.label}>
{label}
<RedPin />
<Icon name="pin" />
</span>
<ul>
{logicalErrorList.map((error) => (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import GreenPin from '@assets/icons/greenPin.svg?react';
import Icon from '@/common/components/icon/icon';

import * as styles from './logical-improvement.styles';

Expand All @@ -14,7 +14,7 @@ export default function LogicalImprovement({ label, improvementText }: LogicalIm
<div css={styles.logicalImprovementTextWrapper}>
<span css={styles.label}>
{label}
<GreenPin />
<Icon name="pin" color="green" />
</span>
<p css={styles.improvementText}>{improvementText}</p>
</div>
Expand Down