@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,113 @@
1
+ // Lib
2
+ import React 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 Menu from "../../menu"
13
+ import type { TOption, TValueOption } from "../.."
14
+ import type { PropsInputSelect } from "./index.type"
15
+
16
+ const InputSelect: React.FC<PropsInputSelect> = ({
17
+ name,
18
+ label,
19
+ disabled = false,
20
+ require = false,
21
+ fullWidth = false,
22
+ options,
23
+ onChange,
24
+ isHideClearIcon = true,
25
+ placeholder,
26
+ isInModal = false,
27
+ }) => {
28
+ const { control } = useFormContext()
29
+
30
+ return (
31
+ <Controller
32
+ name={name}
33
+ control={control}
34
+ render={({ field, fieldState }) => {
35
+ const { value, onChange: setValue } = field
36
+ const { invalid, error } = fieldState
37
+
38
+ const selected = options?.find((e) => e.value === value) as TOption | undefined
39
+
40
+ const handleSelect = (val: TValueOption, close: () => void) => {
41
+ setValue(val)
42
+ onChange?.(val)
43
+ close()
44
+ }
45
+
46
+ const handleClear = (e: React.MouseEvent<HTMLImageElement>) => {
47
+ e.stopPropagation()
48
+ setValue(null)
49
+ onChange?.(null)
50
+ }
51
+
52
+ return (
53
+ <InputBase
54
+ name={name}
55
+ label={label}
56
+ require={require}
57
+ fullWidth={fullWidth}
58
+ isInvalid={invalid}
59
+ errorMessage={error?.message}
60
+ >
61
+ <Menu
62
+ isInModal={isInModal}
63
+ disabled={disabled}
64
+ trigger={({ isOpen }) => (
65
+ <div className="DBui-wrapperInputSelect" data-invalid={invalid} data-disabled={disabled}>
66
+ <div className="DBui-wrapperLabelInputSelect">
67
+ {selected?.label ? (
68
+ <p className="DBui-wrapperLabel">
69
+ <small>{selected.label}</small>
70
+ </p>
71
+ ) : (
72
+ <p className="DBui-placeholder">
73
+ <small>{placeholder}</small>
74
+ </p>
75
+ )}
76
+
77
+ <img
78
+ src={closedSVG}
79
+ className="DBui-clearIconInputSelect"
80
+ onClick={handleClear}
81
+ data-hidden={!value || disabled || isHideClearIcon}
82
+ />
83
+ </div>
84
+
85
+ <img
86
+ src={expandArrowSVG}
87
+ className="DBui-arrowIconInputSelect"
88
+ data-checked={disabled ? true : !isOpen}
89
+ />
90
+ </div>
91
+ )}
92
+ >
93
+ {({ close }) =>
94
+ options.map((data, index) => (
95
+ <p
96
+ key={index}
97
+ className="DBui-menuItemInputSelect"
98
+ onClick={() => handleSelect(data.value, close)}
99
+ data-checked={value === data.value}
100
+ >
101
+ {data.label}
102
+ </p>
103
+ ))
104
+ }
105
+ </Menu>
106
+ </InputBase>
107
+ )
108
+ }}
109
+ />
110
+ )
111
+ }
112
+
113
+ export default InputSelect
@@ -0,0 +1,15 @@
1
+ import type { TOption, TValueOption } from "../.."
2
+
3
+ export type PropsInputSelect = {
4
+ name: string
5
+ label?: string
6
+ placeholder?: string
7
+ disabled?: boolean
8
+ require?: boolean
9
+ fullWidth?: boolean
10
+ options: TOption[]
11
+ onChange?: (value: TValueOption | null) => void
12
+ value?: string
13
+ isHideClearIcon?: boolean
14
+ isInModal?: boolean
15
+ }
@@ -63,7 +63,7 @@
63
63
  }
64
64
 
65
65
  input:checked+.DBui-slider {
66
- background-color: #6F5EE0;
66
+ background-color: #477cee;
67
67
  }
68
68
 
69
69
  input:checked+.DBui-slider:before {
@@ -0,0 +1,44 @@
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 type { PropsInputSwitch } from "./index.type"
8
+
9
+ const InputSwitch: React.FC<PropsInputSwitch> = ({ name, disabled }) => {
10
+ const { control } = useFormContext()
11
+
12
+ return (
13
+ <Controller
14
+ name={name}
15
+ control={control}
16
+ render={({ field }) => {
17
+ const { value, onChange } = field
18
+
19
+ return (
20
+ <div className="DBui-wrapInputList" data-disabled={disabled}>
21
+ <label className="DBui-switch">
22
+ <span className="DBui-minus"></span>
23
+ <span className="DBui-miniCircle"></span>
24
+
25
+ <input
26
+ type="checkbox"
27
+ checked={Boolean(value)}
28
+ disabled={disabled}
29
+ onChange={(e) => {
30
+ e.stopPropagation()
31
+ onChange(!value)
32
+ }}
33
+ />
34
+
35
+ <span className="DBui-slider"></span>
36
+ </label>
37
+ </div>
38
+ )
39
+ }}
40
+ />
41
+ )
42
+ }
43
+
44
+ export default InputSwitch
@@ -0,0 +1,4 @@
1
+ export type PropsInputSwitch = {
2
+ name: string
3
+ disabled?: boolean
4
+ }
@@ -0,0 +1,48 @@
1
+ // Lib
2
+ import React from "react"
3
+ import { useFormContext, useFormState } from "react-hook-form"
4
+
5
+ // Include in project
6
+ import "./index.scss"
7
+ import InputBase from "../input-base"
8
+ import type { PropsInputTextarea } from "./index.type"
9
+
10
+ const InputTextarea: React.FC<PropsInputTextarea> = ({
11
+ name,
12
+ label,
13
+ placeholder,
14
+ disabled = false,
15
+ require = false,
16
+ fullWidth = false,
17
+ rows = 2,
18
+ cols = 50,
19
+ }) => {
20
+ const { register, control } = useFormContext()
21
+ const { errors } = useFormState({ control, name })
22
+
23
+ const error = errors?.[name]
24
+ const isInvalid = Boolean(error)
25
+
26
+ return (
27
+ <InputBase
28
+ name={name}
29
+ label={label}
30
+ require={require}
31
+ fullWidth={fullWidth}
32
+ isInvalid={isInvalid}
33
+ errorMessage={error?.message}
34
+ >
35
+ <textarea
36
+ {...register(name)}
37
+ className="DBui-inputTextarea"
38
+ placeholder={placeholder}
39
+ disabled={disabled}
40
+ rows={rows}
41
+ cols={cols}
42
+ data-invalid={isInvalid}
43
+ />
44
+ </InputBase>
45
+ )
46
+ }
47
+
48
+ export default InputTextarea
@@ -0,0 +1,10 @@
1
+ export type PropsInputTextarea = {
2
+ name: string
3
+ label?: string
4
+ placeholder?: string
5
+ disabled?: boolean
6
+ require?: boolean
7
+ fullWidth?: boolean
8
+ rows?: number
9
+ cols?: number
10
+ }
@@ -0,0 +1,136 @@
1
+ // Lib
2
+ import React, { useEffect, useRef, useState } from "react"
3
+ import { createPortal } from "react-dom"
4
+ import "./index.scss"
5
+ import type { PropsMenu } from "./index.type"
6
+
7
+ const Menu: React.FC<PropsMenu> = ({ children, trigger, disabled, isInModal = false }) => {
8
+ const [isOpen, setIsOpen] = useState(false)
9
+ const [menuStyle, setMenuStyle] = useState({})
10
+
11
+ const triggerRef = useRef<HTMLDivElement>(null)
12
+ const menuRef = useRef<HTMLDivElement>(null)
13
+
14
+ // Toggle dropdown
15
+ const toggleMenu = () => {
16
+ if (!disabled) setIsOpen((prev) => !prev)
17
+ }
18
+
19
+ // ------------ MAIN POSITIONING FUNCTION ------------
20
+ const calculateMenuPosition = () => {
21
+ if (!triggerRef.current || !menuRef.current) return
22
+
23
+ const triggerRect = triggerRef.current.getBoundingClientRect()
24
+ const menu = menuRef.current
25
+
26
+ const viewportH = window.innerHeight
27
+ const viewportW = window.innerWidth
28
+ const menuWidth = menu.offsetWidth
29
+
30
+ const spaceAbove = triggerRect.top
31
+ const spaceBelow = viewportH - triggerRect.bottom
32
+
33
+ // วางล่างถ้าพื้นที่ล่างมากกว่า
34
+ const placeBottom = spaceBelow >= spaceAbove
35
+
36
+ let top = placeBottom
37
+ ? triggerRect.bottom // show below
38
+ : triggerRect.top - menu.offsetHeight // show above (height กำหนดจาก CSS max-height)
39
+
40
+ // เปลี่ยน logic ตรงนี้ → กันเมนูล้นจอจาก CSS max-height
41
+ // ถ้าด้านบนพื้นที่ไม่พอ ให้ clamp
42
+ if (!placeBottom) {
43
+ const minTop = 8
44
+ if (top < minTop) top = minTop
45
+ }
46
+
47
+ let left = triggerRect.left
48
+ if (left + menuWidth > viewportW - 8) {
49
+ left = viewportW - menuWidth - 8
50
+ }
51
+ if (left < 8) left = 8
52
+
53
+ setMenuStyle({
54
+ position: "fixed",
55
+ top,
56
+ left,
57
+ })
58
+ }
59
+
60
+ // ------------ Double RAF (ให้ DOM render ก่อน 100%) ------------
61
+ useEffect(() => {
62
+ if (!isOpen) return
63
+
64
+ requestAnimationFrame(() => {
65
+ requestAnimationFrame(() => {
66
+ calculateMenuPosition()
67
+ })
68
+ })
69
+ }, [isOpen, children])
70
+
71
+ // ------------ Reposition when scroll/resize/orientation ------------
72
+ useEffect(() => {
73
+ if (!isOpen) return
74
+
75
+ const handler = () => calculateMenuPosition()
76
+
77
+ window.addEventListener("scroll", handler, true)
78
+ window.addEventListener("resize", handler)
79
+ window.addEventListener("orientationchange", handler)
80
+
81
+ return () => {
82
+ window.removeEventListener("scroll", handler, true)
83
+ window.removeEventListener("resize", handler)
84
+ window.removeEventListener("orientationchange", handler)
85
+ }
86
+ }, [isOpen, children])
87
+
88
+ // ------------ Close On Click Outside ------------
89
+ useEffect(() => {
90
+ if (!isOpen) return
91
+
92
+ const handleClick = (e: MouseEvent) => {
93
+ const t = triggerRef.current
94
+ const m = menuRef.current
95
+ if (!t || !m) return
96
+
97
+ if (!t.contains(e.target as Node) && !m.contains(e.target as Node)) {
98
+ setIsOpen(false)
99
+ }
100
+ }
101
+
102
+ document.addEventListener("mousedown", handleClick)
103
+ return () => document.removeEventListener("mousedown", handleClick)
104
+ }, [isOpen])
105
+
106
+ // Portal root
107
+ const portalRoot = document.getElementById("root") || document.getElementById("__next") || document.body
108
+
109
+ return (
110
+ <>
111
+ <div ref={triggerRef} className="DBui-wrapperMenu" onClick={toggleMenu}>
112
+ {trigger({ isOpen })}
113
+ </div>
114
+
115
+ {isOpen &&
116
+ createPortal(
117
+ <div
118
+ ref={menuRef}
119
+ className="DBui-wrapperMenuItem"
120
+ style={menuStyle}
121
+ data-hidden={!isOpen}
122
+ data-is-in-modal={isInModal}
123
+ >
124
+ {typeof children === "function"
125
+ ? children({
126
+ close: () => setIsOpen(false),
127
+ })
128
+ : children}
129
+ </div>,
130
+ portalRoot,
131
+ )}
132
+ </>
133
+ )
134
+ }
135
+
136
+ export default Menu
@@ -0,0 +1,8 @@
1
+ import type { JSX } from "react"
2
+
3
+ export type PropsMenu = {
4
+ trigger: (props: { isOpen: boolean }) => JSX.Element
5
+ children: (args: { close: () => void }) => JSX.Element | JSX.Element[]
6
+ disabled?: boolean
7
+ isInModal?: boolean
8
+ }
@@ -0,0 +1,99 @@
1
+ // Lib
2
+ import React, { useContext, useEffect, useRef, type JSX } from "react"
3
+
4
+ //Images
5
+ import closedSVG from "../assets/closed.svg"
6
+
7
+ // Include in project
8
+ import "./index.scss"
9
+ import Blackdrop from "../blackdrop"
10
+ import { DBuiContext } from "../context"
11
+ import type { PropsModal } from "./index.type"
12
+
13
+ const Modal: React.FC<PropsModal> = ({ children, open, onClose, id }): JSX.Element => {
14
+ const { openModalCount, setOpenModalCount } = useContext(DBuiContext)
15
+ const setModalIncress = () => setOpenModalCount?.((prev) => prev + 1)
16
+ const setModalDecress = () => setOpenModalCount?.((prev) => prev - 1)
17
+ const setModalZero = () => setOpenModalCount?.(0)
18
+
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ const modalRef = useRef<any>(null)
21
+
22
+ // whitelist for portal UI
23
+ const WHITELIST_CLASSES = [
24
+ "DBui-wrapperMenuItem",
25
+ "DBui-inputDateWrapperCalendar",
26
+ "DBui-inputDateRangeRowMonthYearSelected",
27
+ "DBui-inputDateTimeWrapperCalendar",
28
+ ]
29
+
30
+ useEffect(() => {
31
+ // Update the global counter and adjust the overflow style
32
+ if (open) {
33
+ setModalIncress()
34
+ document.body.style.overflow = "hidden"
35
+ } else {
36
+ if (openModalCount <= 0) {
37
+ setModalZero()
38
+ document.body.style.overflow = "visible"
39
+ } else {
40
+ setModalDecress()
41
+ }
42
+ }
43
+
44
+ return () => {
45
+ if (openModalCount === 0) {
46
+ document.body.style.overflow = "visible"
47
+ }
48
+ }
49
+ }, [open])
50
+
51
+ useEffect(() => {
52
+ const modalElements = document.querySelectorAll(".DBui-modalBase")
53
+ const modalArray = Array.from(modalElements)
54
+ const showModals = modalArray.filter((modal) => modal.getAttribute("data-hidden") === "false")
55
+ const topModal = showModals[showModals.length - 1]
56
+
57
+ if (topModal?.getAttribute("id") !== id) return
58
+
59
+ // ---------- ESC CLOSE ----------
60
+ function handleKeydown(event: KeyboardEvent) {
61
+ if (event.key === "Escape" || event.keyCode === 27) {
62
+ onClose()
63
+ }
64
+ }
65
+
66
+ // ---------- CLICK OUTSIDE CLOSE ----------
67
+ function handleClickOutside(e: MouseEvent) {
68
+ const target = e.target as HTMLElement
69
+
70
+ // ถ้า click ใน modal → ไม่ปิด
71
+ if (modalRef.current?.contains(target)) return
72
+
73
+ // ถ้า click ใน whitelist (menu, dropdown, select panel)
74
+ const isInWhitelist = WHITELIST_CLASSES.some((cls) => target.closest("." + cls))
75
+ if (isInWhitelist) return
76
+
77
+ onClose()
78
+ }
79
+
80
+ document.addEventListener("keydown", handleKeydown)
81
+ document.addEventListener("mousedown", handleClickOutside)
82
+
83
+ return () => {
84
+ document.removeEventListener("keydown", handleKeydown)
85
+ document.removeEventListener("mousedown", handleClickOutside)
86
+ }
87
+ }, [openModalCount])
88
+
89
+ return (
90
+ <Blackdrop open={open}>
91
+ <div className="DBui-modalBase DBui-modalContent" data-hidden={!open} ref={modalRef} id={id}>
92
+ <img src={closedSVG} alt="" className="DBui-close" onClick={onClose} />
93
+ <div>{children}</div>
94
+ </div>
95
+ </Blackdrop>
96
+ )
97
+ }
98
+
99
+ export default Modal
@@ -0,0 +1,8 @@
1
+ import type { JSX } from "react"
2
+
3
+ export type PropsModal = {
4
+ children: JSX.Element | JSX.Element[]
5
+ open: boolean
6
+ onClose: () => void
7
+ id: string
8
+ }
package/src/index.scss ADDED
@@ -0,0 +1,44 @@
1
+ * {
2
+ outline: none !important;
3
+ box-sizing: border-box;
4
+ }
5
+
6
+ body {
7
+ margin: 1em 0;
8
+ padding: 0;
9
+ }
10
+
11
+ .row {
12
+ display: flex;
13
+ flex-direction: row;
14
+ align-items: center;
15
+ justify-content: center;
16
+ width: 70%;
17
+ min-height: 100px;
18
+ background-color: #FAFAFA;
19
+ border: 1px solid #E3E3E8;
20
+ margin: 1em 0;
21
+ }
22
+
23
+ .column {
24
+ display: flex;
25
+ flex-direction: column;
26
+ }
27
+
28
+ .align-center {
29
+ align-items: center;
30
+ }
31
+
32
+ .gap-sm {
33
+ gap: 1em;
34
+ }
35
+
36
+ h1,
37
+ h2,
38
+ h3,
39
+ h4,
40
+ h5,
41
+ h6,
42
+ p {
43
+ margin: 0;
44
+ }
package/src/index.ts ADDED
@@ -0,0 +1,62 @@
1
+ export { default as Button } from "./components/button"
2
+ export type { PropsButton } from "./components/button/index.type"
3
+
4
+ export { default as Blackdrop } from "./components/blackdrop" // Need variable --z-index-backdrop
5
+ export type { PropsBlackdrop } from "./components/blackdrop/index.type"
6
+
7
+ export { default as Chip } from "./components/chip"
8
+ export type { PropsChip } from "./components/chip/index.type"
9
+
10
+ export { default as Divider } from "./components/divider"
11
+
12
+ export { default as Menu } from "./components/menu" // Need variable --z-index-menu
13
+ export type { PropsMenu } from "./components/menu/index.type"
14
+
15
+ export { default as Modal } from "./components/modal" // Need variable --z-index-normale
16
+ export type { PropsModal } from "./components/modal/index.type"
17
+
18
+ export { default as Input } from "./components/input/input-basic"
19
+ export type { PropsInputBasic as PropsInput } from "./components/input/input-basic/index.type"
20
+
21
+ export { default as InputNumber } from "./components/input/input-number"
22
+ export type { PropsInputNumber } from "./components/input/input-number/index.type"
23
+
24
+ export { default as InputDate } from "./components/input/input-date"
25
+ export type { PropsInputDate } from "./components/input/input-date/index.type"
26
+
27
+ export { default as InputDateRange } from "./components/input/input-date-range"
28
+ export type { PropsInputDateRange } from "./components/input/input-date-range/index.type"
29
+
30
+ export { default as InputDateTime } from "./components/input/input-date-time"
31
+ export type { PropsInputDateTime } from "./components/input/input-date-time/index.type"
32
+
33
+ export { default as InputPassword } from "./components/input/input-password"
34
+ export type { PropsInputPassword } from "./components/input/input-password/index.type"
35
+
36
+ export { default as InputRadio } from "./components/input/input-radio"
37
+ export type { PropsInputRadio } from "./components/input/input-radio/index.type"
38
+
39
+ export { default as InputCheckbox } from "./components/input/input-checkbox"
40
+ export type { PropsInputCheckbox } from "./components/input/input-checkbox/index.type"
41
+
42
+ export { default as InputTextarea } from "./components/input/input-textarea"
43
+ export type { PropsInputTextarea } from "./components/input/input-textarea/index.type"
44
+
45
+ export { default as InputSelect } from "./components/input/input-select"
46
+ export type { PropsInputSelect } from "./components/input/input-select/index.type"
47
+
48
+ export { default as InputAutoComplete } from "./components/input/input-auto-complete"
49
+ export type { PropsInputAutoComplete } from "./components/input/input-auto-complete/index.type"
50
+
51
+ export { default as InputSwitch } from "./components/input/input-switch"
52
+ export type { PropsInputSwitch } from "./components/input/input-switch/index.type"
53
+
54
+ // Type
55
+ export type TValueOption = string | number
56
+ export type TOption = {
57
+ label: string
58
+ value: TValueOption
59
+ }
60
+
61
+ // Context
62
+ export { DBuiContext, DBuiProvider } from "./components/context"
@@ -0,0 +1,3 @@
1
+ <svg width="13" height="14" viewBox="0 0 13 14" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M3.77508 4.84992C3.69885 4.77369 3.65602 4.6703 3.65602 4.5625C3.65602 4.4547 3.69885 4.35131 3.77508 4.27508C3.85131 4.19885 3.9547 4.15602 4.0625 4.15602C4.1703 4.15602 4.27369 4.19885 4.34992 4.27508L6.09375 6.01941V1.71875C6.09375 1.61101 6.13655 1.50767 6.21274 1.43149C6.28892 1.3553 6.39226 1.3125 6.5 1.3125C6.60774 1.3125 6.71108 1.3553 6.78726 1.43149C6.86345 1.50767 6.90625 1.61101 6.90625 1.71875V6.01941L8.65008 4.27508C8.72631 4.19885 8.8297 4.15602 8.9375 4.15602C9.0453 4.15602 9.14869 4.19885 9.22492 4.27508C9.30115 4.35131 9.34398 4.4547 9.34398 4.5625C9.34398 4.6703 9.30115 4.77369 9.22492 4.84992L6.78742 7.28742C6.74969 7.32519 6.70489 7.35516 6.65557 7.3756C6.60625 7.39605 6.55339 7.40657 6.5 7.40657C6.44661 7.40657 6.39375 7.39605 6.34443 7.3756C6.29511 7.35516 6.25031 7.32519 6.21258 7.28742L3.77508 4.84992ZM12.1875 7.40625V10.6562C12.1875 10.8717 12.1019 11.0784 11.9495 11.2308C11.7972 11.3831 11.5905 11.4688 11.375 11.4688H1.625C1.40951 11.4688 1.20285 11.3831 1.05048 11.2308C0.898102 11.0784 0.8125 10.8717 0.8125 10.6562V7.40625C0.8125 7.19076 0.898102 6.9841 1.05048 6.83173C1.20285 6.67935 1.40951 6.59375 1.625 6.59375H4.28594C4.31262 6.59373 4.33905 6.59897 4.3637 6.60916C4.38836 6.61935 4.41077 6.63431 4.42965 6.65316L5.63672 7.86328C5.74994 7.9769 5.88449 8.06705 6.03263 8.12857C6.18077 8.19008 6.3396 8.22174 6.5 8.22174C6.6604 8.22174 6.81923 8.19008 6.96737 8.12857C7.11551 8.06705 7.25006 7.9769 7.36328 7.86328L8.57188 6.65469C8.60938 6.61642 8.66048 6.59451 8.71406 6.59375H11.375C11.5905 6.59375 11.7972 6.67935 11.9495 6.83173C12.1019 6.9841 12.1875 7.19076 12.1875 7.40625ZM10.1562 9.03125C10.1562 8.91073 10.1205 8.79291 10.0536 8.6927C9.98659 8.59249 9.89142 8.51438 9.78007 8.46826C9.66872 8.42214 9.5462 8.41007 9.42799 8.43358C9.30978 8.4571 9.2012 8.51513 9.11598 8.60036C9.03076 8.68558 8.97272 8.79416 8.94921 8.91237C8.9257 9.03057 8.93776 9.1531 8.98389 9.26445C9.03001 9.3758 9.10811 9.47097 9.20832 9.53793C9.30854 9.60489 9.42635 9.64062 9.54688 9.64062C9.70849 9.64062 9.86349 9.57642 9.97777 9.46214C10.092 9.34786 10.1562 9.19287 10.1562 9.03125Z" fill="#C3C3C3"/>
3
+ </svg>
package/src/main.tsx ADDED
@@ -0,0 +1,9 @@
1
+ import { StrictMode } from "react"
2
+ import { createRoot } from "react-dom/client"
3
+ import App from "./App.tsx"
4
+
5
+ createRoot(document.getElementById("root")!).render(
6
+ <StrictMode>
7
+ <App />
8
+ </StrictMode>,
9
+ )
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2022",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "types": ["vite/client"],
9
+ "skipLibCheck": true,
10
+
11
+ /* Bundler mode */
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "moduleDetection": "force",
16
+ "noEmit": true,
17
+ "jsx": "react-jsx",
18
+
19
+ /* Linting */
20
+ "strict": true,
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": true,
23
+ "erasableSyntaxOnly": true,
24
+ "noFallthroughCasesInSwitch": true,
25
+ "noUncheckedSideEffectImports": true
26
+ },
27
+ "include": ["src"]
28
+ }