Skip to content

Commit ea82b00

Browse files
authored
Merge pull request #56 from SOPT-36-NINEDOT/feat/#39/textField
[Feat]: 만다라트 TextField 컴포넌트
2 parents 8a191af + fc04fc3 commit ea82b00

File tree

12 files changed

+523
-0
lines changed

12 files changed

+523
-0
lines changed

public/svg/ic_lock.svg

Lines changed: 3 additions & 0 deletions
Loading

public/svg/ic_textdelete.svg

Lines changed: 4 additions & 0 deletions
Loading

src/assets/svg/IcLock.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { SVGProps } from 'react';
2+
const SvgIcLock = (props: SVGProps<SVGSVGElement>) => (
3+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20" {...props}>
4+
<path
5+
fill="currentColor"
6+
d="M10 2.625c2.577 0 4.75 1.97 4.75 4.5V8.38l.053.004c.672.083 1.197.78 1.197 1.628v5.726c0 .848-.525 1.544-1.197 1.628l-.136.009H5.333l-.136-.009C4.525 17.282 4 16.586 4 15.738v-5.726c0-.87.553-1.58 1.25-1.632V7.125c0-2.53 2.173-4.5 4.75-4.5m-4.48 7.25a.5.5 0 0 0-.02.137v5.726c0 .058.01.103.02.137h8.96c.01-.034.02-.08.02-.137v-5.726a.5.5 0 0 0-.02-.137zM10 4.125c-1.841 0-3.25 1.388-3.25 3v1.25h6.5v-1.25c0-1.612-1.409-3-3.25-3"
7+
/>
8+
</svg>
9+
);
10+
export default SvgIcLock;

src/assets/svg/IcTextdelete.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { SVGProps } from 'react';
2+
const SvgIcTextdelete = (props: SVGProps<SVGSVGElement>) => (
3+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 33" {...props}>
4+
<path fill="#282C33" d="M0 .5h32v32H0z" />
5+
<path
6+
stroke="#E3E4E5"
7+
strokeLinecap="round"
8+
strokeLinejoin="round"
9+
strokeWidth={2.5}
10+
d="M6.666 25.835 25.333 7.168m-18.667 0 18.667 18.667"
11+
/>
12+
</svg>
13+
);
14+
export default SvgIcTextdelete;

src/assets/svg/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
export { default as IcLock } from './IcLock';
2+
export { default as IcTextdelete } from './IcTextdelete';
13
export { default as IcBigNext } from './IcBigNext';
24
export { default as IcCheckboxChecked } from './IcCheckboxChecked';
35
export { default as IcCheckboxDefault } from './IcCheckboxDefault';
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import { style, styleVariants } from '@vanilla-extract/css';
2+
3+
import { colors } from '@/style/token/color.css';
4+
import { fonts } from '@/style/token/typography.css';
5+
6+
// ====== 공통 base 스타일 ======
7+
const bigGoalBase = {
8+
display: 'flex',
9+
alignItems: 'center',
10+
flexShrink: 0,
11+
width: '57.1rem',
12+
height: '8rem',
13+
borderRadius: '12px',
14+
padding: '2rem 3rem',
15+
fontSize: fonts.title01.fontSize,
16+
fontWeight: fonts.title01.fontWeight,
17+
lineHeight: fonts.title01.lineHeight,
18+
};
19+
const subGoalBase = {
20+
display: 'flex',
21+
alignItems: 'center',
22+
flexShrink: 0,
23+
width: '57.1rem',
24+
height: '5.6rem',
25+
borderRadius: '8px',
26+
padding: '1.4rem 2rem',
27+
fontSize: fonts.subtitle03.fontSize,
28+
fontWeight: fonts.subtitle03.fontWeight,
29+
lineHeight: fonts.subtitle03.lineHeight,
30+
};
31+
const todoBase = {
32+
display: 'flex',
33+
alignItems: 'center',
34+
flexShrink: 0,
35+
width: '43.6rem',
36+
height: '5.6rem',
37+
borderRadius: '8px',
38+
padding: '1.4rem 2rem',
39+
fontSize: fonts.subtitle03.fontSize,
40+
fontWeight: fonts.subtitle03.fontWeight,
41+
lineHeight: fonts.subtitle03.lineHeight,
42+
};
43+
44+
export const bigGoalBaseClass = style(bigGoalBase);
45+
export const subGoalBaseClass = style(subGoalBase);
46+
export const todoBaseClass = style(todoBase);
47+
48+
export const inputBase = style({
49+
display: 'block',
50+
width: '100%',
51+
height: '100%',
52+
background: 'transparent',
53+
border: 'none',
54+
outline: 'none',
55+
padding: 0,
56+
color: 'inherit',
57+
});
58+
59+
const makeInputStyle = (font: any) =>
60+
style({
61+
fontFamily: 'Pretendard',
62+
...font,
63+
'::placeholder': {
64+
color: colors.grey6,
65+
fontFamily: 'Pretendard',
66+
...font,
67+
},
68+
});
69+
export const inputBigGoal = makeInputStyle(fonts.title01);
70+
export const inputSubGoal = makeInputStyle(fonts.subtitle03);
71+
export const inputTodo = makeInputStyle(fonts.subtitle03);
72+
73+
export const inputVariants = styleVariants({
74+
bigGoal: [inputBase, inputBigGoal],
75+
subGoal: [inputBase, inputSubGoal],
76+
todo: [inputBase, inputTodo],
77+
});
78+
79+
export const bigGoalVariants = styleVariants({
80+
default: {
81+
...bigGoalBase,
82+
border: '0.3rem solid transparent',
83+
background: colors.grey4,
84+
color: colors.grey6,
85+
textAlign: 'left',
86+
},
87+
clicked: {
88+
...bigGoalBase,
89+
border: `0.3rem solid ${colors.grey5}`,
90+
background: colors.grey3,
91+
color: colors.grey6,
92+
textAlign: 'left',
93+
},
94+
typing: {
95+
...bigGoalBase,
96+
border: `0.3rem solid ${colors.grey5}`,
97+
background: colors.grey3,
98+
color: colors.grey10,
99+
textAlign: 'left',
100+
justifyContent: 'space-between',
101+
},
102+
filled: {
103+
...bigGoalBase,
104+
border: '0.3rem solid transparent',
105+
background: colors.grey4,
106+
color: colors.grey10,
107+
textAlign: 'center',
108+
},
109+
hover: {
110+
...bigGoalBase,
111+
border: '0.3rem solid transparent',
112+
background: colors.grey3,
113+
color: colors.grey6,
114+
textAlign: 'center',
115+
},
116+
});
117+
118+
const createVariantStyles = (baseStyle: object) => ({
119+
default: {
120+
...baseStyle,
121+
border: '2px solid transparent',
122+
background: colors.grey4,
123+
color: colors.grey6,
124+
textAlign: 'center' as const,
125+
},
126+
clicked: {
127+
...baseStyle,
128+
border: `2px solid ${colors.blue06}`,
129+
background: colors.grey3,
130+
color: colors.grey6,
131+
textAlign: 'center' as const,
132+
},
133+
typing: {
134+
...baseStyle,
135+
border: `2px solid ${colors.blue06}`,
136+
background: colors.grey3,
137+
color: colors.grey10,
138+
textAlign: 'left' as const,
139+
justifyContent: 'space-between' as const,
140+
},
141+
filled: {
142+
...baseStyle,
143+
border: '2px solid transparent',
144+
background: colors.grey4,
145+
color: colors.grey10,
146+
textAlign: 'center' as const,
147+
fontWeight: 600,
148+
},
149+
hover: {
150+
...baseStyle,
151+
border: '2px solid transparent',
152+
background: colors.grey3,
153+
color: colors.grey6,
154+
textAlign: 'center' as const,
155+
},
156+
});
157+
158+
export const subGoalVariants = styleVariants(createVariantStyles(subGoalBase));
159+
export const todoVariants = styleVariants(createVariantStyles(todoBase));
160+
161+
const CLEAR_BUTTON_SIZE = '3.2rem';
162+
const CLEAR_BUTTON_SMALL_SIZE = '2.4rem';
163+
export const clearButton = style({
164+
display: 'flex',
165+
alignItems: 'center',
166+
justifyContent: 'center',
167+
width: CLEAR_BUTTON_SIZE,
168+
height: CLEAR_BUTTON_SIZE,
169+
aspectRatio: '1/1',
170+
background: 'none',
171+
border: 'none',
172+
padding: 0,
173+
cursor: 'pointer',
174+
});
175+
export const clearButtonSmall = style({
176+
display: 'flex',
177+
alignItems: 'center',
178+
justifyContent: 'center',
179+
width: CLEAR_BUTTON_SMALL_SIZE,
180+
height: CLEAR_BUTTON_SMALL_SIZE,
181+
background: 'none',
182+
border: 'none',
183+
padding: 0,
184+
cursor: 'pointer',
185+
});
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { useState } from 'react';
2+
import type { Meta, StoryObj } from '@storybook/react-vite';
3+
4+
import MandalartTextField from './MandalartTextField';
5+
6+
const meta: Meta<typeof MandalartTextField> = {
7+
title: 'Common/MandalartTextField',
8+
component: MandalartTextField,
9+
parameters: {
10+
layout: 'centered',
11+
docs: {
12+
description: {
13+
component:
14+
'상위목표(bigGoal), 하위목표(subGoal), 할 일(todo) 등 다양한 텍스트필드 용도로 사용할 수 있습니다. variant별로 스타일/placeholder/최대글자수 등이 자동 적용됩니다.',
15+
},
16+
},
17+
},
18+
tags: ['autodocs'],
19+
argTypes: {
20+
variant: {
21+
control: 'radio',
22+
options: ['bigGoal', 'subGoal', 'todo'],
23+
description: '텍스트필드 타입 (상위목표/하위목표/할 일)',
24+
},
25+
value: { control: 'text', description: '입력값' },
26+
placeholder: { control: 'text', description: 'placeholder (미입력 시 자동 적용)' },
27+
maxLength: { control: 'number', description: '최대 글자수 (상위목표만 30자 제한)' },
28+
disabled: { control: 'boolean', description: '비활성화 여부' },
29+
onChange: { action: 'changed', description: '입력값 변경 이벤트' },
30+
},
31+
};
32+
33+
export default meta;
34+
type Story = StoryObj<typeof meta>;
35+
36+
export const Interactive: Story = {
37+
args: {
38+
variant: 'bigGoal',
39+
value: '',
40+
onChange: () => {},
41+
placeholder: '',
42+
maxLength: 30,
43+
disabled: false,
44+
},
45+
render: (args) => {
46+
const [value, setValue] = useState('');
47+
return <MandalartTextField {...args} value={value} onChange={setValue} />;
48+
},
49+
parameters: {
50+
docs: {
51+
description: {
52+
story: '실제 입력/클리어/포커스 등 모든 상태를 직접 테스트할 수 있습니다.',
53+
},
54+
},
55+
},
56+
};
57+
58+
const variants = [
59+
{ variant: 'bigGoal', label: '상위목표 (bigGoal)' },
60+
{ variant: 'subGoal', label: '하위목표 (subGoal)' },
61+
{ variant: 'todo', label: '할 일 (todo)' },
62+
] as const;
63+
64+
export const Variants: Story = {
65+
render: () => {
66+
const [values, setValues] = useState(['', '', '']);
67+
return (
68+
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem', width: '60rem' }}>
69+
{variants.map(({ variant, label }, idx) => (
70+
<div key={variant}>
71+
<div style={{ marginBottom: '0.5rem', fontWeight: 600 }}>{label}</div>
72+
<MandalartTextField
73+
variant={variant}
74+
value={values[idx]}
75+
onChange={(v) => setValues((vals) => vals.map((val, i) => (i === idx ? v : val)))}
76+
/>
77+
</div>
78+
))}
79+
</div>
80+
);
81+
},
82+
parameters: {
83+
docs: {
84+
description: {
85+
story: 'variant별 스타일/placeholder/최대글자수 자동 적용 예시입니다.',
86+
},
87+
},
88+
},
89+
};
90+
91+
export const Disabled: Story = {
92+
args: {
93+
variant: 'bigGoal',
94+
value: '비활성화 예시',
95+
onChange: () => {},
96+
disabled: true,
97+
},
98+
render: (args) => <MandalartTextField {...args} />,
99+
parameters: {
100+
docs: {
101+
description: {
102+
story: 'disabled=true 시 스타일/동작 예시입니다.',
103+
},
104+
},
105+
},
106+
};

0 commit comments

Comments
 (0)