teachable-design-system 0.1.15 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +108 -78
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.esm.js +108 -78
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/Button/Button.d.ts.map +1 -1
- package/dist/types/components/Button/index.d.ts +1 -1
- package/dist/types/components/Button/index.d.ts.map +1 -1
- package/dist/types/components/Input/Input.d.ts +2 -2
- package/dist/types/components/Input/Input.d.ts.map +1 -1
- package/dist/types/components/Input/index.d.ts.map +1 -1
- package/dist/types/components/Input/style.d.ts +34 -4
- package/dist/types/components/Input/style.d.ts.map +1 -1
- package/dist/types/types/input.types.d.ts +6 -2
- package/dist/types/types/input.types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Button/Button.stories.tsx +1 -0
- package/src/components/Button/Button.tsx +1 -0
- package/src/components/Button/index.ts +1 -1
- package/src/components/Input/Input.stories.tsx +242 -113
- package/src/components/Input/Input.tsx +54 -27
- package/src/components/Input/index.ts +1 -1
- package/src/components/Input/style.ts +127 -83
- package/src/types/input.types.ts +12 -8
|
@@ -1,38 +1,50 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from
|
|
2
|
-
import { Input } from
|
|
3
|
-
import React, { useState } from
|
|
4
|
-
import { InputProps } from
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Input } from "./Input";
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
import { InputProps } from "../../types/input.types";
|
|
5
5
|
|
|
6
6
|
const meta: Meta<InputProps> = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
title: "Components/Input",
|
|
8
|
+
component: Input,
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: "centered",
|
|
11
|
+
},
|
|
12
|
+
tags: ["autodocs"],
|
|
13
|
+
argTypes: {
|
|
14
|
+
width: {
|
|
15
|
+
control: "text",
|
|
16
|
+
description: "인풋의 너비 (예: 100%, 300px, 500px)",
|
|
11
17
|
},
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
control: 'select',
|
|
16
|
-
options: ['small', 'medium', 'large'],
|
|
17
|
-
description: '인풋의 크기',
|
|
18
|
-
},
|
|
19
|
-
label: {
|
|
20
|
-
control: 'boolean',
|
|
21
|
-
description: 'label 표시 여부',
|
|
22
|
-
},
|
|
23
|
-
labelText: {
|
|
24
|
-
control: 'text',
|
|
25
|
-
description: 'label에 표시될 텍스트',
|
|
26
|
-
},
|
|
27
|
-
placeholder: {
|
|
28
|
-
control: 'text',
|
|
29
|
-
description: 'placeholder 텍스트',
|
|
30
|
-
},
|
|
31
|
-
disabled: {
|
|
32
|
-
control: 'boolean',
|
|
33
|
-
description: '비활성화 여부',
|
|
34
|
-
},
|
|
18
|
+
height: {
|
|
19
|
+
control: "text",
|
|
20
|
+
description: "인풋의 높이 (예: 40px, 50px, 60px)",
|
|
35
21
|
},
|
|
22
|
+
size: {
|
|
23
|
+
control: "select",
|
|
24
|
+
options: ["small", "medium", "large"],
|
|
25
|
+
description: "인풋의 크기",
|
|
26
|
+
},
|
|
27
|
+
label: {
|
|
28
|
+
control: "boolean",
|
|
29
|
+
description: "label 표시 여부",
|
|
30
|
+
},
|
|
31
|
+
labelText: {
|
|
32
|
+
control: "text",
|
|
33
|
+
description: "label에 표시될 텍스트",
|
|
34
|
+
},
|
|
35
|
+
placeholder: {
|
|
36
|
+
control: "text",
|
|
37
|
+
description: "placeholder 텍스트",
|
|
38
|
+
},
|
|
39
|
+
disabled: {
|
|
40
|
+
control: "boolean",
|
|
41
|
+
description: "비활성화 여부",
|
|
42
|
+
},
|
|
43
|
+
isPassword: {
|
|
44
|
+
control: "boolean",
|
|
45
|
+
description: "비밀번호 입력 여부",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
36
48
|
} satisfies Meta<typeof Input>;
|
|
37
49
|
|
|
38
50
|
export default meta;
|
|
@@ -40,113 +52,230 @@ type Story = StoryObj<typeof meta>;
|
|
|
40
52
|
|
|
41
53
|
// 기본 스토리
|
|
42
54
|
export const Default: Story = {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
args: {
|
|
56
|
+
size: "medium",
|
|
57
|
+
label: true,
|
|
58
|
+
labelText: "Label",
|
|
59
|
+
placeholder: "Enter text...",
|
|
60
|
+
disabled: false,
|
|
61
|
+
},
|
|
50
62
|
};
|
|
51
63
|
|
|
52
64
|
// Small 사이즈
|
|
53
65
|
export const Small: Story = {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
args: {
|
|
67
|
+
size: "small",
|
|
68
|
+
label: true,
|
|
69
|
+
labelText: "Small Input",
|
|
70
|
+
placeholder: "Small size input",
|
|
71
|
+
},
|
|
60
72
|
};
|
|
61
73
|
|
|
62
74
|
// Medium 사이즈
|
|
63
75
|
export const Medium: Story = {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
args: {
|
|
77
|
+
size: "medium",
|
|
78
|
+
label: true,
|
|
79
|
+
labelText: "Medium Input",
|
|
80
|
+
placeholder: "Medium size input",
|
|
81
|
+
},
|
|
70
82
|
};
|
|
71
83
|
|
|
72
84
|
// Large 사이즈
|
|
73
85
|
export const Large: Story = {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
86
|
+
args: {
|
|
87
|
+
size: "large",
|
|
88
|
+
label: true,
|
|
89
|
+
labelText: "Large Input",
|
|
90
|
+
placeholder: "Large size input",
|
|
91
|
+
},
|
|
80
92
|
};
|
|
81
93
|
|
|
82
94
|
// Label 없음
|
|
83
95
|
export const WithoutLabel: Story = {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
96
|
+
args: {
|
|
97
|
+
size: "medium",
|
|
98
|
+
label: false,
|
|
99
|
+
placeholder: "Input without label",
|
|
100
|
+
},
|
|
89
101
|
};
|
|
90
102
|
|
|
91
103
|
// Disabled 상태
|
|
92
104
|
export const Disabled: Story = {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
105
|
+
args: {
|
|
106
|
+
size: "medium",
|
|
107
|
+
label: true,
|
|
108
|
+
labelText: "Disabled Input",
|
|
109
|
+
placeholder: "This input is disabled",
|
|
110
|
+
disabled: true,
|
|
111
|
+
},
|
|
100
112
|
};
|
|
101
113
|
|
|
102
114
|
// 인터랙티브 예제 (Controlled Input)
|
|
103
115
|
export const Controlled: Story = {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
116
|
+
render: (args) => {
|
|
117
|
+
const [value, setValue] = useState("");
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<div style={{ width: "300px" }}>
|
|
121
|
+
<Input
|
|
122
|
+
{...args}
|
|
123
|
+
value={value}
|
|
124
|
+
onChange={(e) => setValue(e.target.value)}
|
|
125
|
+
/>
|
|
126
|
+
<p style={{ marginTop: "10px", fontSize: "14px", color: "#666" }}>
|
|
127
|
+
Current value: {value || "(empty)"}
|
|
128
|
+
</p>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
},
|
|
132
|
+
args: {
|
|
133
|
+
size: "medium",
|
|
134
|
+
label: true,
|
|
135
|
+
labelText: "Controlled Input",
|
|
136
|
+
placeholder: "Type something...",
|
|
137
|
+
},
|
|
126
138
|
};
|
|
127
139
|
|
|
128
140
|
// 모든 사이즈 비교
|
|
129
141
|
export const AllSizes: Story = {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
142
|
+
render: () => (
|
|
143
|
+
<div
|
|
144
|
+
style={{
|
|
145
|
+
display: "flex",
|
|
146
|
+
flexDirection: "column",
|
|
147
|
+
gap: "20px",
|
|
148
|
+
width: "300px",
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
<Input
|
|
152
|
+
size="small"
|
|
153
|
+
label={true}
|
|
154
|
+
labelText="Small"
|
|
155
|
+
placeholder="Small input"
|
|
156
|
+
/>
|
|
157
|
+
<Input
|
|
158
|
+
size="medium"
|
|
159
|
+
label={true}
|
|
160
|
+
labelText="Medium"
|
|
161
|
+
placeholder="Medium input"
|
|
162
|
+
/>
|
|
163
|
+
<Input
|
|
164
|
+
size="large"
|
|
165
|
+
label={true}
|
|
166
|
+
labelText="Large"
|
|
167
|
+
placeholder="Large input"
|
|
168
|
+
/>
|
|
169
|
+
</div>
|
|
170
|
+
),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// 비밀번호 입력
|
|
174
|
+
export const Password: Story = {
|
|
175
|
+
args: {
|
|
176
|
+
size: "medium",
|
|
177
|
+
label: true,
|
|
178
|
+
labelText: "Password",
|
|
179
|
+
placeholder: "Enter your password",
|
|
180
|
+
isPassword: true,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// 비밀번호 입력 (Small)
|
|
185
|
+
export const PasswordSmall: Story = {
|
|
186
|
+
args: {
|
|
187
|
+
size: "small",
|
|
188
|
+
label: true,
|
|
189
|
+
labelText: "Password",
|
|
190
|
+
placeholder: "Enter password",
|
|
191
|
+
isPassword: true,
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// 비밀번호 입력 (Large)
|
|
196
|
+
export const PasswordLarge: Story = {
|
|
197
|
+
args: {
|
|
198
|
+
size: "large",
|
|
199
|
+
label: true,
|
|
200
|
+
labelText: "Password",
|
|
201
|
+
placeholder: "Enter your password",
|
|
202
|
+
isPassword: true,
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// 비밀번호 입력 인터랙티브
|
|
207
|
+
export const PasswordControlled: Story = {
|
|
208
|
+
render: (args) => {
|
|
209
|
+
const [value, setValue] = useState("");
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<div style={{ width: "300px" }}>
|
|
213
|
+
<Input
|
|
214
|
+
{...args}
|
|
215
|
+
value={value}
|
|
216
|
+
onChange={(e) => setValue(e.target.value)}
|
|
217
|
+
isPassword={true}
|
|
218
|
+
/>
|
|
219
|
+
<p style={{ marginTop: "10px", fontSize: "14px", color: "#666" }}>
|
|
220
|
+
Password length: {value.length}
|
|
221
|
+
</p>
|
|
222
|
+
</div>
|
|
223
|
+
);
|
|
224
|
+
},
|
|
225
|
+
args: {
|
|
226
|
+
size: "medium",
|
|
227
|
+
label: true,
|
|
228
|
+
labelText: "Create Password",
|
|
229
|
+
placeholder: "Enter a strong password",
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// 커스텀 너비
|
|
234
|
+
export const CustomWidth: Story = {
|
|
235
|
+
args: {
|
|
236
|
+
width: "500px",
|
|
237
|
+
size: "medium",
|
|
238
|
+
label: true,
|
|
239
|
+
labelText: "Custom Width Input",
|
|
240
|
+
placeholder: "Width: 500px",
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// 커스텀 높이
|
|
245
|
+
export const CustomHeight: Story = {
|
|
246
|
+
args: {
|
|
247
|
+
height: "60px",
|
|
248
|
+
size: "medium",
|
|
249
|
+
label: true,
|
|
250
|
+
labelText: "Custom Height Input",
|
|
251
|
+
placeholder: "Height: 60px",
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// 커스텀 너비와 높이
|
|
256
|
+
export const CustomWidthAndHeight: Story = {
|
|
257
|
+
args: {
|
|
258
|
+
width: "400px",
|
|
259
|
+
height: "70px",
|
|
260
|
+
size: "medium",
|
|
261
|
+
label: true,
|
|
262
|
+
labelText: "Custom Width & Height",
|
|
263
|
+
placeholder: "Width: 400px, Height: 70px",
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// 퍼센트 너비
|
|
268
|
+
export const PercentageWidth: Story = {
|
|
269
|
+
render: (args) => (
|
|
270
|
+
<div style={{ width: "600px", padding: "20px", border: "1px dashed #ccc" }}>
|
|
271
|
+
<Input {...args} />
|
|
272
|
+
</div>
|
|
273
|
+
),
|
|
274
|
+
args: {
|
|
275
|
+
width: "100%",
|
|
276
|
+
size: "medium",
|
|
277
|
+
label: true,
|
|
278
|
+
labelText: "100% Width Input",
|
|
279
|
+
placeholder: "This input fills the container",
|
|
280
|
+
},
|
|
281
|
+
};
|
|
@@ -1,31 +1,58 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Eye, EyeOff } from "lucide-react";
|
|
3
|
+
import {
|
|
4
|
+
InputWrapper,
|
|
5
|
+
Label,
|
|
6
|
+
InputContainer,
|
|
7
|
+
StyledInput,
|
|
8
|
+
IconButton,
|
|
9
|
+
} from "./style";
|
|
10
|
+
import { InputProps } from "../../types/input.types";
|
|
5
11
|
|
|
6
12
|
export const Input = ({
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
id,
|
|
14
|
+
width,
|
|
15
|
+
height,
|
|
16
|
+
size = "medium",
|
|
17
|
+
label = false,
|
|
18
|
+
labelText = "",
|
|
19
|
+
placeholder = "",
|
|
20
|
+
value,
|
|
21
|
+
onChange,
|
|
22
|
+
disabled = false,
|
|
23
|
+
isPassword = false,
|
|
14
24
|
}: InputProps) => {
|
|
15
|
-
|
|
25
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
26
|
+
|
|
27
|
+
const inputType = isPassword ? (showPassword ? "text" : "password") : "text";
|
|
16
28
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
29
|
+
return (
|
|
30
|
+
<InputWrapper width={width}>
|
|
31
|
+
{label && labelText && <Label htmlFor={id}>{labelText}</Label>}
|
|
32
|
+
<InputContainer>
|
|
33
|
+
<StyledInput
|
|
34
|
+
id={id}
|
|
35
|
+
type={inputType}
|
|
36
|
+
inputSize={size}
|
|
37
|
+
width={width}
|
|
38
|
+
height={height}
|
|
39
|
+
disabled={disabled}
|
|
40
|
+
isPassword={isPassword}
|
|
41
|
+
placeholder={placeholder}
|
|
42
|
+
value={value}
|
|
43
|
+
onChange={onChange}
|
|
44
|
+
/>
|
|
45
|
+
{isPassword && (
|
|
46
|
+
<IconButton
|
|
47
|
+
type="button"
|
|
48
|
+
disabled={disabled}
|
|
49
|
+
onClick={() => setShowPassword(!showPassword)}
|
|
50
|
+
aria-label={showPassword ? "Hide password" : "Show password"}
|
|
51
|
+
>
|
|
52
|
+
{showPassword ? <Eye size={20} /> : <EyeOff size={20} />}
|
|
53
|
+
</IconButton>
|
|
54
|
+
)}
|
|
55
|
+
</InputContainer>
|
|
56
|
+
</InputWrapper>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './Input'
|
|
1
|
+
export * from './Input';
|