-
Notifications
You must be signed in to change notification settings - Fork 0
[feat/checkbox] - 체크박스 컴포넌트 추가 + *Card-Image 컴포넌트 삭제* #4
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,13 @@ | ||
| import { render, screen } from "@testing-library/react"; | ||
| import userEvent from "@testing-library/user-event"; | ||
| import { test, expect, vi } from "vitest"; | ||
| import Button from "./Button"; | ||
| import { render, screen } from '@testing-library/react'; | ||
| import userEvent from '@testing-library/user-event'; | ||
| import { test, expect, vi } from 'vitest'; | ||
|
|
||
| test("renders 버튼과 클릭", async () => { | ||
| import Button from './Button'; | ||
|
|
||
| test('renders 버튼과 클릭', async () => { | ||
| const onClick = vi.fn(); | ||
| render(<Button onClick={onClick}>클릭</Button>); | ||
| const btn = screen.getByRole("button", { name: /클릭/i }); | ||
| const btn = screen.getByRole('button', { name: /클릭/i }); | ||
| await userEvent.click(btn); | ||
| expect(onClick).toHaveBeenCalled(); | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,17 +1,18 @@ | ||||||||||||||
| import React from 'react'; | ||||||||||||||
| import './Button.scss'; | ||||||||||||||
|
|
||||||||||||||
| export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & { | ||||||||||||||
| export interface ButtonProps | ||||||||||||||
| extends React.ButtonHTMLAttributes<HTMLButtonElement> { | ||||||||||||||
| variant?: 'default' | 'secondary'; | ||||||||||||||
| }; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const Button: React.FC<ButtonProps> = ({ | ||||||||||||||
| children, | ||||||||||||||
| variant = 'default', | ||||||||||||||
| ...rest | ||||||||||||||
| }) => { | ||||||||||||||
| // xp-btn, secondary 클래스 동적 조합 | ||||||||||||||
| const classNames = ['xp-btn', variant === 'secondary' ? 'secondary' : ''] | ||||||||||||||
| // btn, secondary 클래스 동적 조합 | ||||||||||||||
| const classNames = ['btn', variant === 'secondary' ? 'secondary' : ''] | ||||||||||||||
| .filter(Boolean) | ||||||||||||||
| .join(' '); | ||||||||||||||
|
||||||||||||||
| const classNames = ['btn', variant === 'secondary' ? 'secondary' : ''] | |
| .filter(Boolean) | |
| .join(' '); | |
| const classNames = ['btn', variant !== 'default' && variant] | |
| .filter(Boolean) | |
| .join(' '); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| @use '../../styles/index' as s; | ||
| @use 'sass:map'; | ||
|
|
||
| .checkbox { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 8px; | ||
| position: relative; | ||
| width: fit-content; | ||
| height: fit-content; | ||
| } | ||
|
|
||
| .checkbox_png { | ||
| position: absolute; | ||
| left: 3px; | ||
| top: -5px; | ||
| width: 20px; | ||
| height: 20px; | ||
| background: url('../../assets/Chevron_down.svg') center center no-repeat; | ||
| background-size: contain; | ||
| pointer-events: none; | ||
| opacity: 0; | ||
| transition: opacity 0.2s; | ||
| z-index: 3; | ||
| @media (max-width: 640px) { | ||
| width: 16px; | ||
| height: 16px; | ||
| left: 4px; | ||
| top: -2px; | ||
| } | ||
| } | ||
|
|
||
| .checkbox_input { | ||
| appearance: none; | ||
| width: 18px; | ||
| height: 18px; | ||
| border-radius: 0; | ||
| border: 2px solid s.color(gray-400); | ||
| background: s.color(white); | ||
| cursor: pointer; | ||
| transition: | ||
| background-color 0.2s, | ||
| border-color 0.2s; | ||
|
|
||
| &:checked { | ||
| ~ .checkbox_png { | ||
| opacity: 1; | ||
| } | ||
| } | ||
|
|
||
| @media (max-width: 640px) { | ||
| width: 16px; | ||
| height: 16px; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,24 @@ | ||||||
| import type { Meta, StoryObj } from '@storybook/react-vite'; | ||||||
| import Checkbox from './Checkbox'; | ||||||
|
|
||||||
| const meta: Meta<typeof Checkbox> = { | ||||||
| title: 'Components/Checkbox', | ||||||
| component: Checkbox, | ||||||
| argTypes: { onClick: { action: 'clicked' } } | ||||||
|
||||||
| argTypes: { onClick: { action: 'clicked' } } | |
| argTypes: { onChange: { action: 'changed' } } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { render, screen } from '@testing-library/react'; | ||
| import userEvent from '@testing-library/user-event'; | ||
| import { test, expect, vi } from 'vitest'; | ||
|
|
||
| import Checkbox from './Checkbox'; | ||
|
|
||
| test('체크박스 렌더링과 체크 이벤트', async () => { | ||
| const onChange = vi.fn(); | ||
| render(<Checkbox label="동의" onChange={onChange} />); | ||
| const checkbox = screen.getByRole('checkbox', { name: /동의/i }); | ||
| expect(checkbox).not.toBeChecked(); | ||
| await userEvent.click(checkbox); | ||
| expect(checkbox).toBeChecked(); | ||
| expect(onChange).toHaveBeenCalled(); | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,21 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| import React from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import './Checkbox.scss'; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export interface CheckboxProps | ||||||||||||||||||||||||||||||||||||||||||||||||
| extends React.InputHTMLAttributes<HTMLInputElement> { | ||||||||||||||||||||||||||||||||||||||||||||||||
| label: string; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const Checkbox: React.FC<CheckboxProps> = ({ label, ...rest }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const id = rest.id ?? `checkbox-${label}`; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||
| <label className="checkbox"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <input id={id} type="checkbox" className="checkbox_input" {...rest} /> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="checkbox_png" /> | ||||||||||||||||||||||||||||||||||||||||||||||||
| {label} | ||||||||||||||||||||||||||||||||||||||||||||||||
| </label> | ||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export default Checkbox; | ||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| @use 'sass:map'; | ||
| @use 'variables' as v; | ||
|
|
||
| @function color($name) { | ||
| $val: map.get(v.$colors, $name); | ||
| @if $val == null { | ||
| @error "Unknown color token: #{$name}"; | ||
| } | ||
| @return $val; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,22 +1,36 @@ | ||||||||||||||||||||||||||||||||||||||
| @use 'sass:map'; | ||||||||||||||||||||||||||||||||||||||
| @use 'variables' as v; | ||||||||||||||||||||||||||||||||||||||
| @use 'functions' as f; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // 반응형 미디어 쿼리 믹스인 | ||||||||||||||||||||||||||||||||||||||
| // 반응형 미디어 쿼리 | ||||||||||||||||||||||||||||||||||||||
| @mixin mq($breakpoint) { | ||||||||||||||||||||||||||||||||||||||
| @if map-has-key(v.$breakpoints, $breakpoint) { | ||||||||||||||||||||||||||||||||||||||
| @media (min-width: map-get(v.$breakpoints, $breakpoint)) { | ||||||||||||||||||||||||||||||||||||||
| @if map.has-key(v.$breakpoints, $breakpoint) { | ||||||||||||||||||||||||||||||||||||||
| @media (min-width: map.get(v.$breakpoints, $breakpoint)) { | ||||||||||||||||||||||||||||||||||||||
| @content; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
6
to
12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // 타이포그래피 믹스인 | ||||||||||||||||||||||||||||||||||||||
| // 기본 타이포 | ||||||||||||||||||||||||||||||||||||||
| @mixin text-style($size, $weight: 400) { | ||||||||||||||||||||||||||||||||||||||
| font-size: map-get(map-get(v.$typography, $size), fontSize); | ||||||||||||||||||||||||||||||||||||||
| line-height: map-get(map-get(v.$typography, $size), lineHeight); | ||||||||||||||||||||||||||||||||||||||
| $scale: map.get(v.$typography, $size); | ||||||||||||||||||||||||||||||||||||||
| @if $scale == null { | ||||||||||||||||||||||||||||||||||||||
| @error "Unknown typography scale: #{$size}"; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| font-size: map.get($scale, fontSize); | ||||||||||||||||||||||||||||||||||||||
| line-height: map.get($scale, lineHeight); | ||||||||||||||||||||||||||||||||||||||
| font-weight: $weight; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // flex 믹스인 | ||||||||||||||||||||||||||||||||||||||
| // 확장 타이포 (색상) | ||||||||||||||||||||||||||||||||||||||
| @mixin text-style-extended($size, $weight: 400, $color: null) { | ||||||||||||||||||||||||||||||||||||||
| @include text-style($size, $weight); | ||||||||||||||||||||||||||||||||||||||
| @if $color != null { | ||||||||||||||||||||||||||||||||||||||
| color: f.color($color); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // flex | ||||||||||||||||||||||||||||||||||||||
| @mixin flex-box($justify: center, $align: center, $direction: row) { | ||||||||||||||||||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||||||||||||||||||
| flex-direction: $direction; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| @forward "variables"; | ||
| @forward "mixins"; | ||
| @forward 'variables'; | ||
| @forward 'functions'; | ||
| @forward 'mixins'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
box-shadow의 색상 값이 리팩터링 과정에서 기존과 달라져 버튼의 시각적 디자인이 변경될 수 있습니다. 기존 그림자 색상인#222는s.color(gray-900)(#212121)에 더 가깝지만,s.color(gray-800)(#424242)으로 변경되었습니다. 의도된 변경이 아니라면, 기존 디자인과 일관성을 유지하기 위해gray-900변수를 사용하는 것을 고려해 보세요.