@axzydev/axzy_ui_system 1.2.0 → 1.2.2

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 (202) hide show
  1. package/dist/index.css +82 -197
  2. package/dist/index.css.map +1 -1
  3. package/package.json +2 -2
  4. package/src/App.tsx +354 -0
  5. package/src/assets/logo.png +0 -0
  6. package/src/assets/react.svg +1 -0
  7. package/src/components/alert/alert.props.ts +13 -0
  8. package/src/components/alert/alert.stories.tsx +41 -0
  9. package/src/components/alert/alert.tsx +53 -0
  10. package/src/components/avatar/avatar.props.ts +14 -0
  11. package/src/components/avatar/avatar.stories.tsx +46 -0
  12. package/src/components/avatar/avatar.tsx +53 -0
  13. package/src/components/badget/badget.props.ts +12 -0
  14. package/src/components/badget/badget.stories.tsx +76 -0
  15. package/src/components/badget/badget.tsx +61 -0
  16. package/src/components/breadcrumbs/breadcrumbs.props.ts +13 -0
  17. package/src/components/breadcrumbs/breadcrumbs.stories.tsx +21 -0
  18. package/src/components/breadcrumbs/breadcrumbs.tsx +34 -0
  19. package/src/components/button/button.props.ts +18 -0
  20. package/src/components/button/button.stories.tsx +174 -0
  21. package/src/components/button/button.tsx +117 -0
  22. package/src/components/calendar/calendar.props.ts +33 -0
  23. package/src/components/calendar/calendar.stories.tsx +91 -0
  24. package/src/components/calendar/calendar.tsx +608 -0
  25. package/src/components/calendar/index.ts +3 -0
  26. package/src/components/card/card.props.ts +13 -0
  27. package/src/components/card/card.stories.tsx +58 -0
  28. package/src/components/card/card.tsx +79 -0
  29. package/src/components/checkbox/checkbox.props.ts +11 -0
  30. package/src/components/checkbox/checkbox.stories.tsx +54 -0
  31. package/src/components/checkbox/checkbox.tsx +52 -0
  32. package/src/components/confirm-dialog/confirm-dialog.props.ts +14 -0
  33. package/src/components/confirm-dialog/confirm-dialog.stories.tsx +33 -0
  34. package/src/components/confirm-dialog/confirm-dialog.tsx +45 -0
  35. package/src/components/data-table/ITDataTable.stories.tsx +213 -0
  36. package/src/components/data-table/dataTable.props.ts +69 -0
  37. package/src/components/data-table/dataTable.tsx +313 -0
  38. package/src/components/date-picker/date-picker.props.ts +30 -0
  39. package/src/components/date-picker/date-picker.stories.tsx +90 -0
  40. package/src/components/date-picker/datePicker.tsx +307 -0
  41. package/src/components/dialog/dialog.props.ts +9 -0
  42. package/src/components/dialog/dialog.stories.tsx +80 -0
  43. package/src/components/dialog/dialog.tsx +88 -0
  44. package/src/components/divider/divider.props.ts +8 -0
  45. package/src/components/divider/divider.stories.tsx +34 -0
  46. package/src/components/divider/divider.tsx +21 -0
  47. package/src/components/drawer/drawer.props.ts +14 -0
  48. package/src/components/drawer/drawer.stories.tsx +41 -0
  49. package/src/components/drawer/drawer.tsx +53 -0
  50. package/src/components/dropfile/dropfile.stories.tsx +75 -0
  51. package/src/components/dropfile/dropfile.tsx +407 -0
  52. package/src/components/empty-state/empty-state.props.ts +9 -0
  53. package/src/components/empty-state/empty-state.stories.tsx +20 -0
  54. package/src/components/empty-state/empty-state.tsx +21 -0
  55. package/src/components/flex/flex.props.ts +22 -0
  56. package/src/components/flex/flex.stories.tsx +71 -0
  57. package/src/components/flex/flex.tsx +79 -0
  58. package/src/components/form-builder/fieldRenderer.tsx +218 -0
  59. package/src/components/form-builder/formBuilder.context.tsx +70 -0
  60. package/src/components/form-builder/formBuilder.props.ts +43 -0
  61. package/src/components/form-builder/formBuilder.stories.tsx +317 -0
  62. package/src/components/form-builder/formBuilder.tsx +186 -0
  63. package/src/components/form-builder/useFormBuilder.ts +80 -0
  64. package/src/components/form-header/form-header.props.ts +5 -0
  65. package/src/components/form-header/form-header.tsx +38 -0
  66. package/src/components/grid/grid.props.ts +17 -0
  67. package/src/components/grid/grid.stories.tsx +72 -0
  68. package/src/components/grid/grid.tsx +69 -0
  69. package/src/components/image/image.props.ts +7 -0
  70. package/src/components/image/image.tsx +38 -0
  71. package/src/components/input/input.props.ts +49 -0
  72. package/src/components/input/input.stories.tsx +115 -0
  73. package/src/components/input/input.tsx +615 -0
  74. package/src/components/layout/layout.props.ts +10 -0
  75. package/src/components/layout/layout.stories.tsx +114 -0
  76. package/src/components/layout/layout.tsx +80 -0
  77. package/src/components/loader/loader.props.ts +8 -0
  78. package/src/components/loader/loader.stories.tsx +105 -0
  79. package/src/components/loader/loader.tsx +108 -0
  80. package/src/components/navbar/navbar.props.ts +37 -0
  81. package/src/components/navbar/navbar.tsx +328 -0
  82. package/src/components/page/page.props.ts +19 -0
  83. package/src/components/page/page.stories.tsx +98 -0
  84. package/src/components/page/page.tsx +90 -0
  85. package/src/components/page-header/page-header.props.ts +11 -0
  86. package/src/components/page-header/page-header.stories.tsx +61 -0
  87. package/src/components/page-header/page-header.tsx +62 -0
  88. package/src/components/pagination/pagination.props.ts +53 -0
  89. package/src/components/pagination/pagination.stories.tsx +111 -0
  90. package/src/components/pagination/pagination.tsx +241 -0
  91. package/src/components/popover/popover.props.ts +12 -0
  92. package/src/components/popover/popover.stories.tsx +25 -0
  93. package/src/components/popover/popover.tsx +45 -0
  94. package/src/components/progress/progress.props.ts +12 -0
  95. package/src/components/progress/progress.stories.tsx +40 -0
  96. package/src/components/progress/progress.tsx +52 -0
  97. package/src/components/radio/radio.props.ts +16 -0
  98. package/src/components/radio/radio.stories.tsx +50 -0
  99. package/src/components/radio/radio.tsx +58 -0
  100. package/src/components/search-select/index.ts +2 -0
  101. package/src/components/search-select/search-select.props.ts +46 -0
  102. package/src/components/search-select/search-select.stories.tsx +129 -0
  103. package/src/components/search-select/search-select.tsx +229 -0
  104. package/src/components/searchTable/components/EditableCell.tsx +149 -0
  105. package/src/components/searchTable/components/PaginationControls.tsx +86 -0
  106. package/src/components/searchTable/components/PaginationInfo.tsx +20 -0
  107. package/src/components/searchTable/components/SearchAndSortBar.tsx +53 -0
  108. package/src/components/searchTable/components/SearchInput.tsx +33 -0
  109. package/src/components/searchTable/components/SortButton.tsx +50 -0
  110. package/src/components/searchTable/components/TableEmptyState.tsx +22 -0
  111. package/src/components/searchTable/components/TableHeader.tsx +35 -0
  112. package/src/components/searchTable/components/TableHeaderCell.tsx +43 -0
  113. package/src/components/searchTable/components/TableRow.tsx +144 -0
  114. package/src/components/searchTable/searchTable.props.ts +56 -0
  115. package/src/components/searchTable/searchTable.tsx +187 -0
  116. package/src/components/segmented-control/segmented-control.props.ts +18 -0
  117. package/src/components/segmented-control/segmented-control.stories.tsx +63 -0
  118. package/src/components/segmented-control/segmented-control.tsx +52 -0
  119. package/src/components/select/select.props.ts +25 -0
  120. package/src/components/select/select.stories.tsx +86 -0
  121. package/src/components/select/select.tsx +150 -0
  122. package/src/components/sidebar/sidebar.props.ts +28 -0
  123. package/src/components/sidebar/sidebar.stories.tsx +117 -0
  124. package/src/components/sidebar/sidebar.tsx +313 -0
  125. package/src/components/skeleton/skeleton.props.ts +12 -0
  126. package/src/components/skeleton/skeleton.stories.tsx +30 -0
  127. package/src/components/skeleton/skeleton.tsx +45 -0
  128. package/src/components/slide/slide.props.ts +45 -0
  129. package/src/components/slide/slide.stories.tsx +121 -0
  130. package/src/components/slide/slide.tsx +109 -0
  131. package/src/components/slider/slider.props.ts +10 -0
  132. package/src/components/slider/slider.stories.tsx +30 -0
  133. package/src/components/slider/slider.tsx +49 -0
  134. package/src/components/stack/stack.props.ts +19 -0
  135. package/src/components/stack/stack.stories.tsx +79 -0
  136. package/src/components/stack/stack.tsx +79 -0
  137. package/src/components/stat-card/stat-card.props.ts +13 -0
  138. package/src/components/stat-card/stat-card.stories.tsx +41 -0
  139. package/src/components/stat-card/stat-card.tsx +44 -0
  140. package/src/components/stepper/stepper.css +26 -0
  141. package/src/components/stepper/stepper.props.ts +29 -0
  142. package/src/components/stepper/stepper.stories.tsx +155 -0
  143. package/src/components/stepper/stepper.tsx +227 -0
  144. package/src/components/table/table.props.ts +43 -0
  145. package/src/components/table/table.stories.tsx +189 -0
  146. package/src/components/table/table.tsx +376 -0
  147. package/src/components/tabs/tabs.props.ts +18 -0
  148. package/src/components/tabs/tabs.stories.tsx +32 -0
  149. package/src/components/tabs/tabs.tsx +74 -0
  150. package/src/components/text/text.props.ts +9 -0
  151. package/src/components/text/text.tsx +20 -0
  152. package/src/components/textarea/textarea.props.ts +15 -0
  153. package/src/components/textarea/textarea.stories.tsx +27 -0
  154. package/src/components/textarea/textarea.tsx +55 -0
  155. package/src/components/theme-provider/themeProvider.props.ts +28 -0
  156. package/src/components/theme-provider/themeProvider.tsx +1854 -0
  157. package/src/components/time-picker/timePicker.props.ts +16 -0
  158. package/src/components/time-picker/timePicker.stories.tsx +131 -0
  159. package/src/components/time-picker/timePicker.tsx +317 -0
  160. package/src/components/toast/toast.css +32 -0
  161. package/src/components/toast/toast.props.ts +13 -0
  162. package/src/components/toast/toast.stories.tsx +138 -0
  163. package/src/components/toast/toast.tsx +87 -0
  164. package/src/components/tooltip/tooltip.props.ts +11 -0
  165. package/src/components/tooltip/tooltip.stories.tsx +20 -0
  166. package/src/components/tooltip/tooltip.tsx +55 -0
  167. package/src/components/topbar/topbar.props.ts +21 -0
  168. package/src/components/topbar/topbar.stories.tsx +80 -0
  169. package/src/components/topbar/topbar.tsx +205 -0
  170. package/src/components/triple-filter/tripleFilter.props.ts +15 -0
  171. package/src/components/triple-filter/tripleFilter.stories.tsx +32 -0
  172. package/src/components/triple-filter/tripleFilter.tsx +50 -0
  173. package/src/hooks/useClickOutside.ts +21 -0
  174. package/src/hooks/useDebouncedSearch.ts +55 -0
  175. package/src/hooks/useEditableRow.ts +157 -0
  176. package/src/hooks/useTableState.ts +122 -0
  177. package/src/index.css +168 -0
  178. package/src/index.ts +165 -0
  179. package/src/main.tsx +9 -0
  180. package/src/showcases/DataShowcases.tsx +260 -0
  181. package/src/showcases/FeedbackShowcases.tsx +268 -0
  182. package/src/showcases/FormShowcases.tsx +1159 -0
  183. package/src/showcases/HomeShowcase.tsx +324 -0
  184. package/src/showcases/LayoutPrimitivesShowcases.tsx +569 -0
  185. package/src/showcases/NavigationShowcases.tsx +193 -0
  186. package/src/showcases/PageShowcases.tsx +207 -0
  187. package/src/showcases/ShowcaseLayout.tsx +139 -0
  188. package/src/showcases/StructureShowcases.tsx +152 -0
  189. package/src/types/badget.types.ts +37 -0
  190. package/src/types/button.types.ts +16 -0
  191. package/src/types/colors.types.ts +3 -0
  192. package/src/types/field.types.ts +103 -0
  193. package/src/types/formik.types.ts +15 -0
  194. package/src/types/input.types.ts +14 -0
  195. package/src/types/loader.types.ts +9 -0
  196. package/src/types/sizes.types.ts +1 -0
  197. package/src/types/table.types.ts +15 -0
  198. package/src/types/toast.types.ts +8 -0
  199. package/src/types/yup.types.ts +11 -0
  200. package/src/utils/color.utils.ts +99 -0
  201. package/src/utils/styles.ts +120 -0
  202. package/src/utils/table.utils.ts +10 -0
@@ -0,0 +1,328 @@
1
+ import useClickOutside from "@/hooks/useClickOutside";
2
+ import { useRef, useState } from "react";
3
+ import { FaChevronDown, FaChevronRight, FaUserCircle } from "react-icons/fa";
4
+ import { ITNavbarProps, ITNavigationItem } from "./navbar.props";
5
+ import ITText from "@/components/text/text";
6
+
7
+ export default function ITNavbar({
8
+ logo,
9
+ logoText,
10
+ navigationItems = [],
11
+ userMenu,
12
+ children,
13
+ // Legacy props for backward compatibility
14
+ navItems,
15
+ showSidebar = false,
16
+ showSidebarOnMobile = false,
17
+ sidebarItems,
18
+ }: ITNavbarProps) {
19
+ const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
20
+ const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
21
+
22
+ const userMenuRef = useRef<HTMLDivElement>(null);
23
+
24
+ useClickOutside(userMenuRef, () => setIsUserMenuOpen(false));
25
+
26
+ const toggleUserMenu = () => {
27
+ setIsUserMenuOpen(!isUserMenuOpen);
28
+ };
29
+
30
+ const toggleExpanded = (itemId: string) => {
31
+ const newExpanded = new Set(expandedItems);
32
+ if (newExpanded.has(itemId)) {
33
+ newExpanded.delete(itemId);
34
+ } else {
35
+ newExpanded.add(itemId);
36
+ }
37
+ setExpandedItems(newExpanded);
38
+ };
39
+
40
+ const handleItemClick = (item: ITNavigationItem) => {
41
+ if (item.subitems && item.subitems.length > 0) {
42
+ toggleExpanded(item.id);
43
+ } else if (item.action) {
44
+ item.action();
45
+ }
46
+ };
47
+
48
+ // Use new navigation items if provided, otherwise fall back to legacy
49
+ const shouldUseLegacy = !navigationItems.length && (navItems || sidebarItems);
50
+
51
+ if (shouldUseLegacy) {
52
+ // Legacy behavior - original navbar implementation
53
+ return (
54
+ <div className="flex flex-col h-screen">
55
+ <nav className="bg-white border-b border-gray-200">
56
+ <div className="flex items-center justify-between mx-auto p-4">
57
+ <div className="flex items-center space-x-3 rtl:space-x-reverse">
58
+ {logo && <div className="h-8">{logo}</div>}
59
+ {logoText && (
60
+ <ITText as="span" className="self-center text-2xl font-semibold whitespace-nowrap text-gray-900">
61
+ {logoText}
62
+ </ITText>
63
+ )}
64
+ </div>
65
+
66
+ <div className="flex items-center justify-end w-full md:w-auto md:order-2">
67
+ <div className="flex items-center space-x-4 md:order-2">
68
+ <ul className="hidden md:flex space-x-4">{navItems}</ul>
69
+
70
+ {userMenu && (
71
+ <div className="relative">
72
+ <button
73
+ type="button"
74
+ className="flex text-sm bg-gray-200 rounded-full md:me-0 focus:ring-4 focus:ring-gray-300"
75
+ onClick={toggleUserMenu}
76
+ >
77
+ {userMenu.userImage ? (
78
+ <img
79
+ className="w-8 h-8 rounded-full"
80
+ src={userMenu.userImage}
81
+ alt="user photo"
82
+ />
83
+ ) : (
84
+ <FaUserCircle className="w-8 h-8 text-gray-500" />
85
+ )}
86
+ </button>
87
+
88
+ {isUserMenuOpen && (
89
+ <div
90
+ ref={userMenuRef}
91
+ className="z-50 absolute right-0 mt-2 text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow-sm"
92
+ >
93
+ <div className="px-4 py-3">
94
+ <ITText as="span" className="block text-sm text-gray-900">
95
+ {userMenu.userName}
96
+ </ITText>
97
+ <ITText as="span" className="block text-sm text-gray-500 truncate">
98
+ {userMenu.userEmail}
99
+ </ITText>
100
+ </div>
101
+ <ul className="py-2">
102
+ {userMenu.menuItems.map((item, index) => (
103
+ <li key={index}>
104
+ <button
105
+ onClick={() => {
106
+ item.onClick();
107
+ setIsUserMenuOpen(false);
108
+ }}
109
+ className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 w-full text-left"
110
+ >
111
+ <ITText as="span">{item.label}</ITText>
112
+ </button>
113
+ </li>
114
+ ))}
115
+ </ul>
116
+ </div>
117
+ )}
118
+ </div>
119
+ )}
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </nav>
124
+
125
+ <div className="flex-1 flex overflow-hidden relative">
126
+ {(showSidebar || showSidebarOnMobile) && (
127
+ <aside className="fixed inset-y-0 left-0 w-64 bg-gray-50 transform transition-transform duration-300 ease-in-out z-50 shadow-lg md:static md:transform-none md:shadow-none md:border-r md:border-gray-200">
128
+ <div className="h-full overflow-y-auto py-4 px-3">
129
+ <ul className="space-y-2 font-medium">{sidebarItems}</ul>
130
+ </div>
131
+ </aside>
132
+ )}
133
+ <main className="flex-1 bg-gray-100 overflow-y-auto">
134
+ {children}
135
+ </main>
136
+ </div>
137
+ </div>
138
+ );
139
+ }
140
+
141
+ // New sidebar design
142
+ return (
143
+ <div className="flex h-screen font-sans" style={{ backgroundColor: "var(--layout-bg, #f8fafc)" }}>
144
+ {/* Sidebar */}
145
+ <aside className="w-72 shadow-xl flex flex-col transition-all duration-300 ease-in-out" style={{ backgroundColor: "var(--sidebar-bg, #0f172a)", borderRight: "1px solid var(--sidebar-border, #1e293b)" }}>
146
+ {/* Logo Section */}
147
+ <div className="p-6 flex items-center gap-3" style={{ borderBottom: "1px solid var(--sidebar-border, #1e293b)" }}>
148
+ {logo && <div className="h-8 w-auto object-contain transition-transform hover:scale-105">{logo}</div>}
149
+ {logoText && (
150
+ <ITText as="span" className="text-lg font-bold tracking-wide" style={{ color: "var(--sidebar-active-color, #ffffff)" }}>
151
+ {logoText}
152
+ </ITText>
153
+ )}
154
+ </div>
155
+
156
+ {/* Navigation Items */}
157
+ <nav className="flex-1 px-4 py-6 overflow-y-auto custom-scrollbar">
158
+ <ul className="flex flex-col gap-1.5">
159
+ {navigationItems.map((item) => (
160
+ <li key={item.id}>
161
+ <div
162
+ className={`group flex items-center justify-between px-4 py-3 rounded-xl cursor-pointer transition-all duration-200 border-l-4 ${
163
+ item.isActive
164
+ ? 'shadow-sm'
165
+ : 'hover:shadow-sm'
166
+ }`}
167
+ onClick={() => handleItemClick(item)}
168
+ style={{
169
+ backgroundColor: item.isActive ? "var(--sidebar-active-bg, rgba(255,255,255,0.1))" : "transparent",
170
+ borderColor: item.isActive ? "var(--sidebar-active-icon, #3b82f6)" : "transparent",
171
+ color: item.isActive ? "var(--sidebar-active-color, #ffffff)" : "var(--sidebar-label-color, #94a3b8)"
172
+ }}
173
+ onMouseEnter={(e) => {
174
+ if (!item.isActive) {
175
+ e.currentTarget.style.backgroundColor = "var(--sidebar-hover-bg, rgba(255,255,255,0.05))";
176
+ e.currentTarget.style.color = "var(--sidebar-active-color, #ffffff)";
177
+ }
178
+ }}
179
+ onMouseLeave={(e) => {
180
+ if (!item.isActive) {
181
+ e.currentTarget.style.backgroundColor = "transparent";
182
+ e.currentTarget.style.color = "var(--sidebar-label-color, #94a3b8)";
183
+ }
184
+ }}
185
+ >
186
+ <div className="flex items-center gap-3">
187
+ {/* Icon */}
188
+ {item.icon && (
189
+ <div className="text-xl transition-colors" style={{
190
+ color: item.isActive ? "var(--sidebar-active-icon, #3b82f6)" : "var(--sidebar-icon-color, #64748b)"
191
+ }}>
192
+ {item.icon}
193
+ </div>
194
+ )}
195
+
196
+ {/* Label */}
197
+ <ITText as="span" className={`font-medium text-sm ${item.isActive ? 'font-semibold' : ''}`}>{item.label}</ITText>
198
+ </div>
199
+
200
+ {/* Chevron for expandable items */}
201
+ {item.subitems && item.subitems.length > 0 && (
202
+ <div className="transition-transform" style={{ color: "var(--sidebar-icon-color, #64748b)" }}>
203
+ {expandedItems.has(item.id) ? (
204
+ <FaChevronDown className="w-3 h-3" />
205
+ ) : (
206
+ <FaChevronRight className="w-3 h-3" />
207
+ )}
208
+ </div>
209
+ )}
210
+ </div>
211
+
212
+ {/* Submenu */}
213
+ {item.subitems &&
214
+ item.subitems.length > 0 &&
215
+ expandedItems.has(item.id) && (
216
+ <ul className="mt-1 ml-4 pl-4 space-y-1" style={{ borderLeft: "1px solid var(--sidebar-border, #1e293b)" }}>
217
+ {item.subitems.map((subitem) => (
218
+ <li key={subitem.id}>
219
+ <button
220
+ onClick={subitem.action}
221
+ className="block w-full text-left px-4 py-2.5 rounded-lg text-sm transition-all duration-200"
222
+ style={{
223
+ color: subitem.isActive ? "var(--sidebar-active-color, #ffffff)" : "var(--sidebar-label-color, #94a3b8)",
224
+ backgroundColor: subitem.isActive ? "var(--sidebar-active-bg, rgba(255,255,255,0.1))" : "transparent"
225
+ }}
226
+ onMouseEnter={(e) => {
227
+ if (!subitem.isActive) {
228
+ e.currentTarget.style.backgroundColor = "var(--sidebar-hover-bg, rgba(255,255,255,0.05))";
229
+ }
230
+ }}
231
+ onMouseLeave={(e) => {
232
+ if (!subitem.isActive) {
233
+ e.currentTarget.style.backgroundColor = "transparent";
234
+ }
235
+ }}
236
+ >
237
+ <ITText as="span">{subitem.label}</ITText>
238
+ </button>
239
+ </li>
240
+ ))}
241
+ </ul>
242
+ )}
243
+ </li>
244
+ ))}
245
+ </ul>
246
+ </nav>
247
+
248
+ {/* User Menu */}
249
+ {userMenu && (
250
+ <div className="p-4" style={{ borderTop: "1px solid var(--sidebar-border, #1e293b)" }}>
251
+ <div className="relative">
252
+ <button
253
+ type="button"
254
+ className="flex items-center gap-3 w-full p-3 rounded-xl transition-colors duration-200 group"
255
+ style={{ color: "var(--sidebar-label-color, #94a3b8)" }}
256
+ onMouseEnter={(e) => { e.currentTarget.style.backgroundColor = "var(--sidebar-hover-bg, rgba(255,255,255,0.05))"; e.currentTarget.style.color = "var(--sidebar-active-color, #ffffff)"; }}
257
+ onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = "transparent"; e.currentTarget.style.color = "var(--sidebar-label-color, #94a3b8)"; }}
258
+ onClick={toggleUserMenu}
259
+ >
260
+ {userMenu.userImage ? (
261
+ <img
262
+ className="w-10 h-10 rounded-full border-2 transition-colors"
263
+ src={userMenu.userImage}
264
+ alt="user photo"
265
+ style={{ borderColor: "var(--sidebar-border, #1e293b)" }}
266
+ />
267
+ ) : (
268
+ <div className="w-10 h-10 rounded-full flex items-center justify-center transition-colors" style={{ backgroundColor: "var(--sidebar-hover-bg, rgba(255,255,255,0.1))", color: "var(--sidebar-icon-color, #64748b)" }}>
269
+ <FaUserCircle className="w-6 h-6" />
270
+ </div>
271
+ )}
272
+ <div className="flex-1 text-left overflow-hidden">
273
+ <ITText as="div" className="font-medium text-sm truncate" style={{ color: "var(--sidebar-active-color, #ffffff)" }}>
274
+ {userMenu.userName}
275
+ </ITText>
276
+ <ITText as="div" className="text-xs truncate" style={{ color: "var(--sidebar-label-color, #94a3b8)" }}>
277
+ {userMenu.userEmail}
278
+ </ITText>
279
+ </div>
280
+ <FaChevronRight className="w-3 h-3" style={{ color: "var(--sidebar-icon-color, #64748b)" }} />
281
+ </button>
282
+
283
+ {isUserMenuOpen && (
284
+ <div
285
+ ref={userMenuRef}
286
+ className="absolute bottom-full left-0 mb-3 w-full rounded-xl shadow-2xl overflow-hidden transform transition-all duration-200 origin-bottom"
287
+ style={{ backgroundColor: "var(--topbar-user-dropdown-bg, #ffffff)", border: "1px solid var(--topbar-user-dropdown-border, #e2e8f0)" }}
288
+ >
289
+ <div className="px-4 py-3" style={{ backgroundColor: "var(--topbar-user-bg, #f8fafc)", borderBottom: "1px solid var(--topbar-user-dropdown-border, #e2e8f0)" }}>
290
+ <ITText as="span" className="block text-sm font-semibold" style={{ color: "var(--topbar-user-text, #0f172a)" }}>
291
+ {userMenu.userName}
292
+ </ITText>
293
+ <ITText as="span" className="block text-xs truncate" style={{ color: "var(--topbar-user-subtitle, #64748b)" }}>
294
+ {userMenu.userEmail}
295
+ </ITText>
296
+ </div>
297
+ <ul className="py-1">
298
+ {userMenu.menuItems.map((item, index) => (
299
+ <li key={index}>
300
+ <button
301
+ onClick={() => {
302
+ item.onClick();
303
+ setIsUserMenuOpen(false);
304
+ }}
305
+ className="flex items-center w-full px-4 py-2.5 text-sm transition-colors"
306
+ style={{ color: "var(--topbar-user-text, #334155)" }}
307
+ onMouseEnter={(e) => { e.currentTarget.style.backgroundColor = "var(--topbar-user-item-hover, #f8fafc)"; }}
308
+ onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = "transparent"; }}
309
+ >
310
+ <ITText as="span">{item.label}</ITText>
311
+ </button>
312
+ </li>
313
+ ))}
314
+ </ul>
315
+ </div>
316
+ )}
317
+ </div>
318
+ </div>
319
+ )}
320
+ </aside>
321
+
322
+ {/* Main Content */}
323
+ <main className="flex-1 overflow-y-auto relative" style={{ backgroundColor: "var(--layout-bg, #f8fafc)" }}>
324
+ {children}
325
+ </main>
326
+ </div>
327
+ );
328
+ }
@@ -0,0 +1,19 @@
1
+ import { ReactNode } from "react";
2
+ import { ITBreadcrumbItem } from "../breadcrumbs/breadcrumbs.props";
3
+
4
+ export interface ITPageProps {
5
+ title?: string;
6
+ description?: string;
7
+ breadcrumbs?: ITBreadcrumbItem[];
8
+ actions?: ReactNode;
9
+ backAction?: () => void;
10
+ loading?: boolean;
11
+ error?: string | null;
12
+ onRetry?: () => void;
13
+ empty?: boolean;
14
+ emptyTitle?: string;
15
+ emptyDescription?: string;
16
+ emptyAction?: ReactNode;
17
+ className?: string;
18
+ children: ReactNode;
19
+ }
@@ -0,0 +1,98 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import ITPage from "./page";
3
+ import ITButton from "../button/button";
4
+ import ITCard from "../card/card";
5
+
6
+ const meta: Meta<typeof ITPage> = {
7
+ title: "Components/Layout/ITPage",
8
+ component: ITPage,
9
+ tags: ["autodocs"],
10
+ };
11
+
12
+ export default meta;
13
+ type Story = StoryObj<typeof ITPage>;
14
+
15
+ export const Default: Story = {
16
+ args: {
17
+ title: "Usuarios",
18
+ description: "Gestión de usuarios del sistema",
19
+ children: (
20
+ <ITCard title="Contenido">
21
+ <p className="text-sm text-slate-600 dark:text-slate-400">
22
+ Aquí va el contenido de la página.
23
+ </p>
24
+ </ITCard>
25
+ ),
26
+ },
27
+ };
28
+
29
+ export const WithBreadcrumbsAndActions: Story = {
30
+ args: {
31
+ title: "Editar Producto",
32
+ description: "Modifica los datos del producto",
33
+ breadcrumbs: [
34
+ { label: "Productos", href: "#" },
35
+ { label: "Editar Producto" },
36
+ ],
37
+ actions: (
38
+ <>
39
+ <ITButton label="Cancelar" variant="outlined" size="small" />
40
+ <ITButton label="Guardar" size="small" />
41
+ </>
42
+ ),
43
+ children: (
44
+ <ITCard title="Información General">
45
+ <p className="text-sm text-slate-600 dark:text-slate-400">
46
+ Contenido del formulario aquí.
47
+ </p>
48
+ </ITCard>
49
+ ),
50
+ },
51
+ };
52
+
53
+ export const Loading: Story = {
54
+ args: {
55
+ title: "Dashboard",
56
+ loading: true,
57
+ children: null,
58
+ },
59
+ };
60
+
61
+ export const WithError: Story = {
62
+ args: {
63
+ title: "Órdenes",
64
+ error: "No se pudieron cargar las órdenes. Verifica tu conexión.",
65
+ onRetry: () => alert("Retry clicked"),
66
+ children: null,
67
+ },
68
+ };
69
+
70
+ export const Empty: Story = {
71
+ args: {
72
+ title: "Notificaciones",
73
+ empty: true,
74
+ emptyTitle: "Sin notificaciones",
75
+ emptyDescription: "No tienes notificaciones pendientes.",
76
+ emptyAction: <ITButton label="Recargar" size="small" onClick={() => alert("Refresh")} />,
77
+ children: null,
78
+ },
79
+ };
80
+
81
+ export const WithBackAction: Story = {
82
+ args: {
83
+ title: "Detalle del Usuario",
84
+ description: "Información completa del usuario",
85
+ backAction: () => alert("Back"),
86
+ breadcrumbs: [
87
+ { label: "Usuarios", href: "#" },
88
+ { label: "Detalle" },
89
+ ],
90
+ children: (
91
+ <ITCard title="Perfil">
92
+ <p className="text-sm text-slate-600 dark:text-slate-400">
93
+ Contenido del detalle aquí.
94
+ </p>
95
+ </ITCard>
96
+ ),
97
+ },
98
+ };
@@ -0,0 +1,90 @@
1
+ import clsx from "clsx";
2
+ import { ITPageProps } from "./page.props";
3
+ import ITPageHeader from "../page-header/page-header";
4
+ import ITSkeleton from "../skeleton/skeleton";
5
+ import ITEmptyState from "../empty-state/empty-state";
6
+ import ITButton from "../button/button";
7
+ import ITStack from "../stack/stack";
8
+
9
+ export default function ITPage({
10
+ title,
11
+ description,
12
+ breadcrumbs,
13
+ actions,
14
+ backAction,
15
+ loading = false,
16
+ error = null,
17
+ onRetry,
18
+ empty = false,
19
+ emptyTitle,
20
+ emptyDescription,
21
+ emptyAction,
22
+ className,
23
+ children,
24
+ }: ITPageProps) {
25
+ if (loading) {
26
+ return (
27
+ <div className={className}>
28
+ {title && (
29
+ <ITPageHeader title={title} />
30
+ )}
31
+ <div className="mt-6">
32
+ <ITStack spacing={4}>
33
+ <ITSkeleton variant="rectangular" height={40} width="40%" />
34
+ <ITSkeleton variant="rectangular" height={200} />
35
+ <ITSkeleton variant="rectangular" height={200} />
36
+ </ITStack>
37
+ </div>
38
+ </div>
39
+ );
40
+ }
41
+
42
+ if (error) {
43
+ return (
44
+ <div className={className}>
45
+ {title && (
46
+ <ITPageHeader title={title} />
47
+ )}
48
+ <ITEmptyState
49
+ title="Error"
50
+ description={error}
51
+ action={
52
+ onRetry ? (
53
+ <ITButton label="Reintentar" onClick={onRetry} size="small" />
54
+ ) : undefined
55
+ }
56
+ />
57
+ </div>
58
+ );
59
+ }
60
+
61
+ if (empty) {
62
+ return (
63
+ <div className={className}>
64
+ {title && (
65
+ <ITPageHeader title={title} />
66
+ )}
67
+ <ITEmptyState
68
+ title={emptyTitle || "Sin datos"}
69
+ description={emptyDescription || "No hay información para mostrar"}
70
+ action={emptyAction}
71
+ />
72
+ </div>
73
+ );
74
+ }
75
+
76
+ return (
77
+ <div className={clsx("space-y-6", className)}>
78
+ {(title || breadcrumbs || actions || backAction) && (
79
+ <ITPageHeader
80
+ title={title || ""}
81
+ description={description}
82
+ breadcrumbs={breadcrumbs}
83
+ actions={actions}
84
+ backAction={backAction}
85
+ />
86
+ )}
87
+ {children}
88
+ </div>
89
+ );
90
+ }
@@ -0,0 +1,11 @@
1
+ import { ReactNode } from "react";
2
+ import { ITBreadcrumbItem } from "../breadcrumbs/breadcrumbs.props";
3
+
4
+ export interface ITPageHeaderProps {
5
+ title: string;
6
+ description?: string;
7
+ breadcrumbs?: ITBreadcrumbItem[];
8
+ actions?: ReactNode;
9
+ backAction?: () => void;
10
+ className?: string;
11
+ }
@@ -0,0 +1,61 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import ITPageHeader from "./page-header";
3
+ import ITButton from "../button/button";
4
+
5
+ const meta: Meta<typeof ITPageHeader> = {
6
+ title: "Components/Layout/ITPageHeader",
7
+ component: ITPageHeader,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof ITPageHeader>;
13
+
14
+ export const Default: Story = {
15
+ args: {
16
+ title: "Usuarios",
17
+ description: "Gestiona los usuarios del sistema",
18
+ },
19
+ };
20
+
21
+ export const WithBreadcrumbs: Story = {
22
+ args: {
23
+ title: "Editar Usuario",
24
+ description: "Modifica los datos del usuario seleccionado",
25
+ breadcrumbs: [
26
+ { label: "Inicio", href: "#" },
27
+ { label: "Usuarios", href: "#" },
28
+ { label: "Editar Usuario" },
29
+ ],
30
+ },
31
+ };
32
+
33
+ export const WithActions: Story = {
34
+ args: {
35
+ title: "Órdenes de Compra",
36
+ description: "Listado de órdenes activas",
37
+ breadcrumbs: [
38
+ { label: "Dashboard", href: "#" },
39
+ { label: "Órdenes" },
40
+ ],
41
+ actions: (
42
+ <>
43
+ <ITButton label="Exportar" variant="outlined" size="small" />
44
+ <ITButton label="Nueva Orden" size="small" />
45
+ </>
46
+ ),
47
+ },
48
+ };
49
+
50
+ export const WithBackAction: Story = {
51
+ args: {
52
+ title: "Detalle del Producto",
53
+ description: "Información completa del producto",
54
+ backAction: () => alert("Back clicked"),
55
+ breadcrumbs: [
56
+ { label: "Productos", href: "#" },
57
+ { label: "Detalle" },
58
+ ],
59
+ actions: <ITButton label="Editar" size="small" />,
60
+ },
61
+ };
@@ -0,0 +1,62 @@
1
+ import clsx from "clsx";
2
+ import { FaChevronLeft } from "react-icons/fa";
3
+ import { ITPageHeaderProps } from "./page-header.props";
4
+ import ITBreadcrumbs from "../breadcrumbs/breadcrumbs";
5
+ import ITText from "@/components/text/text";
6
+
7
+ export default function ITPageHeader({
8
+ title,
9
+ description,
10
+ breadcrumbs,
11
+ actions,
12
+ backAction,
13
+ className,
14
+ }: ITPageHeaderProps) {
15
+ const showTopRow = breadcrumbs?.length || backAction;
16
+
17
+ return (
18
+ <div className={clsx(className)}>
19
+ {showTopRow && (
20
+ <div className="flex items-center justify-between gap-4 mb-1">
21
+ <div className="flex items-center gap-2 min-w-0">
22
+ {backAction && (
23
+ <button
24
+ onClick={backAction}
25
+ className="p-1.5 rounded-lg text-slate-400 hover:text-slate-600 hover:bg-slate-100 dark:text-slate-500 dark:hover:text-slate-200 dark:hover:bg-slate-800 transition-colors flex-shrink-0"
26
+ aria-label="Volver"
27
+ >
28
+ <FaChevronLeft size={14} />
29
+ </button>
30
+ )}
31
+ {breadcrumbs && breadcrumbs.length > 0 && (
32
+ <ITBreadcrumbs items={breadcrumbs} />
33
+ )}
34
+ </div>
35
+ {actions && (
36
+ <div className="flex items-center gap-2 flex-shrink-0">
37
+ {actions}
38
+ </div>
39
+ )}
40
+ </div>
41
+ )}
42
+
43
+ <div className="flex items-start justify-between gap-4">
44
+ <div className="min-w-0">
45
+ <ITText as="h1" className="text-2xl font-extrabold text-slate-800 dark:text-white tracking-tight">
46
+ {title}
47
+ </ITText>
48
+ {description && (
49
+ <ITText as="p" className="text-sm text-slate-500 dark:text-slate-400 mt-0.5">
50
+ {description}
51
+ </ITText>
52
+ )}
53
+ </div>
54
+ {!showTopRow && actions && (
55
+ <div className="flex items-center gap-2 flex-shrink-0">
56
+ {actions}
57
+ </div>
58
+ )}
59
+ </div>
60
+ </div>
61
+ );
62
+ }