@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,276 @@
1
+ import React from 'react';
2
+
3
+ export interface NavItem {
4
+ label: string;
5
+ href?: string;
6
+ icon?: React.ReactNode;
7
+ active?: boolean;
8
+ badge?: string | number;
9
+ submenu?: NavItem[];
10
+ }
11
+
12
+ export interface NavigationProps extends React.HTMLAttributes<HTMLElement> {
13
+ items: NavItem[];
14
+ orientation?: 'horizontal' | 'vertical';
15
+ size?: 'sm' | 'md' | 'lg';
16
+ variant?: 'default' | 'filled' | 'pills' | 'minimal';
17
+ onItemClick?: (item: NavItem) => void;
18
+ }
19
+
20
+ export const NavHorizontal: React.FC<NavigationProps> = ({
21
+ items,
22
+ size = 'md',
23
+ variant = 'default',
24
+ onItemClick,
25
+ className = '',
26
+ ...props
27
+ }) => {
28
+ const sizeClass = { sm: 'px-2 py-1 text-sm', md: 'px-4 py-2 text-base', lg: 'px-6 py-3 text-lg' }[size];
29
+
30
+ return (
31
+ <nav className={`flex gap-4 ${className}`} {...props}>
32
+ {items.map((item, idx) => (
33
+ <button
34
+ key={idx}
35
+ onClick={() => onItemClick?.(item)}
36
+ className={`flex items-center gap-2 rounded ${sizeClass} transition-colors ${item.active ? 'bg-blue-600 text-white' : 'text-gray-700 hover:bg-gray-100'}`}
37
+ >
38
+ {item.icon}
39
+ {item.label}
40
+ {item.badge && <span className="ml-2 bg-red-500 text-white text-xs px-2 py-1 rounded-full">{item.badge}</span>}
41
+ </button>
42
+ ))}
43
+ </nav>
44
+ );
45
+ };
46
+
47
+ export const NavVertical: React.FC<NavigationProps> = ({
48
+ items,
49
+ size = 'md',
50
+ variant = 'default',
51
+ onItemClick,
52
+ className = '',
53
+ ...props
54
+ }) => {
55
+ const sizeClass = { sm: 'px-3 py-2 text-sm', md: 'px-4 py-3 text-base', lg: 'px-6 py-4 text-lg' }[size];
56
+
57
+ return (
58
+ <nav className={`flex flex-col gap-1 ${className}`} {...props}>
59
+ {items.map((item, idx) => (
60
+ <button
61
+ key={idx}
62
+ onClick={() => onItemClick?.(item)}
63
+ className={`w-full text-left flex items-center gap-2 rounded ${sizeClass} transition-colors ${item.active ? 'bg-blue-600 text-white' : 'text-gray-700 hover:bg-gray-100'}`}
64
+ >
65
+ {item.icon}
66
+ {item.label}
67
+ {item.badge && <span className="ml-auto bg-red-500 text-white text-xs px-2 py-1 rounded-full">{item.badge}</span>}
68
+ </button>
69
+ ))}
70
+ </nav>
71
+ );
72
+ };
73
+
74
+ export const NavTabs: React.FC<NavigationProps> = ({
75
+ items,
76
+ onItemClick,
77
+ className = '',
78
+ ...props
79
+ }) => {
80
+ return (
81
+ <nav className={`flex border-b border-gray-200 gap-1 ${className}`} {...props}>
82
+ {items.map((item, idx) => (
83
+ <button
84
+ key={idx}
85
+ onClick={() => onItemClick?.(item)}
86
+ className={`px-6 py-3 border-b-2 transition-colors font-medium ${item.active ? 'border-blue-600 text-blue-600' : 'border-transparent text-gray-600 hover:text-gray-900'}`}
87
+ >
88
+ {item.label}
89
+ </button>
90
+ ))}
91
+ </nav>
92
+ );
93
+ };
94
+
95
+ export const NavPills: React.FC<NavigationProps> = ({
96
+ items,
97
+ onItemClick,
98
+ className = '',
99
+ ...props
100
+ }) => {
101
+ return (
102
+ <nav className={`flex gap-2 flex-wrap ${className}`} {...props}>
103
+ {items.map((item, idx) => (
104
+ <button
105
+ key={idx}
106
+ onClick={() => onItemClick?.(item)}
107
+ className={`px-4 py-2 rounded-full font-medium transition-colors ${item.active ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}`}
108
+ >
109
+ {item.label}
110
+ </button>
111
+ ))}
112
+ </nav>
113
+ );
114
+ };
115
+
116
+ export const NavBreadcrumbs: React.FC<NavigationProps> = ({
117
+ items,
118
+ onItemClick,
119
+ className = '',
120
+ ...props
121
+ }) => {
122
+ return (
123
+ <nav className={`flex items-center gap-2 ${className}`} {...props}>
124
+ {items.map((item, idx) => (
125
+ <React.Fragment key={idx}>
126
+ <button
127
+ onClick={() => onItemClick?.(item)}
128
+ className="text-blue-600 hover:text-blue-700 hover:underline"
129
+ >
130
+ {item.label}
131
+ </button>
132
+ {idx < items.length - 1 && <span className="text-gray-400">/</span>}
133
+ </React.Fragment>
134
+ ))}
135
+ </nav>
136
+ );
137
+ };
138
+
139
+ export const NavSteps: React.FC<
140
+ NavigationProps & {
141
+ currentStep?: number;
142
+ }
143
+ > = ({
144
+ items,
145
+ currentStep = 0,
146
+ onItemClick,
147
+ className = '',
148
+ ...props
149
+ }) => {
150
+ return (
151
+ <div className={`flex items-center gap-4 ${className}`} {...props}>
152
+ {items.map((item, idx) => (
153
+ <React.Fragment key={idx}>
154
+ <div className="flex flex-col items-center gap-2">
155
+ <div
156
+ className={`w-10 h-10 rounded-full flex items-center justify-center font-bold ${idx <= currentStep ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-600'}`}
157
+ >
158
+ {idx + 1}
159
+ </div>
160
+ <span className="text-sm text-gray-600">{item.label}</span>
161
+ </div>
162
+ {idx < items.length - 1 && (
163
+ <div
164
+ className={`flex-1 h-1 ${idx < currentStep ? 'bg-blue-600' : 'bg-gray-200'}`}
165
+ style={{ minWidth: '40px' }}
166
+ ></div>
167
+ )}
168
+ </React.Fragment>
169
+ ))}
170
+ </div>
171
+ );
172
+ };
173
+
174
+ export const NavTree: React.FC<NavigationProps & { expandable?: boolean }> = ({
175
+ items,
176
+ expandable = true,
177
+ onItemClick,
178
+ className = '',
179
+ ...props
180
+ }) => {
181
+ const [expanded, setExpanded] = React.useState<Set<number>>(new Set());
182
+
183
+ const toggleExpanded = (idx: number) => {
184
+ const newExpanded = new Set(expanded);
185
+ if (newExpanded.has(idx)) {
186
+ newExpanded.delete(idx);
187
+ } else {
188
+ newExpanded.add(idx);
189
+ }
190
+ setExpanded(newExpanded);
191
+ };
192
+
193
+ return (
194
+ <nav className={`space-y-1 ${className}`} {...props}>
195
+ {items.map((item, idx) => (
196
+ <div key={idx}>
197
+ <div className="flex items-center gap-2">
198
+ {item.submenu && expandable && (
199
+ <button
200
+ onClick={() => toggleExpanded(idx)}
201
+ className="w-4 h-4 flex items-center justify-center"
202
+ >
203
+ {expanded.has(idx) ? '▼' : '▶'}
204
+ </button>
205
+ )}
206
+ <button
207
+ onClick={() => onItemClick?.(item)}
208
+ className="flex-1 text-left px-3 py-2 rounded text-gray-700 hover:bg-gray-100"
209
+ >
210
+ {item.label}
211
+ </button>
212
+ </div>
213
+ {item.submenu && expanded.has(idx) && (
214
+ <div className="ml-4 border-l border-gray-200">
215
+ {item.submenu.map((subitem, sidx) => (
216
+ <button
217
+ key={sidx}
218
+ onClick={() => onItemClick?.(subitem)}
219
+ className="w-full text-left px-3 py-2 rounded text-gray-600 hover:bg-gray-100 text-sm"
220
+ >
221
+ {subitem.label}
222
+ </button>
223
+ ))}
224
+ </div>
225
+ )}
226
+ </div>
227
+ ))}
228
+ </nav>
229
+ );
230
+ };
231
+
232
+ export const NavMegamenu: React.FC<
233
+ NavigationProps & {
234
+ columns?: number;
235
+ }
236
+ > = ({
237
+ items,
238
+ columns = 3,
239
+ onItemClick,
240
+ className = '',
241
+ ...props
242
+ }) => {
243
+ const [openMenu, setOpenMenu] = React.useState<number | null>(null);
244
+
245
+ return (
246
+ <nav className={`relative ${className}`} {...props}>
247
+ <div className="flex gap-6">
248
+ {items.map((item, idx) => (
249
+ <div key={idx} className="relative group">
250
+ <button
251
+ onClick={() => setOpenMenu(openMenu === idx ? null : idx)}
252
+ className="px-4 py-3 font-medium text-gray-700 hover:text-gray-900"
253
+ >
254
+ {item.label}
255
+ </button>
256
+ {item.submenu && (
257
+ <div className="absolute top-full left-0 hidden group-hover:block bg-white shadow-lg rounded-lg p-6 min-w-max">
258
+ <div className={`grid grid-cols-${columns} gap-6`}>
259
+ {item.submenu.map((subitem, sidx) => (
260
+ <button
261
+ key={sidx}
262
+ onClick={() => onItemClick?.(subitem)}
263
+ className="text-left text-gray-700 hover:text-blue-600"
264
+ >
265
+ <div className="font-medium">{subitem.label}</div>
266
+ </button>
267
+ ))}
268
+ </div>
269
+ </div>
270
+ )}
271
+ </div>
272
+ ))}
273
+ </div>
274
+ </nav>
275
+ );
276
+ };
@@ -0,0 +1,94 @@
1
+ import React from 'react';
2
+ import { cn } from '../utilities/classNames';
3
+
4
+ export interface PaginationProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ currentPage: number;
6
+ totalPages: number;
7
+ onPageChange: (page: number) => void;
8
+ maxVisible?: number;
9
+ }
10
+
11
+ export const Pagination = React.forwardRef<HTMLDivElement, PaginationProps>(
12
+ (
13
+ {
14
+ currentPage,
15
+ totalPages,
16
+ onPageChange,
17
+ maxVisible = 5,
18
+ className,
19
+ ...props
20
+ },
21
+ ref,
22
+ ) => {
23
+ const getPageNumbers = () => {
24
+ const pages: (number | string)[] = [];
25
+ let start = Math.max(1, currentPage - Math.floor(maxVisible / 2));
26
+ let end = Math.min(totalPages, start + maxVisible - 1);
27
+
28
+ if (end - start < maxVisible - 1) {
29
+ start = Math.max(1, end - maxVisible + 1);
30
+ }
31
+
32
+ if (start > 1) {
33
+ pages.push(1);
34
+ if (start > 2) pages.push('...');
35
+ }
36
+
37
+ for (let i = start; i <= end; i++) {
38
+ pages.push(i);
39
+ }
40
+
41
+ if (end < totalPages) {
42
+ if (end < totalPages - 1) pages.push('...');
43
+ pages.push(totalPages);
44
+ }
45
+
46
+ return pages;
47
+ };
48
+
49
+ return (
50
+ <div
51
+ ref={ref}
52
+ className={cn('flex items-center gap-1', className)}
53
+ {...props}
54
+ >
55
+ <button
56
+ onClick={() => onPageChange(currentPage - 1)}
57
+ disabled={currentPage === 1}
58
+ className="px-3 py-2 border border-gray-300 rounded-md disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50"
59
+ aria-label="Previous page"
60
+ >
61
+ ←
62
+ </button>
63
+
64
+ {getPageNumbers().map((page, index) => (
65
+ <button
66
+ key={index}
67
+ onClick={() => typeof page === 'number' && onPageChange(page)}
68
+ disabled={page === '...'}
69
+ className={cn(
70
+ 'px-3 py-2 border rounded-md transition-colors',
71
+ page === currentPage
72
+ ? 'bg-blue-600 text-white border-blue-600'
73
+ : 'border-gray-300 hover:bg-gray-50 disabled:cursor-not-allowed',
74
+ )}
75
+ aria-current={page === currentPage ? 'page' : undefined}
76
+ >
77
+ {page}
78
+ </button>
79
+ ))}
80
+
81
+ <button
82
+ onClick={() => onPageChange(currentPage + 1)}
83
+ disabled={currentPage === totalPages}
84
+ className="px-3 py-2 border border-gray-300 rounded-md disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50"
85
+ aria-label="Next page"
86
+ >
87
+ →
88
+ </button>
89
+ </div>
90
+ );
91
+ },
92
+ );
93
+
94
+ Pagination.displayName = 'Pagination';
@@ -0,0 +1,94 @@
1
+ import React from 'react';
2
+ import { cn } from '../utilities/classNames';
3
+
4
+ export interface PaginationProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ currentPage: number;
6
+ totalPages: number;
7
+ onPageChange: (page: number) => void;
8
+ maxVisible?: number;
9
+ }
10
+
11
+ export const Pagination = React.forwardRef<HTMLDivElement, PaginationProps>(
12
+ (
13
+ {
14
+ currentPage,
15
+ totalPages,
16
+ onPageChange,
17
+ maxVisible = 5,
18
+ className,
19
+ ...props
20
+ },
21
+ ref,
22
+ ) => {
23
+ const getPageNumbers = () => {
24
+ const pages: (number | string)[] = [];
25
+ let start = Math.max(1, currentPage - Math.floor(maxVisible / 2));
26
+ let end = Math.min(totalPages, start + maxVisible - 1);
27
+
28
+ if (end - start < maxVisible - 1) {
29
+ start = Math.max(1, end - maxVisible + 1);
30
+ }
31
+
32
+ if (start > 1) {
33
+ pages.push(1);
34
+ if (start > 2) pages.push('...');
35
+ }
36
+
37
+ for (let i = start; i <= end; i++) {
38
+ pages.push(i);
39
+ }
40
+
41
+ if (end < totalPages) {
42
+ if (end < totalPages - 1) pages.push('...');
43
+ pages.push(totalPages);
44
+ }
45
+
46
+ return pages;
47
+ };
48
+
49
+ return (
50
+ <div
51
+ ref={ref}
52
+ className={cn('flex items-center gap-1', className)}
53
+ {...props}
54
+ >
55
+ <button
56
+ onClick={() => onPageChange(currentPage - 1)}
57
+ disabled={currentPage === 1}
58
+ className="px-3 py-2 border border-gray-300 rounded-md disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50"
59
+ aria-label="Previous page"
60
+ >
61
+ ←
62
+ </button>
63
+
64
+ {getPageNumbers().map((page, index) => (
65
+ <button
66
+ key={index}
67
+ onClick={() => typeof page === 'number' && onPageChange(page)}
68
+ disabled={page === '...'}
69
+ className={cn(
70
+ 'px-3 py-2 border rounded-md transition-colors',
71
+ page === currentPage
72
+ ? 'bg-blue-600 text-white border-blue-600'
73
+ : 'border-gray-300 hover:bg-gray-50 disabled:cursor-not-allowed',
74
+ )}
75
+ aria-current={page === currentPage ? 'page' : undefined}
76
+ >
77
+ {page}
78
+ </button>
79
+ ))}
80
+
81
+ <button
82
+ onClick={() => onPageChange(currentPage + 1)}
83
+ disabled={currentPage === totalPages}
84
+ className="px-3 py-2 border border-gray-300 rounded-md disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50"
85
+ aria-label="Next page"
86
+ >
87
+ →
88
+ </button>
89
+ </div>
90
+ );
91
+ },
92
+ );
93
+
94
+ Pagination.displayName = 'Pagination';
@@ -0,0 +1,108 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { cn } from '../utilities/classNames';
3
+
4
+ export interface PopoverProps {
5
+ children: React.ReactNode;
6
+ content: React.ReactNode;
7
+ position?: 'top' | 'bottom' | 'left' | 'right';
8
+ trigger?: 'hover' | 'click';
9
+ className?: string;
10
+ contentClassName?: string;
11
+ arrow?: boolean;
12
+ offset?: number;
13
+ }
14
+
15
+ export const Popover = React.forwardRef<HTMLDivElement, PopoverProps>(
16
+ (
17
+ {
18
+ children,
19
+ content,
20
+ position = 'top',
21
+ trigger = 'hover',
22
+ className,
23
+ contentClassName,
24
+ arrow = true,
25
+ offset = 8,
26
+ },
27
+ ref
28
+ ) => {
29
+ const [isOpen, setIsOpen] = useState(false);
30
+ const triggerRef = useRef<HTMLDivElement>(null);
31
+ const popoverRef = useRef<HTMLDivElement>(null);
32
+
33
+ useEffect(() => {
34
+ if (trigger === 'click') {
35
+ const handleClickOutside = (e: MouseEvent) => {
36
+ if (
37
+ popoverRef.current &&
38
+ triggerRef.current &&
39
+ !popoverRef.current.contains(e.target as Node) &&
40
+ !triggerRef.current.contains(e.target as Node)
41
+ ) {
42
+ setIsOpen(false);
43
+ }
44
+ };
45
+
46
+ if (isOpen) {
47
+ document.addEventListener('mousedown', handleClickOutside);
48
+ return () => {
49
+ document.removeEventListener('mousedown', handleClickOutside);
50
+ };
51
+ }
52
+ }
53
+ }, [isOpen, trigger]);
54
+
55
+ const positionClasses = {
56
+ top: 'bottom-full mb-2 left-1/2 -translate-x-1/2',
57
+ bottom: 'top-full mt-2 left-1/2 -translate-x-1/2',
58
+ left: 'right-full mr-2 top-1/2 -translate-y-1/2',
59
+ right: 'left-full ml-2 top-1/2 -translate-y-1/2',
60
+ };
61
+
62
+ const arrowClasses = {
63
+ top: 'bottom-[-4px] left-1/2 -translate-x-1/2 border-t-gray-900 border-l-transparent border-r-transparent border-b-transparent',
64
+ bottom: 'top-[-4px] left-1/2 -translate-x-1/2 border-b-gray-900 border-l-transparent border-r-transparent border-t-transparent',
65
+ left: 'left-[-4px] top-1/2 -translate-y-1/2 border-l-gray-900 border-t-transparent border-b-transparent border-r-transparent',
66
+ right: 'right-[-4px] top-1/2 -translate-y-1/2 border-r-gray-900 border-t-transparent border-b-transparent border-l-transparent',
67
+ };
68
+
69
+ return (
70
+ <div
71
+ ref={ref}
72
+ className={cn('relative inline-block', className)}
73
+ onMouseEnter={() => trigger === 'hover' && setIsOpen(true)}
74
+ onMouseLeave={() => trigger === 'hover' && setIsOpen(false)}
75
+ >
76
+ <div
77
+ ref={triggerRef}
78
+ onClick={() => trigger === 'click' && setIsOpen(!isOpen)}
79
+ >
80
+ {children}
81
+ </div>
82
+
83
+ {isOpen && (
84
+ <div
85
+ ref={popoverRef}
86
+ className={cn(
87
+ 'absolute z-50 bg-gray-900 text-white text-sm rounded-lg shadow-lg p-3 max-w-xs',
88
+ positionClasses[position],
89
+ contentClassName
90
+ )}
91
+ >
92
+ {content}
93
+ {arrow && (
94
+ <div
95
+ className={cn(
96
+ 'absolute w-0 h-0 border-4',
97
+ arrowClasses[position]
98
+ )}
99
+ />
100
+ )}
101
+ </div>
102
+ )}
103
+ </div>
104
+ );
105
+ }
106
+ );
107
+
108
+ Popover.displayName = 'Popover';
@@ -0,0 +1,108 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { cn } from '../utilities/classNames';
3
+
4
+ export interface PopoverProps {
5
+ children: React.ReactNode;
6
+ content: React.ReactNode;
7
+ position?: 'top' | 'bottom' | 'left' | 'right';
8
+ trigger?: 'hover' | 'click';
9
+ className?: string;
10
+ contentClassName?: string;
11
+ arrow?: boolean;
12
+ offset?: number;
13
+ }
14
+
15
+ export const Popover = React.forwardRef<HTMLDivElement, PopoverProps>(
16
+ (
17
+ {
18
+ children,
19
+ content,
20
+ position = 'top',
21
+ trigger = 'hover',
22
+ className,
23
+ contentClassName,
24
+ arrow = true,
25
+ offset = 8,
26
+ },
27
+ ref
28
+ ) => {
29
+ const [isOpen, setIsOpen] = useState(false);
30
+ const triggerRef = useRef<HTMLDivElement>(null);
31
+ const popoverRef = useRef<HTMLDivElement>(null);
32
+
33
+ useEffect(() => {
34
+ if (trigger === 'click') {
35
+ const handleClickOutside = (e: MouseEvent) => {
36
+ if (
37
+ popoverRef.current &&
38
+ triggerRef.current &&
39
+ !popoverRef.current.contains(e.target as Node) &&
40
+ !triggerRef.current.contains(e.target as Node)
41
+ ) {
42
+ setIsOpen(false);
43
+ }
44
+ };
45
+
46
+ if (isOpen) {
47
+ document.addEventListener('mousedown', handleClickOutside);
48
+ return () => {
49
+ document.removeEventListener('mousedown', handleClickOutside);
50
+ };
51
+ }
52
+ }
53
+ }, [isOpen, trigger]);
54
+
55
+ const positionClasses = {
56
+ top: 'bottom-full mb-2 left-1/2 -translate-x-1/2',
57
+ bottom: 'top-full mt-2 left-1/2 -translate-x-1/2',
58
+ left: 'right-full mr-2 top-1/2 -translate-y-1/2',
59
+ right: 'left-full ml-2 top-1/2 -translate-y-1/2',
60
+ };
61
+
62
+ const arrowClasses = {
63
+ top: 'bottom-[-4px] left-1/2 -translate-x-1/2 border-t-gray-900 border-l-transparent border-r-transparent border-b-transparent',
64
+ bottom: 'top-[-4px] left-1/2 -translate-x-1/2 border-b-gray-900 border-l-transparent border-r-transparent border-t-transparent',
65
+ left: 'left-[-4px] top-1/2 -translate-y-1/2 border-l-gray-900 border-t-transparent border-b-transparent border-r-transparent',
66
+ right: 'right-[-4px] top-1/2 -translate-y-1/2 border-r-gray-900 border-t-transparent border-b-transparent border-l-transparent',
67
+ };
68
+
69
+ return (
70
+ <div
71
+ ref={ref}
72
+ className={cn('relative inline-block', className)}
73
+ onMouseEnter={() => trigger === 'hover' && setIsOpen(true)}
74
+ onMouseLeave={() => trigger === 'hover' && setIsOpen(false)}
75
+ >
76
+ <div
77
+ ref={triggerRef}
78
+ onClick={() => trigger === 'click' && setIsOpen(!isOpen)}
79
+ >
80
+ {children}
81
+ </div>
82
+
83
+ {isOpen && (
84
+ <div
85
+ ref={popoverRef}
86
+ className={cn(
87
+ 'absolute z-50 bg-gray-900 text-white text-sm rounded-lg shadow-lg p-3 max-w-xs',
88
+ positionClasses[position],
89
+ contentClassName
90
+ )}
91
+ >
92
+ {content}
93
+ {arrow && (
94
+ <div
95
+ className={cn(
96
+ 'absolute w-0 h-0 border-4',
97
+ arrowClasses[position]
98
+ )}
99
+ />
100
+ )}
101
+ </div>
102
+ )}
103
+ </div>
104
+ );
105
+ }
106
+ );
107
+
108
+ Popover.displayName = 'Popover';