@uniai-fe/uds-primitives 0.0.1

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 (217) hide show
  1. package/README.md +63 -0
  2. package/package.json +85 -0
  3. package/src/components/alternate/hooks/index.ts +4 -0
  4. package/src/components/alternate/img/.gitkeep +0 -0
  5. package/src/components/alternate/index.scss +1 -0
  6. package/src/components/alternate/index.tsx +4 -0
  7. package/src/components/alternate/markup/index.tsx +4 -0
  8. package/src/components/alternate/styles/index.scss +3 -0
  9. package/src/components/alternate/types/index.ts +4 -0
  10. package/src/components/alternate/utils/index.ts +4 -0
  11. package/src/components/badge/hooks/index.ts +4 -0
  12. package/src/components/badge/img/.gitkeep +0 -0
  13. package/src/components/badge/index.scss +1 -0
  14. package/src/components/badge/index.tsx +6 -0
  15. package/src/components/badge/markup/Badge.tsx +51 -0
  16. package/src/components/badge/markup/index.tsx +1 -0
  17. package/src/components/badge/styles/index.scss +189 -0
  18. package/src/components/badge/types/index.ts +55 -0
  19. package/src/components/badge/utils/index.ts +21 -0
  20. package/src/components/button/hooks/index.ts +4 -0
  21. package/src/components/button/img/.gitkeep +0 -0
  22. package/src/components/button/index.scss +1 -0
  23. package/src/components/button/index.tsx +6 -0
  24. package/src/components/button/markup/Button.tsx +175 -0
  25. package/src/components/button/markup/index.tsx +1 -0
  26. package/src/components/button/styles/index.scss +847 -0
  27. package/src/components/button/types/index.ts +79 -0
  28. package/src/components/button/utils/index.ts +58 -0
  29. package/src/components/calendar/hooks/index.ts +4 -0
  30. package/src/components/calendar/img/.gitkeep +0 -0
  31. package/src/components/calendar/index.scss +1 -0
  32. package/src/components/calendar/index.tsx +4 -0
  33. package/src/components/calendar/markup/index.tsx +4 -0
  34. package/src/components/calendar/styles/index.scss +3 -0
  35. package/src/components/calendar/types/index.ts +4 -0
  36. package/src/components/calendar/utils/index.ts +4 -0
  37. package/src/components/checkbox/hooks/index.ts +4 -0
  38. package/src/components/checkbox/img/.gitkeep +0 -0
  39. package/src/components/checkbox/img/check-large.svg +3 -0
  40. package/src/components/checkbox/img/check-medium.svg +3 -0
  41. package/src/components/checkbox/img/check.svg +3 -0
  42. package/src/components/checkbox/index.scss +1 -0
  43. package/src/components/checkbox/index.tsx +4 -0
  44. package/src/components/checkbox/markup/Checkbox.tsx +127 -0
  45. package/src/components/checkbox/markup/index.ts +1 -0
  46. package/src/components/checkbox/styles/index.scss +164 -0
  47. package/src/components/checkbox/types/checkbox.ts +21 -0
  48. package/src/components/checkbox/types/index.ts +1 -0
  49. package/src/components/chip/hooks/index.ts +4 -0
  50. package/src/components/chip/img/.gitkeep +0 -0
  51. package/src/components/chip/img/remove.svg +3 -0
  52. package/src/components/chip/index.scss +1 -0
  53. package/src/components/chip/index.tsx +6 -0
  54. package/src/components/chip/markup/Chip.tsx +103 -0
  55. package/src/components/chip/markup/index.tsx +1 -0
  56. package/src/components/chip/styles/index.scss +140 -0
  57. package/src/components/chip/types/index.ts +52 -0
  58. package/src/components/chip/utils/index.ts +36 -0
  59. package/src/components/dialog/hooks/index.ts +4 -0
  60. package/src/components/dialog/img/.gitkeep +0 -0
  61. package/src/components/dialog/index.scss +1 -0
  62. package/src/components/dialog/index.tsx +3 -0
  63. package/src/components/dialog/markup/confirm-dialog.tsx +316 -0
  64. package/src/components/dialog/markup/index.tsx +4 -0
  65. package/src/components/dialog/markup/notice-dialog.tsx +191 -0
  66. package/src/components/dialog/styles/base.scss +153 -0
  67. package/src/components/dialog/styles/confirm.scss +58 -0
  68. package/src/components/dialog/styles/index.scss +3 -0
  69. package/src/components/dialog/styles/notice.scss +65 -0
  70. package/src/components/dialog/types/index.ts +70 -0
  71. package/src/components/dialog/utils/index.ts +4 -0
  72. package/src/components/drawer/hooks/index.ts +113 -0
  73. package/src/components/drawer/img/.gitkeep +0 -0
  74. package/src/components/drawer/img/close.svg +3 -0
  75. package/src/components/drawer/index.scss +1 -0
  76. package/src/components/drawer/index.tsx +3 -0
  77. package/src/components/drawer/markup/drawer.tsx +421 -0
  78. package/src/components/drawer/markup/index.tsx +3 -0
  79. package/src/components/drawer/styles/index.scss +232 -0
  80. package/src/components/drawer/types/index.ts +51 -0
  81. package/src/components/drawer/utils/context.ts +15 -0
  82. package/src/components/drawer/utils/index.tsx +77 -0
  83. package/src/components/dropdown/hooks/index.ts +4 -0
  84. package/src/components/dropdown/img/.gitkeep +0 -0
  85. package/src/components/dropdown/index.scss +1 -0
  86. package/src/components/dropdown/index.tsx +4 -0
  87. package/src/components/dropdown/markup/index.tsx +4 -0
  88. package/src/components/dropdown/styles/index.scss +3 -0
  89. package/src/components/dropdown/types/index.ts +4 -0
  90. package/src/components/dropdown/utils/index.ts +4 -0
  91. package/src/components/input/hooks/index.ts +4 -0
  92. package/src/components/input/img/.gitkeep +0 -0
  93. package/src/components/input/img/check-correct.svg +3 -0
  94. package/src/components/input/img/check-default.svg +3 -0
  95. package/src/components/input/img/check-incorrect.svg +3 -0
  96. package/src/components/input/img/error.svg +5 -0
  97. package/src/components/input/img/hide-off.svg +4 -0
  98. package/src/components/input/img/hide-on.svg +6 -0
  99. package/src/components/input/img/reset.svg +3 -0
  100. package/src/components/input/img/search.svg +4 -0
  101. package/src/components/input/img/success.svg +3 -0
  102. package/src/components/input/index.scss +1 -0
  103. package/src/components/input/index.tsx +6 -0
  104. package/src/components/input/markup/index.tsx +1 -0
  105. package/src/components/input/markup/text/Base.tsx +311 -0
  106. package/src/components/input/markup/text/Identification.tsx +145 -0
  107. package/src/components/input/markup/text/Password.tsx +71 -0
  108. package/src/components/input/markup/text/Phone.tsx +115 -0
  109. package/src/components/input/markup/text/Search.tsx +35 -0
  110. package/src/components/input/markup/text/index.ts +10 -0
  111. package/src/components/input/styles/index.scss +375 -0
  112. package/src/components/input/types/index.ts +56 -0
  113. package/src/components/input/utils/index.ts +54 -0
  114. package/src/components/label/hooks/index.ts +4 -0
  115. package/src/components/label/img/.gitkeep +0 -0
  116. package/src/components/label/index.scss +1 -0
  117. package/src/components/label/index.tsx +4 -0
  118. package/src/components/label/markup/index.tsx +4 -0
  119. package/src/components/label/styles/index.scss +3 -0
  120. package/src/components/label/types/index.ts +4 -0
  121. package/src/components/label/utils/index.ts +4 -0
  122. package/src/components/navigation/hooks/index.ts +4 -0
  123. package/src/components/navigation/img/.gitkeep +0 -0
  124. package/src/components/navigation/index.scss +1 -0
  125. package/src/components/navigation/index.tsx +8 -0
  126. package/src/components/navigation/markup/index.tsx +2 -0
  127. package/src/components/navigation/markup/mobile/BottomNavigation.tsx +127 -0
  128. package/src/components/navigation/markup/mobile/index.ts +1 -0
  129. package/src/components/navigation/markup/web/index.ts +4 -0
  130. package/src/components/navigation/styles/index.scss +133 -0
  131. package/src/components/navigation/types/index.ts +38 -0
  132. package/src/components/navigation/utils/index.ts +23 -0
  133. package/src/components/pagination/hooks/index.ts +4 -0
  134. package/src/components/pagination/img/.gitkeep +0 -0
  135. package/src/components/pagination/index.scss +1 -0
  136. package/src/components/pagination/index.tsx +6 -0
  137. package/src/components/pagination/markup/Carousel.tsx +76 -0
  138. package/src/components/pagination/markup/Count.tsx +54 -0
  139. package/src/components/pagination/markup/Pagination.tsx +83 -0
  140. package/src/components/pagination/markup/index.tsx +3 -0
  141. package/src/components/pagination/styles/index.scss +155 -0
  142. package/src/components/pagination/types/index.ts +68 -0
  143. package/src/components/pagination/utils/index.ts +58 -0
  144. package/src/components/radio/hooks/index.ts +4 -0
  145. package/src/components/radio/img/.gitkeep +0 -0
  146. package/src/components/radio/index.scss +1 -0
  147. package/src/components/radio/index.tsx +7 -0
  148. package/src/components/radio/markup/Radio.tsx +121 -0
  149. package/src/components/radio/markup/RadioCard.tsx +68 -0
  150. package/src/components/radio/markup/RadioCardGroup.tsx +75 -0
  151. package/src/components/radio/markup/index.tsx +3 -0
  152. package/src/components/radio/styles/index.scss +252 -0
  153. package/src/components/radio/types/index.ts +1 -0
  154. package/src/components/radio/types/radio.ts +63 -0
  155. package/src/components/radio/utils/index.ts +4 -0
  156. package/src/components/scrollbar/hooks/index.ts +4 -0
  157. package/src/components/scrollbar/img/.gitkeep +0 -0
  158. package/src/components/scrollbar/index.scss +1 -0
  159. package/src/components/scrollbar/index.tsx +4 -0
  160. package/src/components/scrollbar/markup/index.tsx +4 -0
  161. package/src/components/scrollbar/styles/index.scss +3 -0
  162. package/src/components/scrollbar/types/index.ts +4 -0
  163. package/src/components/scrollbar/utils/index.ts +4 -0
  164. package/src/components/segmented-control/index.scss +1 -0
  165. package/src/components/segmented-control/index.tsx +7 -0
  166. package/src/components/segmented-control/markup/SegmentedControl.tsx +117 -0
  167. package/src/components/segmented-control/markup/index.ts +1 -0
  168. package/src/components/segmented-control/styles/index.scss +113 -0
  169. package/src/components/segmented-control/types/index.ts +22 -0
  170. package/src/components/select/hooks/index.ts +4 -0
  171. package/src/components/select/img/.gitkeep +0 -0
  172. package/src/components/select/index.scss +1 -0
  173. package/src/components/select/index.tsx +4 -0
  174. package/src/components/select/markup/index.tsx +4 -0
  175. package/src/components/select/styles/index.scss +3 -0
  176. package/src/components/select/types/index.ts +4 -0
  177. package/src/components/select/utils/index.ts +4 -0
  178. package/src/components/spinner/hooks/index.ts +4 -0
  179. package/src/components/spinner/img/.gitkeep +0 -0
  180. package/src/components/spinner/index.scss +1 -0
  181. package/src/components/spinner/index.tsx +4 -0
  182. package/src/components/spinner/markup/index.tsx +4 -0
  183. package/src/components/spinner/styles/index.scss +3 -0
  184. package/src/components/spinner/types/index.ts +4 -0
  185. package/src/components/spinner/utils/index.ts +4 -0
  186. package/src/components/tab/hooks/index.ts +4 -0
  187. package/src/components/tab/img/.gitkeep +0 -0
  188. package/src/components/tab/index.scss +1 -0
  189. package/src/components/tab/index.tsx +6 -0
  190. package/src/components/tab/markup/TabContent.tsx +29 -0
  191. package/src/components/tab/markup/TabList.tsx +60 -0
  192. package/src/components/tab/markup/TabRoot.tsx +74 -0
  193. package/src/components/tab/markup/TabTrigger.tsx +47 -0
  194. package/src/components/tab/markup/index.tsx +4 -0
  195. package/src/components/tab/styles/index.scss +182 -0
  196. package/src/components/tab/types/index.ts +46 -0
  197. package/src/components/tab/utils/index.ts +5 -0
  198. package/src/components/tab/utils/tab-context.ts +20 -0
  199. package/src/components/table/hooks/index.ts +4 -0
  200. package/src/components/table/img/.gitkeep +0 -0
  201. package/src/components/table/index.scss +1 -0
  202. package/src/components/table/index.tsx +4 -0
  203. package/src/components/table/markup/index.tsx +4 -0
  204. package/src/components/table/styles/index.scss +3 -0
  205. package/src/components/table/types/index.ts +4 -0
  206. package/src/components/table/utils/index.ts +4 -0
  207. package/src/hooks/index.ts +4 -0
  208. package/src/img/.gitkeep +0 -0
  209. package/src/index.scss +3 -0
  210. package/src/index.tsx +26 -0
  211. package/src/init/dayjs.ts +14 -0
  212. package/src/theme/ThemeProvider.tsx +25 -0
  213. package/src/theme/config.ts +29 -0
  214. package/src/theme/index.ts +3 -0
  215. package/src/theme/overrides.scss +215 -0
  216. package/src/types/index.ts +4 -0
  217. package/src/utils/index.ts +4 -0
@@ -0,0 +1,3 @@
1
+ export { Pagination } from "./Pagination";
2
+ export { PaginationCarousel } from "./Carousel";
3
+ export { PaginationCount } from "./Count";
@@ -0,0 +1,155 @@
1
+ @use "@uniai-fe/uds-foundation/css";
2
+
3
+ /* Pagination native 구현 스타일 */
4
+
5
+ .pagination {
6
+ --pagination-gap: var(--spacing-gap-2, 8px);
7
+ --pagination-number-height: var(--theme-size-small-2, 24px);
8
+ --pagination-number-min-width: var(--theme-size-small-2, 24px);
9
+ --pagination-number-radius: var(--theme-radius-medium-1, 6px);
10
+ --pagination-color-active-bg: var(--color-cool-gray-10, #18191b);
11
+ --pagination-color-active-label: var(--color-common-100, #ffffff);
12
+ --pagination-color-inactive-label: var(--color-label-alternative, #afb1b6);
13
+ --pagination-dot-size: 8px;
14
+ --pagination-dot-bg: var(--color-cool-gray-85, #d2d3d7);
15
+ --pagination-dot-active-bg: var(--color-primary-default, #0061ff);
16
+ --pagination-carousel-height: 8px;
17
+ --pagination-carousel-dot-width: 8px;
18
+ --pagination-carousel-active-width: 20px;
19
+ --pagination-count-bg: var(--color-cool-gray-10, #18191b);
20
+ --pagination-count-divider: var(--color-common-100, #ffffff);
21
+ --pagination-count-total: var(--color-label-alternative, #afb1b6);
22
+
23
+ display: inline-flex;
24
+ gap: var(--pagination-gap);
25
+ align-items: center;
26
+ padding: 0;
27
+ margin: 0;
28
+ list-style: none;
29
+ }
30
+
31
+ .pagination[data-interactive="false"] .pagination-button,
32
+ .pagination-button:disabled {
33
+ cursor: default;
34
+ }
35
+
36
+ .pagination-item {
37
+ list-style: none;
38
+ }
39
+
40
+ .pagination-button {
41
+ appearance: none;
42
+ border: none;
43
+ background: transparent;
44
+ padding: 0;
45
+ margin: 0;
46
+ display: inline-flex;
47
+ align-items: center;
48
+ justify-content: center;
49
+ cursor: pointer;
50
+ color: inherit;
51
+ font: inherit;
52
+ transition:
53
+ background-color 0.16s ease,
54
+ color 0.16s ease,
55
+ opacity 0.16s ease;
56
+ }
57
+
58
+ .pagination--variant-list {
59
+ gap: var(--spacing-gap-1, 4px);
60
+ }
61
+
62
+ .pagination--variant-list .pagination-button {
63
+ min-width: var(--pagination-number-min-width);
64
+ height: var(--pagination-number-height);
65
+ padding-inline: var(--spacing-padding-2, 8px);
66
+ border-radius: var(--pagination-number-radius);
67
+ background-color: transparent;
68
+ color: var(--pagination-color-inactive-label);
69
+ font-size: var(--font-label-medium-size, 12px);
70
+ font-weight: var(--font-label-medium-weight, 400);
71
+ line-height: var(--font-label-medium-line-height, 1.5);
72
+ }
73
+
74
+ .pagination[data-interactive="true"]
75
+ .pagination--variant-list
76
+ .pagination-button:not([data-active="true"]):hover {
77
+ background-color: var(--color-bg-alternative-cool-gray, #f0f1f5);
78
+ color: var(--color-label-strong, #5b5c60);
79
+ }
80
+
81
+ .pagination--variant-list .pagination-button[data-active="true"] {
82
+ background-color: var(--pagination-color-active-bg);
83
+ color: var(--pagination-color-active-label);
84
+ }
85
+
86
+ .pagination-number {
87
+ min-width: 1ch;
88
+ text-align: center;
89
+ }
90
+
91
+ .pagination--variant-carousel {
92
+ gap: 5px;
93
+ min-height: var(--pagination-carousel-height);
94
+ align-items: center;
95
+ }
96
+
97
+ .pagination--variant-carousel .pagination-button {
98
+ width: auto;
99
+ height: var(--pagination-carousel-height);
100
+ padding: 0;
101
+ }
102
+
103
+ .pagination--variant-carousel .pagination-dot {
104
+ display: inline-flex;
105
+ width: var(--pagination-carousel-dot-width);
106
+ height: var(--pagination-carousel-height);
107
+ border-radius: calc(var(--pagination-carousel-height) / 2);
108
+ background-color: var(--pagination-dot-bg);
109
+ transition:
110
+ width 0.16s ease,
111
+ background-color 0.16s ease;
112
+ }
113
+
114
+ .pagination--variant-carousel
115
+ .pagination-button[data-active="true"]
116
+ .pagination-dot {
117
+ width: var(--pagination-carousel-active-width);
118
+ background-color: var(--pagination-dot-active-bg);
119
+ }
120
+
121
+ .pagination--variant-count {
122
+ gap: 0;
123
+ }
124
+
125
+ .pagination--variant-count .pagination-button {
126
+ border-radius: var(--theme-radius-large-2, 16px);
127
+ padding-inline: var(--spacing-padding-4, 16px);
128
+ background-color: var(--pagination-count-bg);
129
+ color: var(--pagination-color-active-label);
130
+ font-weight: var(--font-label-medium-weight, 400);
131
+ line-height: 1.5;
132
+ }
133
+
134
+ .pagination--variant-count.pagination--count-size-small .pagination-button {
135
+ height: var(--theme-size-small-2, 24px);
136
+ font-size: var(--font-label-medium-size, 12px);
137
+ }
138
+
139
+ .pagination--variant-count.pagination--count-size-xsmall .pagination-button {
140
+ height: var(--theme-size-small-1, 20px);
141
+ font-size: var(--font-label-small-size, 11px);
142
+ }
143
+
144
+ .pagination-count-current {
145
+ color: var(--pagination-color-active-label);
146
+ }
147
+
148
+ .pagination-count-divider {
149
+ margin-inline: var(--spacing-gap-1, 4px);
150
+ color: var(--pagination-count-divider);
151
+ }
152
+
153
+ .pagination-count-total {
154
+ color: var(--pagination-count-total);
155
+ }
@@ -0,0 +1,68 @@
1
+ import type { ComponentPropsWithoutRef } from "react";
2
+
3
+ export const PAGINATION_VARIANTS = ["list", "carousel", "count"] as const;
4
+ export const PAGINATION_COUNT_SIZES = ["small", "xsmall"] as const;
5
+
6
+ export type PaginationVariant = (typeof PAGINATION_VARIANTS)[number];
7
+ export type PaginationCountSize = (typeof PAGINATION_COUNT_SIZES)[number];
8
+
9
+ type NativeListProps = ComponentPropsWithoutRef<"ul">;
10
+ type NativeDivProps = ComponentPropsWithoutRef<"div">;
11
+
12
+ interface PaginationBaseProps {
13
+ /**
14
+ * 전체 페이지/step 개수. 최소 1 이상으로 보정된다.
15
+ */
16
+ total: number;
17
+ /**
18
+ * 현재 활성 페이지(1-indexed). total 범위 밖이면 자동으로 보정된다.
19
+ */
20
+ current?: number;
21
+ }
22
+
23
+ interface PaginationInteractiveProps {
24
+ /**
25
+ * 페이지 변경 핸들러. 제공되지 않으면 클릭이 비활성화된다.
26
+ */
27
+ onPageChange?: (page: number) => void;
28
+ }
29
+
30
+ /**
31
+ * 숫자 페이지용 Pagination props
32
+ */
33
+ export interface PaginationProps
34
+ extends
35
+ Omit<NativeListProps, "children" | "onChange">,
36
+ PaginationBaseProps,
37
+ PaginationInteractiveProps {}
38
+
39
+ /**
40
+ * Carousel 점 indicator props
41
+ */
42
+ export interface PaginationCarouselProps
43
+ extends
44
+ Omit<NativeListProps, "children" | "onChange">,
45
+ PaginationBaseProps,
46
+ PaginationInteractiveProps {}
47
+
48
+ /**
49
+ * Count(step) indicator props
50
+ */
51
+ export interface PaginationCountProps
52
+ extends Omit<NativeDivProps, "children">, PaginationBaseProps {
53
+ /**
54
+ * count variant 전용 크기 옵션.
55
+ */
56
+ size?: PaginationCountSize;
57
+ }
58
+
59
+ export interface PaginationClassNameOptions {
60
+ variant: PaginationVariant;
61
+ countSize?: PaginationCountSize;
62
+ className?: string;
63
+ }
64
+
65
+ export interface PaginationNormalizationResult {
66
+ total: number;
67
+ current: number;
68
+ }
@@ -0,0 +1,58 @@
1
+ import clsx from "clsx";
2
+ import type {
3
+ PaginationClassNameOptions,
4
+ PaginationNormalizationResult,
5
+ } from "../types";
6
+
7
+ const PAGINATION_CLASSNAME = "pagination";
8
+ const PAGINATION_ITEM_CLASSNAME = "pagination-item";
9
+ const PAGINATION_BUTTON_CLASSNAME = "pagination-button";
10
+
11
+ /**
12
+ * pagination 루트 className composer
13
+ */
14
+ const composePaginationClassName = ({
15
+ variant,
16
+ countSize,
17
+ className,
18
+ }: PaginationClassNameOptions) =>
19
+ clsx(
20
+ PAGINATION_CLASSNAME,
21
+ `${PAGINATION_CLASSNAME}--variant-${variant}`,
22
+ variant === "count" && countSize
23
+ ? `${PAGINATION_CLASSNAME}--count-size-${countSize}`
24
+ : null,
25
+ className,
26
+ );
27
+
28
+ /**
29
+ * total/current 값을 보정해 최소 1~total 범위를 유지한다.
30
+ */
31
+ const normalizePaginationState = ({
32
+ total,
33
+ current = 1,
34
+ }: {
35
+ total: number;
36
+ current?: number;
37
+ }): PaginationNormalizationResult => {
38
+ const normalizedTotal = Math.max(1, Math.floor(total));
39
+ return {
40
+ total: normalizedTotal,
41
+ current: Math.min(Math.max(1, Math.floor(current)), normalizedTotal),
42
+ };
43
+ };
44
+
45
+ /**
46
+ * 페이지 리스트 생성 helper
47
+ */
48
+ const createPaginationPages = (total: number) =>
49
+ Array.from({ length: total }, (_, index) => index + 1);
50
+
51
+ export {
52
+ PAGINATION_BUTTON_CLASSNAME,
53
+ PAGINATION_CLASSNAME,
54
+ PAGINATION_ITEM_CLASSNAME,
55
+ composePaginationClassName,
56
+ createPaginationPages,
57
+ normalizePaginationState,
58
+ };
@@ -0,0 +1,4 @@
1
+ /**
2
+ * TODO(radio): 접근성/상태 계산 hook을 정의한다.
3
+ */
4
+ export {};
File without changes
@@ -0,0 +1 @@
1
+ @use "./styles/index.scss";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * radio 카테고리 배럴; markup과 타입 exports를 노출한다.
3
+ */
4
+ import "./index.scss";
5
+
6
+ export * from "./markup";
7
+ export type * from "./types";
@@ -0,0 +1,121 @@
1
+ import clsx from "clsx";
2
+ import { forwardRef, useId } from "react";
3
+ import {
4
+ Indicator as RadixRadioIndicator,
5
+ Item as RadixRadioItem,
6
+ } from "@radix-ui/react-radio-group";
7
+ import type { RadioFieldProps, RadioProps } from "../types";
8
+
9
+ const RADIO_CLASSNAME = "radio";
10
+ const RADIO_INDICATOR_CLASSNAME = "radio-indicator";
11
+ const RADIO_FIELD_CLASSNAME = "radio-field";
12
+ const RADIO_LABEL_WRAPPER_CLASSNAME = "radio-label-wrapper";
13
+ const RADIO_LABEL_TEXT_CLASSNAME = "radio-label-text";
14
+ const RADIO_HELPER_CLASSNAME = "radio-helper";
15
+
16
+ /**
17
+ * Radio component; Radix RadioGroup item thin wrapper
18
+ * @component
19
+ * @param {RadioProps} props
20
+ * @param {"medium" | "large"} [props.size]
21
+ * @example
22
+ * <Radio value="option" />
23
+ */
24
+ export const Radio = forwardRef<HTMLButtonElement, RadioProps>(function Radio(
25
+ { size = "medium", className, disabled, ...restProps },
26
+ ref,
27
+ ) {
28
+ return (
29
+ <RadixRadioItem
30
+ ref={ref}
31
+ className={clsx(RADIO_CLASSNAME, className)}
32
+ data-size={size}
33
+ data-disabled={disabled ? "true" : undefined}
34
+ disabled={disabled}
35
+ {...restProps}
36
+ >
37
+ {/* indicator가 기본/선택 상태 모두에서 렌더되도록 forceMount를 설정한다. */}
38
+ <RadixRadioIndicator
39
+ forceMount
40
+ className={RADIO_INDICATOR_CLASSNAME}
41
+ aria-hidden
42
+ />
43
+ </RadixRadioItem>
44
+ );
45
+ });
46
+
47
+ /**
48
+ * RadioField component; label/helper wrapper for single radioItem
49
+ * @component
50
+ * @param {RadioFieldProps} props
51
+ * @param {"medium" | "large"} [props.size]
52
+ * @example
53
+ * <RadioField label="옵션" helperText="필수" value="option" />
54
+ */
55
+ export const RadioField = forwardRef<HTMLButtonElement, RadioFieldProps>(
56
+ function RadioField(
57
+ {
58
+ label,
59
+ helperText,
60
+ helperTextProps,
61
+ labelProps,
62
+ fieldClassName,
63
+ labelWrapperClassName,
64
+ size = "medium",
65
+ id,
66
+ disabled,
67
+ ...restProps
68
+ },
69
+ ref,
70
+ ) {
71
+ const generatedId = useId();
72
+ const radioId = id ?? generatedId;
73
+ const labelId = label ? `${radioId}-label` : undefined;
74
+ const helperId = helperText ? `${radioId}-helper` : undefined;
75
+
76
+ // label/helper를 포함해 radio의 접근성 데이터를 연결한다.
77
+ return (
78
+ <div
79
+ className={clsx(RADIO_FIELD_CLASSNAME, fieldClassName)}
80
+ data-size={size}
81
+ data-disabled={disabled ? "true" : undefined}
82
+ >
83
+ <label
84
+ htmlFor={radioId}
85
+ {...labelProps}
86
+ className={clsx(
87
+ RADIO_LABEL_WRAPPER_CLASSNAME,
88
+ labelWrapperClassName,
89
+ labelProps?.className,
90
+ )}
91
+ data-disabled={disabled ? "true" : undefined}
92
+ aria-disabled={disabled ? "true" : undefined}
93
+ >
94
+ <Radio
95
+ {...restProps}
96
+ id={radioId}
97
+ size={size}
98
+ disabled={disabled}
99
+ aria-labelledby={labelId}
100
+ aria-describedby={helperId}
101
+ ref={ref}
102
+ />
103
+ {label ? (
104
+ <span id={labelId} className={RADIO_LABEL_TEXT_CLASSNAME}>
105
+ {label}
106
+ </span>
107
+ ) : null}
108
+ </label>
109
+ {helperText ? (
110
+ <p
111
+ id={helperId}
112
+ {...helperTextProps}
113
+ className={clsx(RADIO_HELPER_CLASSNAME, helperTextProps?.className)}
114
+ >
115
+ {helperText}
116
+ </p>
117
+ ) : null}
118
+ </div>
119
+ );
120
+ },
121
+ );
@@ -0,0 +1,68 @@
1
+ import clsx from "clsx";
2
+ import { forwardRef } from "react";
3
+ import {
4
+ Indicator as RadixRadioIndicator,
5
+ Item as RadixRadioItem,
6
+ } from "@radix-ui/react-radio-group";
7
+ import type { RadioCardProps } from "../types";
8
+
9
+ const RADIO_CARD_CLASSNAME = "radio-card";
10
+ const RADIO_CARD_CONTENT_CLASSNAME = "radio-card-content";
11
+ const RADIO_CARD_TITLE_ROW_CLASSNAME = "radio-card-title-row";
12
+ const RADIO_CARD_TITLE_CLASSNAME = "radio-card-title";
13
+ const RADIO_CARD_BADGE_CLASSNAME = "radio-card-badge";
14
+ const RADIO_CARD_DESCRIPTION_CLASSNAME = "radio-card-description";
15
+ const RADIO_CARD_INDICATOR_WRAPPER_CLASSNAME = "radio-card-indicator-wrapper";
16
+ const RADIO_CARD_INDICATOR_CLASSNAME = "radio-card-indicator";
17
+
18
+ /**
19
+ * RadioCard component; entire card acts as radio trigger
20
+ * @component
21
+ * @param {RadioCardProps} props
22
+ * @example
23
+ * <RadioCard title="행복1농장" value="farm-1" />
24
+ */
25
+ export const RadioCard = forwardRef<HTMLButtonElement, RadioCardProps>(
26
+ function RadioCard(
27
+ {
28
+ title,
29
+ description,
30
+ badge,
31
+ className,
32
+ size = "medium",
33
+ disabled,
34
+ ...restProps
35
+ },
36
+ ref,
37
+ ) {
38
+ return (
39
+ <RadixRadioItem
40
+ ref={ref}
41
+ className={clsx(RADIO_CARD_CLASSNAME, className)}
42
+ data-size={size}
43
+ data-disabled={disabled ? "true" : undefined}
44
+ disabled={disabled}
45
+ {...restProps}
46
+ >
47
+ <div className={RADIO_CARD_CONTENT_CLASSNAME}>
48
+ <div className={RADIO_CARD_TITLE_ROW_CLASSNAME}>
49
+ <span className={RADIO_CARD_TITLE_CLASSNAME}>{title}</span>
50
+ {badge ? (
51
+ <span className={RADIO_CARD_BADGE_CLASSNAME}>{badge}</span>
52
+ ) : null}
53
+ </div>
54
+ {description ? (
55
+ <p className={RADIO_CARD_DESCRIPTION_CLASSNAME}>{description}</p>
56
+ ) : null}
57
+ </div>
58
+ <div className={RADIO_CARD_INDICATOR_WRAPPER_CLASSNAME}>
59
+ <RadixRadioIndicator
60
+ forceMount
61
+ className={RADIO_CARD_INDICATOR_CLASSNAME}
62
+ aria-hidden
63
+ />
64
+ </div>
65
+ </RadixRadioItem>
66
+ );
67
+ },
68
+ );
@@ -0,0 +1,75 @@
1
+ import clsx from "clsx";
2
+ import { forwardRef, useState } from "react";
3
+ import * as RadixRadioGroup from "@radix-ui/react-radio-group";
4
+ import type { ElementRef } from "react";
5
+ import { RadioCard } from "./RadioCard";
6
+ import type { RadioCardGroupProps } from "../types";
7
+
8
+ const RADIO_CARD_GROUP_CLASSNAME = "radio-card-group";
9
+
10
+ /**
11
+ * RadioCardGroup component; renders RadioCard list with internal Radix Root
12
+ * @component
13
+ * @param {RadioCardGroupProps} props
14
+ * @example
15
+ * <RadioCardGroup options={[{ id: "farm-1", title: "농장" }]} value="farm-1" />
16
+ */
17
+ export const RadioCardGroup = forwardRef<
18
+ ElementRef<typeof RadixRadioGroup.Root>,
19
+ RadioCardGroupProps
20
+ >(function RadioCardGroup(
21
+ {
22
+ options,
23
+ size = "medium",
24
+ highlightedValue,
25
+ renderBadge,
26
+ className,
27
+ value,
28
+ defaultValue,
29
+ onValueChange,
30
+ ...restProps
31
+ },
32
+ ref,
33
+ ) {
34
+ const [internalValue, setInternalValue] = useState(defaultValue);
35
+
36
+ const handleValueChange = (nextValue: string) => {
37
+ setInternalValue(nextValue);
38
+ onValueChange?.(nextValue);
39
+ };
40
+
41
+ const effectiveValue = value ?? internalValue ?? defaultValue;
42
+
43
+ return (
44
+ <RadixRadioGroup.Root
45
+ ref={ref}
46
+ className={clsx(RADIO_CARD_GROUP_CLASSNAME, className)}
47
+ value={value}
48
+ defaultValue={defaultValue}
49
+ onValueChange={handleValueChange}
50
+ {...restProps}
51
+ >
52
+ {options.map(option => {
53
+ const isSelected = effectiveValue === option.id;
54
+ const isHighlighted = highlightedValue === option.id;
55
+
56
+ const badgeNode = renderBadge
57
+ ? renderBadge({ option, isSelected, isHighlighted })
58
+ : option.badge;
59
+
60
+ return (
61
+ <RadioCard
62
+ key={option.id}
63
+ value={option.id}
64
+ size={option.size ?? size}
65
+ title={option.title}
66
+ description={option.description}
67
+ badge={badgeNode}
68
+ disabled={option.disabled}
69
+ className={option.className}
70
+ />
71
+ );
72
+ })}
73
+ </RadixRadioGroup.Root>
74
+ );
75
+ });
@@ -0,0 +1,3 @@
1
+ export { Radio, RadioField } from "./Radio";
2
+ export { RadioCard } from "./RadioCard";
3
+ export { RadioCardGroup } from "./RadioCardGroup";