Skip to content

[Feat] #74 - 공통 Empty States 컴포넌트 추가#76

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

[Feat] #74 - 공통 Empty States 컴포넌트 추가#76
jaeu5325 merged 31 commits into
devfrom
feat/shared-empty_states

Conversation

@jaeu5325
Copy link
Copy Markdown
Collaborator

@jaeu5325 jaeu5325 commented Mar 28, 2026

🔎 What is this PR?

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

📝 Changes


📸 Screenshots (선택)

스크린샷 2026-03-28 오후 10 23 05 스크린샷 2026-03-28 오후 10 23 18 스크린샷 2026-03-28 오후 10 23 28 스크린샷 2026-03-28 오후 10 23 37 스크린샷 2026-03-28 오후 10 23 48 스크린샷 2026-03-28 오후 10 23 57 스크린샷 2026-03-28 오후 10 24 05 스크린샷 2026-03-28 오후 10 24 16

📚 Background / Context (선택)


✔ Checklist

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

🙏 Request

Summary by CodeRabbit

  • New Features

    • Added avatar component with multiple size options and status indicators
    • Added avatar group for displaying multiple avatars in an overlapping stack with a count for hidden items
    • Added new icons: Inbox, User Solid, File Text, Bell, Alert Circle, and Clock
    • Added list item component with optional image, meta text, and checkbox support
    • Added interactive table component with column customization, row selection, and empty states
    • Added empty state component with six predefined variants and customizable content
  • Tests

    • Added Storybook documentation for all new components and their interactive variants

@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 6 minutes and 7 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 6 minutes and 7 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: eef8a833-f028-41a9-a080-a992db28817f

📥 Commits

Reviewing files that changed from the base of the PR and between 3934d3e and d6b6021.

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

Walkthrough

The PR adds multiple new shared UI components including Avatar, AvatarGroup, ListItem, Table, and EmptyState, along with six new icon components (InboxIcon, UserSolidIcon, FileTextIcon, BellIcon, AlertCircleIcon, ClockIcon). Comprehensive Storybook stories document each component with various configurations and interactive examples showcasing sizing, variants, states, and customization options.

Changes

Cohort / File(s) Summary
Avatar Components
src/shared/ui/avatars/avatars.tsx, src/shared/ui/avatars/avatars.stories.tsx
New Avatar component supporting image, initial text, and fallback icon with configurable sizing, status indicator, badge overlay, and grouping support. AvatarGroup renders stacked overlapping avatars with +N counter. Stories demonstrate all sizes (xs3xl), rendering priority/variants, status states, badges, and group configurations.
Icon Exports
src/shared/ui/icons/index.tsx
Added six new 24px SVG icon components: InboxIcon, UserSolidIcon, FileTextIcon, BellIcon, AlertCircleIcon, ClockIcon with standard IconProps forwarding.
List & Table Components
src/shared/ui/lists/lists.tsx, src/shared/ui/lists/lists.stories.tsx
New ListItem component with optional description, image, checkbox, chevron arrow, and active/disabled states. Table component with configurable columns, optional empty-state, row selection/checkbox callbacks, and per-row disabled/selected styling. Stories include interactive state management wrappers demonstrating list/table selection, activation, and bulk checkbox toggling.
Empty State Component
src/shared/ui/empty_states/empty-states.tsx, src/shared/ui/empty_states/empty-states.stories.tsx
New EmptyState component with six variants (no-content, no-results, no-notifications, error, access-denied, coming-soon), configurable title/description/icon, optional background, and primary/secondary action buttons. Stories showcase each variant with and without background, plus custom text overrides.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • ajou-industry-matching/aim-frontend#68 — Directly removes the same lists.tsx, lists.stories.tsx, and Storybook patterns that this PR introduces, suggesting a potential conflict or reversion scenario.

Suggested reviewers

  • kimsman06
  • sebeeeen
  • kwqnyewest

Poem

🐰 In gardens of shared UI we hop and play,
Avatars, lists, and empty states display,
Icons and tables all sparkle so bright,
Component by component, we craft pure delight!
Six stories to tell, from the largest to small,

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description includes the core objective but is mostly incomplete: 'What is this PR?' section is present and filled, but 'Changes' section uses only a placeholder (***), and other sections lack meaningful content. Complete the 'Changes' section with bullet points listing the new components and supporting files added. Consider adding brief context explaining the purpose of these empty states.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a shared Empty States component, matching the primary file additions (empty-states.tsx and stories).
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-empty_states

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.

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

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

144-145: Consider adding keyboard accessibility for interactive list items.

The ListItem uses a div with onClick for interaction, but lacks keyboard support (role, tabIndex, onKeyDown). Screen reader users and keyboard-only users cannot interact with these items.

♿ Proposed accessibility improvement
   return (
     <div
       className={getListItemClasses(isActive, isDisabled)}
       onClick={handleItemClick}
+      role="button"
+      tabIndex={isDisabled ? -1 : 0}
+      onKeyDown={(e) => {
+        if (e.key === "Enter" || e.key === " ") {
+          e.preventDefault();
+          handleItemClick();
+        }
+      }}
+      aria-disabled={isDisabled}
     >
🤖 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 144 - 145, The ListItem currently
renders an interactive div without keyboard accessibility; update the ListItem
component (the element using getListItemClasses(isActive, isDisabled) and
handleItemClick) to be keyboard-focusable and screen-reader friendly by adding
role="button", tabIndex={isDisabled ? -1 : 0}, and an onKeyDown handler that
triggers handleItemClick when Enter or Space is pressed (and ignores input when
isDisabled is true); ensure focus/disabled behavior matches existing click
behavior and that ARIA state (e.g., aria-pressed or aria-disabled) is applied as
appropriate.

284-289: Same keyboard accessibility concern for table rows.

Table rows with onClick should also support keyboard navigation for accessibility compliance.

♿ Proposed accessibility improvement
             return (
               <div
                 key={row.id}
                 className={getTableRowClasses(isSelected, isDisabled)}
                 onClick={handleRowClick}
+                role="row"
+                tabIndex={isDisabled ? -1 : 0}
+                onKeyDown={(e) => {
+                  if (e.key === "Enter" || e.key === " ") {
+                    e.preventDefault();
+                    handleRowClick();
+                  }
+                }}
+                aria-disabled={isDisabled}
               >
🤖 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 284 - 289, The table row
currently uses an onClick handler (handleRowClick) but lacks keyboard
accessibility; update the rendered div (the element with key={row.id} and
className={getTableRowClasses(isSelected, isDisabled)}) to be keyboard-focusable
and activatable: add tabIndex={isDisabled ? -1 : 0}, a suitable role (e.g.,
role="row" or "button" depending on semantics), and an onKeyDown handler that
calls the same activation logic as handleRowClick when Enter or Space is pressed
while ignoring input when isDisabled is true; ensure focus/disabled styling and
ARIA states (aria-selected/aria-disabled) mirror the visual state returned by
getTableRowClasses.

260-267: Consider reusing the EmptyState component.

The Table's empty state rendering is implemented inline. For consistency across the design system, consider using the shared EmptyState component from this PR, potentially with a minimal variant or custom styling.

🤖 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 260 - 267, Replace the inline
empty-state div in Lists (the conditional that checks isEmpty || data.length ===
0) with the shared EmptyState component: import EmptyState into
src/shared/ui/lists/lists.tsx, remove the custom div that renders InboxIcon and
the text, and render <EmptyState> (or the minimal/variant prop) passing the
InboxIcon (or an icon prop) and the message "데이터가 없습니다" so styling and behavior
are consistent across the app; ensure the conditional still uses isEmpty/data
and that the new component receives any necessary className or size props to
match the previous layout.
src/shared/ui/lists/lists.stories.tsx (1)

159-172: Clarify the interaction pattern between row click and checkbox.

In InteractiveTable, handleRowCheck (lines 159-161) toggles individual row selection (multi-select), while handleRowClick (lines 169-172) clears all selections and selects only the clicked row (single-select). These conflicting behaviors in the same demo may confuse users exploring the component.

Consider adding a comment explaining this is demonstrating two different selection modes, or unify the behavior within this story.

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

In `@src/shared/ui/lists/lists.stories.tsx` around lines 159 - 172, The story
mixes two conflicting selection modes: handleRowCheck implements multi-select
while handleRowClick enforces single-select; update the story to either unify
behavior or clearly document both modes. Option A: make handleRowClick align
with multi-select (toggle the clicked row like handleRowCheck does) so clicking
and checkbox behave consistently; Option B: keep both behaviors but add a clear
comment at the top of the story explaining that handleRowCheck/handleCheckAll
demonstrate checkbox multi-select and handleRowClick demonstrates a separate
single-select interaction. Reference the functions handleRowCheck,
handleCheckAll and handleRowClick when making the change or adding the comment.
src/shared/ui/empty_states/emptyStates.tsx (1)

121-127: The .filter(Boolean) is unnecessary here.

All values in the array are guaranteed to be non-empty strings, so filtering for truthy values has no effect.

♻️ Suggested simplification
   const containerClasses = [
     containerBaseClasses,
     hasBackground ? "bg-[color:var(--color-gray-50,`#F9F9F9`)] rounded-lg" : "bg-transparent",
     className,
-  ]
-    .filter(Boolean)
-    .join(" ");
+  ].join(" ");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/empty_states/emptyStates.tsx` around lines 121 - 127, The array
used to build containerClasses unnecessarily calls .filter(Boolean) even though
containerBaseClasses, hasBackground branch, and className are always non-empty
strings; remove the .filter(Boolean) call and directly join the array created
from containerBaseClasses, the hasBackground conditional string, and className
(used in the containerClasses declaration) to simplify the code.
🤖 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 78-80: Trim the input before computing initials: update getInitial
to call name = name.trim() before using name.charAt(0).toUpperCase() and return
a safe fallback (e.g., empty string) when the trimmed name is empty; apply the
same change to the other initials helper used around lines 133-135 (e.g.,
getInitials or any function that reads name parts) so all initials logic trims
whitespace and handles empty names gracefully.
- Around line 164-166: Clamp and integer-normalize maxVisible before using it:
ensure maxVisible is floored and constrained to the range [0, avatars.length]
and then compute visibleAvatars and hiddenCount from that clamped value; update
references to visibleAvatars, hiddenCount, avatars, and maxVisible so slicing
and subtraction use the sanitized/clampedMaxVisible to avoid negative or
fractional results.
- Around line 123-125: The avatar wrapper currently renders a plain div with
props avatarClasses and onClick, which prevents keyboard users from
focusing/activating it; update the wrapper in avatars.tsx so when onClick is
provided it becomes accessible by adding role="button" and tabIndex={0}, and
implement an onKeyDown handler on the same element that calls the onClick
callback when Enter or Space is pressed (also prevent default for Space), while
preserving the existing onClick behavior and passing through any aria-label/alt
text if available for screen readers.

In `@src/shared/ui/empty_states/emptyStates.stories.tsx`:
- Around line 1-2: Change the import source for Storybook types: replace
importing Meta and StoryObj from "@storybook/react-vite" with importing them
from "@storybook/react" in the file that references the Meta and StoryObj types
(used alongside the EmptyState component/EmptyState story definitions), so
TypeScript uses the Storybook 10 CSF3-supported types.

---

Nitpick comments:
In `@src/shared/ui/empty_states/emptyStates.tsx`:
- Around line 121-127: The array used to build containerClasses unnecessarily
calls .filter(Boolean) even though containerBaseClasses, hasBackground branch,
and className are always non-empty strings; remove the .filter(Boolean) call and
directly join the array created from containerBaseClasses, the hasBackground
conditional string, and className (used in the containerClasses declaration) to
simplify the code.

In `@src/shared/ui/lists/lists.stories.tsx`:
- Around line 159-172: The story mixes two conflicting selection modes:
handleRowCheck implements multi-select while handleRowClick enforces
single-select; update the story to either unify behavior or clearly document
both modes. Option A: make handleRowClick align with multi-select (toggle the
clicked row like handleRowCheck does) so clicking and checkbox behave
consistently; Option B: keep both behaviors but add a clear comment at the top
of the story explaining that handleRowCheck/handleCheckAll demonstrate checkbox
multi-select and handleRowClick demonstrates a separate single-select
interaction. Reference the functions handleRowCheck, handleCheckAll and
handleRowClick when making the change or adding the comment.

In `@src/shared/ui/lists/lists.tsx`:
- Around line 144-145: The ListItem currently renders an interactive div without
keyboard accessibility; update the ListItem component (the element using
getListItemClasses(isActive, isDisabled) and handleItemClick) to be
keyboard-focusable and screen-reader friendly by adding role="button",
tabIndex={isDisabled ? -1 : 0}, and an onKeyDown handler that triggers
handleItemClick when Enter or Space is pressed (and ignores input when
isDisabled is true); ensure focus/disabled behavior matches existing click
behavior and that ARIA state (e.g., aria-pressed or aria-disabled) is applied as
appropriate.
- Around line 284-289: The table row currently uses an onClick handler
(handleRowClick) but lacks keyboard accessibility; update the rendered div (the
element with key={row.id} and className={getTableRowClasses(isSelected,
isDisabled)}) to be keyboard-focusable and activatable: add tabIndex={isDisabled
? -1 : 0}, a suitable role (e.g., role="row" or "button" depending on
semantics), and an onKeyDown handler that calls the same activation logic as
handleRowClick when Enter or Space is pressed while ignoring input when
isDisabled is true; ensure focus/disabled styling and ARIA states
(aria-selected/aria-disabled) mirror the visual state returned by
getTableRowClasses.
- Around line 260-267: Replace the inline empty-state div in Lists (the
conditional that checks isEmpty || data.length === 0) with the shared EmptyState
component: import EmptyState into src/shared/ui/lists/lists.tsx, remove the
custom div that renders InboxIcon and the text, and render <EmptyState> (or the
minimal/variant prop) passing the InboxIcon (or an icon prop) and the message
"데이터가 없습니다" so styling and behavior are consistent across the app; ensure the
conditional still uses isEmpty/data and that the new component receives any
necessary className or size props to match the previous layout.
🪄 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: a3836179-d3bf-4e75-b208-20cc16656455

📥 Commits

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

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

Comment on lines +78 to +80
const getInitial = (name: string): string => {
return name.charAt(0).toUpperCase();
};
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 | 🟡 Minor

Trim name input before computing/displaying initials.

Whitespace-prefixed names can currently render an empty-looking initial.

✂️ Suggested fix
 const getInitial = (name: string): string => {
-  return name.charAt(0).toUpperCase();
+  return name.trim().charAt(0).toUpperCase();
 };
@@
 }: AvatarProps): React.ReactElement => {
+  const normalizedName = name?.trim();
   const isClickable = !!onClick;
@@
-          alt={name || "User avatar"}
+          alt={normalizedName || "User avatar"}
           className="w-full h-full object-cover rounded-full"
         />
       ) : /* 2. 이미지가 없으면 이름 이니셜 */
-      name ? (
-        <span className={`${avatarTextSizeClasses[size]} tracking-tight`}>{getInitial(name)}</span>
+      normalizedName ? (
+        <span className={`${avatarTextSizeClasses[size]} tracking-tight`}>{getInitial(normalizedName)}</span>
       ) : (

Also applies to: 133-135

🤖 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 78 - 80, Trim the input
before computing initials: update getInitial to call name = name.trim() before
using name.charAt(0).toUpperCase() and return a safe fallback (e.g., empty
string) when the trimmed name is empty; apply the same change to the other
initials helper used around lines 133-135 (e.g., getInitials or any function
that reads name parts) so all initials logic trims whitespace and handles empty
names gracefully.

Comment on lines +123 to +125
return (
<div className={avatarClasses} onClick={onClick}>
{/* 1. 이미지 우선 렌더링 */}
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

Make clickable avatars keyboard-accessible.

When onClick is set, the wrapper is still a plain div, so keyboard users can’t reliably focus/activate it.

♿ Suggested fix
-    <div className={avatarClasses} onClick={onClick}>
+    <div
+      className={avatarClasses}
+      onClick={onClick}
+      role={isClickable ? "button" : undefined}
+      tabIndex={isClickable ? 0 : undefined}
+      aria-label={isClickable ? (name?.trim() ? `${name.trim()} avatar` : "User avatar") : undefined}
+      onKeyDown={
+        isClickable
+          ? (e) => {
+              if (e.key === "Enter" || e.key === " ") {
+                e.preventDefault();
+                onClick?.();
+              }
+            }
+          : 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 123 - 125, The avatar wrapper
currently renders a plain div with props avatarClasses and onClick, which
prevents keyboard users from focusing/activating it; update the wrapper in
avatars.tsx so when onClick is provided it becomes accessible by adding
role="button" and tabIndex={0}, and implement an onKeyDown handler on the same
element that calls the onClick callback when Enter or Space is pressed (also
prevent default for Space), while preserving the existing onClick behavior and
passing through any aria-label/alt text if available for screen readers.

Comment thread src/shared/ui/avatars/avatars.tsx Outdated
Comment thread src/shared/ui/empty_states/emptyStates.stories.tsx Outdated
Comment on lines +91 to +96
// Button/Primary/Large 스타일 모방 (실제 Button 컴포넌트가 있다면 대체 가능)
const primaryBtnClasses =
"inline-flex items-center justify-center h-[48px] px-6 rounded-lg bg-[color:var(--color-primary-800,#004A9C)] text-white font-medium text-[16px] transition-colors hover:bg-[color:var(--color-primary-900,#003875)] active:bg-[color:var(--color-primary-950,#002B5E)]";
// Button/Ghost 스타일 모방
const secondaryBtnClasses =
"inline-flex items-center justify-center h-[48px] px-6 rounded-lg bg-transparent text-[color:var(--color-gray-600,#666666)] font-medium text-[16px] transition-colors hover:bg-[color:var(--color-gray-100,#F2F2F2)] mt-[12px]";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

현재 EmptyState 안에서 버튼 스타일을 따로 만들고 있는데
이미 shared/ui에 공용 Button 컴포넌트가 있어 이를 재사용해주시면 감사하겠습니다~

이렇게 하면 버튼 스타일을 한 곳에서 관리할 수 있어서 유지보수도 더 쉬워질 것 같습니다!

@sebeeeen
Copy link
Copy Markdown
Member

// 현재
emptyStates.tsx
emptyStates.stories.tsx

// 컨벤션 (케밥 케이스)
empty-states.tsx
empty-states.stories.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.

Actionable comments posted: 1

🤖 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/empty_states/empty-states.stories.tsx`:
- Around line 38-85: The story handlers for ErrorState and AccessDenied are
using destructive browser APIs (window.location.reload and history.back) which
can disrupt Storybook; update the onClick handlers in the
ErrorState.args.primaryAction and AccessDenied.args.primaryAction to
non-destructive, story-safe alternatives (e.g., show an alert or console.log)
while keeping the labels the same so the Story definitions (ErrorState,
AccessDenied) remain unchanged and interactive without navigating away.
🪄 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: b6cedd18-ac47-4310-9674-cbd6f9f3afd2

📥 Commits

Reviewing files that changed from the base of the PR and between b35110c and 3934d3e.

📒 Files selected for processing (2)
  • src/shared/ui/empty_states/empty-states.stories.tsx
  • src/shared/ui/empty_states/empty-states.tsx

Comment on lines +38 to +85
export const NoContent: Story = {
args: {
variant: "no-content",
primaryAction: {
label: "첫 게시물 작성하기",
onClick: () => alert("게시물 작성 모달 오픈!"),
},
},
};

export const NoResults: Story = {
args: {
variant: "no-results",
primaryAction: {
label: "검색어 초기화",
onClick: () => alert("검색어가 초기화되었습니다."),
},
},
};

export const NoNotifications: Story = {
args: {
variant: "no-notifications",
},
};

export const ErrorState: Story = {
args: {
variant: "error",
primaryAction: {
label: "다시 시도",
onClick: () => window.location.reload(),
},
},
};

export const AccessDenied: Story = {
args: {
variant: "access-denied",
primaryAction: {
label: "이전 페이지로",
onClick: () => history.back(),
},
secondaryAction: {
label: "홈으로 이동",
onClick: () => alert("홈으로 이동합니다."),
},
},
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

Avoid destructive browser side effects in stories.

Line 69 (window.location.reload()) and Line 79 (history.back()) can kick users out of Storybook (or reset docs) when clicking demo buttons. Keep story handlers non-destructive so docs stay usable.

Proposed story-safe handler changes
 export const NoContent: Story = {
   args: {
     variant: "no-content",
     primaryAction: {
       label: "첫 게시물 작성하기",
-      onClick: () => alert("게시물 작성 모달 오픈!"),
+      onClick: () => {},
     },
   },
 };
@@
 export const NoResults: Story = {
   args: {
     variant: "no-results",
     primaryAction: {
       label: "검색어 초기화",
-      onClick: () => alert("검색어가 초기화되었습니다."),
+      onClick: () => {},
     },
   },
 };
@@
 export const ErrorState: Story = {
   args: {
     variant: "error",
     primaryAction: {
       label: "다시 시도",
-      onClick: () => window.location.reload(),
+      onClick: () => {},
     },
   },
 };
@@
 export const AccessDenied: Story = {
   args: {
     variant: "access-denied",
     primaryAction: {
       label: "이전 페이지로",
-      onClick: () => history.back(),
+      onClick: () => {},
     },
     secondaryAction: {
       label: "홈으로 이동",
-      onClick: () => alert("홈으로 이동합니다."),
+      onClick: () => {},
     },
   },
 };
📝 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 const NoContent: Story = {
args: {
variant: "no-content",
primaryAction: {
label: "첫 게시물 작성하기",
onClick: () => alert("게시물 작성 모달 오픈!"),
},
},
};
export const NoResults: Story = {
args: {
variant: "no-results",
primaryAction: {
label: "검색어 초기화",
onClick: () => alert("검색어가 초기화되었습니다."),
},
},
};
export const NoNotifications: Story = {
args: {
variant: "no-notifications",
},
};
export const ErrorState: Story = {
args: {
variant: "error",
primaryAction: {
label: "다시 시도",
onClick: () => window.location.reload(),
},
},
};
export const AccessDenied: Story = {
args: {
variant: "access-denied",
primaryAction: {
label: "이전 페이지로",
onClick: () => history.back(),
},
secondaryAction: {
label: "홈으로 이동",
onClick: () => alert("홈으로 이동합니다."),
},
},
export const NoContent: Story = {
args: {
variant: "no-content",
primaryAction: {
label: "첫 게시물 작성하기",
onClick: () => {},
},
},
};
export const NoResults: Story = {
args: {
variant: "no-results",
primaryAction: {
label: "검색어 초기화",
onClick: () => {},
},
},
};
export const NoNotifications: Story = {
args: {
variant: "no-notifications",
},
};
export const ErrorState: Story = {
args: {
variant: "error",
primaryAction: {
label: "다시 시도",
onClick: () => {},
},
},
};
export const AccessDenied: Story = {
args: {
variant: "access-denied",
primaryAction: {
label: "이전 페이지로",
onClick: () => {},
},
secondaryAction: {
label: "홈으로 이동",
onClick: () => {},
},
},
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/empty_states/empty-states.stories.tsx` around lines 38 - 85,
The story handlers for ErrorState and AccessDenied are using destructive browser
APIs (window.location.reload and history.back) which can disrupt Storybook;
update the onClick handlers in the ErrorState.args.primaryAction and
AccessDenied.args.primaryAction to non-destructive, story-safe alternatives
(e.g., show an alert or console.log) while keeping the labels the same so the
Story definitions (ErrorState, AccessDenied) remain unchanged and interactive
without navigating away.

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