@team-monolith/cds 0.1.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.
Files changed (165) hide show
  1. package/README.md +46 -0
  2. package/dist/CodleDesignSystemProvider.d.ts +5 -0
  3. package/dist/CodleDesignSystemProvider.js +96 -0
  4. package/dist/components/AlertDialog/AlertDialog.d.ts +14 -0
  5. package/dist/components/AlertDialog/AlertDialog.js +45 -0
  6. package/dist/components/AlertDialog/AlertDialogActions.d.ts +8 -0
  7. package/dist/components/AlertDialog/AlertDialogActions.js +35 -0
  8. package/dist/components/AlertDialog/AlertDialogContent.d.ts +8 -0
  9. package/dist/components/AlertDialog/AlertDialogContent.js +36 -0
  10. package/dist/components/AlertDialog/AlertDialogTitle.d.ts +13 -0
  11. package/dist/components/AlertDialog/AlertDialogTitle.js +38 -0
  12. package/dist/components/AlertDialog/index.d.ts +4 -0
  13. package/dist/components/AlertDialog/index.js +4 -0
  14. package/dist/components/Banner.d.ts +29 -0
  15. package/dist/components/Banner.js +65 -0
  16. package/dist/components/Button.d.ts +26 -0
  17. package/dist/components/Button.js +72 -0
  18. package/dist/components/CheckboxInput.d.ts +27 -0
  19. package/dist/components/CheckboxInput.js +77 -0
  20. package/dist/components/Input.d.ts +17 -0
  21. package/dist/components/Input.js +72 -0
  22. package/dist/components/InputBase.d.ts +42 -0
  23. package/dist/components/InputBase.js +52 -0
  24. package/dist/components/Pagination.d.ts +27 -0
  25. package/dist/components/Pagination.js +32 -0
  26. package/dist/components/PinInput.d.ts +36 -0
  27. package/dist/components/PinInput.js +154 -0
  28. package/dist/components/RadioInput.d.ts +23 -0
  29. package/dist/components/RadioInput.js +78 -0
  30. package/dist/components/SquareButton.d.ts +26 -0
  31. package/dist/components/SquareButton.js +80 -0
  32. package/dist/components/Switch.d.ts +19 -0
  33. package/dist/components/Switch.js +59 -0
  34. package/dist/components/Tag.d.ts +21 -0
  35. package/dist/components/Tag.js +61 -0
  36. package/dist/components/Tooltip.d.ts +26 -0
  37. package/dist/components/Tooltip.js +50 -0
  38. package/dist/foundation/color.d.ts +75 -0
  39. package/dist/foundation/color.js +75 -0
  40. package/dist/foundation/shadows.d.ts +9 -0
  41. package/dist/foundation/shadows.js +10 -0
  42. package/dist/icons/arrows.d.ts +16 -0
  43. package/dist/icons/arrows.js +17 -0
  44. package/dist/icons/brand.d.ts +4 -0
  45. package/dist/icons/brand.js +13 -0
  46. package/dist/icons/map.d.ts +4 -0
  47. package/dist/icons/map.js +13 -0
  48. package/dist/icons/system.d.ts +25 -0
  49. package/dist/icons/system.js +20 -0
  50. package/dist/index.d.ts +2 -0
  51. package/dist/index.js +2 -0
  52. package/dist/patterns/Dropdown/Dropdown.d.ts +27 -0
  53. package/dist/patterns/Dropdown/Dropdown.js +41 -0
  54. package/dist/patterns/Dropdown/DropdownItem.d.ts +42 -0
  55. package/dist/patterns/Dropdown/DropdownItem.js +89 -0
  56. package/dist/patterns/Dropdown/DropdownMenu.d.ts +30 -0
  57. package/dist/patterns/Dropdown/DropdownMenu.js +85 -0
  58. package/dist/patterns/Dropdown/index.d.ts +2 -0
  59. package/dist/patterns/Dropdown/index.js +2 -0
  60. package/dist/patterns/EmptyState/EmptyState.d.ts +16 -0
  61. package/dist/patterns/EmptyState/EmptyState.js +36 -0
  62. package/dist/patterns/EmptyState/index.d.ts +2 -0
  63. package/dist/patterns/EmptyState/index.js +2 -0
  64. package/dist/patterns/Grid/EnhancedTableCell.d.ts +9 -0
  65. package/dist/patterns/Grid/EnhancedTableCell.js +122 -0
  66. package/dist/patterns/Grid/Grid.d.ts +51 -0
  67. package/dist/patterns/Grid/Grid.js +140 -0
  68. package/dist/patterns/Grid/index.d.ts +3 -0
  69. package/dist/patterns/Grid/index.js +2 -0
  70. package/dist/patterns/SegmentedControl/SegmentedControlButton.d.ts +8 -0
  71. package/dist/patterns/SegmentedControl/SegmentedControlButton.js +41 -0
  72. package/dist/patterns/SegmentedControl/SegmentedControlGroup.d.ts +26 -0
  73. package/dist/patterns/SegmentedControl/SegmentedControlGroup.js +50 -0
  74. package/dist/patterns/SegmentedControl/SegmentedControlGroupPropsContext.d.ts +5 -0
  75. package/dist/patterns/SegmentedControl/SegmentedControlGroupPropsContext.js +5 -0
  76. package/dist/patterns/SegmentedControl/SegmentedControlSquareButton.d.ts +8 -0
  77. package/dist/patterns/SegmentedControl/SegmentedControlSquareButton.js +45 -0
  78. package/dist/patterns/SegmentedControl/index.d.ts +3 -0
  79. package/dist/patterns/SegmentedControl/index.js +3 -0
  80. package/dist/patterns/Table/Table.d.ts +16 -0
  81. package/dist/patterns/Table/Table.js +33 -0
  82. package/dist/patterns/Table/TableBody.d.ts +8 -0
  83. package/dist/patterns/Table/TableBody.js +26 -0
  84. package/dist/patterns/Table/TableCell.d.ts +15 -0
  85. package/dist/patterns/Table/TableCell.js +78 -0
  86. package/dist/patterns/Table/TableHead.d.ts +8 -0
  87. package/dist/patterns/Table/TableHead.js +26 -0
  88. package/dist/patterns/Table/TableRow.d.ts +12 -0
  89. package/dist/patterns/Table/TableRow.js +29 -0
  90. package/dist/patterns/Table/TableSizeContext.d.ts +7 -0
  91. package/dist/patterns/Table/TableSizeContext.js +3 -0
  92. package/dist/patterns/Table/TableVariantContext.d.ts +6 -0
  93. package/dist/patterns/Table/TableVariantContext.js +3 -0
  94. package/dist/patterns/Table/index.d.ts +7 -0
  95. package/dist/patterns/Table/index.js +6 -0
  96. package/dist/utils/hover.d.ts +3 -0
  97. package/dist/utils/hover.js +14 -0
  98. package/dist/utils/reset.d.ts +2 -0
  99. package/dist/utils/reset.js +8 -0
  100. package/dist/utils/zIndex.d.ts +3 -0
  101. package/dist/utils/zIndex.js +3 -0
  102. package/package.json +52 -0
  103. package/public/favicon.ico +0 -0
  104. package/public/index.html +43 -0
  105. package/public/logo192.png +0 -0
  106. package/public/logo512.png +0 -0
  107. package/public/manifest.json +25 -0
  108. package/public/robots.txt +3 -0
  109. package/src/App.tsx +7 -0
  110. package/src/cds/CodleDesignSystemProvider.tsx +93 -0
  111. package/src/cds/README.md +23 -0
  112. package/src/cds/components/AlertDialog/AlertDialog.tsx +101 -0
  113. package/src/cds/components/AlertDialog/AlertDialogActions.tsx +34 -0
  114. package/src/cds/components/AlertDialog/AlertDialogContent.tsx +38 -0
  115. package/src/cds/components/AlertDialog/AlertDialogTitle.tsx +63 -0
  116. package/src/cds/components/AlertDialog/index.tsx +4 -0
  117. package/src/cds/components/Banner.tsx +176 -0
  118. package/src/cds/components/Button.tsx +239 -0
  119. package/src/cds/components/CheckboxInput.tsx +270 -0
  120. package/src/cds/components/Input.tsx +166 -0
  121. package/src/cds/components/InputBase.tsx +226 -0
  122. package/src/cds/components/Pagination.tsx +99 -0
  123. package/src/cds/components/PinInput.tsx +322 -0
  124. package/src/cds/components/RadioInput.tsx +226 -0
  125. package/src/cds/components/SquareButton.tsx +229 -0
  126. package/src/cds/components/Switch.tsx +129 -0
  127. package/src/cds/components/Tag.tsx +155 -0
  128. package/src/cds/components/Tooltip.tsx +104 -0
  129. package/src/cds/emotion.d.ts +70 -0
  130. package/src/cds/foundation/color.ts +83 -0
  131. package/src/cds/foundation/shadows.ts +17 -0
  132. package/src/cds/icons/arrows.tsx +61 -0
  133. package/src/cds/icons/brand.tsx +13 -0
  134. package/src/cds/icons/map.tsx +14 -0
  135. package/src/cds/icons/system.tsx +113 -0
  136. package/src/cds/index.ts +3 -0
  137. package/src/cds/patterns/Dropdown/Dropdown.tsx +111 -0
  138. package/src/cds/patterns/Dropdown/DropdownItem.tsx +203 -0
  139. package/src/cds/patterns/Dropdown/DropdownMenu.tsx +176 -0
  140. package/src/cds/patterns/Dropdown/index.tsx +2 -0
  141. package/src/cds/patterns/EmptyState/EmptyState.tsx +91 -0
  142. package/src/cds/patterns/EmptyState/empty-state-icon.svg +36 -0
  143. package/src/cds/patterns/EmptyState/index.tsx +2 -0
  144. package/src/cds/patterns/Grid/EnhancedTableCell.tsx +180 -0
  145. package/src/cds/patterns/Grid/Grid.tsx +360 -0
  146. package/src/cds/patterns/Grid/index.tsx +4 -0
  147. package/src/cds/patterns/SegmentedControl/SegmentedControlButton.tsx +41 -0
  148. package/src/cds/patterns/SegmentedControl/SegmentedControlGroup.tsx +81 -0
  149. package/src/cds/patterns/SegmentedControl/SegmentedControlGroupPropsContext.tsx +9 -0
  150. package/src/cds/patterns/SegmentedControl/SegmentedControlSquareButton.tsx +51 -0
  151. package/src/cds/patterns/SegmentedControl/index.ts +3 -0
  152. package/src/cds/patterns/Table/Table.tsx +56 -0
  153. package/src/cds/patterns/Table/TableBody.tsx +30 -0
  154. package/src/cds/patterns/Table/TableCell.tsx +242 -0
  155. package/src/cds/patterns/Table/TableHead.tsx +30 -0
  156. package/src/cds/patterns/Table/TableRow.tsx +54 -0
  157. package/src/cds/patterns/Table/TableSizeContext.tsx +10 -0
  158. package/src/cds/patterns/Table/TableVariantContext.tsx +9 -0
  159. package/src/cds/patterns/Table/index.tsx +15 -0
  160. package/src/cds/utils/hover.tsx +24 -0
  161. package/src/cds/utils/reset.tsx +19 -0
  162. package/src/cds/utils/zIndex.tsx +3 -0
  163. package/src/index.tsx +10 -0
  164. package/src/react-app-env.d.ts +1 -0
  165. package/tsconfig.json +22 -0
@@ -0,0 +1,99 @@
1
+ /** @jsxImportSource @emotion/react */
2
+ import { css } from "@emotion/react";
3
+ import * as React from "react";
4
+ import styled from "@emotion/styled";
5
+ import { Pagination as MuiPagination } from "@mui/material";
6
+ import { HOVER } from "../utils/hover";
7
+
8
+ export interface PaginationProps {
9
+ className?: string;
10
+ // component: React.ElementType; // 불필요한 dom 중첩을 막기 위해 사용하지 않습니다.
11
+ /** 시작과 끝에 오는 페이지의 수 */
12
+ boundaryCount?: number;
13
+ /** 전체 페이지의 수 */
14
+ count?: number;
15
+ /** 기본 페이지 (UnControlled) */
16
+ defaultPage?: number;
17
+ /** 비활성화 여부 */
18
+ disabled?: boolean;
19
+ /** 다음 페이지로 이동하는 버튼 숨김여부 */
20
+ hideNextButton?: boolean;
21
+ /** 이전 페이지로 이동하는 버튼 숨김여부 */
22
+ hidePrevButton?: boolean;
23
+ /** 페이지가 변경되었을 때 콜백함수 */
24
+ onChange?: (event: React.ChangeEvent<unknown>, page: number) => void;
25
+ /** 현재 페이지 전후로 나타나는 페이지의 수 */
26
+ siblingCount?: number;
27
+ /** 현재 페이지 */
28
+ page: number;
29
+ }
30
+
31
+ /**
32
+ * [피그마](https://www.figma.com/file/yhrRFizzmhPoHdw9FbYow2/Codle-PD-Kit---Components?type=design&node-id=35-798&t=6OdVgm0hiYaq5PFw-0)
33
+ */
34
+ const Pagination = React.forwardRef<any, PaginationProps>(
35
+ (props, ref): React.ReactElement => (
36
+ <StyledPagination siblingCount={2} shape="rounded" {...props} ref={ref} />
37
+ ));
38
+
39
+ const StyledPagination = styled(MuiPagination)(
40
+ ({ theme }) => css`
41
+ &.MuiPagination-root {
42
+ & .MuiPagination-ul {
43
+ li {
44
+ width: 32px;
45
+ height: 32px;
46
+ margin-left: 4px;
47
+ margin-right: 4px;
48
+ button {
49
+ padding: 0;
50
+ margin: 0;
51
+ }
52
+ }
53
+ }
54
+ & .MuiPaginationItem-root {
55
+ ${HOVER(css`
56
+ background-color: ${theme.color.background.neutralAlt};
57
+ `)}
58
+ }
59
+ & .MuiPaginationItem-text {
60
+ display: flex;
61
+ flex-direction: column;
62
+ align-items: center;
63
+ text-align: center;
64
+ border-radius: 8px;
65
+ color: ${theme.color.foreground.neutralBase};
66
+ font-weight: 400;
67
+ font-size: 14px;
68
+ line-height: 24px;
69
+ padding: 1px 7px;
70
+ margin: 0;
71
+ }
72
+ & .Mui-selected {
73
+ border: 2px solid ${theme.color.background.primary};
74
+ background-color: transparent;
75
+ color: ${theme.color.background.primary};
76
+ font-weight: 700;
77
+ ${HOVER(css`
78
+ background-color: transparent;
79
+ `)}
80
+ }
81
+ & .Mui-disabled {
82
+ background-color: transparent;
83
+ color: ${theme.color.foreground.neutralBaseDisabled};
84
+ }
85
+ & .Mui-disabled.Mui-selected {
86
+ border: 2px solid ${theme.color.background.primaryDisabled};
87
+ background-color: transparent;
88
+ color: ${theme.color.background.primaryDisabled};
89
+ }
90
+ & .MuiPaginationItem-ellipsis {
91
+ ${HOVER(css`
92
+ background-color: transparent;
93
+ `)}
94
+ }
95
+ }
96
+ `
97
+ );
98
+
99
+ export default Pagination;
@@ -0,0 +1,322 @@
1
+ /** @jsxImportSource @emotion/react */
2
+ import { Theme, css } from "@emotion/react";
3
+ import styled from "@emotion/styled";
4
+ import React, { useRef, useState } from "react";
5
+ import { InputBase } from "./InputBase";
6
+
7
+ export type PinInputColor =
8
+ | "default"
9
+ | "activePrimary"
10
+ | "activeDanger"
11
+ | "activeSuccess";
12
+
13
+ export type PinInputSize = "large" | "medium" | "small";
14
+
15
+ export interface PinInputProps {
16
+ className?: string;
17
+ // component?: React.ElementType; Input은 component를 받지 않습니다.
18
+
19
+ /** `true` 값일 때 첫번째 Input에 자동으로 포커싱합니다. */
20
+ autoFocus?: boolean;
21
+
22
+ /** 컴포넌트 크기 */
23
+ size: PinInputSize;
24
+
25
+ /** 입력되기 전 PinInput 컴포넌트에 노출될 문자열 */
26
+ placeholder?: string;
27
+
28
+ /** Input 컴포넌트 상단에 노출될 문자열 */
29
+ label?: string;
30
+
31
+ /** Input 컴포넌트 하단에 노출될 문자열 */
32
+ hintText?: string;
33
+
34
+ /** hint 영역 좌측에 노출될 아이콘 */
35
+ hintIcon?: React.ReactNode;
36
+
37
+ /** 비활성화 여부 */
38
+ disabled?: boolean;
39
+
40
+ /** 에러 상태 여부 */
41
+ error?: boolean;
42
+
43
+ /** 성공 상태 여부 */
44
+ positive?: boolean;
45
+
46
+ /** 값이 변경될때 호출될 콜백 함수 */
47
+ onChange: (e: {
48
+ values: string[];
49
+ event: React.ChangeEvent<HTMLInputElement>;
50
+ }) => void;
51
+
52
+ /** PinInput의 value (Controlled Component) */
53
+ values: string[];
54
+ }
55
+
56
+ const SIZE_TO_STYLES = (size: PinInputSize) =>
57
+ ({
58
+ small: css`
59
+ width: 36px;
60
+ `,
61
+ medium: css`
62
+ width: 48px;
63
+ `,
64
+ large: css`
65
+ width: 56px;
66
+ `,
67
+ }[size]);
68
+
69
+ const COLOR_TO_HINT_STYLES = (
70
+ theme: Theme,
71
+ color: PinInputColor,
72
+ disabled: boolean
73
+ ) =>
74
+ disabled
75
+ ? css`
76
+ color: ${theme.color.foreground.neutralBaseDisabled};
77
+ `
78
+ : {
79
+ default: css`
80
+ color: ${theme.color.foreground.neutralBaseDisabled};
81
+ `,
82
+ activePrimary: css`
83
+ color: ${theme.color.foreground.neutralBaseDisabled};
84
+ `,
85
+ activeDanger: css`
86
+ color: ${theme.color.foreground.danger};
87
+ `,
88
+ activeSuccess: css`
89
+ color: ${theme.color.foreground.success};
90
+ `,
91
+ }[color];
92
+
93
+ const SIZE_TO_FONT_STYLES = (size: PinInputSize) =>
94
+ ({
95
+ small: css`
96
+ font-size: 14px;
97
+ line-height: 20px;
98
+ svg {
99
+ width: 16px;
100
+ height: 16px;
101
+ }
102
+ `,
103
+ medium: css`
104
+ font-size: 16px;
105
+ line-height: 24px;
106
+ svg {
107
+ width: 16px;
108
+ height: 16px;
109
+ }
110
+ `,
111
+ large: css`
112
+ font-size: 18px;
113
+ line-height: 28px;
114
+ svg {
115
+ width: 18px;
116
+ height: 18px;
117
+ }
118
+ `,
119
+ }[size]);
120
+
121
+ /** i번째 값을 value로 바꾸는 setState Wrapper 함수 */
122
+ function updateFocus(
123
+ value: boolean,
124
+ i: number,
125
+ setFocus: React.Dispatch<React.SetStateAction<boolean[]>>
126
+ ) {
127
+ setFocus((prevFocus) => {
128
+ const newFocus = prevFocus.slice();
129
+ newFocus[i] = value;
130
+ return newFocus;
131
+ });
132
+ }
133
+
134
+ /**
135
+ * [피그마](https://www.figma.com/file/yhrRFizzmhPoHdw9FbYow2/Codle-PD-Kit---Components?type=design&node-id=26-11031&t=n7a8XP4k8R2TGmiO-0)
136
+ */
137
+ const PinInput = React.forwardRef<any, PinInputProps>(
138
+ (props, ref): React.ReactElement => {
139
+ const {
140
+ className,
141
+ autoFocus,
142
+ size,
143
+ placeholder,
144
+ label,
145
+ hintText,
146
+ hintIcon,
147
+ disabled = false,
148
+ error,
149
+ positive,
150
+ onChange,
151
+ values,
152
+ ...other
153
+ } = props;
154
+
155
+ return (
156
+ <PinInputWrapper className={className} size={size} ref={ref} {...other}>
157
+ {label ? (
158
+ <Label disabled={disabled}>
159
+ {label} <PinInputComponent {...props} />
160
+ </Label>
161
+ ) : (
162
+ <PinInputComponent {...props} />
163
+ )}
164
+ {hintText && (
165
+ <Hint
166
+ color={
167
+ error ? "activeDanger" : positive ? "activeSuccess" : "default"
168
+ }
169
+ disabled={disabled}
170
+ >
171
+ {hintIcon}
172
+ {hintText}
173
+ </Hint>
174
+ )}
175
+ </PinInputWrapper>
176
+ );
177
+ }
178
+ );
179
+
180
+ function PinInputComponent(props: PinInputProps) {
181
+ const {
182
+ autoFocus,
183
+ size,
184
+ placeholder,
185
+ disabled = false,
186
+ error,
187
+ positive,
188
+ onChange,
189
+ values,
190
+ } = props;
191
+
192
+ const [focus, setFocus] = useState<boolean[]>([]);
193
+ const refs = useRef<(HTMLInputElement | null)[]>([]);
194
+
195
+ return (
196
+ <PinInputContainer>
197
+ {values.map((value, i) => (
198
+ <SqaureInput
199
+ key={i}
200
+ color={
201
+ error
202
+ ? "activeDanger"
203
+ : positive
204
+ ? "activeSuccess"
205
+ : focus[i]
206
+ ? "activePrimary"
207
+ : "default"
208
+ }
209
+ inputRef={(element) => {
210
+ refs.current[i] = element;
211
+ }}
212
+ onChange={(event) => {
213
+ const eventValue = event.target.value;
214
+
215
+ if (eventValue.length > 2) {
216
+ // 복붙으로 입력을 입력한 경우
217
+ if (
218
+ eventValue.length === values.length &&
219
+ eventValue.match(/^[0-9]+$/)
220
+ ) {
221
+ onChange({ values: eventValue.split(""), event });
222
+ }
223
+ return;
224
+ }
225
+
226
+ // 값을 지웠을 때
227
+ if (eventValue === "") {
228
+ const newValues = values.slice();
229
+ newValues[i] = "";
230
+ onChange({ values: newValues, event });
231
+ return;
232
+ }
233
+
234
+ // 원래 값이 있었고, 새 값을 입력했을 때
235
+ // 입력 완료된 값(2자리)으로부터 새로 입력한 숫자를 추출한다.
236
+ const currentValue = values[i];
237
+ let newValue = eventValue;
238
+ if (currentValue[0] === eventValue[0]) {
239
+ // 예) 이전 값: '2', 입력된 상태: '23' 인 경우
240
+ newValue = eventValue[1]; // 뒷 자리가 새로 입력된 숫자
241
+ } else if (currentValue[0] === eventValue[1]) {
242
+ // 예) 이전 값: '2', 입력된 상태: '32' 인 경우
243
+ newValue = eventValue[0]; // 앞 자리가 새로 입력된 숫자
244
+ }
245
+
246
+ // 새로 입력된 값이 숫자일때만 onChange를 실행
247
+ if (newValue.match(/^[0-9]$/)) {
248
+ const newValues = values.slice();
249
+ newValues[i] = newValue;
250
+ onChange({ values: newValues, event });
251
+ // 자동으로 다음 input으로 포커스 이동시킴.
252
+ if (i < values.length - 1) {
253
+ refs.current[i + 1]?.focus();
254
+ }
255
+ }
256
+ }}
257
+ placeholder={focus[i] ? "" : placeholder}
258
+ disabled={disabled}
259
+ size={size}
260
+ inputProps={{
261
+ autoFocus: autoFocus ? i === 0 : false,
262
+ onBlur: () => updateFocus(false, i, setFocus),
263
+ onFocus: () => updateFocus(true, i, setFocus),
264
+ onKeyDown: (e) => {
265
+ // Backspace 키로 이전 input으로의 포커스 이동
266
+ if (e.key === "Backspace" && values[i] === "" && i > 0) {
267
+ refs.current[i - 1]?.focus();
268
+ }
269
+ },
270
+ }}
271
+ fullWidth={true}
272
+ value={value}
273
+ />
274
+ ))}
275
+ </PinInputContainer>
276
+ );
277
+ }
278
+
279
+ const PinInputWrapper = styled.span<{ size: PinInputSize }>`
280
+ display: inline-flex;
281
+ flex-direction: column;
282
+ gap: 8px;
283
+ ${({ size }) => SIZE_TO_FONT_STYLES(size)}
284
+ `;
285
+
286
+ const PinInputContainer = styled.span`
287
+ display: inline-flex;
288
+ gap: 8px;
289
+ `;
290
+
291
+ const SqaureInput = styled(InputBase)<{ size: PinInputSize }>`
292
+ ${({ size }) => SIZE_TO_STYLES(size)}
293
+ padding: 0;
294
+ input {
295
+ text-align: center;
296
+ }
297
+ `;
298
+
299
+ const Hint = styled.div<{
300
+ color: PinInputColor;
301
+ disabled: boolean;
302
+ }>(
303
+ ({ theme, color, disabled }) => css`
304
+ display: flex;
305
+ align-items: center;
306
+ gap: 4px;
307
+ ${COLOR_TO_HINT_STYLES(theme, color, disabled)};
308
+ `
309
+ );
310
+
311
+ const Label = styled.label<{ disabled: boolean }>`
312
+ display: flex;
313
+ flex-direction: column;
314
+ gap: 8px;
315
+ ${({ theme, disabled }) =>
316
+ disabled &&
317
+ css`
318
+ color: ${theme.color.foreground.neutralBaseDisabled};
319
+ `}
320
+ `;
321
+
322
+ export default PinInput;
@@ -0,0 +1,226 @@
1
+ /** @jsxImportSource @emotion/react */
2
+ import { SerializedStyles, css, Theme, useTheme } from "@emotion/react";
3
+ import * as React from "react";
4
+ import styled from "@emotion/styled";
5
+ import { ZINDEX } from "../utils/zIndex";
6
+ import { InputHTMLAttributes } from "react";
7
+
8
+ export type RadioInputType = "default" | "selected";
9
+
10
+ const TYPE_TO_ICON_STYLE = (
11
+ theme: Theme,
12
+ type: RadioInputType,
13
+ disabled: boolean
14
+ ): SerializedStyles => {
15
+ return {
16
+ default: css`
17
+ path {
18
+ stroke: ${theme.color.background.neutralAltActive};
19
+ fill: ${theme.color.background.neutralBase};
20
+ }
21
+ ${disabled
22
+ ? css`
23
+ cursor: default;
24
+ path {
25
+ stroke: ${theme.color.background.primaryDisabled};
26
+ fill: ${theme.color.background.neutralAlt};
27
+ }
28
+ `
29
+ : css`
30
+ input[type="radio"]:hover + & {
31
+ path {
32
+ stroke: ${theme.color.background.primary};
33
+ }
34
+ }
35
+ `}
36
+ `,
37
+ selected: css`
38
+ path {
39
+ stroke: ${theme.color.background.primary};
40
+ fill: ${theme.color.background.neutralBase};
41
+ }
42
+ rect {
43
+ fill: ${theme.color.background.primary};
44
+ }
45
+ ${disabled
46
+ ? css`
47
+ cursor: default;
48
+ path {
49
+ stroke: ${theme.color.background.primaryDisabled};
50
+ fill: ${theme.color.background.neutralAlt};
51
+ }
52
+ rect {
53
+ fill: ${theme.color.background.primaryDisabled};
54
+ }
55
+ `
56
+ : css`
57
+ input[type="radio"]:hover + & {
58
+ path {
59
+ stroke: ${theme.color.background.primaryActive};
60
+ }
61
+ rect {
62
+ fill: ${theme.color.background.primaryActive};
63
+ }
64
+ }
65
+ `}
66
+ `,
67
+ }[type];
68
+ };
69
+
70
+ export interface RadioInputProps {
71
+ className?: string;
72
+ // component: React.ElementType; // 불필요한 dom 중첩을 막기 위해 사용하지 않습니다.
73
+
74
+ /** 라디오 버튼의 선택 여부(Controlled) */
75
+ checked: boolean;
76
+ /** 라디오 버튼의 라벨 */
77
+ label?: string;
78
+ /** 라디오 버튼의 하단 마진(8px) 여부 */
79
+ spacer?: boolean;
80
+ /** 라디오 버튼의 비활성화 여부 */
81
+ disabled?: boolean;
82
+ /** 라디오 버튼 선택값 변경시 호출되는 함수 */
83
+ onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
84
+ /** HTML input 태그에 전달될 props */
85
+ inputProps?: InputHTMLAttributes<HTMLInputElement>;
86
+ }
87
+
88
+ /**
89
+ * [피그마](https://www.figma.com/file/yhrRFizzmhPoHdw9FbYow2/Codle-PD-Kit---Components?type=design&node-id=420-3704&t=lvpbJKdtz7Lb8wIQ-0)
90
+ */
91
+ const RadioInput = React.forwardRef<any, RadioInputProps>((props, ref) => {
92
+ const {
93
+ className,
94
+ checked,
95
+ label,
96
+ spacer,
97
+ disabled,
98
+ onChange,
99
+ inputProps,
100
+ ...other
101
+ } = props;
102
+
103
+ if (!label) {
104
+ return <RadioButton {...props} ref={ref} />;
105
+ }
106
+
107
+ const radioProps = {
108
+ checked,
109
+ spacer,
110
+ disabled,
111
+ onChange,
112
+ inputProps,
113
+ };
114
+
115
+ return (
116
+ <Label className={className} ref={ref} {...other}>
117
+ <RadioButton {...radioProps} />
118
+ <span
119
+ css={css`
120
+ margin-left: 8px;
121
+ `}
122
+ >
123
+ {label}
124
+ </span>
125
+ </Label>
126
+ );
127
+ });
128
+
129
+ const RadioButton = React.forwardRef<any, RadioInputProps>((props, ref) => {
130
+ const {
131
+ className,
132
+ checked,
133
+ label,
134
+ spacer = false,
135
+ disabled = false,
136
+ onChange,
137
+ inputProps,
138
+ ...other
139
+ } = props;
140
+ const type: RadioInputType = props.checked ? "selected" : "default";
141
+
142
+ return (
143
+ <RadioButtonContainer
144
+ className={className}
145
+ spacer={spacer}
146
+ ref={ref}
147
+ {...other}
148
+ >
149
+ <StyledInput
150
+ type="radio"
151
+ {...inputProps}
152
+ checked={checked}
153
+ disabled={disabled}
154
+ onChange={onChange}
155
+ />
156
+ <RadioButtonIcon type={type} disabled={disabled} />
157
+ </RadioButtonContainer>
158
+ );
159
+ });
160
+
161
+ const Label = styled.label`
162
+ display: flex;
163
+ position: relative;
164
+ width: fit-content;
165
+ font-size: 14px;
166
+ font-weight: 500;
167
+ line-height: 16px;
168
+ color: ${({ theme }) => theme.color.foreground.neutralBase};
169
+ align-self: center;
170
+ `;
171
+
172
+ const RadioButtonContainer = styled.span<{
173
+ spacer: boolean;
174
+ }>(
175
+ ({ spacer }) => css`
176
+ display: inline-flex;
177
+ position: relative;
178
+ width: 16px;
179
+ height: 16px;
180
+ cursor: pointer;
181
+ margin-bottom: ${spacer ? "8px" : "0"};
182
+ `
183
+ );
184
+
185
+ const StyledInput = styled.input`
186
+ cursor: inherit;
187
+ position: absolute;
188
+ top: 0;
189
+ left: 0;
190
+ opacity: 0;
191
+ width: 16px;
192
+ height: 16px;
193
+ margin: 0;
194
+ padding: 0;
195
+ // input 컴포넌트는 보이지 않지만, 클릭이 가능하게 z-index를 설정합니다.
196
+ z-index: ${ZINDEX.inputBase};
197
+ `;
198
+
199
+ // default와 selected 구조가 유사하여 하나의 컴포넌트로 표현합니다.
200
+ const RadioButtonIcon = (props: {
201
+ type: RadioInputType;
202
+ disabled: boolean;
203
+ }) => {
204
+ const theme = useTheme();
205
+ return (
206
+ <svg
207
+ width="16"
208
+ height="16"
209
+ viewBox="0 0 16 16"
210
+ fill="none"
211
+ xmlns="http://www.w3.org/2000/svg"
212
+ css={TYPE_TO_ICON_STYLE(theme, props.type, props.disabled)}
213
+ >
214
+ <path
215
+ d="M0.5 8C0.5 3.85786 3.85786 0.5 8 0.5C12.1421 0.5 15.5 3.85786 15.5 8C15.5 12.1421 12.1421 15.5 8 15.5C3.85786 15.5 0.5 12.1421 0.5 8Z"
216
+ fill="currentColor"
217
+ stroke="currentColor"
218
+ />
219
+ {props.type === "selected" && (
220
+ <rect x="4" y="4" width="8" height="8" rx="4" fill="currentColor" />
221
+ )}
222
+ </svg>
223
+ );
224
+ };
225
+
226
+ export default RadioInput;