Skip to content

Conversation

@clyde-yoonjae
Copy link
Contributor

@clyde-yoonjae clyde-yoonjae commented Feb 21, 2025

📝 주요 작업 내용

아이콘 컴포넌트 생성

📺 스크린샷

/preview/tech-stack

image

🔗 참고 사항

처음엔, assets하위에 하나하나 전부 넣었으나, 디자인이 언제 변경될지 모르고, 크기나 색상등 자유로운 변경을 위해 어떻게 관리할까 고민한 결과입니다. 저도 처음본 패턴이기에 공유하기위하여 작성합니다.

설명에 앞서 ui폴더 하위에 Icon을 생성하여

components/
├── common/
└── ui/
   └── icon/
       ├── BaseIcon.tsx
       ├── getPath.ts
       ├── iconData.ts
       └── IconRegistry.ts

로 분류하였습니다.

1. BaseIcon.tsx

BaseIcon 컴포넌트는 모든 SVG 아이콘의 기본 템플릿 역할을 하는 핵심 컴포넌트입니다. IconRegistry.ts에서 각각의 아이콘을 생성할 때 이 BaseIcon을 기반으로 만듭니다.

2. getPath.ts

https://simpleicons.org/

해당 링크(무료 svg를 제공해주는 사이트)에서 svg를 가져와, path를 분리해주는 유틸 함수입니다. Icon에만 존속되기 때문에 따로 분류는 하지않았습니다.

3. iconData.ts

모든 기술스택들의 리스트 배열입니다.

interface IconConfig {
 name: string;
 color: string;
 path: string;
 category: 'frontend' | 'backend' | 'design';
}

4. IconRegistry.ts

리스트와 BaseIcon.tsx을 토대로 새로운 컴포넌트를 만들어내는 함수입니다.

type IconProps = Omit<BaseIconProps, 'path'>;

Omit은 특정 타입에서 원하는 키를 제거하는 타입입니다. 따라 기존 BaseIconProps에서 path타입을 제거한 새로운 IconProps 타입을 먼저 생성합니다.

function createIconComponent(config: IconConfig) {
  // 클로저를 활용한 컴포넌트 팩토리 함수
  return function Icon(props: IconProps) {
    return createElement(
      BaseIcon,  // 첫 번째 인자: 컴포넌트
      {         // 두 번째 인자: props
        ...props,  // 기존 props 전개
        color: props.color ?? config.color,  // Nullish 병합 연산자
        path: config.path,  // 고정된 path 주입
      }
    );
  };
}

그리고 위의 함수를 통해 아이콘 컴포넌트 생성함수를 작성합니다.

return문에 쓰인 createElement 메소드 : https://ko.react.dev/reference/react/createElement


그리고 하위 카테고리별 컴포넌트 생성

Record<K, T>

Record타입은 키가 K타입이고, 값이 T타입인 객체 타입을 정의하는 방법입니다.

따라

Record<string, Record<string, (props: IconProps) => JSX.Element>>

{
  frontend: {                    // 첫 번째 Record의 string
    ReactIcon: (props) => JSX.Element,  // 두 번째 Record의 string과 함수
    VueIcon: (props) => JSX.Element
  },
  backend: {
    NodeIcon: (props) => JSX.Element
  }
}

다음과 같이 해석됩니다.

다음으론
Reduce 메서드 분석입니다.
먼저 reduce메서드는 배열의 여러개의 값을 하나의 값으로 축소해주는 배열메서드 입니다.
첫번째 인수 자리에 콜백함수가 들어오고 두번째 인수자리에는 초기값이 들어옵니다. 즉, 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백함수를 반복호출합니다.(이때 원본 배열은 변경되지 않습니다.)
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce - mdn글입니다

ICON_LIST.reduce((acc, config) => {
  // 로직
}, {});

첫번째 파라미터인 acc는 이전 반복에서 return한 값이 다음 반복의 acc가 됩니다. 초기값은 빈 객체인 {} 형태입니다.

// 첫 번째 반복
acc = {} // 초기값

// 두 번째 반복
acc = {
  frontend: {
    ReactIcon: Component
  }
}

// 세 번째 반복
acc = {
  frontend: {
    ReactIcon: Component,
    VueIcon: Component
  }
}

두 번 파라미터인 config는 현재요소로 ICON_LIST 배열의 현재 처리중인 아이템입니다.

// 첫 번째 반복
acc = {}
config = { name: 'React', ... }
 acc = { frontend: { ReactIcon: Component } }

// 두 번째 반복
acc = { frontend: { ReactIcon: Component } }
config = { name: 'Vue', ... }
 acc = { frontend: { ReactIcon: Component, VueIcon: Component } }

이렇게 acc는 계속 누적되면서 최종 결과물을 만들고, config는 ICON_LIST의 각 아이템을 순차적으로 처리하는 역할을 합니다.

그리고

if (!acc[category]) {
    acc[category] = {};
}

이 부분은 카테고리 체크 및 초기화의 역할로 해당 카테고리가 없을 때 빈 객체를 생성합니다.
다음과 같이 수행됩니다.

// 첫 React 아이콘 처리 시
acc = {} // 현재 상태
category = 'frontend'

if (!acc['frontend']) { // true - frontend 카테고리가 없음
    acc['frontend'] = {} // 빈 객체 생성
}

// 결과
acc = {
    frontend: {}
}
acc[category][componentName] = createIconComponent(config);

그리고 생성된 카테고리 객체에 새로운 아이콘 컴포넌트를 추가하게 됩니다.

전체 과정 예시입니다.

// 1. React 아이콘 처리
acc = {} // 초기 상태
category = 'frontend'
componentName = 'ReactIcon'

// 카테고리 없음 → 생성
acc['frontend'] = {}

// 컴포넌트 추가
acc['frontend']['ReactIcon'] = createIconComponent(reactConfig)

// 결과
acc = {
    frontend: {
        ReactIcon: Component
    }
}

// 2. Vue 아이콘 처리 (같은 카테고리)
category = 'frontend'
componentName = 'VueIcon'

// 카테고리 이미 있음 → 스킵
if (!acc['frontend']) { ... } // false

// 컴포넌트 추가
acc['frontend']['VueIcon'] = createIconComponent(vueConfig)

// 결과
acc = {
    frontend: {
        ReactIcon: Component,
        VueIcon: Component
    }
}

// 3. Node 아이콘 처리 (새로운 카테고리)
category = 'backend'
componentName = 'NodeIcon'

// 카테고리 없음 → 생성
acc['backend'] = {}

// 컴포넌트 추가
acc['backend']['NodeIcon'] = createIconComponent(nodeConfig)

// 최종 결과
acc = {
    frontend: {
        ReactIcon: Component,
        VueIcon: Component
    },
    backend: {
        NodeIcon: Component
    }
}

++) 추가로 학습한 부분

title 태그

export function BaseIcon({
  color,
  size = 24,
  radius = 'none',
  className,
  path,
  ariaLabel,
  title,
}: BaseIconProps) {
  const radiusMap = {
    none: 'rounded-none',
    sm: 'rounded-sm',
    md: 'rounded',
    lg: 'rounded-lg',
    full: 'rounded-full',
  };

  return (
    <svg
      role="img"
      width={size}
      height={size}
      viewBox="0 0 24 24"
      xmlns="http://www.w3.org/2000/svg"
      fill={color}
      className={cn(radiusMap[radius], className)}
      aria-label={ariaLabel}
      preserveAspectRatio="xMidYMid meet"
    >
      {title && <title>{title}</title>}  // <---title 부분
      <path d={path} />
    </svg>
  );
}

SVG의 <title> 태그는 HTML의 title 속성과는 다르게 동작합니다.

HTML의 title 속성

<div title="툴팁 내용">마우스를 올려보세요</div>
  • 요소에 마우스를 올렸을 때(hover) 툴팁으로 표시됨
  • 모든 HTML 요소에서 사용 가능
  • 시각적으로 보이지 않음 (hover 시에만 툴팁으로 표시)
  • 스크린 리더가 읽을 수 있음

SVG의 title 태그

<svg>
  <title>SVG 설명</title>
  <path d="..." />
</svg>
  • SVG의 의미를 설명하는 메타데이터로 사용됨
  • SVG 내부의 첫 번째 자식 요소로 위치해야 함
  • 시각적으로 보이지 않음
  • 스크린 리더가 읽을 수 있음
  • 대부분의 브라우저에서 마우스 hover 시 툴팁으로도 표시됨

HTML의 <title> 태그

<title>페이지 제목</title>
  • HTML 문서의 제목을 정의
  • 섹션 안에서만 사용
  • 브라우저의 탭이나 타이틀 바에 표시됨
  • 검색 엔진이 페이지를 식별하는 데 사용

💬 리뷰 요구사항

ex) 중점적으로 리뷰해줬으면 하는 부분

📃 관련 이슈

DEVING-41

Summary by CodeRabbit

  • New Features
    • 새로운 기술 스택 페이지가 추가되어 프론트엔드, 백엔드, 디자인 도구 등 다양한 기술 정보를 한눈에 확인할 수 있습니다.
    • 사용자 인터페이스의 아이콘 표시 방식이 개선되어, 기술 관련 아이콘들이 일관되고 시각적으로 더욱 향상되었습니다.

@vercel
Copy link

vercel bot commented Feb 21, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
moim-fe ✅ Ready (Inspect) Visit Preview 💬 Add feedback Feb 21, 2025 6:17am
moim-fe-1kc6 ✅ Ready (Inspect) Visit Preview 💬 Add feedback Feb 21, 2025 6:17am

@coderabbitai
Copy link

coderabbitai bot commented Feb 21, 2025

Walkthrough

이번 변경 사항에서는 새로운 UI 기능 구현을 위한 여러 React 컴포넌트와 유틸리티 함수들이 도입되었습니다. TechStackPage 컴포넌트가 추가되어 프론트엔드, 백엔드, 디자인 도구 관련 기술 아이콘들을 카테고리 별로 표시하며, 아이콘 렌더링을 위한 BaseIcon, IconData, getPath, iconRegistry 등의 모듈이 새롭게 포함되었습니다.

Changes

File(s) Change Summary
src/app/preview/tech-stack/page.tsx 새로운 TechStackPage 컴포넌트 추가 – 기술 카테고리(Frontend, Backend, Design Tools) 별 아이콘 렌더링.
src/components/ui/Icon/BaseIcon.tsx 새로운 BaseIcon 컴포넌트 및 BaseIconProps 인터페이스 추가 – SVG 아이콘 렌더링을 위한 기본 구성 요소 정의.
src/components/ui/Icon/IconData.ts IconConfig 인터페이스와 ICON_LIST 상수 추가 – 애플리케이션 전반에서 사용되는 아이콘 설정 정보를 정의.
src/components/ui/Icon/getPath.ts SVG 문자열에서 d 속성을 추출하는 getPath 함수 추가.
src/components/ui/Icon/iconRegistry.ts 아이콘 설정을 기반으로 아이콘 컴포넌트를 생성하는 createIconComponent 및 관련 상수(Icons, frontend, backend, design, AllIcons) 추가.

Sequence Diagram(s)

sequenceDiagram
    participant TS as TechStackPage
    participant IR as IconRegistry
    participant GP as getPath
    participant BI as BaseIcon

    TS->>IR: 요청 - 아이콘 컴포넌트 생성
    IR->>GP: SVG 문자열에서 경로 추출
    GP-->>IR: 추출된 SVG 경로 데이터
    IR->>BI: BaseIcon 생성 (속성 전달)
    BI-->>IR: 렌더링된 아이콘 반환
    IR-->>TS: 구성된 아이콘 컴포넌트 반환
Loading

Possibly related PRs

  • Feat/component/gnb/DEVING-15 #13: TechStackPage 컴포넌트를 도입하여 아이콘을 사용하는 변경 사항으로, BaseIcon 컴포넌트와의 코드적 연결이 있습니다.

Suggested labels

💄 style, ♻️ refactor

Suggested reviewers

  • lee1nna
  • Lee-Dong-Seok

Poem

나는 귀여운 토끼,
새 컴포넌트를 뛰어넘네.
아이콘들이 춤추듯 빛나고,
코드 숲을 누비며
변화의 길을 노래하네! 🐇🌟
함께 달려 가자, 이제 새로운 시작!
행복한 코드와 웃음이 가득하기를!


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 30a73a1 and 16d4e05.

📒 Files selected for processing (2)
  • src/components/ui/Icon/BaseIcon.tsx (1 hunks)
  • src/components/ui/Icon/iconRegistry.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/ui/Icon/iconRegistry.ts
  • src/components/ui/Icon/BaseIcon.tsx

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@codecov
Copy link

codecov bot commented Feb 21, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

@github-actions
Copy link

CI Status Report

검사 결과

  • Lint: ✅ success
  • Format: ✅ success
  • Type Check: ✅ success
  • Tests: ✅ success
  • Build: ✅ success

✅ 모든 검사가 통과되었습니다.

@github-actions
Copy link

CI Status Report

검사 결과

  • Lint: ✅ success
  • Format: ✅ success
  • Type Check: ✅ success
  • Tests: ✅ success
  • Build: ✅ success

✅ 모든 검사가 통과되었습니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (7)
src/components/ui/Icon/getPath.ts (1)

1-4: SVG 경로 추출 로직 개선이 필요합니다.

현재 구현은 기본적인 SVG 경로 추출만 지원합니다. 다음과 같은 개선사항을 고려해주세요:

  1. 여러 path 요소가 있는 경우 처리
  2. 잘못된 SVG 문자열에 대한 오류 처리
  3. 다양한 SVG 포맷 지원
 export default function getPath(svgString: string): string {
+  if (!svgString) return '';
+  try {
+    const paths = svgString.match(/d="([^"]+)"/g);
+    if (!paths) return '';
+    return paths.map(p => p.match(/d="([^"]+)"/)[1]).join(' ');
+  } catch (error) {
+    console.error('SVG 경로 추출 중 오류 발생:', error);
+    return '';
+  }
-  const pathMatch = svgString.match(/d="([^"]+)"/);
-  return pathMatch ? pathMatch[1] : '';
 }
src/components/ui/Icon/BaseIcon.tsx (1)

26-39: SVG 최적화 속성을 추가해주세요.

SVG 렌더링 성능 향상을 위한 추가 속성이 필요합니다.

   return (
     <svg
       role="img"
+      aria-label={ariaLabel}
       width={size}
       height={size}
       viewBox="0 0 24 24"
       xmlns="http://www.w3.org/2000/svg"
       fill={color}
       className={cn(radiusMap[radius], className)}
       preserveAspectRatio="xMidYMid meet"
+      focusable="false"
     >
+      {title && <title>{title}</title>}
       <path d={path} />
     </svg>
   );
src/components/ui/Icon/iconRegistry.ts (2)

8-16: 컴포넌트 생성 성능 최적화가 필요합니다.

createIconComponent 함수의 결과를 메모이제이션하여 불필요한 재생성을 방지할 수 있습니다.

+const iconComponentCache = new Map<string, (props: IconProps) => JSX.Element>();
+
 function createIconComponent(config: IconConfig) {
+  const cacheKey = `${config.category}-${config.name}`;
+  if (iconComponentCache.has(cacheKey)) {
+    return iconComponentCache.get(cacheKey)!;
+  }
+
   return function Icon(props: IconProps) {
     return createElement(BaseIcon, {
       ...props,
       color: props.color ?? config.color,
       path: config.path,
     });
   };
 }

19-31: 타입 안전성 개선이 필요합니다.

카테고리와 컴포넌트 이름에 대한 타입 안전성을 강화할 수 있습니다.

+type IconCategory = 'frontend' | 'backend' | 'design';
+type IconName = IconConfig['name'];
+
 export const Icons = ICON_LIST.reduce<
-  Record<string, Record<string, (props: IconProps) => JSX.Element>>
+  Record<IconCategory, Record<`${IconName}Icon`, (props: IconProps) => JSX.Element>>
 >((acc, config) => {
   const category = config.category;
   const componentName = `${config.name}Icon`;
src/app/preview/tech-stack/page.tsx (1)

6-104: 섹션 컴포넌트를 분리해주세요.

기술 스택 섹션들이 반복되는 구조를 가지고 있습니다. 재사용 가능한 컴포넌트로 분리하면 코드 유지보수가 용이해질 것 같습니다.

+interface TechSectionProps {
+  title: string;
+  categories: {
+    title: string;
+    icons: Array<{ Icon: typeof frontend[keyof typeof frontend]; className?: string }>;
+  }[];
+}
+
+const TechSection = ({ title, categories }: TechSectionProps) => (
+  <section>
+    <h2 className="mb-6 text-2xl font-bold">{title}</h2>
+    {categories.map((category, index) => (
+      <div key={index} className="mb-8">
+        <h3 className="mb-4 text-lg font-semibold">{category.title}</h3>
+        <div className="flex flex-wrap gap-4">
+          {category.icons.map(({ Icon, className }, iconIndex) => (
+            <TechIcon key={iconIndex} icon={Icon} className={className} />
+          ))}
+        </div>
+      </div>
+    ))}
+  </section>
+);
src/components/ui/Icon/IconData.ts (2)

3-8: 인터페이스 정의가 잘 구조화되어 있습니다.

타입스크립트 모범 사례를 잘 따르고 있으며, 카테고리 타입이 유니온 타입으로 적절하게 제한되어 있습니다.

카테고리를 별도의 enum으로 추출하는 것을 고려해보세요.

카테고리 값의 재사용성과 유지보수성을 높이기 위해 다음과 같이 enum으로 분리하는 것을 제안합니다:

+export enum IconCategory {
+  Frontend = 'frontend',
+  Backend = 'backend',
+  Design = 'design'
+}

 export interface IconConfig {
   name: string;
   color: string;
   path: string;
-  category: 'frontend' | 'backend' | 'design';
+  category: IconCategory;
 }

10-268: 아이콘 목록이 잘 구성되어 있습니다.

각 아이콘 항목이 일관된 구조를 따르고 있으며, SVG 경로와 색상 코드가 올바르게 포맷되어 있습니다.

코드 가독성 향상을 위한 제안사항:

  1. JSDoc 주석 추가
  2. 카테고리별 아이콘 그룹화
+/**
+ * 기술 스택 아이콘 목록
+ * @type {IconConfig[]}
+ */
 export const ICON_LIST: IconConfig[] = [
+  // Frontend
   {
     name: 'Javascript',
     // ...
   },
+  // Backend
   {
     name: 'Spring',
     // ...
   },
+  // Design
   {
     name: 'Figma',
     // ...
   },
 ];

색상 코드 유효성 검사 추가를 고려해보세요.

색상 코드의 유효성을 보장하기 위해 유틸리티 함수를 추가하는 것을 제안합니다:

const isValidHexColor = (color: string): boolean => {
  return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(color);
};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb582fc and 30a73a1.

📒 Files selected for processing (5)
  • src/app/preview/tech-stack/page.tsx (1 hunks)
  • src/components/ui/Icon/BaseIcon.tsx (1 hunks)
  • src/components/ui/Icon/IconData.ts (1 hunks)
  • src/components/ui/Icon/getPath.ts (1 hunks)
  • src/components/ui/Icon/iconRegistry.ts (1 hunks)

Comment on lines 3 to 9
export interface BaseIconProps {
color?: string;
size?: number;
radius?: 'none' | 'sm' | 'md' | 'lg' | 'full';
className?: string;
path: string;
}
Copy link

@coderabbitai coderabbitai bot Feb 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

접근성 관련 속성을 추가해주세요.

아이콘의 접근성을 향상시키기 위해 aria-labeltitle 속성을 추가하는 것이 좋습니다.

 export interface BaseIconProps {
   color?: string;
   size?: number;
   radius?: 'none' | 'sm' | 'md' | 'lg' | 'full';
   className?: string;
   path: string;
+  title?: string;
+  ariaLabel?: string;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface BaseIconProps {
color?: string;
size?: number;
radius?: 'none' | 'sm' | 'md' | 'lg' | 'full';
className?: string;
path: string;
}
export interface BaseIconProps {
color?: string;
size?: number;
radius?: 'none' | 'sm' | 'md' | 'lg' | 'full';
className?: string;
path: string;
title?: string;
ariaLabel?: string;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아이콘의 접근성을 향상시키면 어떤 이점을 얻을 수 있어?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@clyde-yoonjae 아이콘 접근성 향상은 다음과 같은 중요한 이점이 있습니다:

  1. 스크린 리더 지원: 시각 장애가 있는 사용자들이 스크린 리더를 통해 아이콘의 의미를 이해할 수 있습니다.
  2. 사용자 경험 개선: 모든 사용자가 마우스 호버 시 툴팁을 통해 아이콘의 목적을 명확히 알 수 있습니다.
  3. SEO 최적화: 검색 엔진이 콘텐츠의 의미를 더 잘 이해할 수 있어 검색 순위가 개선될 수 있습니다.
  4. 법적 준수: 많은 국가의 웹 접근성 법규를 준수하는 데 도움이 됩니다.

🌐 Web query:

What are the benefits of making icons accessible with aria-label and title attributes?

💡 Result:

Using aria-label and title attributes enhances accessibility for icons by bridging gaps between visual and non-visual user experiences. Below are their key benefits:


Benefits of aria-label

  • Provides Screen Reader Context:
    When icons lack visible text (e.g., a button with only an SVG), aria-label supplies an accessible name for assistive technologies[1][3][7]. For example:

    <button aria-label="Close"><svg>...</svg></button>

    Screen readers announce "Close" instead of "Button" or generic descriptions[1][9].

  • Overrides Ambiguous Default Names:
    If an icon’s default name (e.g., alt text) is unclear or missing, aria-label ensures clarity[1][5][7].

  • Prioritizes Accessibility Over Redundant Text:
    Unlike visible labels or alt attributes, aria-label takes precedence in defining the accessible name[1][5].

  • Works Across Interactive Elements:
    Supported on buttons, links, form controls, and landmarks[1][5], making it ideal for icon-based actions like "Search" or "Menu"[3][7].


Benefits of title Attribute

  • Adds Tooltips for Mouse Users:
    Displays a hoverable description (e.g., explaining an icon’s purpose)[2][5].

  • Supplements Visible Labels:
    Useful for non-critical advisory text (e.g., clarifying a link’s destination)[2][5].


Key Considerations

  • aria-label vs. title:

    • Use aria-label when no visible label exists[1][5].
    • Use title for supplementary tooltips[2][5].
  • Avoid Redundancy:

    • Do not combine aria-label with visible labels or alt text unless necessary[7][9].
  • Limitations of title:

    • Not exposed to keyboard-only users or consistently read by screen readers[2][5].

Example Implementation

<!-- Using both attributes: -->
<button 
  aria-label="Email" 
  title="Send email"
>
  <svg>...</svg>
</button>
  • Screen readers announce "Email"[3][9], while mouse users see "Send email"[2][5].

In summary, aria-label ensures accessibility for assistive technologies by labeling untexted icons, while title enhances usability via tooltips—though its impact is limited compared to ARIA solutions[1][2][5].

Citations:


@clyde-yoonjae 아이콘 접근성 향상의 구체적인 이점을 설명드리겠습니다:

  1. 스크린 리더 사용성

    • aria-label을 통해 시각 장애인들이 아이콘의 정확한 목적을 이해할 수 있습니다
    • 스크린 리더가 "버튼" 대신 "닫기 버튼"과 같이 명확한 의미를 전달합니다
  2. 시각적 사용자 경험

    • title 속성으로 마우스 호버 시 툴팁이 표시되어 아이콘의 기능을 직관적으로 이해할 수 있습니다
    • 새로운 사용자나 아이콘의 의미가 모호할 수 있는 상황에서 특히 유용합니다
  3. 웹 표준 준수

    • WCAG(웹 콘텐츠 접근성 지침) 준수를 통해 더 많은 사용자가 서비스를 이용할 수 있습니다
    • 향후 접근성 관련 법적 요구사항에 선제적으로 대응할 수 있습니다
  4. 검색엔진 최적화(SEO)

    • 검색 엔진이 아이콘의 의미를 정확히 파악할 수 있어 콘텐츠 색인이 개선됩니다
    • 전반적인 웹사이트의 접근성 점수가 향상됩니다

✏️ Learnings added
Learnt from: clyde-yoonjae
PR: MoimService/Moim-FE#22
File: src/components/ui/Icon/BaseIcon.tsx:3-9
Timestamp: 2025-02-21T05:01:45.380Z
Learning: Icon components should include accessibility attributes (aria-label, title) to support screen readers, improve user experience, enhance SEO, and comply with accessibility regulations. The aria-label is used for screen readers, while title provides tooltips for sighted users.

Learnt from: clyde-yoonjae
PR: MoimService/Moim-FE#22
File: src/components/ui/Icon/BaseIcon.tsx:3-9
Timestamp: 2025-02-21T05:01:45.380Z
Learning: Icon accessibility should be implemented with both aria-label and title attributes for comprehensive coverage:
- aria-label: Provides essential context for screen readers, taking precedence over other accessible names
- title: Offers supplementary tooltips for sighted users on hover
These attributes serve different purposes and user groups, with aria-label being crucial for assistive technologies while title enhances visual user experience. Implementation should avoid redundancy between visible labels and aria-labels.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ariaLable과 title에 대해 설명해줘. 그리고 title은 어디에쓰여

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clyde-yoonjae ariaLabeltitle의 주요 차이점과 용도를 설명드리겠습니다:

ariaLabel

  • 스크린 리더와 같은 보조 기술을 위한 속성입니다
  • 시각적으로 보이지 않지만, 스크린 리더가 읽어주는 텍스트를 지정합니다
  • 예: ariaLabel="메뉴 닫기" → 스크린 리더가 "메뉴 닫기" 버튼이라고 읽어줍니다

title

  • 마우스를 올렸을 때 나타나는 툴팁 텍스트를 지정합니다
  • 주로 다음과 같은 상황에서 사용됩니다:
    • 아이콘의 기능 설명 (예: title="새 창에서 열기")
    • 추가 정보 제공 (예: title="파일 크기: 2MB")
    • 긴 텍스트의 전체 내용 표시 (예: 말줄임표로 잘린 텍스트)

✏️ Learnings added
Learnt from: clyde-yoonjae
PR: MoimService/Moim-FE#22
File: src/components/ui/Icon/BaseIcon.tsx:3-9
Timestamp: 2025-02-21T05:43:52.418Z
Learning: In React components, title attribute is commonly used for providing tooltip text that appears on hover, particularly useful for:
1. Explaining icon functionality
2. Showing additional information
3. Displaying full text of truncated content
The title attribute serves visual users, unlike ariaLabel which is for screen readers.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수정했어 확인해줘

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@clyde-yoonjae 변경사항을 확인하기 위해 파일 내용을 확인하고 있습니다. 잠시만 기다려주세요.

(_/)
(„• ֊ •„)
╭━━━━━━━━━╮
╰━━━━━━━━━╯


🏁 Script executed:

#!/bin/bash
cat src/components/ui/Icon/BaseIcon.tsx

Length of output: 918


@clyde-yoonjae 변경사항이 정확하게 구현되었네요! 👍

  • ariaLabeltitle props가 올바르게 추가되었습니다
  • SVG에서 aria-labeltitle 태그가 적절하게 사용되었습니다
  • role="img"도 추가되어 있어 접근성이 더욱 향상되었습니다

완벽하게 구현해주셨습니다!

(\ /)

(„• ᴗ •„)

| |

| |

Comment on lines +14 to +25
<div className="flex flex-wrap gap-4">
<frontend.JavascriptIcon size={24} radius="lg" />
<frontend.TypescriptIcon size={24} radius="lg" />
<frontend.ReactIcon size={24} radius="lg" />
<frontend.VueIcon size={24} radius="lg" />
</div>
<div className="flex flex-wrap gap-4">
<frontend.JavascriptIcon size={12} radius="lg" />
<frontend.TypescriptIcon size={12} radius="lg" />
<frontend.ReactIcon size={12} radius="lg" />
<frontend.VueIcon size={12} radius="lg" />
</div>
Copy link

@coderabbitai coderabbitai bot Feb 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

아이콘 크기 중복 코드를 제거해주세요.

동일한 아이콘이 다른 크기로 반복되어 있습니다. 컴포넌트로 분리하여 재사용성을 높일 수 있습니다.

+const TechIcon = ({ icon: Icon, size = 24 }) => (
+  <Icon size={size} radius="lg" />
+);
+
 <div className="flex flex-wrap gap-4">
-  <frontend.JavascriptIcon size={24} radius="lg" />
-  <frontend.TypescriptIcon size={24} radius="lg" />
-  <frontend.ReactIcon size={24} radius="lg" />
-  <frontend.VueIcon size={24} radius="lg" />
+  <TechIcon icon={frontend.JavascriptIcon} />
+  <TechIcon icon={frontend.TypescriptIcon} />
+  <TechIcon icon={frontend.ReactIcon} />
+  <TechIcon icon={frontend.VueIcon} />
 </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="flex flex-wrap gap-4">
<frontend.JavascriptIcon size={24} radius="lg" />
<frontend.TypescriptIcon size={24} radius="lg" />
<frontend.ReactIcon size={24} radius="lg" />
<frontend.VueIcon size={24} radius="lg" />
</div>
<div className="flex flex-wrap gap-4">
<frontend.JavascriptIcon size={12} radius="lg" />
<frontend.TypescriptIcon size={12} radius="lg" />
<frontend.ReactIcon size={12} radius="lg" />
<frontend.VueIcon size={12} radius="lg" />
</div>
const TechIcon = ({ icon: Icon, size = 24 }) => (
<Icon size={size} radius="lg" />
);
<div className="flex flex-wrap gap-4">
<TechIcon icon={frontend.JavascriptIcon} />
<TechIcon icon={frontend.TypescriptIcon} />
<TechIcon icon={frontend.ReactIcon} />
<TechIcon icon={frontend.VueIcon} />
</div>
<div className="flex flex-wrap gap-4">
<frontend.JavascriptIcon size={12} radius="lg" />
<frontend.TypescriptIcon size={12} radius="lg" />
<frontend.ReactIcon size={12} radius="lg" />
<frontend.VueIcon size={12} radius="lg" />
</div>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예시 페이지기때문에 무시해줘

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clyde-yoonjae 알겠습니다. 예시 페이지에 대한 리뷰 의견을 철회하겠습니다.


✏️ Learnings added
Learnt from: clyde-yoonjae
PR: MoimService/Moim-FE#22
File: src/app/preview/tech-stack/page.tsx:14-25
Timestamp: 2025-02-21T05:01:06.124Z
Learning: Code in preview/example pages (e.g., paths containing "preview") can have relaxed code quality standards as they serve demonstration purposes rather than production code.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Lee-Dong-Seok
Lee-Dong-Seok previously approved these changes Feb 21, 2025
Copy link
Contributor

@Lee-Dong-Seok Lee-Dong-Seok left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다!!👍👍👍👍

@github-actions
Copy link

CI Status Report

검사 결과

  • Lint: ✅ success
  • Format: ✅ success
  • Type Check: ✅ success
  • Tests: ✅ success
  • Build: ✅ success

✅ 모든 검사가 통과되었습니다.

Copy link
Contributor

@dbswl701 dbswl701 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

svg 이렇게까지 관리할 수 있을지 몰랐네용 배워갑니다!

Copy link
Contributor

@Lee-Dong-Seok Lee-Dong-Seok left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍👍👍👍👍👍👍

Copy link
Contributor

@lee1nna lee1nna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다 !! 👍

@clyde-yoonjae clyde-yoonjae merged commit 1cbb16e into feat/component/DEVING-9 Feb 24, 2025
9 checks passed
@clyde-yoonjae clyde-yoonjae deleted the feat/component/tech-stack/DEVING-41 branch February 24, 2025 04:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants