Skip to content

Commit 2380494

Browse files
authored
Merge pull request #183 from yourssu/develop
v2.2.0
2 parents 0e8209a + 256697e commit 2380494

File tree

13 files changed

+409
-79
lines changed

13 files changed

+409
-79
lines changed

iconsAsset/static/IcSearchLine.svg

Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@yourssu/design-system-react",
33
"packageManager": "[email protected]",
44
"private": false,
5-
"version": "2.1.0",
5+
"version": "2.2.0",
66
"description": "Yourssu Design System for React",
77
"keywords": [
88
"yourssu",
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { Canvas, Meta, Controls } from '@storybook/blocks';
2+
import * as SearchBarStories from './SearchBar.stories';
3+
import { SearchBar } from './SearchBar';
4+
5+
<Meta of={SearchBarStories} />
6+
7+
# Searchbar
8+
9+
SearchBar는 사용자가 검색어를 입력하기 위한 필드를 구성하기 위해 사용합니다.<br />
10+
11+
<Canvas of={SearchBarStories.Primary} />
12+
<Controls />
13+
14+
<br />
15+
<br />
16+
17+
## 사용법
18+
19+
```tsx
20+
import { SearchBar } from '@yourssu/design-system-react';
21+
22+
<SearchBar>
23+
<SearchBar.Input placeholder="검색어를 입력해주세요" />
24+
</SearchBar>;
25+
```
26+
27+
<br />
28+
<br />
29+
30+
### SearchBar.ClearButton
31+
32+
SearchBar에 입력된 검색어를 초기화하기 위한 UI 버튼을 제공합니다.<br />
33+
34+
```tsx
35+
import { SearchBar } from '@yourssu/design-system-react';
36+
37+
<SearchBar>
38+
<SearchBar.Input placeholder="검색어를 입력해주세요" />
39+
<SearchBar.ClearButton />
40+
</SearchBar>;
41+
```
42+
43+
실제 초기화 동작을 위해서는 `onClick` 이벤트로 초기화 로직을 구현해야 합니다.
44+
45+
> **왜 내부적으로 입력 값을 초기화하지 않나요?**<br />
46+
> SearchBar 입력 값으로 컴포넌트 외부의 상태를 사용하는 경우, 외부 상태의 초기화 여부를 판단하기 어렵기 때문이에요.
47+
48+
```tsx
49+
const Component = () => {
50+
const [value, setValue] = useState('');
51+
52+
return (
53+
<SearchBar>
54+
<SearchBar.Input
55+
placeholder="검색어를 입력해주세요"
56+
value={value}
57+
onChange={(e) => setValue(e.target.value)}
58+
/>
59+
<SearchBar.ClearButton onClick={() => setValue('')} />
60+
</SearchBar>
61+
);
62+
};
63+
```
64+
65+
<Canvas of={SearchBarStories.WithClearButton} withSource="none" />
66+
67+
<br />
68+
<br />
69+
70+
### 가변 너비
71+
72+
SearchBar 컴포넌트는 가변 너비 컴포넌트입니다.
73+
부모 컴포넌트의 너비에 따라 자동으로 너비가 조절됩니다.
74+
75+
```tsx
76+
import { SearchBar } from '@yourssu/design-system-react';
77+
78+
<div style={{ width: '800px' }}>
79+
<SearchBar>
80+
<SearchBar.Input placeholder="검색어를 입력해주세요" />
81+
</SearchBar>
82+
</div>;
83+
```
84+
85+
<Canvas of={SearchBarStories.Width} withSource="none" />
86+
87+
<br />
88+
<br />
89+
90+
### Form 제출
91+
92+
SearchBar 컴포넌트는 HTML form 요소를 기반으로 구성되어 있습니다.
93+
올바른 입력 값 제출을 위해서 `SearchBar` 컴포넌트의 `onSubmit` 이벤트를 사용해주세요.
94+
95+
> **`SearchBar.Input` 컴포넌트에 keydown 이벤트를 통한 폼 제출은 지양해주세요.**
96+
97+
```tsx
98+
import { SearchBar } from '@yourssu/design-system-react';
99+
100+
<SearchBar onSubmit={() => alert('제출')}>
101+
<SearchBar.Input placeholder="검색어를 입력해주세요" />
102+
</SearchBar>;
103+
```
104+
105+
<Canvas of={SearchBarStories.Form} withSource="none" />
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useState } from 'react';
2+
3+
import type { Meta, StoryObj } from '@storybook/react';
4+
5+
import { SearchBar } from './SearchBar';
6+
7+
const meta: Meta<typeof SearchBar> = {
8+
title: 'Components/SearchBar',
9+
component: SearchBar,
10+
parameters: {
11+
layout: 'centered',
12+
},
13+
};
14+
15+
export default meta;
16+
type Story = StoryObj<typeof SearchBar>;
17+
18+
const WithClearButtonComponent = () => {
19+
const [value, setValue] = useState('');
20+
21+
return (
22+
<SearchBar>
23+
<SearchBar.Input
24+
placeholder="검색어를 입력해주세요"
25+
value={value}
26+
onChange={(e) => setValue(e.target.value)}
27+
/>
28+
<SearchBar.ClearButton onClick={() => setValue('')} />
29+
</SearchBar>
30+
);
31+
};
32+
33+
export const Primary: Story = {
34+
render: () => (
35+
<SearchBar>
36+
<SearchBar.Input placeholder="검색어를 입력해주세요" />
37+
</SearchBar>
38+
),
39+
};
40+
41+
export const WithClearButton: Story = {
42+
render: WithClearButtonComponent,
43+
};
44+
45+
export const Width: Story = {
46+
render: () => (
47+
<div style={{ width: '800px' }}>
48+
<SearchBar>
49+
<SearchBar.Input placeholder="검색어를 입력해주세요" />
50+
</SearchBar>
51+
</div>
52+
),
53+
};
54+
55+
export const Form: Story = {
56+
render: () => (
57+
<SearchBar onSubmit={() => alert('제출')}>
58+
<SearchBar.Input placeholder="검색어를 입력해주세요" />
59+
</SearchBar>
60+
),
61+
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { styled } from 'styled-components';
2+
3+
export const StyledContainer = styled.form`
4+
position: relative;
5+
6+
width: 100%;
7+
height: 48px;
8+
border-radius: ${({ theme }) => theme.semantic.radius.m}px;
9+
background-color: ${({ theme }) => theme.semantic.color.bgBasicLight};
10+
11+
display: flex;
12+
align-items: center;
13+
gap: 8px;
14+
15+
padding: 12px;
16+
17+
&:has(.searchbar-close-button) {
18+
padding-right: 44px;
19+
}
20+
21+
.searchbar-icon {
22+
color: ${({ theme }) => theme.semantic.color.iconBasicTertiary};
23+
width: 20px;
24+
height: 20px;
25+
}
26+
`;
27+
28+
export const StyledInput = styled.input`
29+
all: unset;
30+
${({ theme }) => theme.typo.B1_Rg_16};
31+
32+
width: 100%;
33+
color: ${({ theme }) => theme.semantic.color.textBasicPrimary};
34+
35+
&::placeholder {
36+
color: ${({ theme }) => theme.semantic.color.textBasicTertiary};
37+
}
38+
`;
39+
40+
export const StyledClearButton = styled.button`
41+
all: unset;
42+
cursor: pointer;
43+
44+
width: 20px;
45+
height: 20px;
46+
47+
position: absolute;
48+
top: 50%;
49+
right: 12px;
50+
transform: translateY(-50%);
51+
52+
svg {
53+
fill: ${({ theme }) => theme.semantic.color.iconBasicTertiary};
54+
width: 100%;
55+
height: 100%;
56+
}
57+
`;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { forwardRef } from 'react';
2+
3+
import { IcCancelFilled, IcSearchLine } from '@/style';
4+
5+
import { StyledClearButton, StyledContainer, StyledInput } from './SearchBar.style';
6+
7+
const SearchBarClearButton = forwardRef<
8+
HTMLButtonElement,
9+
React.ButtonHTMLAttributes<HTMLButtonElement>
10+
>(({ className, ...props }, ref) => {
11+
return (
12+
<StyledClearButton ref={ref} className={`searchbar-close-button ${className}`} {...props}>
13+
<IcCancelFilled />
14+
</StyledClearButton>
15+
);
16+
});
17+
18+
const SearchBarInput = forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
19+
(props, ref) => {
20+
return <StyledInput ref={ref} {...props} />;
21+
}
22+
);
23+
24+
SearchBarInput.displayName = 'SearchBarInput';
25+
26+
export const SearchBar = Object.assign(
27+
forwardRef<HTMLFormElement, React.FormHTMLAttributes<HTMLFormElement>>((props, ref) => {
28+
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
29+
e.preventDefault();
30+
props.onSubmit?.(e);
31+
};
32+
33+
return (
34+
<StyledContainer ref={ref} {...props} onSubmit={onSubmit}>
35+
<IcSearchLine className="searchbar-icon" size="20px" />
36+
{props.children}
37+
</StyledContainer>
38+
);
39+
}),
40+
{
41+
Input: SearchBarInput,
42+
ClearButton: SearchBarClearButton,
43+
}
44+
);

src/components/SearchBar/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { SearchBar } from './SearchBar';

src/components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,5 @@ export type * from './Snackbar';
6161

6262
export { LoadingIndicator } from './LoadingIndicator';
6363
export type { LoadingIndicatorProps } from './LoadingIndicator';
64+
65+
export { SearchBar } from './SearchBar';
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* 이 파일은 iconsAsset/convert.js에 의해 자동 생성되었습니다.
3+
* 직접 수정하는 대신 iconsAsset/convert.js를 수정하세요.
4+
*/
5+
6+
import { memo, forwardRef } from 'react';
7+
8+
import { IconBase } from '../icon.base';
9+
import { IconProps } from '../icon.type';
10+
11+
export const IcSearchFilled = memo(
12+
forwardRef<SVGSVGElement, IconProps>((props, ref) => (
13+
<IconBase ref={ref} viewBox="0 0 24 24" {...props}>
14+
<path
15+
d="M19.9382 7.66937C18.512 4.24473 15.1704 2.01008 11.4607 2.00017C7.45562 1.9761 3.89312 4.54041 2.64466 8.34596C1.39619 12.1515 2.74734 16.3278 5.98831 18.6809C9.22928 21.034 13.6187 21.0256 16.8507 18.6602L19.7207 21.5302C20.0135 21.8226 20.4878 21.8226 20.7807 21.5302C21.0731 21.2374 21.0731 20.763 20.7807 20.4702L17.9907 17.6802C20.5968 15.04 21.3644 11.094 19.9382 7.66937ZM9.13996 8.04179C8.89594 8.62562 8.32342 9.00421 7.69066 9.00017C6.83462 9.00017 6.14066 8.30621 6.14066 7.45017C6.13662 6.81741 6.51521 6.24488 7.09904 6.00086C7.68287 5.75684 8.35627 5.88967 8.80371 6.33711C9.25115 6.78455 9.38398 7.45796 9.13996 8.04179Z"
16+
fillRule="evenodd"
17+
clipRule="evenodd"
18+
xmlns="http://www.w3.org/2000/svg"
19+
/>
20+
</IconBase>
21+
))
22+
);
23+
24+
IcSearchFilled.displayName = 'IcSearchFilled';

0 commit comments

Comments
 (0)