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(checkbox): Checkbox 컴포넌트 구현 #106

Closed
wants to merge 10 commits into from

Conversation

jiji-hoon96
Copy link
Member

@jiji-hoon96 jiji-hoon96 commented Jan 21, 2025

변경사항

@sipe-team/checkbox 패키지 추가

체크박스 컴포넌트 구현 [#6]

  • 3가지 크기(sm, md, lg) 지원
  • 체크박스 상태(checked, unchecked, disabled) 관리
  • label prop을 통한 레이블 텍스트 지원

CheckboxGroup 컴포넌트 구현

  • 여러 체크박스를 하나의 그룹으로 관리 가능
  • 선택된 값들을 배열로 관리
  • 체크박스 그룹의 name 속성 공유

Storybook을 통한 컴포넌트 문서화

  • 기본 사용법, 크기 변형, 상태 변화 등 예시 추가
  • Controlled 상태 관리 예시 추가

시각자료

2025-01-21.4.05.29.mov

체크리스트

  • 기능 명세를 작성하였나요?
  • 테스트 코드를 작성하였나요?

추가 논의사항

  • 현재 체크박스의 색상은 cyan300을 사용하고 있습니다. 일관, 확장성을 위해 변경할 여지가 있어보입니다.
  • 체크박스 그룹에서 indeterminate 상태 지원을 추가하면 좋을 것 같습니다..! (생각해보았는데 좀 어려워서 문서를 찾아보려해요)
  • 접근성 관련하여 aria-label, aria-describedby 등의 추가적인 속성 지원을 고려해볼 수 있습니다. (aria 컨밴션을 다루시는지 궁금합니다)

질문, 변경사항 관련해서 코멘트 달아주시면 감사하겠습니다!

  • P 작업에 적혀있어서 진행하고 보니, 대엽님 작업이더라고.. 확인 못해서 죄송합니다! 대엽님 작업 올리면 비교해봐도 재미있을 것 같네요 (제거 부족한부분이 많으니..)

Summary by CodeRabbit

릴리즈 노트

  • 새로운 기능

    • 체크박스 컴포넌트 및 체크박스 그룹 추가
    • 다양한 크기와 상태를 지원하는 유연한 체크박스 구현
    • Storybook을 통한 체크박스 컴포넌트의 다양한 상태 및 기능 시연
  • 개선 사항

    • 접근성 및 사용자 상호작용을 고려한 체크박스 디자인
    • Radix UI 체크박스 프리미티브 활용
    • CSS 모듈을 통한 스타일링 지원
  • 개발 환경

    • Storybook 설정 추가
    • 단위 테스트 및 타입스크립트 구성 통합
    • Vitest 및 Jest-DOM을 활용한 테스트 환경 구성
    • 새로운 패키지 메타데이터 및 구성 파일 추가

- Add checkbox dependencies
- Fix checkbox element positioning
- Ensure compatibility with Radix UI's CheckedState type
- Enhance type safety with exactOptionalPropertyTypes option
Copy link

changeset-bot bot commented Jan 21, 2025

⚠️ No Changeset found

Latest commit: b1925b8

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

coderabbitai bot commented Jan 21, 2025

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.

packages/checkbox/.storybook/preview.ts

Oops! Something went wrong! :(

ESLint: 9.17.0

ESLint couldn't find an eslint.config.(js|mjs|cjs) file.

From ESLint v9.0.0, the default configuration file is now eslint.config.js.
If you are using a .eslintrc.* file, please follow the migration guide
to update your configuration file to the new format:

https://eslint.org/docs/latest/use/configure/migration-guide

If you still have problems after following the migration guide, please stop by
https://eslint.org/chat/help to chat with the team.

개요

Walkthrough

이 풀 리퀘스트는 @sipe-team/checkbox 패키지의 새로운 체크박스 컴포넌트를 추가합니다. Storybook 구성, TypeScript 설정, CSS 모듈, 테스트 파일 등 컴포넌트 개발에 필요한 다양한 파일들이 포함되어 있습니다. Radix UI의 체크박스 프리미티브를 기반으로 하며, 다양한 크기와 상태를 지원하는 유연한 컴포넌트입니다.

Changes

파일 변경 요약
.storybook/main.ts, .storybook/preview.ts Storybook 구성 파일 추가
global.d.ts CSS 모듈 타입 선언 추가
package.json 패키지 메타데이터 및 의존성 정의
src/Checkbox.module.css 체크박스 스타일 CSS 모듈 생성
src/Checkbox.stories.tsx Storybook 스토리 추가
src/Checkbox.test.tsx 체크박스 컴포넌트 단위 테스트
src/Checkbox.tsx 체크박스 및 체크박스 그룹 컴포넌트 구현
src/index.ts 컴포넌트 내보내기
tsconfig.json, tsup.config.ts, vitest.config.ts, vitest.setup.ts TypeScript 및 테스트 설정 파일 추가

Poem

🐰 체크박스 토끼의 노래 🐰

작은 네모 상자 속에
클릭하면 반짝이는 마법
크기와 상태 자유롭게
토끼의 코드로 피어나네
새로운 컴포넌트 탄생! 🎉


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

codecov bot commented Jan 21, 2025

Codecov Report

Attention: Patch coverage is 96.96970% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/checkbox/src/Checkbox.tsx 94.44% 5 Missing ⚠️
Files with missing lines Coverage Δ
packages/checkbox/src/Checkbox.test.tsx 100.00% <100.00%> (ø)
packages/checkbox/src/Checkbox.tsx 94.44% <94.44%> (ø)

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

🧹 Nitpick comments (7)
packages/checkbox/.storybook/preview.ts (1)

4-14: 접근성 매개변수 추가를 고려해보세요.

현재 설정은 기본적인 기능을 잘 커버하고 있습니다만, 접근성 테스트를 위한 매개변수를 추가하면 좋을 것 같습니다.

다음과 같이 a11y 매개변수를 추가하는 것을 추천드립니다:

 const preview: Preview = {
   parameters: {
     actions: { argTypesRegex: '^on[A-Z].*' },
     controls: {
       matchers: {
         color: /(background|color)$/i,
         date: /Date$/i,
       },
     },
+    a11y: {
+      element: '#storybook-root',
+      config: {
+        rules: [
+          {
+            id: 'color-contrast',
+            enabled: true
+          }
+        ]
+      }
+    }
   },
 };
packages/checkbox/src/Checkbox.stories.tsx (1)

87-102: CheckboxGroup 스토리의 레이아웃 개선이 필요합니다.

현재 체크박스들이 수직으로 쌓이는 레이아웃이 없어 가독성이 떨어집니다.

다음과 같이 수정해보세요:

         return (
-            <CheckboxGroup value={selected} onChange={setSelected}>
+            <CheckboxGroup 
+              value={selected} 
+              onChange={setSelected}
+              style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}
+            >
                 <Checkbox {...args} value="apple" label="사과" />
                 <Checkbox {...args} value="banana" label="바나나" />
                 <Checkbox {...args} value="orange" label="오렌지" />
             </CheckboxGroup>
         );
packages/checkbox/src/Checkbox.test.tsx (3)

7-16: 체크박스 상태 변경 테스트 보완 제안

테스트가 잘 구현되어 있지만, 다음과 같은 추가 검증을 고려해보세요:

  • data-state 속성 검증
  • 접근성 속성(aria-checked) 검증
 test('체크박스를 클릭하면 상태가 변경된다.', async () => {
     const user = userEvent.setup();
     render(<Checkbox label="테스트" />);

     const checkbox = screen.getByRole('checkbox', { name: '테스트' });
     expect(checkbox).not.toBeChecked();
+    expect(checkbox).toHaveAttribute('data-state', 'unchecked');
+    expect(checkbox).toHaveAttribute('aria-checked', 'false');

     await user.click(checkbox);
     expect(checkbox).toBeChecked();
+    expect(checkbox).toHaveAttribute('data-state', 'checked');
+    expect(checkbox).toHaveAttribute('aria-checked', 'true');
 });

18-27: 비활성화된 체크박스 테스트 케이스 보완 제안

현재 테스트는 기본적인 동작을 검증하고 있지만, 다음과 같은 추가 테스트 케이스를 고려해보세요:

  • 스타일 상태 검증 (opacity)
  • 키보드 이벤트 처리 검증
 test('disabled 상태의 체크박스는 클릭할 수 없다.', async () => {
     const user = userEvent.setup();
     render(<Checkbox disabled label="테스트" />);

     const checkbox = screen.getByRole('checkbox', { name: '테스트' });
     expect(checkbox).toBeDisabled();
+    expect(checkbox).toHaveStyle({ opacity: '0.4' });

     await user.click(checkbox);
     expect(checkbox).not.toBeChecked();
+
+    await user.keyboard('[Space]');
+    expect(checkbox).not.toBeChecked();
 });

47-77: 체크박스 그룹 에러 케이스 테스트 추가 제안

현재 테스트는 정상적인 사용 사례만 다루고 있습니다. 다음과 같은 에러 케이스도 테스트하면 좋을 것 같습니다:

  • 잘못된 value 처리
  • 중복된 value 처리
  • 빈 자식 요소 처리

다음과 같은 테스트 케이스를 추가해보세요:

test('체크박스 그룹에서 잘못된 value를 처리할 수 있다.', () => {
  render(
    <CheckboxGroup value={['invalid']}>
      <Checkbox value="1" label="항목 1" />
    </CheckboxGroup>
  );

  const checkbox = screen.getByRole('checkbox', { name: '항목 1' });
  expect(checkbox).not.toBeChecked();
});
packages/checkbox/src/Checkbox.module.css (1)

15-26: 호버 및 포커스 상태 스타일 개선 제안

체크박스의 상호작용 상태에 대한 시각적 피드백을 개선하면 좋을 것 같습니다:

  • 호버 상태 스타일
  • 포커스 상태 스타일
  • 키보드 포커스 링
 .checkbox {
   all: unset;
   width: var(--size);
   height: var(--size);
   border-radius: 4px;
   border: 2px solid var(--border-color);
   background-color: var(--background-color);
   display: flex;
   align-items: center;
   justify-content: center;
   cursor: pointer;
+  transition: all 0.2s ease-in-out;
+}
+
+.checkbox:hover {
+  border-color: var(--checked-border-color);
+}
+
+.checkbox:focus-visible {
+  outline: 2px solid var(--checked-border-color);
+  outline-offset: 2px;
 }
packages/checkbox/package.json (1)

13-22: 빌드 및 테스트 스크립트 개선 제안

다음과 같은 스크립트 및 구성을 추가하면 좋을 것 같습니다:

  • 테스트 커버리지 리포트
  • 번들 크기 분석
  • 성능 테스트
   "scripts": {
     "build": "tsup",
     "build:storybook": "storybook build",
     "dev:storybook": "storybook dev -p 6006",
     "lint:biome": "pnpm exec biome lint",
     "lint:eslint": "pnpm exec eslint --flag unstable_ts_config",
     "test": "vitest",
+    "test:coverage": "vitest run --coverage",
+    "analyze": "tsup --analyze",
+    "perf": "vitest bench",
     "typecheck": "tsc",
     "prepack": "pnpm run build"
   },
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between fba28cc and 5fda386.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • packages/checkbox/.storybook/main.ts (1 hunks)
  • packages/checkbox/.storybook/preview.ts (1 hunks)
  • packages/checkbox/global.d.ts (1 hunks)
  • packages/checkbox/package.json (1 hunks)
  • packages/checkbox/src/Checkbox.module.css (1 hunks)
  • packages/checkbox/src/Checkbox.stories.tsx (1 hunks)
  • packages/checkbox/src/Checkbox.test.tsx (1 hunks)
  • packages/checkbox/src/Checkbox.tsx (1 hunks)
  • packages/checkbox/src/index.ts (1 hunks)
  • packages/checkbox/tsconfig.json (1 hunks)
  • packages/checkbox/tsup.config.ts (1 hunks)
  • packages/checkbox/vitest.config.ts (1 hunks)
  • packages/checkbox/vitest.setup.ts (1 hunks)
✅ Files skipped from review due to trivial changes (5)
  • packages/checkbox/src/index.ts
  • packages/checkbox/global.d.ts
  • packages/checkbox/tsup.config.ts
  • packages/checkbox/vitest.setup.ts
  • packages/checkbox/tsconfig.json
🔇 Additional comments (2)
packages/checkbox/vitest.config.ts (1)

1-11: 테스트 설정이 적절히 구성되었습니다!

기본 설정을 상속받아 프로젝트별 설정을 추가하는 방식으로 잘 구성되어 있습니다.

packages/checkbox/src/Checkbox.stories.tsx (1)

70-85: Controlled 컴포넌트의 타입 안전성 개선이 필요합니다.

checked 상태의 타입이 boolean | 'indeterminate'로 되어있는데, 이는 컴포넌트의 실제 타입과 일치하는지 확인이 필요합니다.

다음 스크립트로 Checkbox 컴포넌트의 타입을 확인해보세요:

✅ Verification successful

타입 안전성이 올바르게 구현되어 있습니다.

checked 상태의 타입 boolean | 'indeterminate'는 Radix UI의 CheckboxPrimitive.CheckedState 타입과 일치하며, 컴포넌트가 이를 적절히 처리하고 있습니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Checkbox 컴포넌트의 checked prop 타입 확인
ast-grep --pattern 'interface CheckboxProps {
  $$$
  checked?: $type
  $$$
}'

Length of output: 80


Script:

#!/bin/bash
# Try different approaches to find the type definition

# Search for type definitions in Checkbox component file
echo "=== Searching in Checkbox component file ==="
fd "Checkbox.tsx" --exec cat {}

# Search for any type definitions containing 'Checkbox'
echo -e "\n=== Searching for type files ==="
fd -e ts -e tsx -g "*types*" --exec grep -l "Checkbox" {}

# Search for checked prop usage
echo -e "\n=== Searching for checked prop usage ==="
rg "checked.*?:" -A 2 -g "*.{ts,tsx}"

Length of output: 7277

Comment on lines +5 to +10
addons: [
'@storybook/addon-onboarding',
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

접근성 애드온 추가가 필요합니다.

체크박스는 접근성이 매우 중요한 컴포넌트입니다. 접근성 테스트를 위한 애드온을 추가하면 좋을 것 같습니다.

다음과 같이 a11y 애드온을 추가해주세요:

   addons: [
     '@storybook/addon-onboarding',
     '@storybook/addon-links',
     '@storybook/addon-essentials',
     '@storybook/addon-interactions',
+    '@storybook/addon-a11y',
   ],
📝 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
addons: [
'@storybook/addon-onboarding',
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
addons: [
'@storybook/addon-onboarding',
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-a11y',
],

Comment on lines +56 to +68
export const States: Story = {
args: {
label: '체크박스',
},
render: (args) => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<Checkbox {...args} label="Unchecked (Default)" />
<Checkbox {...args} label="Checked" defaultChecked />
<Checkbox {...args} label="Disabled" disabled />
<Checkbox {...args} label="Disabled & Checked" disabled defaultChecked />
</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

키보드 상호작용 예제가 필요합니다.

접근성을 위해 키보드 상호작용을 보여주는 스토리를 추가하면 좋을 것 같습니다.

다음과 같은 키보드 인터랙션 스토리를 추가해보세요:

export const KeyboardInteraction: Story = {
    args: {
        label: '키보드 상호작용',
    },
    parameters: {
        docs: {
            description: {
                story: '스페이스바 또는 엔터 키를 사용하여 체크박스를 토글할 수 있습니다.',
            },
        },
    },
    play: async ({ canvasElement }) => {
        const canvas = within(canvasElement);
        const checkbox = canvas.getByRole('checkbox');
        
        // 포커스
        await userEvent.tab();
        // 토글
        await userEvent.keyboard('[Space]');
    },
};

Comment on lines +11 to +29
argTypes: {
size: {
description: '체크박스의 크기를 지정합니다',
options: ['sm', 'md', 'lg'],
control: { type: 'radio' },
},
disabled: {
description: '체크박스의 비활성화 상태를 지정합니다',
control: { type: 'boolean' },
},
checked: {
description: '체크박스의 선택 상태를 지정합니다',
control: { type: 'boolean' },
},
label: {
description: '체크박스의 레이블을 지정합니다',
control: 'text',
},
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

접근성 속성에 대한 설명이 필요합니다.

argTypes에 aria-label과 같은 접근성 속성에 대한 설명이 누락되어 있습니다.

다음과 같이 접근성 관련 argTypes를 추가해주세요:

     argTypes: {
+        'aria-label': {
+            description: '스크린 리더를 위한 레이블을 지정합니다',
+            control: 'text',
+        },
+        'aria-describedby': {
+            description: '스크린 리더를 위한 추가 설명을 지정합니다',
+            control: 'text',
+        },
         size: {
📝 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
argTypes: {
size: {
description: '체크박스의 크기를 지정합니다',
options: ['sm', 'md', 'lg'],
control: { type: 'radio' },
},
disabled: {
description: '체크박스의 비활성화 상태를 지정합니다',
control: { type: 'boolean' },
},
checked: {
description: '체크박스의 선택 상태를 지정합니다',
control: { type: 'boolean' },
},
label: {
description: '체크박스의 레이블을 지정합니다',
control: 'text',
},
},
argTypes: {
'aria-label': {
description: '스크린 리더를 위한 레이블을 지정합니다',
control: 'text',
},
'aria-describedby': {
description: '스크린 리더를 위한 추가 설명을 지정합니다',
control: 'text',
},
size: {
description: '체크박스의 크기를 지정합니다',
options: ['sm', 'md', 'lg'],
control: { type: 'radio' },
},
disabled: {
description: '체크박스의 비활성화 상태를 지정합니다',
control: { type: 'boolean' },
},
checked: {
description: '체크박스의 선택 상태를 지정합니다',
control: { type: 'boolean' },
},
label: {
description: '체크박스의 레이블을 지정합니다',
control: 'text',
},
},

Comment on lines 51 to 96
export const Checkbox = forwardRef<
ElementRef<typeof CheckboxPrimitive.Root>,
CheckboxProps
>(({ className, size = 'md', label, style, id: providedId, ...props }, ref) => {
const generatedId = useId();
const id = providedId || generatedId;
const { checkboxSize, indicatorSize, fontSize, padding } = getSizeStyles(size);

return (
<div
className={styles.root}
style={
{
'--margin': '4px',
'--padding': padding,
'--size': checkboxSize,
'--indicator-size': indicatorSize,
'--border-color': color.gray400,
'--background-color': color.white,
'--checked-border-color': color.cyan300,
'--checked-background-color': color.cyan300,
'--indicator-color': color.white,
...style,
} as React.CSSProperties
}
>
<CheckboxPrimitive.Root
className={styles.checkbox}
ref={ref}
id={id}
{...props}
>
<CheckboxPrimitive.Indicator className={styles.indicator}>
<Check size={indicatorSize} />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
{label && (
<Typography asChild size={fontSize}>
<label htmlFor={id} className={styles.label}>
{label}
</label>
</Typography>
)}
</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

접근성 개선 제안

현재 구현에 다음과 같은 접근성 기능을 추가하면 좋을 것 같습니다:

  • aria-describedby 지원
  • aria-required 지원
  • 키보드 포커스 스타일
 export const Checkbox = forwardRef<
     ElementRef<typeof CheckboxPrimitive.Root>,
     CheckboxProps
->(({ className, size = 'md', label, style, id: providedId, ...props }, ref) => {
+>(({ className, size = 'md', label, style, id: providedId, required, description, ...props }, ref) => {
   const generatedId = useId();
   const id = providedId || generatedId;
+  const descriptionId = description ? `${id}-description` : undefined;
   const { checkboxSize, indicatorSize, fontSize, padding } = getSizeStyles(size);

   return (
       <div
           className={styles.root}
           style={
             {
               '--margin': '4px',
               '--padding': padding,
               '--size': checkboxSize,
               '--indicator-size': indicatorSize,
               '--border-color': color.gray400,
               '--background-color': color.white,
               '--checked-border-color': color.cyan300,
               '--checked-background-color': color.cyan300,
               '--indicator-color': color.white,
               ...style,
             } as React.CSSProperties
           }
       >
         <CheckboxPrimitive.Root
             className={styles.checkbox}
             ref={ref}
             id={id}
+            aria-describedby={descriptionId}
+            aria-required={required}
             {...props}
         >

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 100 to 133
export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
({ children, name, onChange, value }, ref) => {
const handleCheckedChange = (checked: CheckboxPrimitive.CheckedState, itemValue: string | undefined) => {
if (!onChange || !itemValue || typeof checked !== 'boolean') return;

const newValues = checked
? [...(value || []), itemValue]
: (value || []).filter((v) => v !== itemValue);

onChange(newValues);
};

const mappedChildren = Children.map(children, (child) => {
if (!isValidElement<CheckboxProps>(child)) return child;

const checked = value?.includes(child.props.value || '') || false;

return cloneElement(child, {
checked,
name,
onCheckedChange: (state: CheckboxPrimitive.CheckedState) => {
handleCheckedChange(state, child.props.value);
child.props.onCheckedChange?.(state);
},
} as Partial<CheckboxProps>);
});

return (
<div className={styles.group} ref={ref}>
{mappedChildren}
</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

에러 처리 및 타입 안정성 개선 제안

CheckboxGroup 컴포넌트의 에러 처리와 타입 안정성을 개선하면 좋을 것 같습니다:

  • 중복된 value 검증
  • 자식 컴포넌트 타입 검증 강화
  • 콜백 에러 처리
 export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
     ({ children, name, onChange, value }, ref) => {
+      const valueSet = new Set(value);
+      
+      if (valueSet.size !== value?.length) {
+        console.warn('CheckboxGroup: Duplicate values detected');
+      }
+
       const handleCheckedChange = (checked: CheckboxPrimitive.CheckedState, itemValue: string | undefined) => {
         if (!onChange || !itemValue || typeof checked !== 'boolean') return;
 
         const newValues = checked
             ? [...(value || []), itemValue]
             : (value || []).filter((v) => v !== itemValue);
 
-        onChange(newValues);
+        try {
+          onChange(newValues);
+        } catch (error) {
+          console.error('CheckboxGroup: onChange handler error', error);
+        }
       };
📝 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 CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
({ children, name, onChange, value }, ref) => {
const handleCheckedChange = (checked: CheckboxPrimitive.CheckedState, itemValue: string | undefined) => {
if (!onChange || !itemValue || typeof checked !== 'boolean') return;
const newValues = checked
? [...(value || []), itemValue]
: (value || []).filter((v) => v !== itemValue);
onChange(newValues);
};
const mappedChildren = Children.map(children, (child) => {
if (!isValidElement<CheckboxProps>(child)) return child;
const checked = value?.includes(child.props.value || '') || false;
return cloneElement(child, {
checked,
name,
onCheckedChange: (state: CheckboxPrimitive.CheckedState) => {
handleCheckedChange(state, child.props.value);
child.props.onCheckedChange?.(state);
},
} as Partial<CheckboxProps>);
});
return (
<div className={styles.group} ref={ref}>
{mappedChildren}
</div>
);
}
);
export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
({ children, name, onChange, value }, ref) => {
const valueSet = new Set(value);
if (valueSet.size !== value?.length) {
console.warn('CheckboxGroup: Duplicate values detected');
}
const handleCheckedChange = (checked: CheckboxPrimitive.CheckedState, itemValue: string | undefined) => {
if (!onChange || !itemValue || typeof checked !== 'boolean') return;
const newValues = checked
? [...(value || []), itemValue]
: (value || []).filter((v) => v !== itemValue);
try {
onChange(newValues);
} catch (error) {
console.error('CheckboxGroup: onChange handler error', error);
}
};
const mappedChildren = Children.map(children, (child) => {
if (!isValidElement<CheckboxProps>(child)) return child;
const checked = value?.includes(child.props.value || '') || false;
return cloneElement(child, {
checked,
name,
onCheckedChange: (state: CheckboxPrimitive.CheckedState) => {
handleCheckedChange(state, child.props.value);
child.props.onCheckedChange?.(state);
},
} as Partial<CheckboxProps>);
});
return (
<div className={styles.group} ref={ref}>
{mappedChildren}
</div>
);
}
);

Copy link
Contributor

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

@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

♻️ Duplicate comments (1)
packages/checkbox/src/Checkbox.tsx (1)

101-134: 🛠️ Refactor suggestion

CheckboxGroup 컴포넌트의 안정성 개선이 필요합니다

이전 리뷰에서 언급된 중복 값 검증과 에러 처리 외에도, 다음 사항들을 개선하면 좋을 것 같습니다:

  • defaultValuevalue 동시 사용 시 경고 추가
  • 자식 컴포넌트가 없는 경우 처리
  • 체크박스 그룹에 대한 접근성 속성 추가 (예: role="group", aria-label)
 export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
     ({ children, name, onChange, value }, ref) => {
+      if (value !== undefined && defaultValue !== undefined) {
+        console.warn('CheckboxGroup: Both value and defaultValue props are provided. defaultValue will be ignored.');
+      }
+
+      if (!Children.count(children)) {
+        console.warn('CheckboxGroup: No children provided');
+      }
+
       // ... existing code ...
 
       return (
-          <div className={styles.group} ref={ref}>
+          <div 
+              className={styles.group} 
+              ref={ref} 
+              role="group"
+              aria-label={`${name || 'Checkbox'} group`}
+          >
             {mappedChildren}
           </div>
       );
     }
 );
🧹 Nitpick comments (1)
packages/checkbox/src/Checkbox.tsx (1)

10-15: 타입 정의 개선이 필요합니다

CheckboxProps 인터페이스에 다음과 같은 접근성 관련 속성들을 추가하면 좋을 것 같습니다:

  • aria-label: 레이블이 시각적으로 보이지 않을 때 사용
  • aria-describedby: 추가 설명이 필요한 경우 사용
  • required: 필수 입력 여부

또한 value 속성의 타입을 string | undefined로 명시적으로 지정하는 것이 좋습니다.

 export interface CheckboxProps
     extends Omit<ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, 'asChild'> {
   label?: string;
   size?: CheckboxSize;
-  value?: string;
+  value?: string | undefined;
+  'aria-label'?: string;
+  'aria-describedby'?: string;
+  required?: boolean;
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5fda386 and 35f9f4c.

📒 Files selected for processing (3)
  • packages/checkbox/package.json (1 hunks)
  • packages/checkbox/src/Checkbox.module.css (1 hunks)
  • packages/checkbox/src/Checkbox.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/checkbox/package.json
  • packages/checkbox/src/Checkbox.module.css
🔇 Additional comments (2)
packages/checkbox/src/Checkbox.tsx (2)

25-49: 구현이 깔끔합니다!

getSizeStyles 함수가 디자인 토큰을 잘 활용하고 있으며, 각 사이즈별로 일관된 스타일을 제공합니다.


78-87: 🛠️ Refactor suggestion

키보드 접근성 개선이 필요합니다

키보드 사용자를 위한 포커스 관리가 필요합니다:

  • tabIndex 속성 추가
  • 포커스 스타일에 대한 시각적 피드백 강화
 <CheckboxPrimitive.Root
     className={styles.checkbox}
     ref={ref}
     id={id}
+    tabIndex={0}
     {...props}
 >
   <CheckboxPrimitive.Indicator className={styles.indicator}>
     <Check size={indicatorSize} />
   </CheckboxPrimitive.Indicator>
 </CheckboxPrimitive.Root>

Likely invalid or redundant comment.

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 (3)
packages/checkbox/src/Checkbox.tsx (3)

10-15: 접근성 관련 props 추가 필요

접근성을 개선하기 위해 다음과 같은 props를 추가하는 것이 좋겠습니다:

  • aria-label: 스크린 리더를 위한 레이블
  • aria-describedby: 추가 설명을 위한 요소 ID
  • required: 필수 입력 여부
 export interface CheckboxProps
     extends Omit<ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, 'asChild'> {
   label?: string;
   size?: CheckboxSize;
   value?: string;
+  'aria-label'?: string;
+  'aria-describedby'?: string;
+  required?: boolean;
 }

25-49: 타입 안전성 개선 필요

getSizeStyles 함수의 반환 타입을 명시적으로 정의하여 타입 안전성을 개선하면 좋겠습니다.

+interface SizeStyles {
+  checkboxSize: string;
+  indicatorSize: string;
+  fontSize: string;
+  padding: string;
+}
+
-const getSizeStyles = (size: CheckboxSize) => {
+const getSizeStyles = (size: CheckboxSize): SizeStyles => {

63-77: 호버 상태 스타일 추가 필요

사용자 경험 향상을 위해 호버 상태의 스타일을 추가하면 좋겠습니다.

           {
             '--margin': '4px',
             '--border-radius': '4px',
             '--padding': padding,
             '--size': checkboxSize,
             '--indicator-size': indicatorSize,
             '--border-color': color.gray400,
             '--background-color': color.white,
             '--checked-border-color': color.cyan300,
             '--checked-background-color': color.cyan300,
             '--indicator-color': color.white,
             '--focus-border-color': color.cyan500,
+            '--hover-border-color': color.gray500,
+            '--hover-background-color': color.gray50,
             ...style,
           } as React.CSSProperties
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 35f9f4c and 78c4bf2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • packages/checkbox/src/Checkbox.module.css (1 hunks)
  • packages/checkbox/src/Checkbox.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/checkbox/src/Checkbox.module.css
🔇 Additional comments (2)
packages/checkbox/src/Checkbox.tsx (2)

102-112: 중복 값 검증 및 에러 처리 필요

CheckboxGroup에서 중복된 값 검증과 에러 처리를 추가하면 좋겠습니다.

 export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
     ({ children, name, onChange, value }, ref) => {
+      // 중복 값 검증
+      const valueSet = new Set(value);
+      if (valueSet.size !== value?.length) {
+        console.warn('CheckboxGroup: 중복된 값이 감지되었습니다');
+      }
+
       const handleCheckedChange = (checked: CheckboxPrimitive.CheckedState, itemValue: string | undefined) => {
         if (!onChange || !itemValue || typeof checked !== 'boolean') return;
 
         const newValues = checked
             ? [...(value || []), itemValue]
             : (value || []).filter((v) => v !== itemValue);
 
-        onChange(newValues);
+        try {
+          onChange(newValues);
+        } catch (error) {
+          console.error('CheckboxGroup: onChange 핸들러 에러', error);
+        }
       };

130-132: 키보드 포커스 스타일 추가 필요

키보드 접근성 향상을 위해 포커스 스타일을 추가하면 좋겠습니다.

-          <div className={styles.group} ref={ref}>
+          <div 
+              className={styles.group} 
+              ref={ref}
+              style={{
+                '--focus-outline-color': color.cyan500,
+                '--focus-outline-width': '2px',
+              } as React.CSSProperties}
+          >

packages/checkbox/.storybook/preview.ts Outdated Show resolved Hide resolved
packages/checkbox/src/Checkbox.test.tsx Outdated Show resolved Hide resolved
packages/checkbox/src/Checkbox.test.tsx Outdated Show resolved Hide resolved
packages/checkbox/src/Checkbox.test.tsx Outdated Show resolved Hide resolved
packages/checkbox/src/Checkbox.tsx Outdated Show resolved Hide resolved
packages/checkbox/src/Checkbox.tsx Outdated Show resolved Hide resolved
- move non-variable elements to module.css for better separation
- Replace logical operators with ternary operators for clarity
- Clean up type imports and React imports for better organization
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

♻️ Duplicate comments (2)
packages/checkbox/src/Checkbox.tsx (2)

79-83: 🛠️ Refactor suggestion

키보드 포커스 스타일 추가 필요

키보드 사용자를 위한 포커스 스타일이 누락되어 있습니다. CSS 모듈에 :focus-visible 스타일을 추가하는 것이 좋겠습니다.


97-112: 🛠️ Refactor suggestion

CheckboxGroup 에러 처리 개선 필요

다음과 같은 에러 처리 개선이 필요합니다:

  • 중복된 value 검증
  • onChange 콜백의 에러 처리
  • 타입 안정성 강화
export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
    ({ children, name, onChange, value }, ref) => {
+     // 중복 값 검증
+     const valueSet = new Set(value);
+     if (valueSet.size !== value?.length) {
+       console.warn('CheckboxGroup: 중복된 값이 감지되었습니다');
+     }

      const handleCheckedChange = (
          checked: CheckboxPrimitive.CheckedState,
          itemValue: string | undefined,
      ) => {
        if (!onChange || !itemValue || typeof checked !== 'boolean') {
          return;
        }

        const newValues = checked
            ? [...(value ?? []), itemValue]
            : (value ?? []).filter((v) => v !== itemValue);

-       onChange(newValues);
+       try {
+         onChange(newValues);
+       } catch (error) {
+         console.error('CheckboxGroup: onChange 핸들러 에러', error);
+       }
      };
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 104-105: packages/checkbox/src/Checkbox.tsx#L104-L105
Added lines #L104 - L105 were not covered by tests

🧹 Nitpick comments (1)
packages/checkbox/src/Checkbox.tsx (1)

61-64: 컴포넌트 문서화 개선 필요

CheckboxCheckboxGroup 컴포넌트에 JSDoc 주석을 추가하여 사용 방법과 props에 대한 설명을 추가하면 좋겠습니다.

예시:

/**
 * Checkbox 컴포넌트는 사용자가 여러 옵션 중에서 선택할 수 있게 해주는 입력 요소입니다.
 * 
 * @param {CheckboxProps} props - 체크박스 속성
 * @param {string} [props.label] - 체크박스 레이블
 * @param {CheckboxSize} [props.size='md'] - 체크박스 크기
 * @param {string} [props.value] - 체크박스 값
 */

Also applies to: 97-98

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 78c4bf2 and b1925b8.

📒 Files selected for processing (4)
  • packages/checkbox/.storybook/preview.ts (1 hunks)
  • packages/checkbox/src/Checkbox.module.css (1 hunks)
  • packages/checkbox/src/Checkbox.test.tsx (1 hunks)
  • packages/checkbox/src/Checkbox.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/checkbox/.storybook/preview.ts
  • packages/checkbox/src/Checkbox.test.tsx
  • packages/checkbox/src/Checkbox.module.css
🧰 Additional context used
🪛 GitHub Check: codecov/patch
packages/checkbox/src/Checkbox.tsx

[warning] 90-90: packages/checkbox/src/Checkbox.tsx#L90
Added line #L90 was not covered by tests


[warning] 104-105: packages/checkbox/src/Checkbox.tsx#L104-L105
Added lines #L104 - L105 were not covered by tests


[warning] 116-117: packages/checkbox/src/Checkbox.tsx#L116-L117
Added lines #L116 - L117 were not covered by tests

🔇 Additional comments (3)
packages/checkbox/src/Checkbox.tsx (3)

20-25: Props 인터페이스에 description 속성 추가 제안

접근성 향상을 위해 CheckboxProps 인터페이스에 description 속성을 추가하면 좋을 것 같습니다. 이는 스크린 리더 사용자에게 추가적인 컨텍스트를 제공할 수 있습니다.

export interface CheckboxProps
    extends Omit<ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, 'asChild'> {
  label?: string;
  size?: CheckboxSize;
  value?: string;
+ description?: string;
}

35-59: 스타일 로직 CSS 모듈로 이동 제안

현재 getSizeStyles 함수에서 관리되는 고정된 스타일 값들을 CSS 모듈로 이동하면 좋을 것 같습니다. CSS 변수를 활용하면 더 나은 유지보수성과 성능을 얻을 수 있습니다.


90-90: 테스트 커버리지 개선 필요

다음 코드 경로들에 대한 테스트가 누락되어 있습니다:

  • label이 없는 경우 (L90)
  • onChange 콜백이 없는 경우 (L104-105)
  • 유효하지 않은 자식 요소가 있는 경우 (L116-117)

이러한 엣지 케이스들에 대한 테스트를 추가하면 좋겠습니다.

Also applies to: 104-105, 116-117

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 90-90: packages/checkbox/src/Checkbox.tsx#L90
Added line #L90 was not covered by tests

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

Successfully merging this pull request may close these issues.

3 participants