Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 스켈레톤 적용 #181

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open

feat: 스켈레톤 적용 #181

wants to merge 7 commits into from

Conversation

heony704
Copy link
Contributor

@heony704 heony704 commented Feb 4, 2025

📝 PR 설명

스켈레톤을 여러 곳에서 각자 만들어 사용해서 스타일이 통일되지 않는 문제가 있었습니다.
그래서 스켈레톤 컴포넌트를 만들고, 기존 스켈레톤들을 컴포넌트로 분리했습니다.
그리고 기록 페이지의 타이틀 n월 n일 운동 완료! 에도 스켈레톤이 적용되면 좋겠다는 피드백 반영했습니다.

🔍 변경사항

  • 스켈레톤 컴포넌트 구현
  • 스켈레톤 컴포넌트를 이용하여 기존의 댓글 스켈레톤, 달력 스켈레톤을 별도의 컴포넌트로 분리
  • 기록 페이지의 타이틀에 스켈레톤 적용

Skeleton 컴포넌트

  • className?: string
  • width?: ViewStyle["width"]
  • height?: ViewStyle["height"]
  • count?: number
  • circle?: boolean

width와 height은 react native의 스타일 지정 방식을 따릅니다.
숫자는 px으로 적용되고, 문자열로 %도 지정 가능합니다.

예시

// mr-auto 스타일이 적용된 너비 180px, 높이 20px의 막대기 스켈레톤
<Skeleton className="mr-auto" width={180} height={20} />
// 너비, 높이 30px의 원 모양 스켈레톤 7개
<Skeleton width={30} height={30} circle count={7} />

폴더 구조

Skeleton 폴더 안에 기본적인 스타일이 정의된 Skeleton 컴포넌트와, Skeleton 컴포넌트를 이용해 만든 특수 스켈레톤 컴포넌트들이 들어가 있습니다.

image

index.tsx 파일은 단순 배럴 파일이라서 아래처럼 스켈레톤들을 가져다 쓸 수 있습니다.

import { Skeleton, CalendarSkeleton, CommentSkeleton } from "@/components/Skeleton";

📸 스크린샷

댓글창

기록 페이지

🔗 관련 이슈

📌 기타 참고사항

댓글 스켈레톤 디자인 약간 변경

기존 댓글 스켈레톤 디자인은 아래 첫번째 사진처럼 댓글 내용 부분의 오른쪽에 여백이 있습니다.
근데 여백의 너비가 동그라미의 너비와 동일해서, 사용자 입장에서는 스켈레톤만 봤을 때 실제 댓글도 오른쪽에 여백이 있을 것이라고 착각할 것 같습니다.
그래서 댓글 내용 부분에 여백 없이 꽉 차도록 수정했는데요. 혹시 여백 있는게 더 나으면 말해주세요! @YehaYoo

댓글 스켈레톤과 댓글의 크기가 다름 ⚠️

스켈레톤 만들 때 px을 기준으로 스타일했습니다.
왜냐면.. 실제로 기록 페이지 만들 때도 px 기준으로 스타일했고 피그마에도 px 기준인거 같아서...
그랬더니 댓글 스켈레톤은 px 기준으로 스타일했는데, 댓글은 rem 기준으로 스타일돼서 크기가 다르게 표시되더라구요.
둘 중 하나로 통일해야 할 것 같아요.
기록 페이지는 절대 단위(px)로 하기로 했던 거 같은데, 댓글 쪽도 px로 하는 게 맞나요? @YehaYoo

Summary by CodeRabbit

  • 새로운 기능

    • 데이터 로딩 중에 History, 캘린더, 댓글 등 여러 화면에서 플레이스홀더 애니메이션을 표시하여 사용자에게 로딩 상태를 명확하게 전달합니다.
  • 리팩토링

    • 다양한 화면의 로딩 상태 처리를 통합 및 단순화하여 데이터 로드 중에도 일관되고 부드러운 사용자 경험을 제공합니다.

@heony704 heony704 self-assigned this Feb 4, 2025
Copy link

coderabbitai bot commented Feb 4, 2025

Walkthrough

이 PR은 UI의 로딩 상태 관리를 개선하기 위해 스켈레톤 컴포넌트를 도입하고 이를 재사용하도록 수정했습니다. History 및 WorkoutCalendar 컴포넌트에서 isLoading 변수를 활용하여 로딩 상태에 따라 Skeleton 또는 실제 콘텐츠를 렌더링하며, 캘린더 및 댓글 섹션 역시 새로운 스켈레톤 컴포넌트를 사용하도록 업데이트되었습니다.

Changes

Files Change Summary
app/(protected)/(tabs)/history.tsx, components/WorkoutCalendar.tsx 로딩 상태 관리를 개선: 데이터 로딩 시 Skeleton 컴포넌트를 사용하고 isLoading prop을 전달하여 간소화된 렌더링 로직 적용.
components/Skeleton/Skeleton.tsx, components/Skeleton/CalendarSkeleton.tsx, components/Skeleton/CommentSkeleton.tsx, components/Skeleton/index.ts 새로운 Skeleton, CalendarSkeleton, CommentSkeleton 컴포넌트와 관련 인터페이스 및 익스포트를 추가하여 스켈레톤 UI 재사용성을 강화함.
components/comments/CommentItem.tsx, components/comments/CommentsSection.tsx 기존 ReplySkeleton을 제거하고 CommentSkeleton으로 대체하여 댓글 및 응답의 로딩 상태 표현을 통일함.

Sequence Diagram(s)

sequenceDiagram
    participant U as 사용자
    participant H as History 컴포넌트
    participant WC as WorkoutCalendar 컴포넌트
    participant API as 데이터 API
    U->>H: 페이지 로드 요청
    H->>API: 사용자 및 히스토리 데이터 요청
    API-->>H: 데이터 응답 (지연 가능)
    alt 로딩중 (isLoading == true)
        H->>Skeleton: Skeleton 렌더링
        H->>WC: isLoading 상태 전달
        WC->>Skeleton: CalendarSkeleton 렌더링
    else 로딩 완료 (isLoading == false)
        H->>Text: 텍스트 컴포넌트 렌더링
        WC->>Calendar: 실제 달력 콘텐츠 렌더링
    end
Loading

Assessment against linked issues

Objective (이슈 번호) Addressed Explanation
스켈레톤 컴포넌트 재사용 (#132)

Possibly related PRs

Suggested labels

✋Hold

Suggested reviewers

  • CitrusSoda
  • un0211
  • YehaYoo

Poem

나는 작은 토끼, 코드를 뛰노는 날개
새 스켈레톤 UI, 반짝임을 더한 걸음
달력도, 댓글도 한결같은 미소로
경쾌한 로딩, 마치 당근 먹듯 기분 좋아
귀여운 발자국으로 코드를 노래하네 🐰
앞으로도 즐겁게 달려가리라!

Tip

🌐 Web search-backed reviews and chat
  • We have enabled web search-based reviews and chat for all users. This feature allows CodeRabbit to access the latest documentation and information on the web.
  • You can disable this feature by setting web_search: false in the knowledge_base settings.
  • Please share any feedback in the Discord discussion.
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

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

🧹 Nitpick comments (7)
components/Skeleton/CalendarSkeleton.tsx (2)

8-13: 배열 인덱스를 키로 사용하는 것을 개선해 보세요.

현재 배열 인덱스를 키로 사용하고 있는데, 이는 React의 권장 사항에 맞지 않습니다. 고유한 식별자를 생성하는 것이 더 좋은 방법입니다.

다음과 같이 개선해 보세요:

-          key={`calendar-skeleton-${rowIndex}`}
+          key={`calendar-skeleton-row-${Date.now()}-${rowIndex}`}

6-20: 상수 값을 분리하면 좋을 것 같습니다.

매직 넘버들을 상수로 분리하면 코드의 가독성과 유지보수성이 향상될 것 같습니다.

다음과 같이 개선해 보세요:

+const CALENDAR_CONSTANTS = {
+  ROWS: 5,
+  ROW_GAP: 10,
+  HEADER_HEIGHT: 15,
+  ROW_HEIGHT: 10,
+  CELL_SIZE: 30,
+  CELL_COUNT: 7,
+} as const;
+
 export default function CalendarSkeleton() {
   return (
-    <View className="mt-[24px] w-full gap-[10px] px-[3px]">
-      <Skeleton height={15} />
-      {Array.from({ length: 5 }, (_, rowIndex) => (
+    <View className="mt-[24px] w-full gap-[10px] px-[3px]">
+      <Skeleton height={CALENDAR_CONSTANTS.HEADER_HEIGHT} />
+      {Array.from({ length: CALENDAR_CONSTANTS.ROWS }, (_, rowIndex) => (
         <View
           className="gap-[12px]"
           key={`calendar-skeleton-${rowIndex}`}
         >
-          <Skeleton height={10} />
+          <Skeleton height={CALENDAR_CONSTANTS.ROW_HEIGHT} />
           <View className="flex-row justify-between">
-            <Skeleton width={30} height={30} circle count={7} />
+            <Skeleton
+              width={CALENDAR_CONSTANTS.CELL_SIZE}
+              height={CALENDAR_CONSTANTS.CELL_SIZE}
+              circle
+              count={CALENDAR_CONSTANTS.CELL_COUNT}
+            />
           </View>
         </View>
       ))}
components/Skeleton/Skeleton.tsx (2)

3-9: Props 타입에 대한 유효성 검사가 필요합니다.

Props의 타입은 잘 정의되어 있지만, 런타임에서의 유효성 검사가 없습니다.

다음과 같이 개선해 보세요:

+const isValidCount = (count: number) => count > 0 && Number.isInteger(count);
+
 interface SkeletonProps {
   className?: string;
   width?: ViewStyle["width"];
   height?: ViewStyle["height"];
-  count?: number;
+  count?: number; // should be positive integer
   circle?: boolean;
 }

11-30: 성능 최적화를 위한 메모이제이션을 고려해보세요.

컴포넌트가 자주 리렌더링될 수 있으므로, React.memo를 사용하여 최적화하는 것이 좋습니다.

다음과 같이 개선해 보세요:

-export default function Skeleton({
+const Skeleton = React.memo(function Skeleton({
   className = "",
   width,
   height,
   count = 1,
   circle = false,
 }: SkeletonProps) {
+  if (!isValidCount(count)) {
+    console.warn('Skeleton: count should be a positive integer');
+    return null;
+  }
+
   return (
     <>
       {Array.from({ length: count }, (_, index) => (
         <View
           key={`skeleton-component-${index}of${count}`}
           className={`animate-pulse bg-gray-20 ${circle ? "rounded-full" : "rounded-[5px]"} ${className}`}
           style={{ width, height }}
           aria-hidden={true}
         />
       ))}
     </>
   );
-}
+});
+
+Skeleton.displayName = 'Skeleton';
+
+export default Skeleton;
components/Skeleton/CommentSkeleton.tsx (2)

4-6: Props 타입에 대한 유효성 검사가 필요합니다.

count prop에 대한 타입은 정의되어 있지만, 런타임에서의 유효성 검사가 없습니다.

다음과 같이 개선해 보세요:

+const MIN_COUNT = 1;
+const MAX_COUNT = 10;
+
+const isValidCount = (count: number) =>
+  count >= MIN_COUNT && count <= MAX_COUNT && Number.isInteger(count);
+
 interface CommentSkeletonProps {
-  count?: number;
+  count?: number; // should be between MIN_COUNT and MAX_COUNT
 }

8-38: 상수 값을 분리하고 메모이제이션을 적용하면 좋을 것 같습니다.

하드코딩된 값들을 상수로 분리하고, 컴포넌트를 메모이제이션하면 코드의 가독성과 성능이 향상될 것 같습니다.

다음과 같이 개선해 보세요:

+const COMMENT_CONSTANTS = {
+  AVATAR_SIZE: 48,
+  MENU_SIZE: 28,
+  USERNAME_WIDTH: 64,
+  USERNAME_HEIGHT: 16,
+  TIMESTAMP_WIDTH: 42,
+  TIMESTAMP_HEIGHT: 13,
+  CONTENT_HEIGHT: 18,
+  REPLY_WIDTH: 42,
+  REPLY_HEIGHT: 14,
+} as const;
+
-export default function CommentSkeleton({ count = 1 }: CommentSkeletonProps) {
+const CommentSkeleton = React.memo(function CommentSkeleton({
+  count = MIN_COUNT,
+}: CommentSkeletonProps) {
+  if (!isValidCount(count)) {
+    console.warn(
+      `CommentSkeleton: count should be between ${MIN_COUNT} and ${MAX_COUNT}`
+    );
+    return null;
+  }
+
   return (
     <>
       {Array.from({ length: count }, (_, index) => (
         <View
           key={`comment-skeleton-${index}of${count}`}
           className="mb-4 gap-[13px] pb-[16px]"
           aria-hidden={true}
         >
           <View className="flex-row items-center justify-between gap-[8px]">
-            <Skeleton width={48} height={48} circle />
+            <Skeleton
+              width={COMMENT_CONSTANTS.AVATAR_SIZE}
+              height={COMMENT_CONSTANTS.AVATAR_SIZE}
+              circle
+            />
             <View className="flex-1 items-start gap-2">
-              <Skeleton width={64} height={16} />
-              <Skeleton width={42} height={13} />
+              <Skeleton
+                width={COMMENT_CONSTANTS.USERNAME_WIDTH}
+                height={COMMENT_CONSTANTS.USERNAME_HEIGHT}
+              />
+              <Skeleton
+                width={COMMENT_CONSTANTS.TIMESTAMP_WIDTH}
+                height={COMMENT_CONSTANTS.TIMESTAMP_HEIGHT}
+              />
             </View>
-            <Skeleton width={28} height={28} circle />
+            <Skeleton
+              width={COMMENT_CONSTANTS.MENU_SIZE}
+              height={COMMENT_CONSTANTS.MENU_SIZE}
+              circle
+            />
           </View>
-          <Skeleton width="100%" height={18} />
-          <Skeleton width={42} height={14} />
+          <Skeleton width="100%" height={COMMENT_CONSTANTS.CONTENT_HEIGHT} />
+          <Skeleton
+            width={COMMENT_CONSTANTS.REPLY_WIDTH}
+            height={COMMENT_CONSTANTS.REPLY_HEIGHT}
+          />
         </View>
       ))}
     </>
   );
-}
+});
+
+CommentSkeleton.displayName = 'CommentSkeleton';
+
+export default CommentSkeleton;
components/comments/CommentsSection.tsx (1)

1-1: 스켈레톤 컴포넌트 적용이 잘 되었습니다!

기존의 복잡한 스켈레톤 UI 구현을 재사용 가능한 CommentSkeleton 컴포넌트로 대체한 것이 좋습니다.

하지만 count prop을 하드코딩된 숫자 대신 LIMIT 상수를 사용하면 더 좋을 것 같습니다:

-              <CommentSkeleton count={5} />
+              <CommentSkeleton count={LIMIT} />

Also applies to: 301-301

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c58f93e and 07b2202.

📒 Files selected for processing (8)
  • app/(protected)/(tabs)/history.tsx (4 hunks)
  • components/Skeleton/CalendarSkeleton.tsx (1 hunks)
  • components/Skeleton/CommentSkeleton.tsx (1 hunks)
  • components/Skeleton/Skeleton.tsx (1 hunks)
  • components/Skeleton/index.ts (1 hunks)
  • components/WorkoutCalendar.tsx (3 hunks)
  • components/comments/CommentItem.tsx (3 hunks)
  • components/comments/CommentsSection.tsx (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • components/comments/CommentItem.tsx
🔇 Additional comments (4)
components/Skeleton/index.ts (1)

1-3: 내보내기가 잘 구성되어 있습니다!

컴포넌트 내보내기가 깔끔하게 구성되어 있으며, 배럴(barrel) 패턴을 잘 따르고 있습니다.

components/WorkoutCalendar.tsx (1)

3-3: 로딩 상태 처리가 깔끔하게 구현되었습니다!

isLoading prop과 CalendarSkeleton 컴포넌트를 사용하여 로딩 상태를 효과적으로 처리했습니다.

Also applies to: 21-22, 86-88

app/(protected)/(tabs)/history.tsx (2)

12-12: 로딩 상태 통합이 잘 되었습니다!

사용자 데이터와 히스토리 데이터의 로딩 상태를 하나의 isLoading 변수로 통합하여 관리하는 것이 좋습니다.

Also applies to: 50-50


78-85: 스켈레톤 UI가 일관성 있게 적용되었습니다!

제목과 캘린더에 대한 스켈레톤 UI가 적절하게 구현되었습니다.

Copy link
Contributor

@wjsdncl wjsdncl left a comment

Choose a reason for hiding this comment

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

스켈레톤 고생하셨습니다!

댓글 쪽도 같이 해주셔서 감사합니다! 🙇‍♂️

댓글 크기가 좀 작군요 안드로이드는 Inspector가 댓글창보다 뒤에 있어서 제대로 확인을 못했는데 이러면 px로 바꾸는게 더 좋을거 같긴합니다

Copy link
Contributor

@YehaYoo YehaYoo left a comment

Choose a reason for hiding this comment

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

스켈레톤 적용 확인했습니다😊😊👍

  • 댓글 스켈레톤 디자인 약간 변경
    -> 내용 확인했어요.. 개선해주셔서 감사해요 너무 좋아요 꼼꼼한 승헌님 최고🥹

  • 댓글 스켈레톤과 댓글의 크기가 다름
    -> px로 통일하는게 좋을 것 같습니다!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

스켈레톤 컴포넌트로 만들어 재사용
3 participants