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
26 changes: 26 additions & 0 deletions src/assets/check-mark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 29 additions & 30 deletions src/componets/Button/Button.scss
Original file line number Diff line number Diff line change
@@ -1,37 +1,36 @@
@use '../../styles/index' as v;
@use '../../styles/index' as s;
@use 'sass:map';

.xp-btn {
.btn {
background: #d4d4d4;
color: #5a5a5a;
@include s.text-style-extended(lg, 400, gray-900);
border: none;
font-family: 'Tahoma', 'Verdana', sans-serif;
font-size: 16px;
padding: 6px 18px;
cursor: pointer;
box-shadow:
inset 1px 1px 0 0 #fff,
1px 1px 0 0 #868686,
2px 2px 0 0 #222;
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 {
background: #e5e5e5;
color: #222;
background: s.color(gray-300);
color: s.color(gray-900);
box-shadow:
inset 1px 1px 0 0 #fff,
2px 2px 0 0 #222;
inset 1px 1px 0 0 s.color(white),
2px 2px 0 0 s.color(gray-900);
}
&:active {
background: #bbbbbb;
color: #222;
color: s.color(gray-900);
box-shadow:
inset 1px 1px 0 0 #fff,
inset 1px 1px 0 0 s.color(white),
1px 1px 0 0 #868686,
0px 0px 0 0 #222;
0px 0px 0 0 s.color(gray-900);
}

@media (max-width: 640px) {
Expand All @@ -41,30 +40,30 @@
}

// 빨강 버튼 (secondary)
.xp-btn.secondary {
background: #d43c3c;
color: #fff;
.btn.secondary {
background: s.color(red_btn);
color: s.color(white);
border: none;
box-shadow:
inset 1px 1px 0 0 #fff,
1px 1px 0 0 #b92a2a,
2px 2px 0 0 #7a1818;
inset 1px 1px 0 0 s.color(white),
1px 1px 0 0 s.color(red_btn_hover),
2px 2px 0 0 s.color(red_btn_shadow);

&:hover,
&:focus {
background: #b92a2a;
color: #fff;
background: s.color(red_btn_hover);
color: s.color(white);
box-shadow:
inset 1px 1px 0 0 #fff,
2px 2px 0 0 #7a1818;
inset 1px 1px 0 0 s.color(white),
2px 2px 0 0 s.color(red_btn_shadow);
}
&:active {
background: #a12222;
color: #fff;
background: s.color(red_btn_active);
color: s.color(white);
box-shadow:
inset 1px 1px 0 0 #fff,
1px 1px 0 0 #b92a2a,
0px 0px 0 0 #7a1818;
inset 1px 1px 0 0 s.color(white),
1px 1px 0 0 s.color(red_btn_hover),
0px 0px 0 0 s.color(red_btn_shadow);
}

@media (max-width: 640px) {
Expand Down
10 changes: 9 additions & 1 deletion src/componets/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,16 @@ type Story = StoryObj<typeof Button>;

export const Primary: Story = {
args: {
children: '클릭',
children: 'Button',
variant: 'default',
color: 'rgba(255, 0, 0, 1)'
}
};

export const Secondary: Story = {
args: {
children: 'Button',
variant: 'secondary',
color: 'rgba(255, 0, 0, 1)'
}
};
13 changes: 7 additions & 6 deletions src/componets/Button/Button.test.tsx
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();
});
9 changes: 5 additions & 4 deletions src/componets/Button/Button.tsx
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 !== 'default' && variant]
.filter(Boolean)
.join(' ');

Expand Down
55 changes: 55 additions & 0 deletions src/componets/Checkbox/Checkbox.scss
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/check-mark.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;
}
}
24 changes: 24 additions & 0 deletions src/componets/Checkbox/Checkbox.stories.tsx
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: 'changed' } }
};

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

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

export const Secondary: Story = {
args: {
label: 'Checkbox Checked',
checked: true
}
};
15 changes: 15 additions & 0 deletions src/componets/Checkbox/Checkbox.test.tsx
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();
});
21 changes: 21 additions & 0 deletions src/componets/Checkbox/Checkbox.tsx
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 reactId = React.useId();
const id = rest.id ?? reactId;

return (
<label htmlFor={id} className="checkbox">
<input id={id} type="checkbox" className="checkbox_input" {...rest} />
<span className="checkbox_png" />
{label}
</label>
);
};
export default Checkbox;
10 changes: 10 additions & 0 deletions src/styles/_functions.scss
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;
}
28 changes: 21 additions & 7 deletions src/styles/_mixins.scss
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

Choose a reason for hiding this comment

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

medium

mq 믹스인은 존재하지 않는 breakpoint 값을 받았을 때 아무런 동작 없이 조용히 넘어갑니다. 이는 개발 중 실수를 알아차리기 어렵게 만들 수 있습니다. text-style 믹스인처럼, 유효하지 않은 값이 전달되었을 때 @error를 발생시켜 문제를 즉시 인지할 수 있도록 개선하는 것이 좋습니다.

Suggested change
@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;
}
}
}
@mixin mq($breakpoint) {
@if map.has-key(v.$breakpoints, $breakpoint) {
@media (min-width: map.get(v.$breakpoints, $breakpoint)) {
@content;
}
} @else {
@error "Unknown breakpoint: #{$breakpoint}";
}
}


// 타이포그래피 믹스인
// 기본 타이포
@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;
Expand Down
9 changes: 8 additions & 1 deletion src/styles/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,16 @@ $colors: (
gray-300: #e0e0e0,
gray-400: #bdbdbd,
gray-500: #9e9e9e,
gray-600: #757575,
gray-700: #616161,
gray-800: #424242,
gray-900: #212121,

// 빨강 버튼
red_btn: #d43c3c,
red_btn_hover: #b92a2a
red_btn_active: #a12222,
red_btn_hover: #b92a2a,
red_btn_shadow: #7a1818
);

// 타이포그래피 변수
Expand Down
5 changes: 3 additions & 2 deletions src/styles/index.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@forward "variables";
@forward "mixins";
@forward 'variables';
@forward 'functions';
@forward 'mixins';