@datability/8ui 0.1.69 → 1.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 (199) hide show
  1. package/.prettierrc +8 -0
  2. package/.vscode/extensions.json +6 -0
  3. package/README.md +66 -18
  4. package/declaration.d.ts +10 -0
  5. package/docker-compose.yml +20 -0
  6. package/eslint.config.js +23 -0
  7. package/index.html +13 -0
  8. package/package.json +37 -36
  9. package/public/vite.svg +1 -0
  10. package/src/App.tsx +370 -0
  11. package/src/components/blackdrop/index.tsx +18 -0
  12. package/src/components/blackdrop/index.type.ts +7 -0
  13. package/src/components/button/index.tsx +44 -0
  14. package/src/components/button/index.type.ts +13 -0
  15. package/src/components/chip/index.tsx +39 -0
  16. package/src/components/chip/index.type.ts +12 -0
  17. package/src/components/context.tsx +26 -0
  18. package/src/components/divider/index.tsx +13 -0
  19. package/src/components/index.ts +62 -0
  20. package/{dist/components/Input/InputAutoComplete → src/components/input/input-auto-complete}/index.scss +1 -1
  21. package/src/components/input/input-auto-complete/index.tsx +140 -0
  22. package/src/components/input/input-auto-complete/index.type.tsx +13 -0
  23. package/src/components/input/input-base/index.tsx +39 -0
  24. package/src/components/input/input-base/index.type.tsx +13 -0
  25. package/src/components/input/input-basic/index.tsx +47 -0
  26. package/src/components/input/input-basic/index.type.tsx +8 -0
  27. package/src/components/input/input-checkbox/index.tsx +69 -0
  28. package/src/components/input/input-checkbox/index.type.tsx +11 -0
  29. package/src/components/input/input-date/index.tsx +354 -0
  30. package/src/components/input/input-date/index.type.tsx +11 -0
  31. package/src/components/input/input-date-range/index.tsx +284 -0
  32. package/src/components/input/input-date-range/index.type.tsx +11 -0
  33. package/src/components/input/input-date-time/index.tsx +367 -0
  34. package/src/components/input/input-date-time/index.type.tsx +11 -0
  35. package/src/components/input/input-number/index.tsx +118 -0
  36. package/src/components/input/input-number/index.type.tsx +11 -0
  37. package/src/components/input/input-password/index.tsx +60 -0
  38. package/src/components/input/input-password/index.type.tsx +8 -0
  39. package/src/components/input/input-radio/index.tsx +72 -0
  40. package/src/components/input/input-radio/index.type.tsx +12 -0
  41. package/{dist/components/Input/InputSelect → src/components/input/input-select}/index.scss +1 -1
  42. package/src/components/input/input-select/index.tsx +113 -0
  43. package/src/components/input/input-select/index.type.tsx +15 -0
  44. package/{dist/components/InputNonContext/InputSwitch → src/components/input/input-switch}/index.scss +1 -1
  45. package/src/components/input/input-switch/index.tsx +44 -0
  46. package/src/components/input/input-switch/index.type.tsx +4 -0
  47. package/src/components/input/input-textarea/index.tsx +48 -0
  48. package/src/components/input/input-textarea/index.type.tsx +10 -0
  49. package/src/components/menu/index.tsx +136 -0
  50. package/src/components/menu/index.type.ts +8 -0
  51. package/{dist/components/Modal → src/components/modal}/index.scss +0 -0
  52. package/src/components/modal/index.tsx +99 -0
  53. package/src/components/modal/index.type.tsx +8 -0
  54. package/src/index.scss +44 -0
  55. package/src/index.ts +62 -0
  56. package/src/logoDownload.svg +3 -0
  57. package/src/main.tsx +9 -0
  58. package/tsconfig.app.json +28 -0
  59. package/tsconfig.json +42 -0
  60. package/tsconfig.node.json +29 -0
  61. package/vite.config.d.ts +2 -0
  62. package/vite.config.ts +35 -0
  63. package/dist/components/Blackdrop/index.d.ts +0 -5
  64. package/dist/components/Blackdrop/index.js +0 -11
  65. package/dist/components/Blackdrop/index.js.map +0 -1
  66. package/dist/components/Blackdrop/index.type.d.ts +0 -6
  67. package/dist/components/Blackdrop/index.type.js +0 -2
  68. package/dist/components/Blackdrop/index.type.js.map +0 -1
  69. package/dist/components/Button/index.d.ts +0 -5
  70. package/dist/components/Button/index.js +0 -16
  71. package/dist/components/Button/index.js.map +0 -1
  72. package/dist/components/Button/index.type.d.ts +0 -12
  73. package/dist/components/Button/index.type.js +0 -2
  74. package/dist/components/Button/index.type.js.map +0 -1
  75. package/dist/components/Chip/index.d.ts +0 -5
  76. package/dist/components/Chip/index.js +0 -18
  77. package/dist/components/Chip/index.js.map +0 -1
  78. package/dist/components/Chip/index.type.d.ts +0 -9
  79. package/dist/components/Chip/index.type.js +0 -2
  80. package/dist/components/Chip/index.type.js.map +0 -1
  81. package/dist/components/Divider/index.d.ts +0 -4
  82. package/dist/components/Divider/index.js +0 -10
  83. package/dist/components/Divider/index.js.map +0 -1
  84. package/dist/components/Input/InputAutoComplete/index.d.ts +0 -5
  85. package/dist/components/Input/InputAutoComplete/index.js +0 -68
  86. package/dist/components/Input/InputAutoComplete/index.js.map +0 -1
  87. package/dist/components/Input/InputAutoComplete/index.type.d.ts +0 -12
  88. package/dist/components/Input/InputAutoComplete/index.type.js +0 -2
  89. package/dist/components/Input/InputAutoComplete/index.type.js.map +0 -1
  90. package/dist/components/Input/InputBase/index.d.ts +0 -5
  91. package/dist/components/Input/InputBase/index.js +0 -23
  92. package/dist/components/Input/InputBase/index.js.map +0 -1
  93. package/dist/components/Input/InputBase/index.type.d.ts +0 -9
  94. package/dist/components/Input/InputBase/index.type.js +0 -2
  95. package/dist/components/Input/InputBase/index.type.js.map +0 -1
  96. package/dist/components/Input/InputBasic/index.d.ts +0 -5
  97. package/dist/components/Input/InputBasic/index.js +0 -16
  98. package/dist/components/Input/InputBasic/index.js.map +0 -1
  99. package/dist/components/Input/InputBasic/index.type.d.ts +0 -10
  100. package/dist/components/Input/InputBasic/index.type.js +0 -2
  101. package/dist/components/Input/InputBasic/index.type.js.map +0 -1
  102. package/dist/components/Input/InputCheckbox/index.d.ts +0 -5
  103. package/dist/components/Input/InputCheckbox/index.js +0 -19
  104. package/dist/components/Input/InputCheckbox/index.js.map +0 -1
  105. package/dist/components/Input/InputCheckbox/index.type.d.ts +0 -11
  106. package/dist/components/Input/InputCheckbox/index.type.js +0 -2
  107. package/dist/components/Input/InputCheckbox/index.type.js.map +0 -1
  108. package/dist/components/Input/InputDate/index.d.ts +0 -23
  109. package/dist/components/Input/InputDate/index.js +0 -190
  110. package/dist/components/Input/InputDate/index.js.map +0 -1
  111. package/dist/components/Input/InputDate/index.type.d.ts +0 -11
  112. package/dist/components/Input/InputDate/index.type.js +0 -2
  113. package/dist/components/Input/InputDate/index.type.js.map +0 -1
  114. package/dist/components/Input/InputDateRange/index.d.ts +0 -5
  115. package/dist/components/Input/InputDateRange/index.js +0 -137
  116. package/dist/components/Input/InputDateRange/index.js.map +0 -1
  117. package/dist/components/Input/InputDateRange/index.type.d.ts +0 -11
  118. package/dist/components/Input/InputDateRange/index.type.js +0 -2
  119. package/dist/components/Input/InputDateRange/index.type.js.map +0 -1
  120. package/dist/components/Input/InputDateTime/index.d.ts +0 -5
  121. package/dist/components/Input/InputDateTime/index.js +0 -185
  122. package/dist/components/Input/InputDateTime/index.js.map +0 -1
  123. package/dist/components/Input/InputDateTime/index.type.d.ts +0 -11
  124. package/dist/components/Input/InputDateTime/index.type.js +0 -2
  125. package/dist/components/Input/InputDateTime/index.type.js.map +0 -1
  126. package/dist/components/Input/InputNumber/index.d.ts +0 -5
  127. package/dist/components/Input/InputNumber/index.js +0 -78
  128. package/dist/components/Input/InputNumber/index.js.map +0 -1
  129. package/dist/components/Input/InputNumber/index.type.d.ts +0 -10
  130. package/dist/components/Input/InputNumber/index.type.js +0 -2
  131. package/dist/components/Input/InputNumber/index.type.js.map +0 -1
  132. package/dist/components/Input/InputPassword/index.d.ts +0 -5
  133. package/dist/components/Input/InputPassword/index.js +0 -21
  134. package/dist/components/Input/InputPassword/index.js.map +0 -1
  135. package/dist/components/Input/InputPassword/index.type.d.ts +0 -8
  136. package/dist/components/Input/InputPassword/index.type.js +0 -2
  137. package/dist/components/Input/InputPassword/index.type.js.map +0 -1
  138. package/dist/components/Input/InputRadio/index.d.ts +0 -5
  139. package/dist/components/Input/InputRadio/index.js +0 -31
  140. package/dist/components/Input/InputRadio/index.js.map +0 -1
  141. package/dist/components/Input/InputRadio/index.type.d.ts +0 -12
  142. package/dist/components/Input/InputRadio/index.type.js +0 -2
  143. package/dist/components/Input/InputRadio/index.type.js.map +0 -1
  144. package/dist/components/Input/InputSelect/index.d.ts +0 -5
  145. package/dist/components/Input/InputSelect/index.js +0 -45
  146. package/dist/components/Input/InputSelect/index.js.map +0 -1
  147. package/dist/components/Input/InputSelect/index.type.d.ts +0 -14
  148. package/dist/components/Input/InputSelect/index.type.js +0 -2
  149. package/dist/components/Input/InputSelect/index.type.js.map +0 -1
  150. package/dist/components/Input/InputTextarea/index.d.ts +0 -5
  151. package/dist/components/Input/InputTextarea/index.js +0 -16
  152. package/dist/components/Input/InputTextarea/index.js.map +0 -1
  153. package/dist/components/Input/InputTextarea/index.type.d.ts +0 -10
  154. package/dist/components/Input/InputTextarea/index.type.js +0 -2
  155. package/dist/components/Input/InputTextarea/index.type.js.map +0 -1
  156. package/dist/components/InputNonContext/InputSwitch/index.d.ts +0 -5
  157. package/dist/components/InputNonContext/InputSwitch/index.js +0 -19
  158. package/dist/components/InputNonContext/InputSwitch/index.js.map +0 -1
  159. package/dist/components/InputNonContext/InputSwitch/index.type.d.ts +0 -6
  160. package/dist/components/InputNonContext/InputSwitch/index.type.js +0 -2
  161. package/dist/components/InputNonContext/InputSwitch/index.type.js.map +0 -1
  162. package/dist/components/Menu/index.d.ts +0 -5
  163. package/dist/components/Menu/index.js +0 -103
  164. package/dist/components/Menu/index.js.map +0 -1
  165. package/dist/components/Menu/index.type.d.ts +0 -11
  166. package/dist/components/Menu/index.type.js +0 -2
  167. package/dist/components/Menu/index.type.js.map +0 -1
  168. package/dist/components/Modal/index.d.ts +0 -5
  169. package/dist/components/Modal/index.js +0 -83
  170. package/dist/components/Modal/index.js.map +0 -1
  171. package/dist/components/Modal/index.type.d.ts +0 -7
  172. package/dist/components/Modal/index.type.js +0 -2
  173. package/dist/components/Modal/index.type.js.map +0 -1
  174. package/dist/components/context.d.ts +0 -8
  175. package/dist/components/context.js +0 -12
  176. package/dist/components/context.js.map +0 -1
  177. package/dist/components/index.d.ts +0 -41
  178. package/dist/components/index.js +0 -21
  179. package/dist/components/index.js.map +0 -1
  180. /package/{dist → src}/components/assets/closed.svg +0 -0
  181. /package/{dist/components/assets/expandArrow.svg → src/components/assets/expand-arrow.svg} +0 -0
  182. /package/{dist/components/assets/visibilityOff.svg → src/components/assets/visibility-off.svg} +0 -0
  183. /package/{dist → src}/components/assets/visibility.svg +0 -0
  184. /package/{dist/components/Blackdrop → src/components/blackdrop}/index.scss +0 -0
  185. /package/{dist/components/Button → src/components/button}/index.scss +0 -0
  186. /package/{dist/components/Chip → src/components/chip}/index.scss +0 -0
  187. /package/{dist/components/Divider → src/components/divider}/index.scss +0 -0
  188. /package/{dist/components/Input → src/components/input}/extend.scss +0 -0
  189. /package/{dist/components/Input/InputBase → src/components/input/input-base}/index.scss +0 -0
  190. /package/{dist/components/Input/InputBasic → src/components/input/input-basic}/index.scss +0 -0
  191. /package/{dist/components/Input/InputCheckbox → src/components/input/input-checkbox}/index.scss +0 -0
  192. /package/{dist/components/Input/InputDate → src/components/input/input-date}/index.scss +0 -0
  193. /package/{dist/components/Input/InputDateRange → src/components/input/input-date-range}/index.scss +0 -0
  194. /package/{dist/components/Input/InputDateTime → src/components/input/input-date-time}/index.scss +0 -0
  195. /package/{dist/components/Input/InputNumber → src/components/input/input-number}/index.scss +0 -0
  196. /package/{dist/components/Input/InputPassword → src/components/input/input-password}/index.scss +0 -0
  197. /package/{dist/components/Input/InputRadio → src/components/input/input-radio}/index.scss +0 -0
  198. /package/{dist/components/Input/InputTextarea → src/components/input/input-textarea}/index.scss +0 -0
  199. /package/{dist/components/Menu → src/components/menu}/index.scss +0 -0
@@ -0,0 +1,367 @@
1
+ // Lib
2
+ import React, { useEffect, useRef, useState } from "react"
3
+ import { Controller, useFormContext } from "react-hook-form"
4
+
5
+ // Images
6
+ import expandArrowSVG from "../../assets/expand-arrow.svg"
7
+ import closedSVG from "../../assets/closed.svg"
8
+
9
+ // Include in project
10
+ import "./index.scss"
11
+ import InputBase from "../input-base"
12
+ import type { PropsInputDateTime } from "./index.type"
13
+ import Menu from "../../menu"
14
+ import Modal from "../../modal"
15
+ import Button from "../../button"
16
+ import { createPortal } from "react-dom"
17
+ import {
18
+ generateMonthOptions,
19
+ generateYearOptions,
20
+ getDateNow,
21
+ getDayInDateString,
22
+ getDaysInMonthWithWeekday,
23
+ getMonthInDateString,
24
+ getYearInDateString,
25
+ updateMonthInDateString,
26
+ updateYearInDateString,
27
+ } from "../input-date"
28
+
29
+ const InputDateTime: React.FC<PropsInputDateTime> = ({
30
+ name,
31
+ label,
32
+ placeholder,
33
+ disabled = false,
34
+ require = false,
35
+ fullWidth = false,
36
+ isHideClearIcon = true,
37
+ maxYear,
38
+ minYear,
39
+ }) => {
40
+ const { control } = useFormContext()
41
+
42
+ const hourWrapperRef = useRef<HTMLDivElement>(null)
43
+ const minWrapperRef = useRef<HTMLDivElement>(null)
44
+
45
+ const [isOpenModal, setIsOpenModal] = useState(false)
46
+
47
+ const [showDay, setShowDay] = useState(getDateNow())
48
+ const [selectedDay, setSelectedDay] = useState(getDateNow())
49
+ const [selectedHour, setSelectedHour] = useState("00")
50
+ const [selectedMin, setSelectedMin] = useState("00")
51
+
52
+ const yearOption = generateYearOptions(minYear, maxYear)
53
+ const monthOption = generateMonthOptions()
54
+ const hourOption = generateHourOptions()
55
+ const minOption = generateMinuteOptions()
56
+
57
+ useEffect(() => {
58
+ function scrollToChecked(wrapper: HTMLDivElement | null) {
59
+ if (!wrapper) return
60
+ const checkedEl = wrapper.querySelector('[data-checked="true"]') as HTMLElement
61
+ checkedEl?.scrollIntoView({ behavior: "smooth", block: "center" })
62
+ }
63
+ scrollToChecked(hourWrapperRef.current)
64
+ scrollToChecked(minWrapperRef.current)
65
+ }, [selectedHour, selectedMin])
66
+
67
+ function CalendarRow({ year, month }: { year: number; month: string }) {
68
+ const days = getDaysInMonthWithWeekday(year, month)
69
+
70
+ const mapToThaiWeek = (dayIndex: number) => dayIndex % 7
71
+ const firstDayThaiIndex = mapToThaiWeek(new Date(`${year}-${month}-01`).getDay())
72
+ const emptyStartDays = Array(firstDayThaiIndex).fill(null)
73
+
74
+ const allDays = [...emptyStartDays, ...days.map((d) => d.date.split("-")[2])]
75
+ while (allDays.length < 42) allDays.push(null)
76
+
77
+ const weeks: Array<Array<string | null>> = []
78
+ for (let i = 0; i < allDays.length; i += 7) weeks.push(allDays.slice(i, i + 7))
79
+
80
+ return (
81
+ <>
82
+ {weeks.map((week, rowIndex) => (
83
+ <div key={rowIndex} className="DBui-inputDateTimeRowDay">
84
+ {week.map((day, i) => (
85
+ <p
86
+ key={i}
87
+ className="DBui-inputDateTimeDay"
88
+ onClick={() => {
89
+ if (day) {
90
+ setSelectedDay(`${getYearInDateString(showDay)}-${getMonthInDateString(showDay)}-${day}`)
91
+ }
92
+ }}
93
+ data-checked={
94
+ `${getYearInDateString(selectedDay)}-${getMonthInDateString(selectedDay)}-${
95
+ getDayInDateString(selectedDay).split("T")[0]
96
+ }` === `${getYearInDateString(showDay)}-${getMonthInDateString(showDay)}-${day}`
97
+ }
98
+ data-hidden-hover={!day}
99
+ >
100
+ {day || ""}
101
+ </p>
102
+ ))}
103
+ </div>
104
+ ))}
105
+ </>
106
+ )
107
+ }
108
+
109
+ const readingValue = (dateString: string) => {
110
+ const [date, time] = dateString.split("T")
111
+ return `${date} ${time}`
112
+ }
113
+
114
+ const portalRoot =
115
+ (document.getElementById("root") as HTMLElement) ||
116
+ (document.getElementById("__next") as HTMLElement) ||
117
+ document.body
118
+
119
+ return (
120
+ <Controller
121
+ name={name}
122
+ control={control}
123
+ render={({ field, fieldState }) => {
124
+ const realValue: string = field.value || ""
125
+ const isInvalid = fieldState.invalid
126
+ const errorMessage = fieldState.error?.message
127
+ const setValue = field.onChange
128
+
129
+ // ✅ sync RHF value -> local state (เหมือน useEffect [realValue] ของ Formik)
130
+ // eslint-disable-next-line react-hooks/rules-of-hooks
131
+ useEffect(() => {
132
+ if (!realValue) {
133
+ const now = getDateNow()
134
+ setShowDay(now)
135
+ setSelectedDay(now)
136
+ setSelectedHour("00")
137
+ setSelectedMin("00")
138
+ return
139
+ }
140
+
141
+ const [d, t] = realValue.split("T")
142
+ const [h, m] = t.split(":")
143
+
144
+ setShowDay(d)
145
+ setSelectedDay(d)
146
+ setSelectedHour(h || "00")
147
+ setSelectedMin(m || "00")
148
+ }, [realValue])
149
+
150
+ const handleClearState = (e: React.MouseEvent<HTMLImageElement>) => {
151
+ e.stopPropagation()
152
+ setValue("")
153
+ }
154
+
155
+ return (
156
+ <InputBase
157
+ name={name}
158
+ label={label}
159
+ require={require}
160
+ fullWidth={fullWidth}
161
+ isInvalid={isInvalid}
162
+ errorMessage={errorMessage}
163
+ >
164
+ <>
165
+ <div
166
+ className="DBui-inputDateTime"
167
+ onClick={() => (disabled ? null : setIsOpenModal(true))}
168
+ data-invalid={isInvalid}
169
+ data-disabled={disabled}
170
+ >
171
+ <p>{realValue ? readingValue(realValue) : placeholder}</p>
172
+ <img
173
+ src={closedSVG}
174
+ className="DBui-clearIconInputDateTime"
175
+ onClick={handleClearState}
176
+ data-hidden={realValue === "" || disabled || isHideClearIcon}
177
+ />
178
+ </div>
179
+
180
+ {createPortal(
181
+ <Modal id="modalInputDateTime" open={isOpenModal} onClose={() => setIsOpenModal(false)}>
182
+ <div className="DBui-inputDateTimeWrapperCalendar">
183
+ <div className="DBui-inputDateTimeWrapperYearMonthDayTime">
184
+ <div className="DBui-inputDateTimeWrapperYearMonthDay">
185
+ <div className="DBui-inputDateTimeRowMonthYear">
186
+ <img
187
+ src={expandArrowSVG}
188
+ className="DBui-inputDateTimeRowMonthYearSelected"
189
+ style={{ transform: "rotate(90deg)" }}
190
+ onClick={() => {
191
+ setShowDay(
192
+ updateMonthInDateString(
193
+ showDay,
194
+ String(Number(showDay.split("-")[1]) - 1).padStart(2, "0"),
195
+ ),
196
+ )
197
+ }}
198
+ />
199
+
200
+ <Menu
201
+ isInModal
202
+ trigger={() => (
203
+ <h4 className="DBui-inputDateTimeRowMonthYearSelected">{showDay.slice(0, 4)}</h4>
204
+ )}
205
+ >
206
+ {({ close }) =>
207
+ yearOption.map((data, index) => (
208
+ <p
209
+ key={index}
210
+ className="DBui-inputDateTimeRowMonthYearOption"
211
+ onClick={() => {
212
+ setShowDay(updateYearInDateString(showDay, data.value))
213
+ close()
214
+ }}
215
+ data-checked={getYearInDateString(showDay) === data.value}
216
+ >
217
+ {data.label}
218
+ </p>
219
+ ))
220
+ }
221
+ </Menu>
222
+
223
+ <Menu
224
+ isInModal
225
+ trigger={() => (
226
+ <h4 className="DBui-inputDateTimeRowMonthYearSelected" style={{ width: "7rem" }}>
227
+ {monthOption.find((e) => e.value === showDay.slice(5, 7))?.label}
228
+ </h4>
229
+ )}
230
+ >
231
+ {({ close }) =>
232
+ monthOption.map((data, index) => (
233
+ <p
234
+ key={index}
235
+ className="DBui-inputDateTimeRowMonthYearOption"
236
+ onClick={() => {
237
+ setShowDay(updateMonthInDateString(showDay, data.value))
238
+ close()
239
+ }}
240
+ data-checked={getMonthInDateString(showDay) === data.value}
241
+ >
242
+ {data.label}
243
+ </p>
244
+ ))
245
+ }
246
+ </Menu>
247
+
248
+ <img
249
+ src={expandArrowSVG}
250
+ className="DBui-inputDateTimeRowMonthYearSelected"
251
+ style={{ transform: "rotate(-90deg)" }}
252
+ onClick={() => {
253
+ setShowDay(
254
+ updateMonthInDateString(
255
+ showDay,
256
+ String(Number(showDay.split("-")[1]) + 1).padStart(2, "0"),
257
+ ),
258
+ )
259
+ }}
260
+ />
261
+ </div>
262
+
263
+ <div>
264
+ <div className="DBui-inputDateTimeRowHeaderDay">
265
+ <p className="DBui-inputDateTimeHeader">Sun</p>
266
+ <p className="DBui-inputDateTimeHeader">Mon</p>
267
+ <p className="DBui-inputDateTimeHeader">Tue</p>
268
+ <p className="DBui-inputDateTimeHeader">Wed</p>
269
+ <p className="DBui-inputDateTimeHeader">Thu</p>
270
+ <p className="DBui-inputDateTimeHeader">Fri</p>
271
+ <p className="DBui-inputDateTimeHeader">Sat</p>
272
+ </div>
273
+
274
+ <CalendarRow year={getYearInDateString(showDay)} month={getMonthInDateString(showDay)} />
275
+ </div>
276
+ </div>
277
+
278
+ <div className="DBui-inputDateTimeWrapperHourMin">
279
+ <p style={{ textAlign: "center" }}>Hr</p>
280
+ <div className="DBui-inputDateTimeWrapperHourMinOptions" ref={hourWrapperRef}>
281
+ {hourOption.map((hour) => (
282
+ <p
283
+ key={hour.value}
284
+ data-checked={selectedHour === hour.value}
285
+ onClick={() => setSelectedHour(hour.value)}
286
+ >
287
+ {hour.value}
288
+ </p>
289
+ ))}
290
+ </div>
291
+ </div>
292
+
293
+ <div className="DBui-inputDateTimeWrapperHourMin">
294
+ <p style={{ textAlign: "center" }}>Min</p>
295
+ <div className="DBui-inputDateTimeWrapperHourMinOptions" ref={minWrapperRef}>
296
+ {minOption.map((min) => (
297
+ <p
298
+ key={min.value}
299
+ data-checked={selectedMin === min.value}
300
+ onClick={() => setSelectedMin(min.value)}
301
+ >
302
+ {min.value}
303
+ </p>
304
+ ))}
305
+ </div>
306
+ </div>
307
+ </div>
308
+
309
+ <div className="DBui-inputDateTimeWrapperButton">
310
+ <Button
311
+ type="button"
312
+ onClick={() => {
313
+ let hour = getHourNow()
314
+ let min = getMinNow()
315
+ if (selectedHour) hour = selectedHour
316
+ if (selectedMin) min = selectedMin
317
+
318
+ setValue(`${selectedDay}T${hour}:${min}`)
319
+ setIsOpenModal(false)
320
+ }}
321
+ name="Save"
322
+ className="DBui-inputDateTimeButtonSave"
323
+ />
324
+ <Button
325
+ type="button"
326
+ onClick={() => setIsOpenModal(false)}
327
+ name="Cancle"
328
+ className="DBui-inputDateTimeButtonCancle"
329
+ />
330
+ </div>
331
+ </div>
332
+ </Modal>,
333
+ portalRoot,
334
+ )}
335
+ </>
336
+ </InputBase>
337
+ )
338
+ }}
339
+ />
340
+ )
341
+ }
342
+
343
+ export default InputDateTime
344
+
345
+ function generateHourOptions(): { label: string; value: string }[] {
346
+ return Array.from({ length: 24 }, (_, i) => {
347
+ const value = String(i).padStart(2, "0")
348
+ return { label: value, value }
349
+ })
350
+ }
351
+
352
+ function generateMinuteOptions(): { label: string; value: string }[] {
353
+ return Array.from({ length: 60 }, (_, i) => {
354
+ const value = String(i).padStart(2, "0")
355
+ return { label: value, value }
356
+ })
357
+ }
358
+
359
+ function getHourNow(): string {
360
+ const dateNow = new Date()
361
+ return `${dateNow.getHours()}`.padStart(2, "0")
362
+ }
363
+
364
+ function getMinNow(): string {
365
+ const dateNow = new Date()
366
+ return `${dateNow.getMinutes()}`.padStart(2, "0")
367
+ }
@@ -0,0 +1,11 @@
1
+ export type PropsInputDateTime = {
2
+ name: string
3
+ label?: string
4
+ placeholder?: string
5
+ disabled?: boolean
6
+ require?: boolean
7
+ fullWidth?: boolean
8
+ isHideClearIcon?: boolean
9
+ minYear?: number
10
+ maxYear?: number
11
+ }
@@ -0,0 +1,118 @@
1
+ // Lib
2
+ import React, { useEffect, useState } from "react"
3
+ import { useFormContext, Controller } from "react-hook-form"
4
+
5
+ // Include in project
6
+ import "./index.scss"
7
+ import InputBase from "../input-base"
8
+ import type { PropsInputNumber } from "./index.type"
9
+
10
+ const InputNumber: React.FC<PropsInputNumber> = ({
11
+ name,
12
+ label,
13
+ placeholder,
14
+ disabled = false,
15
+ require = false,
16
+ fullWidth = false,
17
+ isPhoneNumber = false,
18
+ isAvailableMinus = false,
19
+ }) => {
20
+ const { control } = useFormContext()
21
+ const [showValue, setShowValue] = useState<string>("")
22
+
23
+ return (
24
+ <Controller
25
+ name={name}
26
+ control={control}
27
+ render={({ field, fieldState }) => {
28
+ const { value, onChange } = field
29
+ const { error, invalid } = fieldState
30
+
31
+ // sync RHF value → showValue
32
+ // eslint-disable-next-line react-hooks/rules-of-hooks
33
+ useEffect(() => {
34
+ if (!isPhoneNumber && typeof value === "number" && !isNaN(value)) {
35
+ setShowValue(value.toLocaleString("en-US"))
36
+ } else if (typeof value === "string") {
37
+ setShowValue(value)
38
+ } else {
39
+ setShowValue(value ?? "")
40
+ }
41
+ }, [value])
42
+
43
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
44
+ const input = event.target.value
45
+
46
+ // ล้างช่อง
47
+ if (input === "") {
48
+ if (isPhoneNumber) {
49
+ onChange("")
50
+ setShowValue("")
51
+ } else {
52
+ onChange(0)
53
+ setShowValue("0")
54
+ }
55
+ return
56
+ }
57
+
58
+ // phone number
59
+ if (isPhoneNumber) {
60
+ onChange(input)
61
+ setShowValue(input)
62
+ return
63
+ }
64
+
65
+ const rawValue = input.replace(/,/g, "")
66
+ const regex = isAvailableMinus ? /^-?[0-9]*\.?[0-9]*$/ : /^[0-9]*\.?[0-9]*$/
67
+
68
+ if (!regex.test(rawValue)) return
69
+
70
+ if (rawValue === "-" || rawValue.endsWith(".")) {
71
+ setShowValue(formatWithComma(rawValue))
72
+ onChange(rawValue)
73
+ return
74
+ }
75
+
76
+ const numericValue = Number(rawValue)
77
+ if (isNaN(numericValue)) return
78
+
79
+ onChange(numericValue)
80
+ setShowValue(formatWithComma(rawValue))
81
+ }
82
+
83
+ return (
84
+ <InputBase
85
+ name={name}
86
+ label={label}
87
+ require={require}
88
+ fullWidth={fullWidth}
89
+ isInvalid={invalid}
90
+ errorMessage={error?.message}
91
+ >
92
+ <input
93
+ className="DBui-inputNumber"
94
+ type="text"
95
+ placeholder={placeholder}
96
+ disabled={disabled}
97
+ value={showValue}
98
+ onChange={handleChange}
99
+ inputMode="decimal"
100
+ pattern="-?[0-9,]*\.?[0-9]*"
101
+ data-invalid={invalid}
102
+ />
103
+ </InputBase>
104
+ )
105
+ }}
106
+ />
107
+ )
108
+ }
109
+
110
+ export default InputNumber
111
+
112
+ const formatWithComma = (val: string): string => {
113
+ if (val === "" || val === "-" || val === "." || val === "-.") return val
114
+
115
+ const [intPart, decPart] = val.split(".")
116
+ const formattedInt = intPart ? Number(intPart).toLocaleString("en-US") : ""
117
+ return decPart !== undefined ? `${formattedInt}.${decPart}` : formattedInt
118
+ }
@@ -0,0 +1,11 @@
1
+ export type PropsInputNumber = {
2
+ name: string
3
+ label?: string
4
+ placeholder?: string
5
+ disabled?: boolean
6
+ require?: boolean
7
+ fullWidth?: boolean
8
+ isPhoneNumber?: boolean // If isPhoneNumber === true type will be string, else type number
9
+ isAvailableMinus?: boolean
10
+ }
11
+
@@ -0,0 +1,60 @@
1
+ // Lib
2
+ import React, { useState } from "react"
3
+ import { useFormContext, useFormState } from "react-hook-form"
4
+
5
+ // Images
6
+ import visibilitySVG from "../../assets/visibility.svg"
7
+ import visibilityOffSVG from "../../assets/visibility-off.svg"
8
+
9
+ // Include in project
10
+ import "./index.scss"
11
+ import InputBase from "../input-base"
12
+ import type { PropsInputPassword } from "./index.type"
13
+
14
+ const InputPassword: React.FC<PropsInputPassword> = ({
15
+ name,
16
+ label,
17
+ placeholder,
18
+ disabled = false,
19
+ require = false,
20
+ fullWidth = false,
21
+ }) => {
22
+ const { register, control } = useFormContext()
23
+ const { errors } = useFormState({ control, name })
24
+
25
+ const error = errors?.[name]
26
+ const isInvalid = Boolean(error)
27
+
28
+ const [isShow, setIsShow] = useState(false)
29
+
30
+ return (
31
+ <InputBase
32
+ name={name}
33
+ label={label}
34
+ require={require}
35
+ fullWidth={fullWidth}
36
+ isInvalid={isInvalid}
37
+ errorMessage={error?.message}
38
+ >
39
+ <div className="DBui-wrapInputPassword">
40
+ <input
41
+ {...register(name)}
42
+ className="DBui-inputPassword"
43
+ type={isShow ? "text" : "password"}
44
+ placeholder={placeholder}
45
+ disabled={disabled}
46
+ data-invalid={isInvalid}
47
+ />
48
+
49
+ <img
50
+ className="DBui-inputPasswordIcon"
51
+ src={isShow ? visibilitySVG : visibilityOffSVG}
52
+ alt="toggle visibility"
53
+ onClick={() => setIsShow((s) => !s)}
54
+ />
55
+ </div>
56
+ </InputBase>
57
+ )
58
+ }
59
+
60
+ export default InputPassword
@@ -0,0 +1,8 @@
1
+ export type PropsInputPassword = {
2
+ name: string
3
+ label?: string
4
+ placeholder?: string
5
+ disabled?: boolean
6
+ require?: boolean
7
+ fullWidth?: boolean
8
+ }
@@ -0,0 +1,72 @@
1
+ // Lib
2
+ import React from "react"
3
+ import { Controller, useFormContext } from "react-hook-form"
4
+
5
+ // Include in project
6
+ import "./index.scss"
7
+ import InputBase from "../input-base"
8
+ import type { PropsInputRadio } from "./index.type"
9
+
10
+ const InputRadio: React.FC<PropsInputRadio> = ({
11
+ name,
12
+ label,
13
+ disabled = false,
14
+ require = false,
15
+ fullWidth = false,
16
+ isVertical = false,
17
+ options,
18
+ }) => {
19
+ const { control } = useFormContext()
20
+
21
+ return (
22
+ <Controller
23
+ name={name}
24
+ control={control}
25
+ render={({ field, fieldState }) => {
26
+ const { value, onChange } = field
27
+ const { invalid, error } = fieldState
28
+
29
+ return (
30
+ <InputBase
31
+ name={name}
32
+ label={label}
33
+ require={require}
34
+ fullWidth={fullWidth}
35
+ isInvalid={invalid}
36
+ errorMessage={error?.message}
37
+ >
38
+ <div className="DBui-wrapInputRadioList" data-vertical={isVertical}>
39
+ {options.map((data, index) => {
40
+ const isChecked = value === data.value
41
+
42
+ return (
43
+ <div key={index} className="DBui-wrapInputRadio">
44
+ <input
45
+ className="DBui-inputRadio"
46
+ type="radio"
47
+ disabled={disabled}
48
+ checked={isChecked}
49
+ onClick={() => {
50
+ // คลิกซ้ำ = uncheck
51
+ if (isChecked) {
52
+ onChange("")
53
+ } else {
54
+ onChange(data.value)
55
+ }
56
+ }}
57
+ />
58
+ <p className="DBui-labelRadio" data-invalid={invalid}>
59
+ <small>{data.label}</small>
60
+ </p>
61
+ </div>
62
+ )
63
+ })}
64
+ </div>
65
+ </InputBase>
66
+ )
67
+ }}
68
+ />
69
+ )
70
+ }
71
+
72
+ export default InputRadio
@@ -0,0 +1,12 @@
1
+ export type PropsInputRadio = {
2
+ name: string
3
+ label?: string
4
+ disabled?: boolean
5
+ require?: boolean
6
+ fullWidth?: boolean
7
+ isVertical?: boolean
8
+ options: {
9
+ label: string
10
+ value: string | number
11
+ }[]
12
+ }
@@ -75,7 +75,7 @@
75
75
  }
76
76
 
77
77
  &[data-checked='true'] {
78
- background-color: #6f5ee0;
78
+ background-color: #0045a5;
79
79
  color: #ffffff;
80
80
  }
81
81
  }