Skip to content

Conversation

@BokyungCodes
Copy link
Contributor

@BokyungCodes BokyungCodes commented Jul 25, 2025

📌 변경 사항 개요

  • 메인페이지 인기 체험 섹션 구현
  • 라이브러리 없이 슬라이드 기능 직접 구현

📝 상세 내용

  • 인기 체험 섹션 UI 및 레이아웃 구현
  • 카드 1장씩 슬라이드되는 캐러셀 기능 직접 구현
  • .no-scrollbar 클래스 추가로 스크롤바 숨김 처리

🔗 관련 이슈

🖼️ 스크린샷(선택사항)

globalnomad-popularexpriences.mp4

💡 참고 사항

  • 추후 API 연동 시 ExperienceCard를 props 기반으로 리팩토링 예정
  • 현재는 더미 데이터 기준으로 4장 고정 렌더링 중

Summary by CodeRabbit

  • 신규 기능

    • 인기 체험(Experience) 카드를 가로 슬라이더 형태로 보여주는 "Popular Experiences" 섹션이 추가되었습니다.
    • 각 체험 카드는 이미지, 평점, 리뷰 수, 제목, 가격 정보를 제공합니다.
  • 스타일

    • 메인 배너와 검색바의 최대 너비가 1200픽셀로 확대되어 대형 화면에서 더 넓게 표시됩니다.
    • 스크롤바를 숨기는 새로운 CSS 유틸리티 클래스(.no-scrollbar)가 추가되었습니다.

@BokyungCodes BokyungCodes linked an issue Jul 25, 2025 that may be closed by this pull request
3 tasks
@coderabbitai
Copy link

coderabbitai bot commented Jul 25, 2025

Walkthrough

이 변경 사항은 기존 BannerSection과 SearchBar 컴포넌트의 최대 너비를 확장하며, 새로운 ExperienceCard 및 PopularExperiences 컴포넌트를 추가합니다. 또한, PopularExperiences를 홈 페이지에 배치하고, 스크롤바를 숨기는 글로벌 CSS 유틸리티 클래스를 도입합니다.

Changes

파일/경로 요약 변경 요약
src/app/(with-header)/components/BannerSection.tsx
src/app/(with-header)/components/SearchBar.tsx
최대 너비를 1152px에서 1200px로 증가
src/app/(with-header)/components/ExperienceCard.tsx ExperienceCard 컴포넌트 신규 추가
src/app/(with-header)/components/PopularExperiences.tsx PopularExperiences 컴포넌트 신규 추가
가로 스크롤 슬라이더 및 카드, 스크롤 버튼 구현
src/app/(with-header)/page.tsx PopularExperiences 컴포넌트 import 및 BannerSection 아래에 렌더링
src/app/globals.css 스크롤바를 숨기는 .no-scrollbar 유틸리티 클래스 추가

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant HomePage
    participant BannerSection
    participant PopularExperiences
    participant ExperienceCard

    User->>HomePage: 페이지 접속
    HomePage->>BannerSection: 배너 렌더링
    HomePage->>PopularExperiences: 인기 체험 슬라이더 렌더링
    PopularExperiences->>ExperienceCard: 각 카드 렌더링 (4개)
    User->>PopularExperiences: 좌/우 화살표 클릭
    PopularExperiences->>PopularExperiences: 슬라이더 스크롤 이동
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

Suggested labels

✨ Feature

Suggested reviewers

  • LeeCh0129
  • minimo-9

Poem

🐰
새로운 카드가 반짝,
인기 체험이 쏙쏙!
스크롤바는 감춰지고
너비도 넓어졌네.
홈 화면이 더 풍성해진 오늘,
토끼는 기쁘게 깡총깡총!

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/app/(with-header)/components/PopularExperiences.tsx

Oops! Something went wrong! :(

ESLint: 9.30.1

ESLint couldn't find the plugin "eslint-plugin-react-hooks".

(The package "eslint-plugin-react-hooks" was not found when loaded as a Node module from the directory "".)

It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:

npm install eslint-plugin-react-hooks@latest --save-dev

The plugin "eslint-plugin-react-hooks" was referenced from the config file in " » eslint-config-next/core-web-vitals » /node_modules/.pnpm/[email protected][email protected][email protected][email protected]/node_modules/eslint-config-next/index.js".

If you still can't figure out the problem, please see https://eslint.org/docs/latest/use/troubleshooting.

src/app/(with-header)/components/SearchBar.tsx

Oops! Something went wrong! :(

ESLint: 9.30.1

ESLint couldn't find the plugin "eslint-plugin-react-hooks".

(The package "eslint-plugin-react-hooks" was not found when loaded as a Node module from the directory "".)

It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:

npm install eslint-plugin-react-hooks@latest --save-dev

The plugin "eslint-plugin-react-hooks" was referenced from the config file in " » eslint-config-next/core-web-vitals » /node_modules/.pnpm/[email protected][email protected][email protected][email protected]/node_modules/eslint-config-next/index.js".

If you still can't figure out the problem, please see https://eslint.org/docs/latest/use/troubleshooting.

src/app/(with-header)/components/BannerSection.tsx

Oops! Something went wrong! :(

ESLint: 9.30.1

ESLint couldn't find the plugin "eslint-plugin-react-hooks".

(The package "eslint-plugin-react-hooks" was not found when loaded as a Node module from the directory "".)

It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:

npm install eslint-plugin-react-hooks@latest --save-dev

The plugin "eslint-plugin-react-hooks" was referenced from the config file in " » eslint-config-next/core-web-vitals » /node_modules/.pnpm/[email protected][email protected][email protected][email protected]/node_modules/eslint-config-next/index.js".

If you still can't figure out the problem, please see https://eslint.org/docs/latest/use/troubleshooting.

  • 3 others

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/45-2

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
🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @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.

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

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5ebfbad and e1e05ee.

📒 Files selected for processing (6)
  • src/app/(with-header)/components/BannerSection.tsx (1 hunks)
  • src/app/(with-header)/components/ExperienceCard.tsx (1 hunks)
  • src/app/(with-header)/components/PopularExperiences.tsx (1 hunks)
  • src/app/(with-header)/components/SearchBar.tsx (1 hunks)
  • src/app/(with-header)/page.tsx (1 hunks)
  • src/app/globals.css (1 hunks)
🔇 Additional comments (10)
src/app/globals.css (1)

135-141: 스크롤바를 숨길 때, 키보드·스크린리더 사용성까지 함께 고려해주세요
.no-scrollbar은 시각적으론 깔끔하지만, 스크롤이 존재함을 인지하지 못해 키보드 사용자·스크린리더 사용자가 컨텐츠 접근에 어려움을 겪을 수 있습니다.

  1. 컨테이너에 role="region" 또는 aria-label을 부여해 영역의 존재를 알리거나,
  2. 포커스가 진입하면 outline을 주어 스크롤이 가능한 영역임을 시각적으로 표시하는 방안을 포함해 보세요.
    [ suggest_optional_refactor ]
src/app/(with-header)/components/BannerSection.tsx (1)

20-20: max-width 확장은 문제 없어 보입니다
lg:max-w-1200 조정으로 SearchBar와 동일한 그리드 폭을 유지해 일관성이 향상되었습니다.
[ approve_code_changes ]

src/app/(with-header)/components/SearchBar.tsx (1)

16-16: 폭 조정 반영 확인 완료
홈 상단 배너와 동일 폭(1200px)으로 맞춰졌습니다. 별도 문제 없습니다.
[ approve_code_changes ]

src/app/(with-header)/page.tsx (1)

2-9: 초기 번들 사이즈를 줄이기 위해 PopularExperiences를 지연 로딩할 것을 권장합니다
홈 진입 시 반드시 필요한 섹션이 아니라면, next/dynamic을 사용해 클라이언트 측에서 Lazy-load 하면 퍼포먼스를 이득 볼 수 있습니다.

-import PopularExperiences from '@/app/(with-header)/components/PopularExperiences';
+import dynamic from 'next/dynamic';
+const PopularExperiences = dynamic(
+  () => import('@/app/(with-header)/components/PopularExperiences'),
+  { ssr: false }
+);

[ suggest_optional_refactor ]

src/app/(with-header)/components/ExperienceCard.tsx (4)

5-6: Tailwind 임의 값 사용법 오류 – 빌드 시 클래스가 무시됩니다
w-186, h-186, md:w-384 등은 Tailwind 기본 스케일에 존재하지 않는 클래스입니다. 임의 픽셀값을 쓰려면 대괄호 표기법을 사용해야 합니다.

-<div className='relative w-186 h-186 md:w-384 md:h-384 rounded-[20px] overflow-hidden shadow-md bg-white'>
+<div className='relative w-[186px] h-[186px] md:w-[384px] md:h-[384px] rounded-[20px] overflow-hidden shadow-md bg-white'>

적용하지 않으면 해당 크기 클래스가 제거되어 의도한 레이아웃이 깨집니다.
[ raise_critical_issue ]


8-12: 정적 이미지·텍스트 하드코딩 → 재사용성 급격히 저하
현재 컴포넌트는 카드 내용이 고정되어 있어 추후 API 연동 시 다시 수정해야 합니다. src, alt, title, price, rating 등을 props로 받아 재사용 가능하도록 리팩터링을 권장합니다.
[ suggest_essential_refactor ]


18-19: 별점 문구의 접근성 개선이 필요합니다
텍스트 앞의 emoji만으로는 스크린리더가 의미를 충분히 설명하지 못합니다.
예) <span aria-label="평점 4.9점, 리뷰 293개">⭐ 4.9 (293)</span> 처럼 aria-label을 추가해 주세요.
[ suggest_optional_refactor ]


20-22: 어두운 배경 위 회색 글씨는 대비가 부족할 수 있습니다
text-gray-600은 RGBA 대비 기준 WCAG AA를 충족하지 못할 가능성이 높습니다. 브랜드 가이드에 맞는 더 밝은 색상(예: text-gray-300)을 검토해 주세요.
[ request_verification ]

src/app/(with-header)/components/PopularExperiences.tsx (2)

1-7: 임포트 구조가 깔끔하고 올바릅니다.

클라이언트 사이드 기능을 위한 'use client' 지시어 사용이 적절하고, 임포트 경로와 구조가 잘 정리되어 있습니다.


8-11: 컴포넌트 선언과 ref 설정이 적절합니다.

TypeScript 타이핑이 정확하고 ref 사용 목적이 명확하게 주석으로 설명되어 있습니다.

Comment on lines +13 to +29
const scrollByCard = (direction: 'left' | 'right') => {
if (!sliderRef.current) return;

// 첫 번째 카드 요소를 찾아서 너비 측정
const card = sliderRef.current.querySelector('.card');
if (!(card instanceof HTMLElement)) return;

const cardWidth = card.offsetWidth; // 카드 너비
const gap = parseInt(getComputedStyle(sliderRef.current).gap) || 0; // gap 값
const distance = cardWidth + gap; // 한 번에 이동할 거리

// 슬라이더 스크롤 이동 (좌/우 방향에 따라)
sliderRef.current.scrollBy({
left: direction === 'left' ? -distance : distance,
behavior: 'smooth',
});
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

스크롤 함수의 안정성을 개선하세요.

함수가 전반적으로 잘 구현되어 있지만 몇 가지 개선 사항이 있습니다:

  1. parseInt에서 NaN 처리가 필요합니다
  2. .card 클래스에 의존하는 구조로 인한 결합도가 높습니다

다음과 같이 개선할 수 있습니다:

  const scrollByCard = (direction: 'left' | 'right') => {
    if (!sliderRef.current) return;

    const card = sliderRef.current.querySelector('.card');
    if (!(card instanceof HTMLElement)) return;

    const cardWidth = card.offsetWidth;
-   const gap = parseInt(getComputedStyle(sliderRef.current).gap) || 0;
+   const gapValue = getComputedStyle(sliderRef.current).gap;
+   const gap = gapValue ? parseInt(gapValue) || 0 : 0;
    const distance = cardWidth + gap;

    sliderRef.current.scrollBy({
      left: direction === 'left' ? -distance : distance,
      behavior: 'smooth',
    });
  };
📝 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 scrollByCard = (direction: 'left' | 'right') => {
if (!sliderRef.current) return;
// 첫 번째 카드 요소를 찾아서 너비 측정
const card = sliderRef.current.querySelector('.card');
if (!(card instanceof HTMLElement)) return;
const cardWidth = card.offsetWidth; // 카드 너비
const gap = parseInt(getComputedStyle(sliderRef.current).gap) || 0; // gap 값
const distance = cardWidth + gap; // 한 번에 이동할 거리
// 슬라이더 스크롤 이동 (좌/우 방향에 따라)
sliderRef.current.scrollBy({
left: direction === 'left' ? -distance : distance,
behavior: 'smooth',
});
};
const scrollByCard = (direction: 'left' | 'right') => {
if (!sliderRef.current) return;
// 첫 번째 카드 요소를 찾아서 너비 측정
const card = sliderRef.current.querySelector('.card');
if (!(card instanceof HTMLElement)) return;
const cardWidth = card.offsetWidth; // 카드 너비
const gapValue = getComputedStyle(sliderRef.current).gap;
const gap = gapValue ? parseInt(gapValue) || 0 : 0;
const distance = cardWidth + gap; // 한 번에 이동할 거리
// 슬라이더 스크롤 이동 (좌/우 방향에 따라)
sliderRef.current.scrollBy({
left: direction === 'left' ? -distance : distance,
behavior: 'smooth',
});
};
🤖 Prompt for AI Agents
In src/app/(with-header)/components/PopularExperiences.tsx around lines 13 to
29, improve the scrollByCard function by adding a check to handle NaN results
from parseInt when reading the gap style, defaulting to 0 if NaN. Also, reduce
coupling by avoiding direct dependency on the '.card' class; instead, consider
passing the card element or its width as a parameter or using a more flexible
selector strategy. Implement these changes to enhance function stability and
maintainability.

Comment on lines +31 to +40
return (
<section className='pt-24 md:pt-34 pl-24 lg:pl-0 pb-40 lg:pb-33 lg:max-w-1200 lg:w-full mx-auto'>
{/* 섹션 제목 + 좌우 화살표 버튼 */}
<div className='flex justify-between items-center pb-16 md:pb-32 mb-6'>
<h2 className='text-xl md:text-3xl font-bold'>🔥 인기 체험</h2>
<div className='flex gap-2'>
<IconArrowLeft size={32} onClick={() => scrollByCard('left')} className='text-2xl px-3' />
<IconArrowRight size={32} onClick={() => scrollByCard('right')} className='text-2xl px-3' />
</div>
</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

접근성 개선이 필요합니다.

화살표 아이콘을 클릭 가능한 요소로 사용하고 있지만 접근성 지원이 부족합니다.

다음과 같이 개선하세요:

        <div className='flex gap-2'>
-         <IconArrowLeft size={32} onClick={() => scrollByCard('left')} className='text-2xl px-3' />
-         <IconArrowRight size={32} onClick={() => scrollByCard('right')} className='text-2xl px-3' />
+         <button 
+           onClick={() => scrollByCard('left')}
+           className='text-2xl px-3 focus:outline-none focus:ring-2 focus:ring-blue-500 rounded'
+           aria-label='이전 체험 보기'
+         >
+           <IconArrowLeft size={32} />
+         </button>
+         <button 
+           onClick={() => scrollByCard('right')}
+           className='text-2xl px-3 focus:outline-none focus:ring-2 focus:ring-blue-500 rounded'
+           aria-label='다음 체험 보기'
+         >
+           <IconArrowRight size={32} />
+         </button>
        </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
return (
<section className='pt-24 md:pt-34 pl-24 lg:pl-0 pb-40 lg:pb-33 lg:max-w-1200 lg:w-full mx-auto'>
{/* 섹션 제목 + 좌우 화살표 버튼 */}
<div className='flex justify-between items-center pb-16 md:pb-32 mb-6'>
<h2 className='text-xl md:text-3xl font-bold'>🔥 인기 체험</h2>
<div className='flex gap-2'>
<IconArrowLeft size={32} onClick={() => scrollByCard('left')} className='text-2xl px-3' />
<IconArrowRight size={32} onClick={() => scrollByCard('right')} className='text-2xl px-3' />
</div>
</div>
return (
<section className='pt-24 md:pt-34 pl-24 lg:pl-0 pb-40 lg:pb-33 lg:max-w-1200 lg:w-full mx-auto'>
{/* 섹션 제목 + 좌우 화살표 버튼 */}
<div className='flex justify-between items-center pb-16 md:pb-32 mb-6'>
<h2 className='text-xl md:text-3xl font-bold'>🔥 인기 체험</h2>
<div className='flex gap-2'>
<button
onClick={() => scrollByCard('left')}
className='text-2xl px-3 focus:outline-none focus:ring-2 focus:ring-blue-500 rounded'
aria-label='이전 체험 보기'
>
<IconArrowLeft size={32} />
</button>
<button
onClick={() => scrollByCard('right')}
className='text-2xl px-3 focus:outline-none focus:ring-2 focus:ring-blue-500 rounded'
aria-label='다음 체험 보기'
>
<IconArrowRight size={32} />
</button>
</div>
</div>
{/* …the rest of the section… */}
</section>
);
🤖 Prompt for AI Agents
In src/app/(with-header)/components/PopularExperiences.tsx around lines 31 to
40, the arrow icons used for scrolling are clickable but lack accessibility
support. To fix this, wrap the IconArrowLeft and IconArrowRight components in
accessible button elements or add appropriate role="button" and tabindex="0"
attributes, and include keyboard event handlers to support keyboard navigation.
Also, add descriptive aria-labels to these elements to clearly convey their
purpose to screen readers.

Comment on lines +42 to +54
{/* 가로 슬라이드 카드 리스트 */}
<div
ref={sliderRef}
className='flex gap-16 md:gap-32 lg:gap-24 overflow-x-auto scroll-smooth no-scrollbar'
>
{[...Array(4)].map((_, idx) => (
// 카드 wrapper: flex-shrink-0으로 크기 고정 + 'card' 클래스로 식별
<div key={idx} className='flex-shrink-0 card'>
<ExperienceCard />
</div>
))}
</div>
</section>
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

🛠️ Refactor suggestion

키보드 네비게이션 지원을 추가하고 데이터 구조를 개선하세요.

현재 구현에서 개선이 필요한 부분들:

  1. 키보드 네비게이션 미지원
  2. 하드코딩된 4개 카드 배열

키보드 이벤트 핸들러 추가:

+  const handleKeyDown = (event: React.KeyboardEvent) => {
+    if (event.key === 'ArrowLeft') {
+      scrollByCard('left');
+    } else if (event.key === 'ArrowRight') {
+      scrollByCard('right');
+    }
+  };

      <div
        ref={sliderRef}
-       className='flex gap-16 md:gap-32 lg:gap-24 overflow-x-auto scroll-smooth no-scrollbar'
+       className='flex gap-16 md:gap-32 lg:gap-24 overflow-x-auto scroll-smooth no-scrollbar'
+       tabIndex={0}
+       onKeyDown={handleKeyDown}
+       role='region'
+       aria-label='인기 체험 목록'
      >

하드코딩된 카드 배열을 props나 API 데이터로 대체하는 코드를 생성해드릴까요?

📝 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
ref={sliderRef}
className='flex gap-16 md:gap-32 lg:gap-24 overflow-x-auto scroll-smooth no-scrollbar'
>
{[...Array(4)].map((_, idx) => (
// 카드 wrapper: flex-shrink-0으로 크기 고정 + 'card' 클래스로 식별
<div key={idx} className='flex-shrink-0 card'>
<ExperienceCard />
</div>
))}
</div>
</section>
// (inside your PopularExperiences component)
const handleKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'ArrowLeft') {
scrollByCard('left');
} else if (event.key === 'ArrowRight') {
scrollByCard('right');
}
};
return (
<>
{/* 가로 슬라이드 카드 리스트 */}
<div
ref={sliderRef}
className='flex gap-16 md:gap-32 lg:gap-24 overflow-x-auto scroll-smooth no-scrollbar'
tabIndex={0}
onKeyDown={handleKeyDown}
role='region'
aria-label='인기 체험 목록'
>
{[...Array(4)].map((_, idx) => (
// 카드 wrapper: flex-shrink-0으로 크기 고정 + 'card' 클래스로 식별
<div key={idx} className='flex-shrink-0 card'>
<ExperienceCard />
</div>
))}
</div>
</>
);
🤖 Prompt for AI Agents
In src/app/(with-header)/components/PopularExperiences.tsx around lines 42 to
54, the current horizontal slider lacks keyboard navigation support and uses a
hardcoded array of 4 cards. To fix this, add keyboard event handlers to enable
arrow key navigation for accessibility, and replace the hardcoded array with
dynamic data passed via props or fetched from an API. This will improve
usability and make the component data-driven.

Comment on lines +47 to +52
{[...Array(4)].map((_, idx) => (
// 카드 wrapper: flex-shrink-0으로 크기 고정 + 'card' 클래스로 식별
<div key={idx} className='flex-shrink-0 card'>
<ExperienceCard />
</div>
))}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

카드 데이터를 동적으로 처리하도록 개선하세요.

현재 하드코딩된 4개 카드 배열은 확장성이 떨어집니다.

컴포넌트를 더 유연하게 만들기 위해 props를 사용하는 것을 고려해보세요:

+interface PopularExperiencesProps {
+  experiences?: ExperienceData[];
+  maxItems?: number;
+}

-export default function PopularExperiences() {
+export default function PopularExperiences({ 
+  experiences = [], 
+  maxItems = 4 
+}: PopularExperiencesProps) {
   // ... existing code ...

-  {[...Array(4)].map((_, idx) => (
+  {(experiences.length > 0 ? experiences : [...Array(maxItems)]).slice(0, maxItems).map((experience, idx) => (
     <div key={idx} className='flex-shrink-0 card'>
-      <ExperienceCard />
+      <ExperienceCard data={experience} />
     </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
{[...Array(4)].map((_, idx) => (
// 카드 wrapper: flex-shrink-0으로 크기 고정 + 'card' 클래스로 식별
<div key={idx} className='flex-shrink-0 card'>
<ExperienceCard />
</div>
))}
// Add a props interface so this component can receive data and a limit
interface PopularExperiencesProps {
experiences?: ExperienceData[];
maxItems?: number;
}
export default function PopularExperiences({
experiences = [],
maxItems = 4,
}: PopularExperiencesProps) {
// ... existing code ...
{(experiences.length > 0 ? experiences : [...Array(maxItems)])
.slice(0, maxItems)
.map((experience, idx) => (
// 카드 wrapper: flex-shrink-0으로 크기 고정 + 'card' 클래스로 식별
<div key={idx} className='flex-shrink-0 card'>
<ExperienceCard data={experience} />
</div>
))}
}
🤖 Prompt for AI Agents
In src/app/(with-header)/components/PopularExperiences.tsx around lines 47 to
52, the code currently renders a fixed array of 4 cards, which limits
scalability. Modify the component to accept an array of card data as a prop and
map over this dynamic data to render ExperienceCard components accordingly. This
change will make the component more flexible and reusable with varying numbers
of cards.

@BokyungCodes BokyungCodes self-assigned this Jul 25, 2025
@BokyungCodes BokyungCodes added ✨ Feature 기능구현 🎨 style 마크업 및 스타일링 labels Jul 25, 2025
>
{[...Array(4)].map((_, idx) => (
// 카드 wrapper: flex-shrink-0으로 크기 고정 + 'card' 클래스로 식별
<div key={idx} className='flex-shrink-0 card'>
Copy link
Contributor

Choose a reason for hiding this comment

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

여기서 클래스를 지정해준다음에 querySelector로 찾도록 하신거군요!
하나 배워갑니다!

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
Contributor

Choose a reason for hiding this comment

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

Ref를 사용하는 UI에 대해서는 아직 부족한데 덕분에 배워갑니다!
추후에 motion라이브러리까지 적용하면 될것같네요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵~! API 연동까지 다 되면 motion 라이브러리 도입해보려구요. 감사합니다!

Copy link
Contributor

@minimo-9 minimo-9 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

@LeeCh0129 LeeCh0129 left a comment

Choose a reason for hiding this comment

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

체험 섹션 UI 구현 고생 많으셨습니다! 주석이 명확하게 달려 있어 흐름 파악이 수월했습니다 👍

@BokyungCodes BokyungCodes merged commit ee9197a into develop Jul 26, 2025
2 checks passed
@BokyungCodes BokyungCodes deleted the feat/45-2 branch July 26, 2025 07:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능구현 🎨 style 마크업 및 스타일링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 메인페이지 전체 구조 및 UI 구현

5 participants