@lp-labs/ui-components 0.0.0-stage → 1.0.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 (244) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/LICENSE +21 -0
  3. package/README.md +256 -2
  4. package/dist/cjs/components/Accordion.d.ts +78 -0
  5. package/dist/cjs/components/Accordion.js +78 -0
  6. package/dist/cjs/components/AdvancedComponents.d.ts +353 -0
  7. package/dist/cjs/components/AdvancedComponents.js +353 -0
  8. package/dist/cjs/components/Alert.d.ts +102 -0
  9. package/dist/cjs/components/Alert.js +102 -0
  10. package/dist/cjs/components/Avatar.d.ts +89 -0
  11. package/dist/cjs/components/Avatar.js +89 -0
  12. package/dist/cjs/components/Badge.d.ts +57 -0
  13. package/dist/cjs/components/Badge.js +57 -0
  14. package/dist/cjs/components/Breadcrumb.d.ts +50 -0
  15. package/dist/cjs/components/Breadcrumb.js +50 -0
  16. package/dist/cjs/components/Button.d.ts +77 -0
  17. package/dist/cjs/components/Button.js +77 -0
  18. package/dist/cjs/components/ButtonVariants.d.ts +261 -0
  19. package/dist/cjs/components/ButtonVariants.js +261 -0
  20. package/dist/cjs/components/Card.d.ts +56 -0
  21. package/dist/cjs/components/Card.js +56 -0
  22. package/dist/cjs/components/CardVariants.d.ts +267 -0
  23. package/dist/cjs/components/CardVariants.js +267 -0
  24. package/dist/cjs/components/Checkbox.d.ts +50 -0
  25. package/dist/cjs/components/Checkbox.js +50 -0
  26. package/dist/cjs/components/Chip.d.ts +104 -0
  27. package/dist/cjs/components/Chip.js +104 -0
  28. package/dist/cjs/components/DataDisplayComponents.d.ts +275 -0
  29. package/dist/cjs/components/DataDisplayComponents.js +275 -0
  30. package/dist/cjs/components/Divider.d.ts +77 -0
  31. package/dist/cjs/components/Divider.js +77 -0
  32. package/dist/cjs/components/Drawer.d.ts +142 -0
  33. package/dist/cjs/components/Drawer.js +142 -0
  34. package/dist/cjs/components/Dropdown.d.ts +139 -0
  35. package/dist/cjs/components/Dropdown.js +139 -0
  36. package/dist/cjs/components/Form.d.ts +41 -0
  37. package/dist/cjs/components/Form.js +41 -0
  38. package/dist/cjs/components/FormGroup.d.ts +58 -0
  39. package/dist/cjs/components/FormGroup.js +58 -0
  40. package/dist/cjs/components/FormInputVariants.d.ts +298 -0
  41. package/dist/cjs/components/FormInputVariants.js +298 -0
  42. package/dist/cjs/components/Image.d.ts +106 -0
  43. package/dist/cjs/components/Image.js +106 -0
  44. package/dist/cjs/components/Input.d.ts +65 -0
  45. package/dist/cjs/components/Input.js +65 -0
  46. package/dist/cjs/components/LayoutComponents.d.ts +258 -0
  47. package/dist/cjs/components/LayoutComponents.js +258 -0
  48. package/dist/cjs/components/Link.d.ts +71 -0
  49. package/dist/cjs/components/Link.js +71 -0
  50. package/dist/cjs/components/LoadingFeedbackComponents.d.ts +230 -0
  51. package/dist/cjs/components/LoadingFeedbackComponents.js +230 -0
  52. package/dist/cjs/components/Menu.d.ts +167 -0
  53. package/dist/cjs/components/Menu.js +167 -0
  54. package/dist/cjs/components/Modal.d.ts +145 -0
  55. package/dist/cjs/components/Modal.js +145 -0
  56. package/dist/cjs/components/ModalTypes.d.ts +288 -0
  57. package/dist/cjs/components/ModalTypes.js +288 -0
  58. package/dist/cjs/components/Navbar.d.ts +181 -0
  59. package/dist/cjs/components/Navbar.js +181 -0
  60. package/dist/cjs/components/NavigationComponents.d.ts +276 -0
  61. package/dist/cjs/components/NavigationComponents.js +276 -0
  62. package/dist/cjs/components/Pagination.d.ts +94 -0
  63. package/dist/cjs/components/Pagination.js +94 -0
  64. package/dist/cjs/components/Popover.d.ts +108 -0
  65. package/dist/cjs/components/Popover.js +108 -0
  66. package/dist/cjs/components/Progress.d.ts +68 -0
  67. package/dist/cjs/components/Progress.js +68 -0
  68. package/dist/cjs/components/Radio.d.ts +93 -0
  69. package/dist/cjs/components/Radio.js +93 -0
  70. package/dist/cjs/components/Rating.d.ts +82 -0
  71. package/dist/cjs/components/Rating.js +82 -0
  72. package/dist/cjs/components/Select.d.ts +74 -0
  73. package/dist/cjs/components/Select.js +74 -0
  74. package/dist/cjs/components/Sidebar.d.ts +174 -0
  75. package/dist/cjs/components/Sidebar.js +174 -0
  76. package/dist/cjs/components/Skeleton.d.ts +57 -0
  77. package/dist/cjs/components/Skeleton.js +57 -0
  78. package/dist/cjs/components/Slider.d.ts +64 -0
  79. package/dist/cjs/components/Slider.js +64 -0
  80. package/dist/cjs/components/Spinner.d.ts +70 -0
  81. package/dist/cjs/components/Spinner.js +70 -0
  82. package/dist/cjs/components/Stack.d.ts +75 -0
  83. package/dist/cjs/components/Stack.js +75 -0
  84. package/dist/cjs/components/Stepper.d.ts +150 -0
  85. package/dist/cjs/components/Stepper.js +150 -0
  86. package/dist/cjs/components/Table.d.ts +173 -0
  87. package/dist/cjs/components/Table.js +173 -0
  88. package/dist/cjs/components/Tabs.d.ts +104 -0
  89. package/dist/cjs/components/Tabs.js +104 -0
  90. package/dist/cjs/components/Tag.d.ts +74 -0
  91. package/dist/cjs/components/Tag.js +74 -0
  92. package/dist/cjs/components/Textarea.d.ts +75 -0
  93. package/dist/cjs/components/Textarea.js +75 -0
  94. package/dist/cjs/components/Timeline.d.ts +124 -0
  95. package/dist/cjs/components/Timeline.js +124 -0
  96. package/dist/cjs/components/Toast.d.ts +155 -0
  97. package/dist/cjs/components/Toast.js +155 -0
  98. package/dist/cjs/components/Toggle.d.ts +75 -0
  99. package/dist/cjs/components/Toggle.js +75 -0
  100. package/dist/cjs/components/Tooltip.d.ts +80 -0
  101. package/dist/cjs/components/Tooltip.js +80 -0
  102. package/dist/cjs/components/TypographyComponents.d.ts +265 -0
  103. package/dist/cjs/components/TypographyComponents.js +265 -0
  104. package/dist/cjs/components/UtilityComponents.d.ts +345 -0
  105. package/dist/cjs/components/UtilityComponents.js +345 -0
  106. package/dist/cjs/components/Video.d.ts +82 -0
  107. package/dist/cjs/components/Video.js +82 -0
  108. package/dist/cjs/components/index.d.ts +311 -0
  109. package/dist/cjs/components/index.js +311 -0
  110. package/dist/cjs/index.d.ts +5 -0
  111. package/dist/cjs/index.js +5 -0
  112. package/dist/cjs/styles/main.css +493 -0
  113. package/dist/cjs/theme/ThemeProvider.d.ts +185 -0
  114. package/dist/cjs/theme/ThemeProvider.js +185 -0
  115. package/dist/cjs/theme/index.d.ts +2 -0
  116. package/dist/cjs/theme/index.js +2 -0
  117. package/dist/cjs/utilities/classNames.d.ts +200 -0
  118. package/dist/cjs/utilities/classNames.js +200 -0
  119. package/dist/cjs/utilities/index.d.ts +3 -0
  120. package/dist/cjs/utilities/index.js +3 -0
  121. package/dist/cjs/utilities/styles.d.ts +94 -0
  122. package/dist/cjs/utilities/styles.js +94 -0
  123. package/dist/esm/components/Accordion.d.ts +78 -0
  124. package/dist/esm/components/Accordion.js +78 -0
  125. package/dist/esm/components/AdvancedComponents.d.ts +353 -0
  126. package/dist/esm/components/AdvancedComponents.js +353 -0
  127. package/dist/esm/components/Alert.d.ts +102 -0
  128. package/dist/esm/components/Alert.js +102 -0
  129. package/dist/esm/components/Avatar.d.ts +89 -0
  130. package/dist/esm/components/Avatar.js +89 -0
  131. package/dist/esm/components/Badge.d.ts +57 -0
  132. package/dist/esm/components/Badge.js +57 -0
  133. package/dist/esm/components/Breadcrumb.d.ts +50 -0
  134. package/dist/esm/components/Breadcrumb.js +50 -0
  135. package/dist/esm/components/Button.d.ts +77 -0
  136. package/dist/esm/components/Button.js +77 -0
  137. package/dist/esm/components/ButtonVariants.d.ts +261 -0
  138. package/dist/esm/components/ButtonVariants.js +261 -0
  139. package/dist/esm/components/Card.d.ts +56 -0
  140. package/dist/esm/components/Card.js +56 -0
  141. package/dist/esm/components/CardVariants.d.ts +267 -0
  142. package/dist/esm/components/CardVariants.js +267 -0
  143. package/dist/esm/components/Checkbox.d.ts +50 -0
  144. package/dist/esm/components/Checkbox.js +50 -0
  145. package/dist/esm/components/Chip.d.ts +104 -0
  146. package/dist/esm/components/Chip.js +104 -0
  147. package/dist/esm/components/DataDisplayComponents.d.ts +275 -0
  148. package/dist/esm/components/DataDisplayComponents.js +275 -0
  149. package/dist/esm/components/Divider.d.ts +77 -0
  150. package/dist/esm/components/Divider.js +77 -0
  151. package/dist/esm/components/Drawer.d.ts +142 -0
  152. package/dist/esm/components/Drawer.js +142 -0
  153. package/dist/esm/components/Dropdown.d.ts +139 -0
  154. package/dist/esm/components/Dropdown.js +139 -0
  155. package/dist/esm/components/Form.d.ts +41 -0
  156. package/dist/esm/components/Form.js +41 -0
  157. package/dist/esm/components/FormGroup.d.ts +58 -0
  158. package/dist/esm/components/FormGroup.js +58 -0
  159. package/dist/esm/components/FormInputVariants.d.ts +298 -0
  160. package/dist/esm/components/FormInputVariants.js +298 -0
  161. package/dist/esm/components/Image.d.ts +106 -0
  162. package/dist/esm/components/Image.js +106 -0
  163. package/dist/esm/components/Input.d.ts +65 -0
  164. package/dist/esm/components/Input.js +65 -0
  165. package/dist/esm/components/LayoutComponents.d.ts +258 -0
  166. package/dist/esm/components/LayoutComponents.js +258 -0
  167. package/dist/esm/components/Link.d.ts +71 -0
  168. package/dist/esm/components/Link.js +71 -0
  169. package/dist/esm/components/LoadingFeedbackComponents.d.ts +230 -0
  170. package/dist/esm/components/LoadingFeedbackComponents.js +230 -0
  171. package/dist/esm/components/Menu.d.ts +167 -0
  172. package/dist/esm/components/Menu.js +167 -0
  173. package/dist/esm/components/Modal.d.ts +145 -0
  174. package/dist/esm/components/Modal.js +145 -0
  175. package/dist/esm/components/ModalTypes.d.ts +288 -0
  176. package/dist/esm/components/ModalTypes.js +288 -0
  177. package/dist/esm/components/Navbar.d.ts +181 -0
  178. package/dist/esm/components/Navbar.js +181 -0
  179. package/dist/esm/components/NavigationComponents.d.ts +276 -0
  180. package/dist/esm/components/NavigationComponents.js +276 -0
  181. package/dist/esm/components/Pagination.d.ts +94 -0
  182. package/dist/esm/components/Pagination.js +94 -0
  183. package/dist/esm/components/Popover.d.ts +108 -0
  184. package/dist/esm/components/Popover.js +108 -0
  185. package/dist/esm/components/Progress.d.ts +68 -0
  186. package/dist/esm/components/Progress.js +68 -0
  187. package/dist/esm/components/Radio.d.ts +93 -0
  188. package/dist/esm/components/Radio.js +93 -0
  189. package/dist/esm/components/Rating.d.ts +82 -0
  190. package/dist/esm/components/Rating.js +82 -0
  191. package/dist/esm/components/Select.d.ts +74 -0
  192. package/dist/esm/components/Select.js +74 -0
  193. package/dist/esm/components/Sidebar.d.ts +174 -0
  194. package/dist/esm/components/Sidebar.js +174 -0
  195. package/dist/esm/components/Skeleton.d.ts +57 -0
  196. package/dist/esm/components/Skeleton.js +57 -0
  197. package/dist/esm/components/Slider.d.ts +64 -0
  198. package/dist/esm/components/Slider.js +64 -0
  199. package/dist/esm/components/Spinner.d.ts +70 -0
  200. package/dist/esm/components/Spinner.js +70 -0
  201. package/dist/esm/components/Stack.d.ts +75 -0
  202. package/dist/esm/components/Stack.js +75 -0
  203. package/dist/esm/components/Stepper.d.ts +150 -0
  204. package/dist/esm/components/Stepper.js +150 -0
  205. package/dist/esm/components/Table.d.ts +173 -0
  206. package/dist/esm/components/Table.js +173 -0
  207. package/dist/esm/components/Tabs.d.ts +104 -0
  208. package/dist/esm/components/Tabs.js +104 -0
  209. package/dist/esm/components/Tag.d.ts +74 -0
  210. package/dist/esm/components/Tag.js +74 -0
  211. package/dist/esm/components/Textarea.d.ts +75 -0
  212. package/dist/esm/components/Textarea.js +75 -0
  213. package/dist/esm/components/Timeline.d.ts +124 -0
  214. package/dist/esm/components/Timeline.js +124 -0
  215. package/dist/esm/components/Toast.d.ts +155 -0
  216. package/dist/esm/components/Toast.js +155 -0
  217. package/dist/esm/components/Toggle.d.ts +75 -0
  218. package/dist/esm/components/Toggle.js +75 -0
  219. package/dist/esm/components/Tooltip.d.ts +80 -0
  220. package/dist/esm/components/Tooltip.js +80 -0
  221. package/dist/esm/components/TypographyComponents.d.ts +265 -0
  222. package/dist/esm/components/TypographyComponents.js +265 -0
  223. package/dist/esm/components/UtilityComponents.d.ts +345 -0
  224. package/dist/esm/components/UtilityComponents.js +345 -0
  225. package/dist/esm/components/Video.d.ts +82 -0
  226. package/dist/esm/components/Video.js +82 -0
  227. package/dist/esm/components/index.d.ts +311 -0
  228. package/dist/esm/components/index.js +311 -0
  229. package/dist/esm/index.d.ts +5 -0
  230. package/dist/esm/index.js +5 -0
  231. package/dist/esm/styles/main.css +493 -0
  232. package/dist/esm/theme/ThemeProvider.d.ts +185 -0
  233. package/dist/esm/theme/ThemeProvider.js +185 -0
  234. package/dist/esm/theme/index.d.ts +2 -0
  235. package/dist/esm/theme/index.js +2 -0
  236. package/dist/esm/utilities/classNames.d.ts +200 -0
  237. package/dist/esm/utilities/classNames.js +200 -0
  238. package/dist/esm/utilities/index.d.ts +3 -0
  239. package/dist/esm/utilities/index.js +3 -0
  240. package/dist/esm/utilities/styles.d.ts +94 -0
  241. package/dist/esm/utilities/styles.js +94 -0
  242. package/dist/styles/main.css +493 -0
  243. package/package.json +124 -6
  244. package/src/styles/main.css +493 -0
@@ -0,0 +1,230 @@
1
+ import React from 'react';
2
+
3
+ export interface LoadingProps extends React.HTMLAttributes<HTMLDivElement> {
4
+ size?: 'sm' | 'md' | 'lg';
5
+ color?: string;
6
+ children?: React.ReactNode;
7
+ }
8
+
9
+ export const SpinnerDots: React.FC<LoadingProps> = ({ size = 'md', color = 'blue', className = '', ...props }) => {
10
+ const sizeClass = { sm: 'w-6 h-6', md: 'w-8 h-8', lg: 'w-12 h-12' }[size];
11
+
12
+ return (
13
+ <div className={`flex gap-1 items-center justify-center ${className}`} {...props}>
14
+ {[0, 1, 2].map((i) => (
15
+ <div
16
+ key={i}
17
+ className={`${sizeClass} bg-${color}-600 rounded-full animate-bounce`}
18
+ style={{ animationDelay: `${i * 0.1}s` }}
19
+ ></div>
20
+ ))}
21
+ </div>
22
+ );
23
+ };
24
+
25
+ export const SpinnerRing: React.FC<LoadingProps> = ({ size = 'md', color = 'blue', className = '', ...props }) => {
26
+ const sizeClass = { sm: 'w-6 h-6', md: 'w-8 h-8', lg: 'w-12 h-12' }[size];
27
+
28
+ return (
29
+ <div className={`${sizeClass} border-4 border-gray-200 border-t-${color}-600 rounded-full animate-spin ${className}`} {...props}></div>
30
+ );
31
+ };
32
+
33
+ export const SpinnerBar: React.FC<LoadingProps> = ({ size = 'md', color = 'blue', className = '', ...props }) => {
34
+ const heightClass = { sm: 'h-1', md: 'h-2', lg: 'h-3' }[size];
35
+
36
+ return (
37
+ <div className={`w-full ${heightClass} bg-gray-200 rounded-full overflow-hidden ${className}`} {...props}>
38
+ <div
39
+ className={`h-full w-1/3 bg-${color}-600 animate-pulse`}
40
+ style={{
41
+ animation: 'slide 1.5s ease-in-out infinite',
42
+ }}
43
+ ></div>
44
+ </div>
45
+ );
46
+ };
47
+
48
+ export const SpinnerBounce: React.FC<LoadingProps> = ({ size = 'md', color = 'blue', className = '', ...props }) => {
49
+ const sizeClass = { sm: 'w-6 h-6', md: 'w-8 h-8', lg: 'w-12 h-12' }[size];
50
+
51
+ return (
52
+ <div className={`flex gap-2 items-end justify-center ${className}`} {...props}>
53
+ {[0, 1, 2].map((i) => (
54
+ <div
55
+ key={i}
56
+ className={`${sizeClass} bg-${color}-600 rounded-full`}
57
+ style={{
58
+ animation: 'bounce 1.4s ease-in-out infinite',
59
+ animationDelay: `${i * 0.16}s`,
60
+ }}
61
+ ></div>
62
+ ))}
63
+ </div>
64
+ );
65
+ };
66
+
67
+ export const SpinnerPulse: React.FC<LoadingProps> = ({ size = 'md', color = 'blue', className = '', ...props }) => {
68
+ const sizeClass = { sm: 'w-6 h-6', md: 'w-8 h-8', lg: 'w-12 h-12' }[size];
69
+
70
+ return (
71
+ <div
72
+ className={`${sizeClass} bg-${color}-600 rounded-full animate-pulse ${className}`}
73
+ {...props}
74
+ ></div>
75
+ );
76
+ };
77
+
78
+ export const SkeletonText: React.FC<LoadingProps & { lines?: number }> = ({
79
+ lines = 3,
80
+ className = '',
81
+ ...props
82
+ }) => {
83
+ return (
84
+ <div className={`space-y-2 ${className}`} {...props}>
85
+ {Array.from({ length: lines }).map((_, i) => (
86
+ <div key={i} className="h-4 bg-gray-200 rounded animate-pulse"></div>
87
+ ))}
88
+ </div>
89
+ );
90
+ };
91
+
92
+ export const SkeletonCard: React.FC<LoadingProps> = ({ className = '', ...props }) => {
93
+ return (
94
+ <div className={`bg-white rounded-lg shadow-md p-6 space-y-4 ${className}`} {...props}>
95
+ <div className="h-8 bg-gray-200 rounded animate-pulse w-1/3"></div>
96
+ <div className="h-4 bg-gray-200 rounded animate-pulse"></div>
97
+ <div className="h-4 bg-gray-200 rounded animate-pulse w-5/6"></div>
98
+ <div className="h-32 bg-gray-200 rounded animate-pulse"></div>
99
+ </div>
100
+ );
101
+ };
102
+
103
+ export const SkeletonImage: React.FC<LoadingProps> = ({
104
+ size = 'md',
105
+ className = '',
106
+ ...props
107
+ }) => {
108
+ const sizeClass = {
109
+ sm: 'w-24 h-24',
110
+ md: 'w-48 h-48',
111
+ lg: 'w-96 h-96',
112
+ }[size];
113
+
114
+ return <div className={`${sizeClass} bg-gray-200 rounded animate-pulse ${className}`} {...props}></div>;
115
+ };
116
+
117
+ export const SkeletonTable: React.FC<LoadingProps & { rows?: number; cols?: number }> = ({
118
+ rows = 5,
119
+ cols = 4,
120
+ className = '',
121
+ ...props
122
+ }) => {
123
+ return (
124
+ <div className={`space-y-2 ${className}`} {...props}>
125
+ {Array.from({ length: rows }).map((_, i) => (
126
+ <div key={i} className="flex gap-2">
127
+ {Array.from({ length: cols }).map((_, j) => (
128
+ <div key={j} className="flex-1 h-6 bg-gray-200 rounded animate-pulse"></div>
129
+ ))}
130
+ </div>
131
+ ))}
132
+ </div>
133
+ );
134
+ };
135
+
136
+ export const ProgressLinear: React.FC<
137
+ LoadingProps & {
138
+ value: number;
139
+ max?: number;
140
+ showLabel?: boolean;
141
+ color?: 'blue' | 'green' | 'red' | 'yellow';
142
+ }
143
+ > = ({ value, max = 100, showLabel = false, color = 'blue', className = '', ...props }) => {
144
+ const percentage = (value / max) * 100;
145
+
146
+ return (
147
+ <div className={`w-full ${className}`} {...props}>
148
+ <div className="h-2 bg-gray-200 rounded-full overflow-hidden">
149
+ <div
150
+ className={`h-full bg-${color}-600 transition-all duration-300`}
151
+ style={{ width: `${Math.min(percentage, 100)}%` }}
152
+ ></div>
153
+ </div>
154
+ {showLabel && <p className="text-sm text-gray-600 mt-1">{Math.round(percentage)}%</p>}
155
+ </div>
156
+ );
157
+ };
158
+
159
+ export const ProgressCircular: React.FC<
160
+ LoadingProps & {
161
+ value: number;
162
+ max?: number;
163
+ showLabel?: boolean;
164
+ color?: 'blue' | 'green' | 'red' | 'yellow';
165
+ }
166
+ > = ({ value, max = 100, showLabel = false, size = 'md', color = 'blue', className = '', ...props }) => {
167
+ const percentage = (value / max) * 100;
168
+ const sizeClass = { sm: 'w-16 h-16', md: 'w-24 h-24', lg: 'w-32 h-32' }[size];
169
+ const circumference = 2 * Math.PI * 45;
170
+
171
+ return (
172
+ <div className={`flex flex-col items-center ${className}`} {...props}>
173
+ <div className={`${sizeClass} relative`}>
174
+ <svg className="transform -rotate-90 w-full h-full">
175
+ <circle cx="50%" cy="50%" r="45" fill="none" stroke="#e5e7eb" strokeWidth="4"></circle>
176
+ <circle
177
+ cx="50%"
178
+ cy="50%"
179
+ r="45"
180
+ fill="none"
181
+ stroke={`currentColor`}
182
+ strokeWidth="4"
183
+ strokeDasharray={circumference}
184
+ strokeDashoffset={circumference - (percentage / 100) * circumference}
185
+ className={`text-${color}-600 transition-all duration-300`}
186
+ ></circle>
187
+ </svg>
188
+ {showLabel && (
189
+ <div className="absolute inset-0 flex items-center justify-center">
190
+ <span className="text-sm font-semibold">{Math.round(percentage)}%</span>
191
+ </div>
192
+ )}
193
+ </div>
194
+ </div>
195
+ );
196
+ };
197
+
198
+ export const ProgressStepped: React.FC<
199
+ LoadingProps & {
200
+ current: number;
201
+ total: number;
202
+ color?: 'blue' | 'green' | 'red' | 'yellow';
203
+ }
204
+ > = ({ current, total, color = 'blue', className = '', ...props }) => {
205
+ return (
206
+ <div className={`flex gap-2 ${className}`} {...props}>
207
+ {Array.from({ length: total }).map((_, i) => (
208
+ <div
209
+ key={i}
210
+ className={`flex-1 h-2 rounded-full ${i < current ? `bg-${color}-600` : 'bg-gray-200'} transition-colors`}
211
+ ></div>
212
+ ))}
213
+ </div>
214
+ );
215
+ };
216
+
217
+ export const Overlay: React.FC<LoadingProps & { isVisible?: boolean }> = ({
218
+ isVisible = true,
219
+ className = '',
220
+ children,
221
+ ...props
222
+ }) => {
223
+ if (!isVisible) return null;
224
+
225
+ return (
226
+ <div className={`absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center ${className}`} {...props}>
227
+ {children}
228
+ </div>
229
+ );
230
+ };
@@ -0,0 +1,167 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { cn } from '../utilities/classNames';
3
+
4
+ export interface MenuItemProps {
5
+ label: string;
6
+ icon?: React.ReactNode;
7
+ onClick?: () => void;
8
+ disabled?: boolean;
9
+ divider?: boolean;
10
+ submenu?: MenuItemProps[];
11
+ className?: string;
12
+ }
13
+
14
+ export interface MenuProps {
15
+ items: MenuItemProps[];
16
+ trigger: React.ReactNode;
17
+ position?: 'top' | 'bottom' | 'left' | 'right';
18
+ className?: string;
19
+ menuClassName?: string;
20
+ }
21
+
22
+ const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps & { onClose?: () => void }>(
23
+ (
24
+ {
25
+ label,
26
+ icon,
27
+ onClick,
28
+ disabled = false,
29
+ divider = false,
30
+ submenu,
31
+ className,
32
+ onClose,
33
+ },
34
+ ref
35
+ ) => {
36
+ const [submenuOpen, setSubmenuOpen] = useState(false);
37
+ const submenuRef = useRef<HTMLDivElement>(null);
38
+
39
+ if (divider) {
40
+ return <div ref={ref} className="my-1 border-t border-gray-200" />;
41
+ }
42
+
43
+ return (
44
+ <div ref={ref} className="relative">
45
+ <button
46
+ onClick={() => {
47
+ if (submenu) {
48
+ setSubmenuOpen(!submenuOpen);
49
+ } else {
50
+ onClick?.();
51
+ onClose?.();
52
+ }
53
+ }}
54
+ disabled={disabled}
55
+ className={cn(
56
+ 'w-full flex items-center gap-2 px-3 py-2 text-sm text-left transition-colors',
57
+ {
58
+ 'text-gray-700 hover:bg-gray-100': !disabled,
59
+ 'text-gray-400 cursor-not-allowed': disabled,
60
+ },
61
+ className
62
+ )}
63
+ >
64
+ {icon && <span className="w-4 h-4 flex-shrink-0">{icon}</span>}
65
+ <span className="flex-1">{label}</span>
66
+ {submenu && (
67
+ <span className={cn('transition-transform', { 'rotate-180': submenuOpen })}>
68
+ ▼
69
+ </span>
70
+ )}
71
+ </button>
72
+
73
+ {submenu && submenuOpen && (
74
+ <div
75
+ ref={submenuRef}
76
+ className="absolute left-full top-0 ml-1 bg-white rounded-lg shadow-lg border border-gray-200 py-1 min-w-max"
77
+ >
78
+ {submenu.map((item, index) => (
79
+ <MenuItem key={index} {...item} onClose={onClose} />
80
+ ))}
81
+ </div>
82
+ )}
83
+ </div>
84
+ );
85
+ }
86
+ );
87
+
88
+ MenuItem.displayName = 'MenuItem';
89
+
90
+ export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(
91
+ (
92
+ {
93
+ items,
94
+ trigger,
95
+ position = 'bottom',
96
+ className,
97
+ menuClassName,
98
+ },
99
+ ref
100
+ ) => {
101
+ const [isOpen, setIsOpen] = useState(false);
102
+ const menuRef = useRef<HTMLDivElement>(null);
103
+ const triggerRef = useRef<HTMLDivElement>(null);
104
+
105
+ useEffect(() => {
106
+ const handleClickOutside = (e: MouseEvent) => {
107
+ if (
108
+ menuRef.current &&
109
+ triggerRef.current &&
110
+ !menuRef.current.contains(e.target as Node) &&
111
+ !triggerRef.current.contains(e.target as Node)
112
+ ) {
113
+ setIsOpen(false);
114
+ }
115
+ };
116
+
117
+ if (isOpen) {
118
+ document.addEventListener('mousedown', handleClickOutside);
119
+ return () => {
120
+ document.removeEventListener('mousedown', handleClickOutside);
121
+ };
122
+ }
123
+ }, [isOpen]);
124
+
125
+ const positionClasses = {
126
+ top: 'bottom-full mb-2',
127
+ bottom: 'top-full mt-2',
128
+ left: 'right-full mr-2',
129
+ right: 'left-full ml-2',
130
+ };
131
+
132
+ return (
133
+ <div ref={ref} className={cn('relative inline-block', className)}>
134
+ <div
135
+ ref={triggerRef}
136
+ onClick={() => setIsOpen(!isOpen)}
137
+ className="cursor-pointer"
138
+ >
139
+ {trigger}
140
+ </div>
141
+
142
+ {isOpen && (
143
+ <div
144
+ ref={menuRef}
145
+ className={cn(
146
+ 'absolute z-50 bg-white rounded-lg shadow-lg border border-gray-200 py-1 min-w-max',
147
+ positionClasses[position],
148
+ menuClassName
149
+ )}
150
+ >
151
+ {items.map((item, index) => (
152
+ <MenuItem
153
+ key={index}
154
+ {...item}
155
+ onClose={() => setIsOpen(false)}
156
+ />
157
+ ))}
158
+ </div>
159
+ )}
160
+ </div>
161
+ );
162
+ }
163
+ );
164
+
165
+ Menu.displayName = 'Menu';
166
+
167
+ export { MenuItem };
@@ -0,0 +1,167 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { cn } from '../utilities/classNames';
3
+
4
+ export interface MenuItemProps {
5
+ label: string;
6
+ icon?: React.ReactNode;
7
+ onClick?: () => void;
8
+ disabled?: boolean;
9
+ divider?: boolean;
10
+ submenu?: MenuItemProps[];
11
+ className?: string;
12
+ }
13
+
14
+ export interface MenuProps {
15
+ items: MenuItemProps[];
16
+ trigger: React.ReactNode;
17
+ position?: 'top' | 'bottom' | 'left' | 'right';
18
+ className?: string;
19
+ menuClassName?: string;
20
+ }
21
+
22
+ const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps & { onClose?: () => void }>(
23
+ (
24
+ {
25
+ label,
26
+ icon,
27
+ onClick,
28
+ disabled = false,
29
+ divider = false,
30
+ submenu,
31
+ className,
32
+ onClose,
33
+ },
34
+ ref
35
+ ) => {
36
+ const [submenuOpen, setSubmenuOpen] = useState(false);
37
+ const submenuRef = useRef<HTMLDivElement>(null);
38
+
39
+ if (divider) {
40
+ return <div ref={ref} className="my-1 border-t border-gray-200" />;
41
+ }
42
+
43
+ return (
44
+ <div ref={ref} className="relative">
45
+ <button
46
+ onClick={() => {
47
+ if (submenu) {
48
+ setSubmenuOpen(!submenuOpen);
49
+ } else {
50
+ onClick?.();
51
+ onClose?.();
52
+ }
53
+ }}
54
+ disabled={disabled}
55
+ className={cn(
56
+ 'w-full flex items-center gap-2 px-3 py-2 text-sm text-left transition-colors',
57
+ {
58
+ 'text-gray-700 hover:bg-gray-100': !disabled,
59
+ 'text-gray-400 cursor-not-allowed': disabled,
60
+ },
61
+ className
62
+ )}
63
+ >
64
+ {icon && <span className="w-4 h-4 flex-shrink-0">{icon}</span>}
65
+ <span className="flex-1">{label}</span>
66
+ {submenu && (
67
+ <span className={cn('transition-transform', { 'rotate-180': submenuOpen })}>
68
+ ▼
69
+ </span>
70
+ )}
71
+ </button>
72
+
73
+ {submenu && submenuOpen && (
74
+ <div
75
+ ref={submenuRef}
76
+ className="absolute left-full top-0 ml-1 bg-white rounded-lg shadow-lg border border-gray-200 py-1 min-w-max"
77
+ >
78
+ {submenu.map((item, index) => (
79
+ <MenuItem key={index} {...item} onClose={onClose} />
80
+ ))}
81
+ </div>
82
+ )}
83
+ </div>
84
+ );
85
+ }
86
+ );
87
+
88
+ MenuItem.displayName = 'MenuItem';
89
+
90
+ export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(
91
+ (
92
+ {
93
+ items,
94
+ trigger,
95
+ position = 'bottom',
96
+ className,
97
+ menuClassName,
98
+ },
99
+ ref
100
+ ) => {
101
+ const [isOpen, setIsOpen] = useState(false);
102
+ const menuRef = useRef<HTMLDivElement>(null);
103
+ const triggerRef = useRef<HTMLDivElement>(null);
104
+
105
+ useEffect(() => {
106
+ const handleClickOutside = (e: MouseEvent) => {
107
+ if (
108
+ menuRef.current &&
109
+ triggerRef.current &&
110
+ !menuRef.current.contains(e.target as Node) &&
111
+ !triggerRef.current.contains(e.target as Node)
112
+ ) {
113
+ setIsOpen(false);
114
+ }
115
+ };
116
+
117
+ if (isOpen) {
118
+ document.addEventListener('mousedown', handleClickOutside);
119
+ return () => {
120
+ document.removeEventListener('mousedown', handleClickOutside);
121
+ };
122
+ }
123
+ }, [isOpen]);
124
+
125
+ const positionClasses = {
126
+ top: 'bottom-full mb-2',
127
+ bottom: 'top-full mt-2',
128
+ left: 'right-full mr-2',
129
+ right: 'left-full ml-2',
130
+ };
131
+
132
+ return (
133
+ <div ref={ref} className={cn('relative inline-block', className)}>
134
+ <div
135
+ ref={triggerRef}
136
+ onClick={() => setIsOpen(!isOpen)}
137
+ className="cursor-pointer"
138
+ >
139
+ {trigger}
140
+ </div>
141
+
142
+ {isOpen && (
143
+ <div
144
+ ref={menuRef}
145
+ className={cn(
146
+ 'absolute z-50 bg-white rounded-lg shadow-lg border border-gray-200 py-1 min-w-max',
147
+ positionClasses[position],
148
+ menuClassName
149
+ )}
150
+ >
151
+ {items.map((item, index) => (
152
+ <MenuItem
153
+ key={index}
154
+ {...item}
155
+ onClose={() => setIsOpen(false)}
156
+ />
157
+ ))}
158
+ </div>
159
+ )}
160
+ </div>
161
+ );
162
+ }
163
+ );
164
+
165
+ Menu.displayName = 'Menu';
166
+
167
+ export { MenuItem };
@@ -0,0 +1,145 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { cn } from '../utilities/classNames';
3
+ import { Button } from './Button';
4
+
5
+ export interface ModalProps {
6
+ open: boolean;
7
+ onClose: () => void;
8
+ title?: string;
9
+ size?: 'sm' | 'md' | 'lg';
10
+ closeButton?: boolean;
11
+ children: React.ReactNode;
12
+ }
13
+
14
+ export const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
15
+ (
16
+ {
17
+ open,
18
+ onClose,
19
+ title,
20
+ size = 'md',
21
+ closeButton = true,
22
+ children,
23
+ },
24
+ ref,
25
+ ) => {
26
+ const dialogRef = useRef<HTMLDivElement>(null);
27
+
28
+ useEffect(() => {
29
+ if (open) {
30
+ document.body.style.overflow = 'hidden';
31
+ } else {
32
+ document.body.style.overflow = 'unset';
33
+ }
34
+
35
+ return () => {
36
+ document.body.style.overflow = 'unset';
37
+ };
38
+ }, [open]);
39
+
40
+ const handleBackdropClick = (e: React.MouseEvent) => {
41
+ if (e.target === e.currentTarget) {
42
+ onClose();
43
+ }
44
+ };
45
+
46
+ if (!open) return null;
47
+
48
+ const sizeStyles = {
49
+ sm: 'max-w-sm',
50
+ md: 'max-w-md',
51
+ lg: 'max-w-lg',
52
+ };
53
+
54
+ return (
55
+ <div
56
+ className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
57
+ onClick={handleBackdropClick}
58
+ >
59
+ <div
60
+ ref={ref || dialogRef}
61
+ className={cn(
62
+ 'bg-white rounded-lg shadow-xl w-full mx-4',
63
+ sizeStyles[size],
64
+ )}
65
+ >
66
+ {(title || closeButton) && (
67
+ <div className="flex items-center justify-between p-6 border-b border-gray-200">
68
+ {title && <h2 className="text-xl font-semibold text-gray-900">{title}</h2>}
69
+ {closeButton && (
70
+ <button
71
+ onClick={onClose}
72
+ className="ml-auto text-gray-400 hover:text-gray-600 transition-colors"
73
+ aria-label="Close modal"
74
+ >
75
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
76
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
77
+ </svg>
78
+ </button>
79
+ )}
80
+ </div>
81
+ )}
82
+ <div className="p-6">
83
+ {children}
84
+ </div>
85
+ </div>
86
+ </div>
87
+ );
88
+ },
89
+ );
90
+
91
+ Modal.displayName = 'Modal';
92
+
93
+ export interface ModalHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
94
+ children: React.ReactNode;
95
+ }
96
+
97
+ export const ModalHeader = React.forwardRef<HTMLDivElement, ModalHeaderProps>(
98
+ ({ children, className, ...props }, ref) => (
99
+ <div
100
+ ref={ref}
101
+ className={cn('px-6 py-4 border-b border-gray-200', className)}
102
+ {...props}
103
+ >
104
+ {children}
105
+ </div>
106
+ ),
107
+ );
108
+
109
+ ModalHeader.displayName = 'ModalHeader';
110
+
111
+ export interface ModalBodyProps extends React.HTMLAttributes<HTMLDivElement> {
112
+ children: React.ReactNode;
113
+ }
114
+
115
+ export const ModalBody = React.forwardRef<HTMLDivElement, ModalBodyProps>(
116
+ ({ children, className, ...props }, ref) => (
117
+ <div
118
+ ref={ref}
119
+ className={cn('px-6 py-4', className)}
120
+ {...props}
121
+ >
122
+ {children}
123
+ </div>
124
+ ),
125
+ );
126
+
127
+ ModalBody.displayName = 'ModalBody';
128
+
129
+ export interface ModalFooterProps extends React.HTMLAttributes<HTMLDivElement> {
130
+ children: React.ReactNode;
131
+ }
132
+
133
+ export const ModalFooter = React.forwardRef<HTMLDivElement, ModalFooterProps>(
134
+ ({ children, className, ...props }, ref) => (
135
+ <div
136
+ ref={ref}
137
+ className={cn('px-6 py-4 border-t border-gray-200 flex justify-end gap-3', className)}
138
+ {...props}
139
+ >
140
+ {children}
141
+ </div>
142
+ ),
143
+ );
144
+
145
+ ModalFooter.displayName = 'ModalFooter';