Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions apps/web/src/shared/constants/member-role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ export const MEMBER_ROLE_LABEL = {
} as const;

export const ROLE_CHIP_TYPE_MAP = {
[MEMBER_ROLE.LEADER]: 'skyblue',
[MEMBER_ROLE.MEMBER]: 'green',
[MEMBER_ROLE.LEADER]: 'primary',
[MEMBER_ROLE.MEMBER]: 'skyblue',
} as const;
6 changes: 3 additions & 3 deletions packages/design-system/src/components/chip/Chip.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const meta = {
argTypes: {
type: {
control: 'select',
options: ['primary', 'secondary', 'opacity', 'gray', 'green'],
options: ['primary', 'secondary', 'skyblue', 'gray', 'green'],
},
},
} satisfies Meta<typeof Chip>;
Expand All @@ -33,9 +33,9 @@ export const Secondary: Story = {
},
};

export const Opacity: Story = {
export const Skyblue: Story = {
args: {
type: 'opacity',
type: 'skyblue',
Comment thread
yummjin marked this conversation as resolved.
children: '리더',
},
};
Expand Down
46 changes: 46 additions & 0 deletions packages/design-system/src/components/input/Input.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import { typography } from '../../shared/styles';
export type InputVariants = NonNullable<Parameters<typeof inputContainer>[0]>;
export type InputState = InputVariants['state'];

export const inputWrapper = style({
display: 'flex',
flexDirection: 'column',
gap: '4px',
});

export const inputContainer = recipe({
base: {
display: 'flex',
Expand Down Expand Up @@ -71,6 +77,26 @@ globalStyle(`${input}[type="number"]::-webkit-outer-spin-button`, {
margin: 0,
});

export const removeButton = style({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
width: '16px',
height: '16px',
borderRadius: '100%',
border: 'none',
background: 'none',
cursor: 'pointer',
backgroundColor: vars.colors.gray20,
});

export const removeButtonIcon = style({
width: '10px',
height: '10px',
color: vars.colors.white,
});

export const iconSlot = style({
display: 'flex',
alignItems: 'center',
Expand All @@ -80,3 +106,23 @@ export const iconSlot = style({
width: 'auto',
height: 'auto',
});

export const inputDescriptionWrapper = style({
display: 'grid',
gridTemplateColumns: '1fr auto',
alignItems: 'center',
gap: '12px',
});

export const inputDescriptionWarning = style({
color: vars.colors.error,
...vars.typography.body.b4,
textWrap: 'wrap',
});

export const inputDescription = style({
color: vars.colors.gray50,
...vars.typography.body.b3,
textWrap: 'nowrap',
textAlign: 'right',
});
55 changes: 55 additions & 0 deletions packages/design-system/src/components/input/Input.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,45 @@ export const WithIcon: Story = {
],
};

export const WithCounter: Story = {
args: {
placeholder: '닉네임을 입력해주세요',
children: (
<Input.Description
left="닉네임은 20자 이내로 입력해주세요."
right="0/20"
/>
),
},
decorators: [
(Story) => (
<div style={{ width: '335px' }}>
<Story />
</div>
),
],
};

export const WithRemoveButton: Story = {
args: {
placeholder: '닉네임을 입력해주세요',
onRemove: () => alert('onRemove'),
children: (
<Input.Description
left="닉네임은 20자 이내로 입력해주세요."
right="0/20"
/>
),
},
decorators: [
(Story) => (
<div style={{ width: '335px' }}>
<Story />
</div>
),
],
};

export const Error: Story = {
args: {
placeholder: '크루 이름을 입력해주세요',
Expand All @@ -69,6 +108,22 @@ export const Error: Story = {
],
};

export const ErrorDescription: Story = {
args: {
placeholder: '크루 이름을 입력해주세요',
state: 'error',
defaultValue: 'AZT123',
children: <Input.Description left="이 필드는 필수 입력 항목입니다." />,
},
decorators: [
(Story) => (
<div style={{ width: '335px' }}>
<Story />
</div>
),
],
};

export const Disabled: Story = {
args: {
placeholder: '크루 이름을 입력해주세요',
Expand Down
63 changes: 51 additions & 12 deletions packages/design-system/src/components/input/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import type { InputHTMLAttributes, ReactNode } from 'react';
import { inputContainer, input, iconSlot, type InputState } from './Input.css';
import {
inputContainer,
input,
iconSlot,
removeButton,
type InputState,
inputWrapper,
inputDescriptionWrapper,
inputDescriptionWarning,
inputDescription,
removeButtonIcon,
} from './Input.css';
import XIcon from '../icon/XIcon';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

❌ 위반 사항

  • [코드 컨벤션] 상대 경로(../icon/XIcon)를 사용하여 컴포넌트를 임포트하고 있습니다.

🔧 개선 제안

  • 레포지토리 스타일 가이드(131라인)의 '절대경로 import' 규칙에 따라 프로젝트에 설정된 경로 별칭(Alias)을 사용하여 절대 경로로 수정해 주세요.
References
  1. 모든 import는 절대 경로를 사용해야 합니다. (link)

import { clsx } from 'clsx';

export interface InputProps extends Omit<
Expand All @@ -8,14 +20,22 @@ export interface InputProps extends Omit<
> {
state?: Exclude<InputState, 'disabled'>;
icon?: ReactNode;
onRemove?: () => void;
}

interface InputDescriptionProps {
left?: ReactNode;
right?: ReactNode;
}

export function Input({
state = 'default',
icon,
onRemove,
type = 'text',
className,
disabled,
children,
...props
}: InputProps) {
const currentState = disabled ? 'disabled' : state;
Expand All @@ -34,17 +54,36 @@ export function Input({
}

return (
<div className={clsx(inputContainer({ state: currentState }), className)}>
{icon && <div className={iconSlot}>{icon}</div>}
<input
style={{ backgroundColor: 'inherit' }}
type={type}
className={input}
disabled={disabled}
autoComplete="off"
{...autoInputProps}
{...props}
/>
<div className={inputWrapper}>
<div className={clsx(inputContainer({ state: currentState }), className)}>
{icon && <div className={iconSlot}>{icon}</div>}
<input
style={{ backgroundColor: 'inherit' }}
type={type}
className={input}
disabled={disabled}
autoComplete="off"
{...autoInputProps}
{...props}
/>
{onRemove && (
<button type="button" className={removeButton} onClick={onRemove}>
<XIcon size={16} className={removeButtonIcon} />
</button>
Comment on lines +70 to +72
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

❌ 위반 사항

  • [접근성] 삭제 버튼(button)에 aria-label이 누락되어 있어 스크린 리더 사용자가 버튼의 용도를 파악할 수 없습니다.
  • [로직 오류] Input 컴포넌트가 disabled 상태일 때도 삭제 버튼이 활성화되어 클릭이 가능한 상태입니다.

🔧 개선 제안

  • 버튼에 aria-label="삭제" 속성을 추가하고, disabled 상태를 버튼에도 적용하여 일관된 사용자 경험을 제공해야 합니다.
          <button
            type="button"
            className={removeButton}
            onClick={onRemove}
            disabled={disabled}
            aria-label="삭제"
          >
            <XIcon size={16} className={removeButtonIcon} />
          </button>
References
  1. 대화형 요소는 스크린 리더 사용자를 위한 적절한 레이블을 가져야 하며, 부모 컴포넌트의 비활성화 상태를 반영해야 합니다.

)}
</div>
{children}
</div>
);
}

function InputDescription({ left, right }: InputDescriptionProps) {
return (
<p className={inputDescriptionWrapper}>
{left && <span className={inputDescriptionWarning}>{left}</span>}
Comment thread
yummjin marked this conversation as resolved.
{right && <span className={inputDescription}>{right}</span>}
</p>
);
}

Input.Description = InputDescription;
20 changes: 10 additions & 10 deletions packages/design-system/src/shared/styles/theme.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { createGlobalTheme } from '@vanilla-extract/css';

export const vars = createGlobalTheme(':root', {
colors: {
gray10: '#E6E7E9',
gray20: '#CDCFD2',
gray30: '#B4B7BC',
gray40: '#9B9FA6',
gray50: '#81878F',
gray60: '#686F79',
gray70: '#4F5763',
gray80: '#363F4D',
gray90: '#1D2736',
gray10: '#F2F4F6',
gray20: '#E5E8EB',
gray30: '#D1D6DC',
gray40: '#AFB8C1',
gray50: '#8B95A1',
gray60: '#6B7684',
gray70: '#4E5968',
gray80: '#333D4B',
gray90: '#191F28',
gray100: '#040F20',
blue10: '#D1E5FE',
blue20: '#B8D4FF',
Expand All @@ -23,7 +23,7 @@ export const vars = createGlobalTheme(':root', {
blue90: '#003483',
blue100: '#00245A',
background: '#FFFFFF',
background_sub: '#F9FAFB',
background_sub: '#F5F6FA',
secondary: '#D1F801',
error: '#FF4545',
green10: '#BAEBC1',
Expand Down
Loading