Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@testing-library/user-event": "^14.6.1",
"@types/react": "^19.1.10",
"@types/react-dom": "^19.1.7",
"@types/testing-library__user-event": "^4.2.0",

Choose a reason for hiding this comment

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

critical

pnpm-lock.yaml에 따르면 @types/testing-library__user-event 패키지는 더 이상 사용되지 않으며(deprecated), testing-library__user-event가 자체적으로 타입을 제공하므로 설치할 필요가 없습니다. 이 불필요한 의존성을 제거해주세요.

"@typescript-eslint/eslint-plugin": "^8.39.1",
"@typescript-eslint/parser": "^8.39.1",
"@vitejs/plugin-react": "^5.0.0",
Expand Down
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ignoredBuiltDependencies:
- unrs-resolver
4 changes: 2 additions & 2 deletions src/componets/Button/Button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
2px 2px 0 0 s.color(gray-900);
}
&:active {
background: #bbbbbb;
background: s.color(gray-300);
color: s.color(gray-900);
box-shadow:
inset 1px 1px 0 0 s.color(white),
1px 1px 0 0 #868686,
1px 1px 0 0 s.color(gray-600),
0px 0px 0 0 s.color(gray-900);
}

Expand Down
5 changes: 3 additions & 2 deletions src/componets/Checkbox/Checkbox.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,17 @@
height: 18px;
border-radius: 0;
border: 2px solid s.color(gray-400);
background: s.color(white);
background: s.color(gray-300);
cursor: pointer;
transition:
transition:
background-color 0.2s,
border-color 0.2s;

&:checked {
~ .checkbox_png {
opacity: 1;
}
border: 2px solid s.color(gray-900);
}

@media (max-width: 640px) {
Expand Down
4 changes: 2 additions & 2 deletions src/componets/Checkbox/Checkbox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ const meta: Meta<typeof Checkbox> = {
export default meta;
type Story = StoryObj<typeof Checkbox>;

export const Primary: Story = {
export const Default: Story = {
args: {
label: 'Checkbox Default'
}
};

export const Secondary: Story = {
export const Checked: Story = {
args: {
label: 'Checkbox Checked',
checked: true
Expand Down
85 changes: 85 additions & 0 deletions src/componets/Input/Input.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
@use '../../styles/index' as s;
@use 'sass:map';


.input_wrapper {
position: relative;
cursor: pointer;
box-shadow:
inset 1px 1px 0 0 s.color(white),
1px 1px 0 0 s.color(gray-600),
2px 2px 0 0 s.color(gray-900);
transition:
background-color 0.12s,
color 0.12s,
box-shadow 0.12s;
}

// 인풋 필드
.input_field {
width: 100%;
padding: 10px;
box-sizing: border-box;
border: none;
background: s.color(gray-300);
color: s.color(gray-900);

box-shadow:
inset 1px 1px 0 0 s.color(white),
1px 1px 0 0 s.color(gray-600),
2px 2px 0 0 s.color(gray-900);
transition:
background-color 0.12s,
color 0.12s,
box-shadow 0.12s;


&:hover,
&:focus {
outline: none;
background: s.color(gray-300);
color: s.color(gray-900);
box-shadow:
inset 1px 1px 0 0 s.color(white),
2px 2px 0 0 s.color(gray-900);
}


&:active {
background: #bbbbbb;
color: s.color(gray-900);
box-shadow:
inset 1px 1px 0 0 s.color(white),
1px 1px 0 0 #868686,
Copy link

Copilot AI Sep 16, 2025

Choose a reason for hiding this comment

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

Hardcoded color values should be replaced with design system tokens for consistency. Use s.color(gray-300) instead of #bbbbbb and s.color(gray-600) instead of #868686 to match the pattern used in other parts of the codebase.

Suggested change
background: #bbbbbb;
color: s.color(gray-900);
box-shadow:
inset 1px 1px 0 0 s.color(white),
1px 1px 0 0 #868686,
background: s.color(gray-300);
color: s.color(gray-900);
box-shadow:
inset 1px 1px 0 0 s.color(white),
1px 1px 0 0 s.color(gray-600),

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Sep 16, 2025

Choose a reason for hiding this comment

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

Hardcoded color values should be replaced with design system tokens for consistency. Use s.color(gray-300) instead of #bbbbbb and s.color(gray-600) instead of #868686 to match the pattern used in other parts of the codebase.

Suggested change
background: #bbbbbb;
color: s.color(gray-900);
box-shadow:
inset 1px 1px 0 0 s.color(white),
1px 1px 0 0 #868686,
background: s.color(gray-300);
color: s.color(gray-900);
box-shadow:
inset 1px 1px 0 0 s.color(white),
1px 1px 0 0 s.color(gray-600),

Copilot uses AI. Check for mistakes.
0px 0px 0 0 s.color(gray-900);
}

Choose a reason for hiding this comment

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

medium

:active 상태에서 색상 값이 하드코딩되어 있습니다. 디자인 시스템의 일관성을 유지하고 유지보수를 용이하게 하기 위해 s.color() 함수를 사용하여 색상 변수를 참조하는 것이 좋습니다. Button.scss 파일에서 유사한 변경이 이루어진 것을 참고할 수 있습니다.

Suggested change
&:active {
background: #bbbbbb;
color: s.color(gray-900);
box-shadow:
inset 1px 1px 0 0 s.color(white),
1px 1px 0 0 #868686,
0px 0px 0 0 s.color(gray-900);
}
&:active {
background: s.color(gray-300);
color: s.color(gray-900);
box-shadow:
inset 1px 1px 0 0 s.color(white),
1px 1px 0 0 s.color(gray-600),
0px 0px 0 0 s.color(gray-900);
}


@media (max-width: 640px) {
@include s.text-style-extended(md, 400, gray-900);
padding: 5px 12px;
}
}

// placeholder 스타일
.input_placeholder {
position: absolute;
left: 8px;
top: -10px;
@include s.text-style-extended(xs, 400, gray-900);
background: s.color(white);
padding: 0 4px;
pointer-events: none;
transition: 0.2s;
box-shadow:
inset 1px 1px 0 0 s.color(white),
1px 1px 0 0 s.color(gray-600),
0px 0px 0 0 s.color(gray-900);
}

// focus 시 placeholder 스타일
.input_field:focus + .input_placeholder,
.input_field:not(:placeholder-shown) + .input_placeholder {
@include s.text-style-extended(xs, 400, gray-900);
background: s.color(white);
padding: 0 4px;
}

Choose a reason for hiding this comment

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

medium

이 스타일 블록에 정의된 속성들(@include s.text-style-extended, background, padding)은 이미 .input_placeholder 클래스에 동일한 값으로 정의되어 있습니다. 이 중복은 불필요하며, 향후 유지보수 시 혼란을 야기할 수 있습니다. 현재 구현에서는 이 블록 자체가 필요 없어 보입니다.

21 changes: 21 additions & 0 deletions src/componets/Input/Input.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import Input from './Input';

const meta: Meta<typeof Input> = {
title: 'Components/Input',
component: Input,
argTypes: { onClick: { action: 'changed' } }
Copy link

Copilot AI Sep 16, 2025

Choose a reason for hiding this comment

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

The argTypes configuration references 'onClick' but Input components typically use 'onChange' for input events. This should be onChange: { action: 'changed' } to properly capture input change events in Storybook.

Suggested change
argTypes: { onClick: { action: 'changed' } }
argTypes: { onChange: { action: 'changed' } }

Copilot uses AI. Check for mistakes.

Choose a reason for hiding this comment

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

medium

input 컴포넌트의 주된 상호작용은 값의 변경이므로, argTypes에서 onClick 액션 대신 onChange 액션을 추적하는 것이 더 적절합니다. 테스트 코드에서도 onChange를 사용하고 있습니다.

Suggested change
argTypes: { onClick: { action: 'changed' } }
argTypes: { onChange: { action: 'changed' } }

};

export default meta;
type Story = StoryObj<typeof Input>;

export const Primary: Story = {

Choose a reason for hiding this comment

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

medium

스토리 이름을 Primary에서 Default로 변경하여 다른 컴포넌트(Checkbox 등)의 스토리와 명명 규칙의 일관성을 맞추는 것이 좋습니다.

Suggested change
export const Primary: Story = {
export const Default: Story = {

args: {
label: 'Input Default',
id: 'input-default',
placeholder: '여기에 입력하세요'
}

};

26 changes: 26 additions & 0 deletions src/componets/Input/Input.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { test, expect, vi } from 'vitest';

import Input from './Input';

test('Input 렌더링 및 label(플레이스홀더) 확인', () => {
render(<Input label="테스트라벨" />);
expect(screen.getByText('테스트라벨')).toBeInTheDocument();
expect(screen.getByRole('textbox')).toBeInTheDocument();
});

test('Input에 placeholder, value props 전달 확인', () => {
render(<Input label="테스트라벨" placeholder="여기에 입력" value="abc" />);
const input = screen.getByRole('textbox');
expect(input).toHaveAttribute('placeholder', '여기에 입력');
expect(input).toHaveValue('abc');
});

test('Input 입력 시 onChange 이벤트 발생', async () => {
const handleChange = vi.fn();
render(<Input label="테스트라벨" onChange={handleChange} />);
const input = screen.getByRole('textbox');
await userEvent.type(input, 'hello');
expect(handleChange).toHaveBeenCalled();
});
18 changes: 18 additions & 0 deletions src/componets/Input/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import './Input.scss';

export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
}

const Input: React.FC<InputProps> = ({ label, id, ...rest }) => {
return (
<div className="input_wrapper">
<input type="text" className="input_field" id={id} {...rest} />
<label htmlFor={id} className="input_placeholder">{label}</label>
Copy link

Copilot AI Sep 16, 2025

Choose a reason for hiding this comment

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

The component requires an id prop for proper label association but doesn't enforce this requirement. Consider making id required in the InputProps interface or generating a unique ID internally if none is provided.

Copilot uses AI. Check for mistakes.
</div>
);
};

export default Input;

Choose a reason for hiding this comment

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

high

Input 컴포넌트의 재사용성과 접근성을 높이기 위해 몇 가지 개선을 제안합니다.

  1. type prop 추가: inputtype"text"로 하드코딩되어 있어 재사용성이 떨어집니다. type을 prop으로 받아 password, email 등 다양한 타입의 input으로 사용할 수 있도록 변경합니다. 기본값은 'text'로 설정합니다.
  2. useId로 접근성 보장: id prop이 제공되지 않을 경우, labelinput의 연결이 끊어져 접근성이 저하됩니다. React 18의 useId 훅을 사용해 id가 없으면 고유 ID를 자동으로 생성하여 htmlForid를 항상 연결하도록 합니다.
import React, { useId } from 'react';
import './Input.scss';

export interface InputProps
  extends React.InputHTMLAttributes<HTMLInputElement> {
  label: string;
}

const Input: React.FC<InputProps> = ({ label, id: propId, type = 'text', ...rest }) => {
  const reactId = useId();
  const id = propId || reactId;

  return (
    <div className="input_wrapper">
      <input type={type} className="input_field" id={id} {...rest} />
      <label htmlFor={id} className="input_placeholder">{label}</label>
    </div>
  );
};

export default Input;

1 change: 1 addition & 0 deletions src/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading