Skip to content

[Feat] #14 - 공통 Tabs 컴포넌트 추가#49

Merged
jaeu5325 merged 10 commits into
devfrom
feat/shared-tabs
Mar 21, 2026
Merged

[Feat] #14 - 공통 Tabs 컴포넌트 추가#49
jaeu5325 merged 10 commits into
devfrom
feat/shared-tabs

Conversation

@jaeu5325
Copy link
Copy Markdown
Collaborator

@jaeu5325 jaeu5325 commented Mar 11, 2026

🔎 What is this PR?

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


📝 Changes

📸 Screenshots (선택)

스크린샷 2026-03-11 오전 9 56 26 스크린샷 2026-03-11 오전 9 56 39

📚 Background / Context (선택)


✔ Checklist

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

🙏 Request

Summary by CodeRabbit

  • New Features

    • Added a Tabs UI component with horizontal, vertical and pill layouts.
    • Supports per-tab badges and disabled states.
    • Storybook examples added to demonstrate variants and interactive behavior.
  • Chores

    • Removed a custom issue template.
    • Updated pre-commit configuration.
    • Added a development dependency for browser mapping.

@jaeu5325 jaeu5325 requested a review from sebeeeen March 11, 2026 00:58
@jaeu5325 jaeu5325 added the enhancement New feature or request label Mar 11, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 11, 2026

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (2)
  • src/shared/ui/lists/lists.stories.tsx
  • src/shared/ui/lists/lists.tsx
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f0dd63f5-8d45-46e2-8888-9f5aa0fb2787

📥 Commits

Reviewing files that changed from the base of the PR and between 57fb44d and 46a1ed1.

📒 Files selected for processing (2)
  • src/shared/ui/lists/lists.stories.tsx
  • src/shared/ui/lists/lists.tsx

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new TypeScript Tabs UI component with Storybook stories; removes a custom issue template; simplifies the pre-commit hook to call lint-staged directly; and adds baseline-browser-mapping@^2.10.0 to devDependencies.

Changes

Cohort / File(s) Summary
Repository & Hooks
\.github/ISSUE_TEMPLATE/custom.md, \.husky/pre-commit, package.json
Deleted the custom issue template, removed Husky shim/boilerplate from the pre-commit hook (now invokes lint-staged directly), and added baseline-browser-mapping@^2.10.0 to devDependencies.
Tabs UI + Stories
src/shared/ui/tabs/tabs.tsx, src/shared/ui/tabs/tabs.stories.tsx
Added a controlled Tabs component with `horizontal

Sequence Diagram(s)

(omitted — changes are component additions and small repo/config edits that do not introduce multi-actor sequential flows requiring a diagram)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • kimsman06

Poem

🐇 Hopping tabs from left to right,

Three styles gleam in morning light,
Badges bob and disabled rest,
Hooks trimmed down — the repo's dressed,
A tiny rabbit cheers the scene,
Code snug, components clean.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title references a Tabs component, but the description claims to implement an inputBox component, creating a mismatch between title and content. Update either the title to '[Feat] #14 - 공통 inputBox 컴포넌트 추가' or the description to accurately reflect that this PR implements a Tabs component, not an inputBox.
Description check ⚠️ Warning The PR description describes implementing an inputBox component, but the actual changes implement a Tabs component. The 'Changes' section is empty, and checklist items are unchecked, indicating incomplete preparation. Update the description to correctly state the PR implements a Tabs component with multiple variants. Fill in the Changes section with bullet points and ensure checklist items reflect actual completion status.
✅ Passed checks (1 passed)
Check name Status Explanation
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-tabs

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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.

Tip

Migrating from UI to YAML configuration.

Use the @coderabbitai configuration command in a PR comment to get a dump of all your UI settings in YAML format. You can then edit this YAML file and upload it to the root of your repository to configure CodeRabbit programmatically.

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

🤖 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/tabs/tabs.stories.tsx`:
- Around line 54-56: The story currently snapshots initial selection with
useState(args.value) and overrides onChange, causing Controls/Actions to drift;
remove the local state and let Storybook args be the single source of truth by
returning the component with value={args.value} and onChange={args.onChange}
(i.e., replace the body of TabsWithState to return <Tabs {...args}
value={args.value} onChange={args.onChange} />), and apply the same change to
the other story wrapper functions referenced (the similar wrappers at the other
noted locations) and remove any per-story onChange: () => {} arg overrides so
the action handler is not suppressed.

In `@src/shared/ui/tabs/tabs.tsx`:
- Around line 36-37: The tab base class string itemBaseClasses currently
includes outline-none which removes the native focus ring without providing a
replacement; restore an accessible visible focus indicator by removing
outline-none or by appending a focus-visible utility (e.g., focus-visible:ring
focus-visible:ring-offset-1 focus-visible:ring-primary) to itemBaseClasses or to
the tab variant classes used in src/shared/ui/tabs/tabs.tsx so keyboard users
receive a clear focus style when a tab element (the components using
itemBaseClasses) receives focus.
- Around line 100-105: The tab buttons are currently bare <button> elements
which default to type="submit" inside forms; update the button in the Tabs
component (the element using item.id, isDisabled and onChange) to include
type="button" so clicking tabs won't submit any surrounding form (keep the
existing aria-selected, disabled and onClick logic unchanged).
- Around line 94-95: The tablist div currently uses role="tablist" but lacks
aria-orientation for vertical mode; update the Tabs component to set
aria-orientation="vertical" on the div with className={containerClasses} (the
element wrapping items.map((item) => ...)) when the component is rendered in
vertical orientation (use the component's orientation/variant prop or class
detection logic that produces containerClasses) so assistive tech is informed of
the vertical interaction model.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f7df05a2-08fb-4f74-accc-a41d4ebce3ee

📥 Commits

Reviewing files that changed from the base of the PR and between a751262 and e7a88db.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • .github/ISSUE_TEMPLATE/custom.md
  • .husky/pre-commit
  • package.json
  • src/shared/ui/tabs/tabs.stories.tsx
  • src/shared/ui/tabs/tabs.tsx
💤 Files with no reviewable changes (2)
  • .husky/pre-commit
  • .github/ISSUE_TEMPLATE/custom.md

Comment on lines +54 to +56
const TabsWithState = (args: React.ComponentProps<typeof Tabs>): React.ReactElement => {
const [selected, setSelected] = useState(args.value);
return <Tabs {...args} value={selected} onChange={setSelected} />;
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

Keep Storybook args as the source of truth.

useState(args.value) only snapshots the initial selection, and the per-story onChange: () => {} args suppress the action handler entirely. After the first interaction, Controls/Actions can drift from what the story is actually rendering.

Also applies to: 70-70, 81-81, 99-99, 114-114, 129-129

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

In `@src/shared/ui/tabs/tabs.stories.tsx` around lines 54 - 56, The story
currently snapshots initial selection with useState(args.value) and overrides
onChange, causing Controls/Actions to drift; remove the local state and let
Storybook args be the single source of truth by returning the component with
value={args.value} and onChange={args.onChange} (i.e., replace the body of
TabsWithState to return <Tabs {...args} value={args.value}
onChange={args.onChange} />), and apply the same change to the other story
wrapper functions referenced (the similar wrappers at the other noted locations)
and remove any per-story onChange: () => {} arg overrides so the action handler
is not suppressed.

Comment on lines +36 to +37
const itemBaseClasses =
"relative flex items-center justify-center cursor-pointer transition-all duration-200 outline-none select-none whitespace-nowrap";
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

Restore a visible focus indicator.

outline-none removes the native ring, but none of the variant classes add a focus-visible replacement. Keyboard users won't be able to tell which tab currently has focus.

🎯 Proposed fix
 const itemBaseClasses =
-  "relative flex items-center justify-center cursor-pointer transition-all duration-200 outline-none select-none whitespace-nowrap";
+  "relative flex items-center justify-center cursor-pointer transition-all duration-200 outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--color-primary-800,`#004A9C`)] select-none whitespace-nowrap";
📝 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
const itemBaseClasses =
"relative flex items-center justify-center cursor-pointer transition-all duration-200 outline-none select-none whitespace-nowrap";
const itemBaseClasses =
"relative flex items-center justify-center cursor-pointer transition-all duration-200 outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--color-primary-800,`#004A9C`)] select-none whitespace-nowrap";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/tabs/tabs.tsx` around lines 36 - 37, The tab base class string
itemBaseClasses currently includes outline-none which removes the native focus
ring without providing a replacement; restore an accessible visible focus
indicator by removing outline-none or by appending a focus-visible utility
(e.g., focus-visible:ring focus-visible:ring-offset-1
focus-visible:ring-primary) to itemBaseClasses or to the tab variant classes
used in src/shared/ui/tabs/tabs.tsx so keyboard users receive a clear focus
style when a tab element (the components using itemBaseClasses) receives focus.

Comment on lines +94 to +95
<div className={containerClasses} role="tablist">
{items.map((item) => {
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

Expose vertical orientation on the tablist.

role="tablist" is announced as horizontal by default. The vertical variant needs aria-orientation="vertical" so assistive tech gets the correct interaction model.

♿ Proposed fix
-    <div className={containerClasses} role="tablist">
+    <div
+      className={containerClasses}
+      role="tablist"
+      aria-orientation={variant === "vertical" ? "vertical" : undefined}
+    >
📝 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={containerClasses} role="tablist">
{items.map((item) => {
<div
className={containerClasses}
role="tablist"
aria-orientation={variant === "vertical" ? "vertical" : undefined}
>
{items.map((item) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/tabs/tabs.tsx` around lines 94 - 95, The tablist div currently
uses role="tablist" but lacks aria-orientation for vertical mode; update the
Tabs component to set aria-orientation="vertical" on the div with
className={containerClasses} (the element wrapping items.map((item) => ...))
when the component is rendered in vertical orientation (use the component's
orientation/variant prop or class detection logic that produces
containerClasses) so assistive tech is informed of the vertical interaction
model.

Comment thread src/shared/ui/tabs/tabs.tsx
Comment thread src/shared/ui/tabs/tabs.tsx Outdated
Comment thread src/shared/ui/tabs/tabs.tsx
Copy link
Copy Markdown
Member

@sebeeeen sebeeeen 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
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: 2

♻️ Duplicate comments (2)
src/shared/ui/tabs/tabs.tsx (2)

94-95: ⚠️ Potential issue | 🟡 Minor

Expose vertical orientation on the tablist.

When variant === "vertical", the tablist should set aria-orientation="vertical" so assistive tech gets the correct interaction model.

♿ Proposed fix
-    <div className={containerClasses} role="tablist">
+    <div
+      className={containerClasses}
+      role="tablist"
+      aria-orientation={variant === "vertical" ? "vertical" : undefined}
+    >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/tabs/tabs.tsx` around lines 94 - 95, The tablist div (where
containerClasses and items.map are used) does not expose orientation for
assistive tech; update the element that sets role="tablist" in the Tabs
component to include aria-orientation="vertical" when variant === "vertical"
(and default to "horizontal" or omit otherwise) so the tab interaction model is
communicated correctly; locate the JSX containing <div
className={containerClasses} role="tablist"> in the Tabs component and add the
conditional aria-orientation attribute tied to the variant prop.

36-37: ⚠️ Potential issue | 🟠 Major

Restore a visible keyboard focus style.

Line 37 removes native focus (outline-none) but doesn’t provide a focus-visible fallback, so keyboard users can lose focus visibility.

♿ Proposed fix
 const itemBaseClasses =
-  "relative flex items-center justify-center cursor-pointer transition-all duration-200 outline-none select-none whitespace-nowrap";
+  "relative flex items-center justify-center cursor-pointer transition-all duration-200 outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--color-primary-800,`#004A9C`)] select-none whitespace-nowrap";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/tabs/tabs.tsx` around lines 36 - 37, The shared constant
itemBaseClasses removes the native focus outline ("outline-none") without
providing a focus-visible fallback, which hides focus for keyboard users; update
itemBaseClasses to preserve mouse-focused styles while adding a visible keyboard
focus style by replacing or augmenting the existing outline-none with a
focus-visible utility (e.g., add focus-visible:outline, focus-visible:outline-2,
focus-visible:outline-offset-2 and a color token like
focus-visible:outline-primary) so keyboard users get a clear focus ring while
pointer interactions remain unaffected.
🤖 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/tabs/tabs.tsx`:
- Around line 109-110: Conditional rendering currently uses a truthiness check
(item.badge && ...) which prevents numeric zero badges from showing; update the
conditional in the Tabs render where item.badge is checked (the expression that
wraps the <span className="ml-[6px] inline-flex items-center
justify-center">{item.badge}</span>) to use a nullish check (item.badge != null)
so that 0 and other valid React nodes render while still excluding
null/undefined.
- Around line 100-101: The mapped tab buttons in the Tabs component are missing
a stable key which causes React warnings; in the items.map(...) return (the
<button> element) add a unique key using the TabItem id (e.g., key={item.id}) so
each mapped element has a stable identifier; locate the map over items in Tabs
(where TabItem objects are iterated) and add key={item.id} to the button JSX.

---

Duplicate comments:
In `@src/shared/ui/tabs/tabs.tsx`:
- Around line 94-95: The tablist div (where containerClasses and items.map are
used) does not expose orientation for assistive tech; update the element that
sets role="tablist" in the Tabs component to include aria-orientation="vertical"
when variant === "vertical" (and default to "horizontal" or omit otherwise) so
the tab interaction model is communicated correctly; locate the JSX containing
<div className={containerClasses} role="tablist"> in the Tabs component and add
the conditional aria-orientation attribute tied to the variant prop.
- Around line 36-37: The shared constant itemBaseClasses removes the native
focus outline ("outline-none") without providing a focus-visible fallback, which
hides focus for keyboard users; update itemBaseClasses to preserve mouse-focused
styles while adding a visible keyboard focus style by replacing or augmenting
the existing outline-none with a focus-visible utility (e.g., add
focus-visible:outline, focus-visible:outline-2, focus-visible:outline-offset-2
and a color token like focus-visible:outline-primary) so keyboard users get a
clear focus ring while pointer interactions remain unaffected.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ddd01a96-b10c-4288-b530-65c2373c1d33

📥 Commits

Reviewing files that changed from the base of the PR and between e7a88db and 57fb44d.

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

Comment on lines +100 to +101
<button
type="button"
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

🧩 Analysis chain

🏁 Script executed:

cat -n src/shared/ui/tabs/tabs.tsx | head -120

Repository: ajou-industry-matching/aim-frontend

Length of output: 5378


Add a stable key prop for mapped tab items.

The <button> element returned from items.map() at line 100 is missing a key prop. Since TabItem includes a stable id field that uniquely identifies each tab, add key={item.id} to prevent React reconciliation issues and warnings.

🛠️ Proposed fix
          <button
+           key={item.id}
            type="button"
            role="tab"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/tabs/tabs.tsx` around lines 100 - 101, The mapped tab buttons
in the Tabs component are missing a stable key which causes React warnings; in
the items.map(...) return (the <button> element) add a unique key using the
TabItem id (e.g., key={item.id}) so each mapped element has a stable identifier;
locate the map over items in Tabs (where TabItem objects are iterated) and add
key={item.id} to the button JSX.

Comment on lines +109 to +110
{item.badge && (
<span className="ml-[6px] inline-flex items-center justify-center">{item.badge}</span>
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

🧩 Analysis chain

🏁 Script executed:

cat -n src/shared/ui/tabs/tabs.tsx | sed -n '100,120p'

Repository: ajou-industry-matching/aim-frontend

Length of output: 983


🏁 Script executed:

# Also check the type definition for the item object
rg -A 10 -B 5 "interface.*Tab|type.*Tab" src/shared/ui/tabs/

Repository: ajou-industry-matching/aim-frontend

Length of output: 4726


🏁 Script executed:

# Search for any usage of badge with numeric values in tests or elsewhere
rg "badge.*[0-9]|badge.*zero" src/

Repository: ajou-industry-matching/aim-frontend

Length of output: 286


Fix nullish check to allow numeric zero badges.

item.badge && suppresses 0 due to truthiness. Since badge accepts React.ReactNode (which includes numbers), numeric zero badges cannot render. Use item.badge != null && to allow zero while still excluding null and undefined.

Proposed fix
-            {item.badge && (
+            {item.badge != null && (
               <span className="ml-[6px] inline-flex items-center justify-center">{item.badge}</span>
             )}
📝 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
{item.badge && (
<span className="ml-[6px] inline-flex items-center justify-center">{item.badge}</span>
{item.badge != null && (
<span className="ml-[6px] inline-flex items-center justify-center">{item.badge}</span>
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/tabs/tabs.tsx` around lines 109 - 110, Conditional rendering
currently uses a truthiness check (item.badge && ...) which prevents numeric
zero badges from showing; update the conditional in the Tabs render where
item.badge is checked (the expression that wraps the <span className="ml-[6px]
inline-flex items-center justify-center">{item.badge}</span>) to use a nullish
check (item.badge != null) so that 0 and other valid React nodes render while
still excluding null/undefined.

@sebeeeen sebeeeen self-requested a review March 18, 2026 00:50
Comment thread package.json Outdated
Comment thread .husky/pre-commit
@jaeu5325 jaeu5325 merged commit f51b190 into dev Mar 21, 2026
1 check passed
@sebeeeen sebeeeen deleted the feat/shared-tabs branch May 25, 2026 14:33
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