Skip to content

[Feat] #69 - 공통 Avatars 컴포넌트 추가#73

Merged
jaeu5325 merged 24 commits into
devfrom
feat/shared-avatars
Mar 31, 2026
Merged

[Feat] #69 - 공통 Avatars 컴포넌트 추가#73
jaeu5325 merged 24 commits into
devfrom
feat/shared-avatars

Conversation

@jaeu5325
Copy link
Copy Markdown
Collaborator

@jaeu5325 jaeu5325 commented Mar 28, 2026

🔎 What is this PR?

  • 공통 Avatars 컴포넌트를 구현하고 Storybook 문서화를 완료

📝 Changes


📸 Screenshots (선택)

스크린샷 2026-03-28 오후 9 16 38 스크린샷 2026-03-28 오후 9 17 14 스크린샷 2026-03-28 오후 9 17 35 스크린샷 2026-03-28 오후 9 17 52 스크린샷 2026-03-28 오후 9 18 01

📚 Background / Context (선택)


✔ Checklist

  • 코드는 로컬에서 정상적으로 빌드됩니다 (pnpm build)
  • ESLint / Prettier 통과 (pnpm lint)
  • 네이밍/레이어 컨벤션 준수 (camelCase/PascalCase, is·has 불린 접두사, alias 계층 규칙)
  • 관련 문서/주석 반영 (필요 시)
  • 주요 로직에 테스트 또는 검증 완료

🙏 Request

Summary by CodeRabbit

  • New Features

    • Avatar component: multiple sizes, image/name/icon fallbacks, status indicators, optional badges, grouped avatars with overflow.
    • List and Table components: selectable rows, per-row checkboxes, “select all” behavior, row active/disabled states, and empty-state UI.
    • New icon set: Inbox, User (solid), FileText, Bell, AlertCircle, Clock.
  • Documentation

    • Storybook demos for avatars (sizes, variants, statuses, badges, groups) and list/table states and interactions.

@jaeu5325 jaeu5325 requested a review from sebeeeen March 28, 2026 12:18
@jaeu5325 jaeu5325 self-assigned this Mar 28, 2026
@jaeu5325 jaeu5325 added the enhancement New feature or request label Mar 28, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 28, 2026

Warning

Rate limit exceeded

@jaeu5325 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 21 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 9 minutes and 21 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8f30f09a-4be1-4832-b08b-35422450285c

📥 Commits

Reviewing files that changed from the base of the PR and between 519684b and a3b93c9.

📒 Files selected for processing (1)
  • src/shared/ui/lists/lists.tsx
📝 Walkthrough

Walkthrough

Adds Avatar and AvatarGroup components (sizes, status, badge, fallbacks and grouped overflow), ListItem and Table components (selection, checkboxes, empty state), Storybook stories for avatars/lists/tables, and several new SVG icon exports.

Changes

Cohort / File(s) Summary
Avatars UI & Stories
src/shared/ui/avatars/avatars.tsx, src/shared/ui/avatars/avatars.stories.tsx
New Avatar and AvatarGroup components and types (sizes, status, badge, click handling, grouped overlap and "+N" overflow). Storybook stories demonstrating sizes, image/name/icon fallbacks, status states, badge usage, and group size/maxVisible combinations.
Lists & Tables UI & Stories
src/shared/ui/lists/lists.tsx, src/shared/ui/lists/lists.stories.tsx
New ListItem (optional checkbox, image, active/disabled state, stopPropagation for checkbox) and Table (configurable columns, select-all, per-row selection, empty-state). Interactive Storybook examples for stateful behaviors.
Icons
src/shared/ui/icons/index.tsx
Added multiple exported SVG icon components: InboxIcon, UserSolidIcon, FileTextIcon, BellIcon, AlertCircleIcon, ClockIcon.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • kimsman06
  • sebeeeen
  • 1-J-1

Poem

🐰
New faces hop into the frame with cheer,
Badges glint, small statuses appear.
Rows align and icons softly chime,
Groups stack snug in playful time.
A rabbit nods — UI is here! 🎨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description includes the core purpose under 'What is this PR?' but the 'Changes' section is incomplete with only a placeholder (***). Several template sections (Background/Context, Request) are unfilled, though a checklist is present. Complete the 'Changes' section with specific bullet points describing the modifications (Avatar component, AvatarGroup component, icons, Storybook stories, and List/Table components).
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding a shared Avatars component. It directly corresponds to the primary feature introduced in the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/shared-avatars

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@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: 4

🧹 Nitpick comments (3)
src/shared/ui/avatars/avatars.tsx (3)

88-90: Consider handling empty string edge case.

While name is guarded by a truthy check before getInitial is called, the function itself doesn't handle whitespace-only strings (e.g., " ".charAt(0) returns " "). Consider trimming or validating input.

🔧 Optional defensive improvement
 const getInitial = (name: string): string => {
-  return name.charAt(0).toUpperCase();
+  const trimmed = name.trim();
+  return trimmed.charAt(0).toUpperCase();
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/avatars/avatars.tsx` around lines 88 - 90, getInitial currently
uses name.charAt(0) and doesn't handle whitespace-only or empty strings; update
the getInitial function to trim the input (use name.trim()), check if the
trimmed string has length > 0, and then return the first character uppercased,
otherwise return a safe fallback (e.g., an empty string or a placeholder like
'?') so callers won't receive a whitespace character for whitespace-only names.

191-197: Overflow indicator font size doesn't scale with avatar size.

The +N indicator uses a fixed text-[14px] regardless of the size prop. For consistency with the avatar text sizing, consider using avatarTextSizeClasses[size] or a scaled-down variant.

✨ Optional: Scale overflow text with size
       {hiddenCount > 0 && (
         <div
-          className={`${avatarSizeClasses[size]} relative inline-flex items-center justify-center rounded-full bg-[color:var(--color-gray-200,`#E5E5E5`)] text-[color:var(--color-gray-600,`#666666`)] font-bold text-[14px] border-[3px] border-white box-content shadow-[0_2px_8px_rgba(0,0,0,0.08)] flex-shrink-0 z-0`}
+          className={`${avatarSizeClasses[size]} ${avatarTextSizeClasses[size]} relative inline-flex items-center justify-center rounded-full bg-[color:var(--color-gray-200,`#E5E5E5`)] text-[color:var(--color-gray-600,`#666666`)] font-bold border-[3px] border-white box-content shadow-[0_2px_8px_rgba(0,0,0,0.08)] flex-shrink-0 z-0`}
         >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/avatars/avatars.tsx` around lines 191 - 197, The overflow
indicator currently uses a fixed text size (text-[14px]) so +{hiddenCount}
doesn't scale with the avatar; update the overflow div inside the Avatars
component to use the existing avatarTextSizeClasses mapping keyed by size (or a
scaled-down variant of avatarTextSizeClasses[size]) instead of text-[14px],
ensuring the class list combines avatarSizeClasses[size],
avatarTextSizeClasses[size] (or its scaled-down alternative), and the existing
layout classes so the +N text scales consistently with other avatar text.

136-141: Handle image load errors gracefully.

If the image URL fails to load (404, network error, etc.), the browser displays a broken image icon instead of falling back to initials or the default icon. Consider using useState to track load errors and render the fallback.

🖼️ Proposed approach for image error handling
+import React, { useState } from "react";
+
 export const Avatar = ({
   src,
   name,
   // ... other props
 }: AvatarProps): React.ReactElement => {
+  const [imgError, setImgError] = useState(false);
   const isClickable = !!onClick;
   // ...
 
   return (
     <div className={avatarClasses} onClick={onClick}>
-      {src ? (
+      {src && !imgError ? (
         <img
           src={src}
           alt={name || "User avatar"}
           className="w-full h-full object-cover rounded-full"
+          onError={() => setImgError(true)}
         />
       ) : name ? (
         // ... initials rendering
       ) : (
         // ... default icon
       )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/avatars/avatars.tsx` around lines 136 - 141, The img element
rendering branch that uses {src ? ( <img src={src} alt={name || "User avatar"}
... /> )} needs graceful error handling: add a useState flag (e.g.,
hasImageError) in the Avatar component (src/shared/ui/avatars/avatars.tsx),
attach an onError handler to the <img> that sets the flag true, and change the
conditional to render the existing fallback (initials or default icon) when
hasImageError is true or when src is falsy; ensure you also clear the flag on
successful load if you add an onLoad handler so re-tries work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/shared/ui/avatars/avatars.tsx`:
- Around line 133-134: The clickable avatar div using avatarClasses and onClick
needs keyboard and screen-reader accessibility: when onClick is provided, add
role="button", tabIndex={0} and an onKeyDown handler that calls the same onClick
for Enter/Space keys, and ensure an appropriate aria-label or pass-through aria
props if available; only attach these attributes/handler when onClick exists so
non-interactive avatars remain semantic. Ensure the onKeyDown logic maps Space
and Enter to preventDefault as needed and invokes the same callback so keyboard
users can activate the avatar.

In `@src/shared/ui/lists/lists.stories.tsx`:
- Line 157: The current isAllChecked calculation uses data.filter((r) =>
!r.isDisabled).every((r) => r.isSelected) which returns true for an empty array;
change it so it first computes the selectable rows (e.g., selectable =
data.filter(r => !r.isDisabled)) and then set isAllChecked to selectable.length
> 0 && selectable.every(r => r.isSelected) so the header checkbox is not checked
when there are zero selectable rows.

In `@src/shared/ui/lists/lists.tsx`:
- Around line 182-183: The clickable container currently rendered as a div
should be made keyboard-accessible: replace or modify the element that uses
getListItemClasses(isActive, isDisabled) and onClick={handleItemClick} so it
supports keyboard activation—either render a semantic <button> (preferred) or
add role="button", tabIndex={0}, and an onKeyDown handler that calls
handleItemClick on Enter/Space; also ensure isDisabled prevents activation (use
disabled prop for a button or aria-disabled when using a div/role) and update
classes via getListItemClasses accordingly so both Line 182 and the similar
container at Line 322–326 use the same accessible behavior.
- Around line 189-195: The checkbox input lacks an accessible name; update the
checkbox in src/shared/ui/lists/lists.tsx to provide an accessible label by
either wrapping the <input> with a <label> that contains visible text or by
adding an explicit aria-label or aria-labelledby attribute that describes the
checkbox purpose (use the same approach for the other checkbox instances using
isChecked/isDisabled). Ensure the label text is meaningful (e.g., "Select item",
or use the item title from props/state) and that aria-labelledby references an
existing element ID if you choose that route so screen readers can announce the
checkbox state correctly.

---

Nitpick comments:
In `@src/shared/ui/avatars/avatars.tsx`:
- Around line 88-90: getInitial currently uses name.charAt(0) and doesn't handle
whitespace-only or empty strings; update the getInitial function to trim the
input (use name.trim()), check if the trimmed string has length > 0, and then
return the first character uppercased, otherwise return a safe fallback (e.g.,
an empty string or a placeholder like '?') so callers won't receive a whitespace
character for whitespace-only names.
- Around line 191-197: The overflow indicator currently uses a fixed text size
(text-[14px]) so +{hiddenCount} doesn't scale with the avatar; update the
overflow div inside the Avatars component to use the existing
avatarTextSizeClasses mapping keyed by size (or a scaled-down variant of
avatarTextSizeClasses[size]) instead of text-[14px], ensuring the class list
combines avatarSizeClasses[size], avatarTextSizeClasses[size] (or its
scaled-down alternative), and the existing layout classes so the +N text scales
consistently with other avatar text.
- Around line 136-141: The img element rendering branch that uses {src ? ( <img
src={src} alt={name || "User avatar"} ... /> )} needs graceful error handling:
add a useState flag (e.g., hasImageError) in the Avatar component
(src/shared/ui/avatars/avatars.tsx), attach an onError handler to the <img> that
sets the flag true, and change the conditional to render the existing fallback
(initials or default icon) when hasImageError is true or when src is falsy;
ensure you also clear the flag on successful load if you add an onLoad handler
so re-tries work.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a05c6ee3-aa9c-4605-9968-a8331bf3a97e

📥 Commits

Reviewing files that changed from the base of the PR and between f9195e7 and fad55fa.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • src/shared/ui/avatars/avatars.stories.tsx
  • src/shared/ui/avatars/avatars.tsx
  • src/shared/ui/lists/lists.stories.tsx
  • src/shared/ui/lists/lists.tsx

Comment on lines +133 to +134
return (
<div className={avatarClasses} onClick={onClick}>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add accessibility attributes for clickable avatars.

When onClick is provided, the <div> becomes interactive but lacks keyboard accessibility. Screen reader users and keyboard-only users cannot interact with it.

♿ Proposed fix for accessibility
     <div
       className={avatarClasses}
       onClick={onClick}
+      role={isClickable ? "button" : undefined}
+      tabIndex={isClickable ? 0 : undefined}
+      onKeyDown={isClickable ? (e) => {
+        if (e.key === "Enter" || e.key === " ") {
+          e.preventDefault();
+          onClick?.();
+        }
+      } : undefined}
+      aria-label={isClickable ? (name || "User avatar") : undefined}
     >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/avatars/avatars.tsx` around lines 133 - 134, The clickable
avatar div using avatarClasses and onClick needs keyboard and screen-reader
accessibility: when onClick is provided, add role="button", tabIndex={0} and an
onKeyDown handler that calls the same onClick for Enter/Space keys, and ensure
an appropriate aria-label or pass-through aria props if available; only attach
these attributes/handler when onClick exists so non-interactive avatars remain
semantic. Ensure the onKeyDown logic maps Space and Enter to preventDefault as
needed and invokes the same callback so keyboard users can activate the avatar.

Comment thread src/shared/ui/lists/lists.stories.tsx Outdated
Comment on lines +182 to +183
<div className={getListItemClasses(isActive, isDisabled)} onClick={handleItemClick}>
{/* 체크박스 */}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keyboard 접근 가능한 인터랙션으로 바꿔주세요

Line 182와 Line 325의 클릭 가능한 컨테이너가 div라서 키보드(Enter/Space)로 동작하지 않습니다. 접근성 관점에서 실제 사용이 막힐 수 있습니다.

♿ Proposed fix
+import React from "react";
+
+const isActivationKey = (key: string): boolean => key === "Enter" || key === " ";
...
 export const ListItem = ({
 ...
 }: ListItemProps): React.ReactElement => {
   const handleItemClick = () => {
     if (!isDisabled && onClick) onClick(id);
   };
+
+  const handleItemKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
+    if (isDisabled || !onClick) return;
+    if (isActivationKey(e.key)) {
+      e.preventDefault();
+      onClick(id);
+    }
+  };
 ...
-  return (
-    <div className={getListItemClasses(isActive, isDisabled)} onClick={handleItemClick}>
+  return (
+    <div
+      className={getListItemClasses(isActive, isDisabled)}
+      onClick={handleItemClick}
+      role="button"
+      tabIndex={isDisabled ? -1 : 0}
+      aria-disabled={isDisabled}
+      onKeyDown={handleItemKeyDown}
+    >
...
           {data.map((row) => {
...
             const handleRowClick = () => {
               if (!isDisabled && onRowClick) onRowClick(row.id);
             };
+
+            const handleRowKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
+              if (isDisabled || !onRowClick) return;
+              if (isActivationKey(e.key)) {
+                e.preventDefault();
+                onRowClick(row.id);
+              }
+            };
...
               <div
                 key={row.id}
                 className={getTableRowClasses(isSelected, isDisabled)}
                 onClick={handleRowClick}
+                role="button"
+                tabIndex={isDisabled ? -1 : 0}
+                aria-disabled={isDisabled}
+                onKeyDown={handleRowKeyDown}
               >

Also applies to: 322-326

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/lists/lists.tsx` around lines 182 - 183, The clickable
container currently rendered as a div should be made keyboard-accessible:
replace or modify the element that uses getListItemClasses(isActive, isDisabled)
and onClick={handleItemClick} so it supports keyboard activation—either render a
semantic <button> (preferred) or add role="button", tabIndex={0}, and an
onKeyDown handler that calls handleItemClick on Enter/Space; also ensure
isDisabled prevents activation (use disabled prop for a button or aria-disabled
when using a div/role) and update classes via getListItemClasses accordingly so
both Line 182 and the similar container at Line 322–326 use the same accessible
behavior.

Comment on lines +189 to +195
<input
type="checkbox"
checked={isChecked}
readOnly
disabled={isDisabled}
className="w-4 h-4 cursor-pointer accent-[color:var(--color-primary-800,#004A9C)]"
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

체크박스에 접근 가능한 라벨이 필요합니다

Line 189, Line 275, Line 332의 체크박스에 접근 가능한 이름이 없어 스크린리더 사용성이 떨어집니다.

🏷️ Proposed fix
           <input
             type="checkbox"
             checked={isChecked}
             readOnly
             disabled={isDisabled}
+            aria-label={`${title} 선택`}
             className="w-4 h-4 cursor-pointer accent-[color:var(--color-primary-800,`#004A9C`)]"
           />
...
             <input
               type="checkbox"
               checked={isAllChecked}
               onChange={(e) => onCheckAll && onCheckAll(e.target.checked)}
+              aria-label="모든 행 선택"
               className="w-4 h-4 cursor-pointer accent-[color:var(--color-primary-800,`#004A9C`)]"
             />
...
                     <input
                       type="checkbox"
                       checked={isSelected}
                       readOnly
                       disabled={isDisabled}
+                      aria-label={`${row.id} 행 선택`}
                       className="w-4 h-4 cursor-pointer accent-[color:var(--color-primary-800,`#004A9C`)]"
                     />

Also applies to: 275-280, 332-338

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/lists/lists.tsx` around lines 189 - 195, The checkbox input
lacks an accessible name; update the checkbox in src/shared/ui/lists/lists.tsx
to provide an accessible label by either wrapping the <input> with a <label>
that contains visible text or by adding an explicit aria-label or
aria-labelledby attribute that describes the checkbox purpose (use the same
approach for the other checkbox instances using isChecked/isDisabled). Ensure
the label text is meaningful (e.g., "Select item", or use the item title from
props/state) and that aria-labelledby references an existing element ID if you
choose that route so screen readers can announce the checkbox state correctly.

jaeu5325 and others added 4 commits March 28, 2026 21:44
isAllChecked 계산식의 빈 배열 케이스를 보정

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@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: 1

♻️ Duplicate comments (3)
src/shared/ui/avatars/avatars.tsx (1)

124-124: ⚠️ Potential issue | 🟠 Major

Make clickable avatars keyboard-accessible.

Line 124 uses a clickable div without keyboard semantics, so keyboard users cannot activate it.

♿ Proposed fix
 export const Avatar = ({
@@
 }: AvatarProps): React.ReactElement => {
   const isClickable = !!onClick;
+  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
+    if (!isClickable) return;
+    if (e.key === "Enter" || e.key === " ") {
+      e.preventDefault();
+      onClick?.();
+    }
+  };
@@
-  return (
-    <div className={avatarClasses} onClick={onClick}>
+  return (
+    <div
+      className={avatarClasses}
+      onClick={onClick}
+      role={isClickable ? "button" : undefined}
+      tabIndex={isClickable ? 0 : undefined}
+      onKeyDown={isClickable ? handleKeyDown : undefined}
+      aria-label={isClickable ? (name ? `${name} avatar` : "User avatar") : undefined}
+    >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/avatars/avatars.tsx` at line 124, The clickable avatar uses a
raw div with avatarClasses and onClick, which is not keyboard-accessible; update
the avatar root element to expose button semantics by adding role="button",
tabIndex={0}, and an onKeyDown handler that calls the same onClick callback when
Enter or Space is pressed (and ensure it prevents default for Space). Keep the
existing onClick behavior and only add these attributes/handler where
avatarClasses and onClick are used so keyboard users can activate the avatar.
src/shared/ui/lists/lists.tsx (2)

145-145: ⚠️ Potential issue | 🟠 Major

Interactive rows still need keyboard activation support.

Line 145 and Line 285-Line 289 use clickable div containers without keyboard semantics (Enter/Space), which blocks keyboard-only usage.

♿ Proposed fix
+const isActivationKey = (key: string): boolean => key === "Enter" || key === " ";

 export const ListItem = ({
@@
 }: ListItemProps): React.ReactElement => {
@@
+  const handleItemKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
+    if (isDisabled || !onClick) return;
+    if (isActivationKey(e.key)) {
+      e.preventDefault();
+      onClick(id);
+    }
+  };

   return (
-    <div className={getListItemClasses(isActive, isDisabled)} onClick={handleItemClick}>
+    <div
+      className={getListItemClasses(isActive, isDisabled)}
+      onClick={handleItemClick}
+      role="button"
+      tabIndex={isDisabled ? -1 : 0}
+      aria-disabled={isDisabled}
+      onKeyDown={handleItemKeyDown}
+    >
@@
             const handleRowClick = () => {
               if (!isDisabled && onRowClick) onRowClick(row.id);
             };
+            const handleRowKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
+              if (isDisabled || !onRowClick) return;
+              if (isActivationKey(e.key)) {
+                e.preventDefault();
+                onRowClick(row.id);
+              }
+            };

             return (
               <div
                 key={row.id}
                 className={getTableRowClasses(isSelected, isDisabled)}
                 onClick={handleRowClick}
+                role="button"
+                tabIndex={isDisabled ? -1 : 0}
+                aria-disabled={isDisabled}
+                onKeyDown={handleRowKeyDown}
               >

Also applies to: 285-289

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/lists/lists.tsx` at line 145, The interactive list rows using
the getListItemClasses(...) container and handleItemClick currently only handle
mouse clicks; add keyboard accessibility by making the container focusable and
semantic: add role="button", tabIndex={isDisabled ? -1 : 0}, and
aria-disabled={isDisabled}, then implement an onKeyDown handler that calls
handleItemClick when Enter is pressed or when Space is pressed (prevent default
for Space to avoid page scroll); ensure the handler respects isDisabled the same
way as the onClick path so disabled rows remain inert.

152-158: ⚠️ Potential issue | 🟠 Major

Add accessible names to all checkboxes.

The checkboxes at Line 152-Line 158, Line 238-Line 243, and Line 295-Line 301 do not expose meaningful labels to screen readers.

🏷️ Proposed fix
           <input
             type="checkbox"
             checked={isChecked}
             readOnly
             disabled={isDisabled}
+            aria-label={`${title} 선택`}
             className="w-4 h-4 cursor-pointer accent-[color:var(--color-primary-800,`#004A9C`)]"
           />
@@
             <input
               type="checkbox"
               checked={isAllChecked}
               onChange={(e) => onCheckAll && onCheckAll(e.target.checked)}
+              aria-label="모든 행 선택"
               className="w-4 h-4 cursor-pointer accent-[color:var(--color-primary-800,`#004A9C`)]"
             />
@@
                     <input
                       type="checkbox"
                       checked={isSelected}
                       readOnly
                       disabled={isDisabled}
+                      aria-label={`${row.id} 행 선택`}
                       className="w-4 h-4 cursor-pointer accent-[color:var(--color-primary-800,`#004A9C`)]"
                     />

Also applies to: 238-243, 295-301

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/lists/lists.tsx` around lines 152 - 158, The checkbox inputs in
src/shared/ui/lists/lists.tsx (the input elements using props isChecked and
isDisabled and the className "w-4 h-4 cursor-pointer…") lack accessible names;
update each checkbox (the ones referencing isChecked/isDisabled at the three
occurrences) to expose a meaningful label by either adding an aria-label or
aria-labelledby that describes the checkbox purpose (or by associating it with a
visible <label> via id/htmlFor), ensuring the label text is specific to the
checkbox context; keep existing props (checked, readOnly, disabled, className)
and add the aria attribute or id/htmlFor pair to each input so screen readers
can announce the control.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/shared/ui/avatars/avatars.tsx`:
- Around line 164-166: The calculations for visibleAvatars and hiddenCount can
produce incorrect results when maxVisible is 0 or negative; clamp maxVisible to
a valid range before using it (e.g., compute a safeMax = Math.max(0,
Math.min(maxVisible, avatars.length))) and then use safeMax in
avatars.slice(...) and in the hiddenCount computation so visibleAvatars and
hiddenCount are never derived from a negative or out-of-range maxVisible.

---

Duplicate comments:
In `@src/shared/ui/avatars/avatars.tsx`:
- Line 124: The clickable avatar uses a raw div with avatarClasses and onClick,
which is not keyboard-accessible; update the avatar root element to expose
button semantics by adding role="button", tabIndex={0}, and an onKeyDown handler
that calls the same onClick callback when Enter or Space is pressed (and ensure
it prevents default for Space). Keep the existing onClick behavior and only add
these attributes/handler where avatarClasses and onClick are used so keyboard
users can activate the avatar.

In `@src/shared/ui/lists/lists.tsx`:
- Line 145: The interactive list rows using the getListItemClasses(...)
container and handleItemClick currently only handle mouse clicks; add keyboard
accessibility by making the container focusable and semantic: add role="button",
tabIndex={isDisabled ? -1 : 0}, and aria-disabled={isDisabled}, then implement
an onKeyDown handler that calls handleItemClick when Enter is pressed or when
Space is pressed (prevent default for Space to avoid page scroll); ensure the
handler respects isDisabled the same way as the onClick path so disabled rows
remain inert.
- Around line 152-158: The checkbox inputs in src/shared/ui/lists/lists.tsx (the
input elements using props isChecked and isDisabled and the className "w-4 h-4
cursor-pointer…") lack accessible names; update each checkbox (the ones
referencing isChecked/isDisabled at the three occurrences) to expose a
meaningful label by either adding an aria-label or aria-labelledby that
describes the checkbox purpose (or by associating it with a visible <label> via
id/htmlFor), ensuring the label text is specific to the checkbox context; keep
existing props (checked, readOnly, disabled, className) and add the aria
attribute or id/htmlFor pair to each input so screen readers can announce the
control.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 16b377f8-4709-42cc-8f46-99027a7e5ea1

📥 Commits

Reviewing files that changed from the base of the PR and between fad55fa and fe19606.

📒 Files selected for processing (4)
  • src/shared/ui/avatars/avatars.tsx
  • src/shared/ui/icons/index.tsx
  • src/shared/ui/lists/lists.stories.tsx
  • src/shared/ui/lists/lists.tsx

Comment thread src/shared/ui/avatars/avatars.tsx Outdated
Comment thread src/shared/ui/avatars/avatars.tsx Outdated
Comment thread src/shared/ui/avatars/avatars.tsx
Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (1)
src/shared/ui/avatars/avatars.tsx (1)

183-188: Consider scaling the "+N" badge text size with the avatar size.

The text-[14px] is fixed regardless of the size prop. For larger avatars (e.g., 2xl, 3xl), the badge text may appear disproportionately small; for xs, it may overflow.

♻️ Suggested approach

Reuse avatarTextSizeClasses[size] or create a dedicated map for badge text sizes:

         <div
-          className={`${avatarSizeClasses[size]} relative inline-flex items-center justify-center rounded-full bg-[color:var(--color-gray-200,`#E5E5E5`)] text-[color:var(--color-gray-600,`#666666`)] font-bold text-[14px] border-[3px] border-white box-content shadow-[0_2px_8px_rgba(0,0,0,0.08)] flex-shrink-0 z-0`}
+          className={`${avatarSizeClasses[size]} ${avatarTextSizeClasses[size]} relative inline-flex items-center justify-center rounded-full bg-[color:var(--color-gray-200,`#E5E5E5`)] text-[color:var(--color-gray-600,`#666666`)] font-bold border-[3px] border-white box-content shadow-[0_2px_8px_rgba(0,0,0,0.08)] flex-shrink-0 z-0`}
         >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/avatars/avatars.tsx` around lines 183 - 188, The "+N" badge
currently uses a fixed class text-[14px] which doesn't scale with the avatar
size; update the badge's class composition inside the Avatars component (the div
rendering +{hiddenCount}) to use the existing avatarTextSizeClasses[size] (or
add a badgeTextSizeClasses[size] map) instead of the hard-coded text-[14px],
ensuring you include the computed size class alongside the other classes so the
badge font scales correctly for sizes like xs, 2xl, 3xl.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/shared/ui/avatars/avatars.tsx`:
- Around line 183-188: The "+N" badge currently uses a fixed class text-[14px]
which doesn't scale with the avatar size; update the badge's class composition
inside the Avatars component (the div rendering +{hiddenCount}) to use the
existing avatarTextSizeClasses[size] (or add a badgeTextSizeClasses[size] map)
instead of the hard-coded text-[14px], ensuring you include the computed size
class alongside the other classes so the badge font scales correctly for sizes
like xs, 2xl, 3xl.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8e62b408-8b19-43d9-a419-fe649fbddb30

📥 Commits

Reviewing files that changed from the base of the PR and between fe19606 and 519684b.

📒 Files selected for processing (1)
  • src/shared/ui/avatars/avatars.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants