@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,313 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { FaChevronDown } from "react-icons/fa";
3
+ import { ITNavigationItem, ITSidebarProps } from "./sidebar.props";
4
+ import ITText from "@/components/text/text";
5
+
6
+ export default function ITSidebar({
7
+ navigationItems = [],
8
+ isCollapsed = false,
9
+ onToggleCollapse,
10
+ className = "",
11
+ visibleOnMobile = false,
12
+ onItemClick,
13
+ onSubItemClick,
14
+ subitemConnector = 'dot',
15
+ }: ITSidebarProps) {
16
+ const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
17
+ const [isHovering, setIsHovering] = useState(false);
18
+ const sidebarRef = useRef<HTMLDivElement>(null);
19
+ const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
20
+ const leaveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
21
+
22
+ useEffect(() => {
23
+ const handleMouseEnter = () => {
24
+ if (hoverTimeoutRef.current) clearTimeout(hoverTimeoutRef.current);
25
+ if (leaveTimeoutRef.current) clearTimeout(leaveTimeoutRef.current);
26
+ setIsHovering(true);
27
+ };
28
+
29
+ const handleMouseLeave = () => {
30
+ leaveTimeoutRef.current = setTimeout(() => {
31
+ setIsHovering(false);
32
+ }, 300);
33
+ };
34
+
35
+ const sidebar = sidebarRef.current;
36
+ if (sidebar) {
37
+ sidebar.addEventListener("mouseenter", handleMouseEnter);
38
+ sidebar.addEventListener("mouseleave", handleMouseLeave);
39
+ }
40
+ return () => {
41
+ if (sidebar) {
42
+ sidebar.removeEventListener("mouseenter", handleMouseEnter);
43
+ sidebar.removeEventListener("mouseleave", handleMouseLeave);
44
+ }
45
+ if (hoverTimeoutRef.current) clearTimeout(hoverTimeoutRef.current);
46
+ if (leaveTimeoutRef.current) clearTimeout(leaveTimeoutRef.current);
47
+ };
48
+ }, [isCollapsed]);
49
+
50
+ // Auto-expand parent items when a subitem is active
51
+ useEffect(() => {
52
+ const activeParents = new Set<string>();
53
+ navigationItems.forEach(item => {
54
+ if (item.subitems && item.subitems.some(sub => sub.isActive)) {
55
+ activeParents.add(item.id);
56
+ }
57
+ });
58
+
59
+ if (activeParents.size > 0) {
60
+ setExpandedItems(prev => {
61
+ const next = new Set(prev);
62
+ let changed = false;
63
+ activeParents.forEach(id => {
64
+ if (!next.has(id)) {
65
+ next.add(id);
66
+ changed = true;
67
+ }
68
+ });
69
+ return changed ? next : prev;
70
+ });
71
+ }
72
+ }, [navigationItems]);
73
+
74
+ const toggleExpanded = (itemId: string) => {
75
+ const newExpanded = new Set(expandedItems);
76
+ if (newExpanded.has(itemId)) newExpanded.delete(itemId);
77
+ else newExpanded.add(itemId);
78
+ setExpandedItems(newExpanded);
79
+ };
80
+
81
+ const handleItemClick = (item: ITNavigationItem) => {
82
+ if (item.subitems && item.subitems.length > 0) {
83
+ toggleExpanded(item.id);
84
+ } else {
85
+ if (item.action) item.action();
86
+ if (onItemClick) onItemClick(item);
87
+ }
88
+ };
89
+
90
+ const isSidebarCollapsed = visibleOnMobile ? false : (!isHovering && isCollapsed);
91
+ const sidebarWidth = isSidebarCollapsed ? "w-[88px]" : "w-[280px]";
92
+
93
+ return (
94
+ <aside
95
+ ref={sidebarRef}
96
+ className={`
97
+ relative flex flex-col
98
+ transition-all duration-400 ease-[cubic-bezier(0.2,0,0,1)]
99
+ ${sidebarWidth}
100
+ ${className}
101
+ ${!visibleOnMobile ? "hidden lg:flex" : "flex"}
102
+ shadow-[4px_0_24px_rgba(0,0,0,0.02)]
103
+ `}
104
+ style={{
105
+ zIndex: 50,
106
+ backgroundColor: "var(--sidebar-bg, rgba(255, 255, 255, 0.90))",
107
+ borderRight: "1px solid var(--sidebar-border, #e2e8f0)",
108
+ WebkitBackdropFilter: 'blur(12px)',
109
+ backdropFilter: 'blur(12px)',
110
+ }}
111
+ >
112
+ {/* Navigation Items */}
113
+ <nav className="flex-1 py-6 overflow-y-auto overflow-x-hidden custom-scrollbar px-4">
114
+ <ul className="space-y-2">
115
+ {navigationItems.map((item) => (
116
+ <li key={item.id} className="relative group/navitem">
117
+ <div
118
+ className={`flex items-center cursor-pointer
119
+ transition-all duration-300 ease-[cubic-bezier(0.2,0,0,1)]
120
+ rounded-xl relative overflow-visible
121
+ ${isSidebarCollapsed ? "justify-center p-2.5 mb-2" : "justify-between px-3.5 py-3 mb-1"}
122
+ `}
123
+ style={{
124
+ backgroundColor: item.isActive ? "var(--sidebar-active-bg, #f8fafc)" : 'transparent',
125
+ boxShadow: item.isActive ? '0 1px 2px 0 rgba(0, 0, 0, 0.05)' : 'none',
126
+ border: item.isActive ? "1px solid var(--sidebar-border, #e2e8f0)" : '1px solid transparent'
127
+ }}
128
+ onMouseEnter={(e) => {
129
+ if (!item.isActive) e.currentTarget.style.backgroundColor = "var(--sidebar-hover-bg, #f1f5f9)";
130
+ }}
131
+ onMouseLeave={(e) => {
132
+ if (!item.isActive) e.currentTarget.style.backgroundColor = 'transparent';
133
+ }}
134
+ onClick={() => handleItemClick(item)}
135
+ >
136
+ {item.isActive && !isSidebarCollapsed && (
137
+ <div
138
+ className="absolute left-0 top-1/4 bottom-1/4 w-[3px] rounded-r-full transition-all"
139
+ style={{ backgroundColor: "var(--sidebar-active-icon, #10b981)", boxShadow: "0 0 10px var(--sidebar-active-icon, #10b981)" }}
140
+ />
141
+ )}
142
+
143
+ <div className={`flex items-center ${!isSidebarCollapsed ? "gap-3.5" : "justify-center"} relative z-10 w-full`}>
144
+ {item.icon && (
145
+ <div
146
+ className={`transition-all duration-300 flex-shrink-0 flex items-center justify-center`}
147
+ style={{
148
+ color: item.isActive ? "var(--sidebar-active-icon, #10b981)" : "var(--sidebar-icon-color, #9ca3af)",
149
+ opacity: item.isActive ? 1 : 0.8,
150
+ fontSize: item.isActive ? '1.35rem' : '1.25rem',
151
+ filter: item.isActive ? 'drop-shadow(0 0 8px rgba(255,255,255,0.2))' : 'none'
152
+ }}
153
+ >
154
+ {item.icon}
155
+ </div>
156
+ )}
157
+
158
+ {!isSidebarCollapsed && (
159
+ <ITText as="span"
160
+ className={`transition-all duration-300 truncate tracking-wide`}
161
+ style={{
162
+ color: item.isActive ? "var(--sidebar-active-color, #ffffff)" : "var(--sidebar-label-color, #d1d5db)",
163
+ fontSize: '0.9rem',
164
+ fontWeight: item.isActive ? '600' : '500'
165
+ }}
166
+ >
167
+ {item.label}
168
+ </ITText>
169
+ )}
170
+ </div>
171
+
172
+ {!isSidebarCollapsed && item.subitems && item.subitems.length > 0 && (
173
+ <div className={`flex-shrink-0 transition-transform duration-300 ease-[cubic-bezier(0.2,0,0,1)] ${expandedItems.has(item.id) ? "rotate-180" : ""}`}
174
+ style={{ color: item.isActive ? "var(--sidebar-active-color, #0f172a)" : "var(--sidebar-icon-color, #64748b)", opacity: 0.7 }}>
175
+ <FaChevronDown className="w-3 h-3" />
176
+ </div>
177
+ )}
178
+
179
+ {item.badge && (
180
+ <span
181
+ className={`
182
+ absolute flex items-center justify-center font-bold shadow-md
183
+ ${isSidebarCollapsed
184
+ ? "top-1 right-1 w-2.5 h-2.5 rounded-full ring-2 ring-white"
185
+ : "right-3 top-1/2 transform -translate-y-1/2 px-2 py-0.5 text-[10px] rounded-full backdrop-blur-sm"}
186
+ `}
187
+ style={{
188
+ backgroundColor: "var(--sidebar-badge-bg, #10b981)",
189
+ color: "var(--sidebar-badge-color, #ffffff)",
190
+ boxShadow: isSidebarCollapsed ? "0 0 0 2px var(--sidebar-bg, #111827)" : 'none'
191
+ }}
192
+ >
193
+ {isSidebarCollapsed ? "" : item.badge}
194
+ </span>
195
+ )}
196
+ </div>
197
+
198
+ {/* Glassmorphism Collapsed Tooltip / Submenu */}
199
+ {isSidebarCollapsed && (
200
+ <div
201
+ className="absolute left-full top-0 ml-4 rounded-2xl opacity-0 invisible group-hover/navitem:opacity-100 group-hover/navitem:visible transition-all duration-300 pointer-events-none z-50 min-w-[220px] overflow-hidden -translate-x-2 group-hover/navitem:translate-x-0 shadow-[0_10px_40px_-10px_rgba(0,0,0,0.15)]"
202
+ style={{
203
+ backgroundColor: "var(--sidebar-bg, #ffffff)",
204
+ border: "1px solid var(--sidebar-border, #e2e8f0)",
205
+ WebkitBackdropFilter: 'blur(16px)',
206
+ backdropFilter: 'blur(16px)',
207
+ }}
208
+ >
209
+ <div className="px-5 py-4 flex items-center gap-3 font-semibold border-b" style={{ borderColor: "var(--sidebar-border, #e2e8f0)", color: "var(--sidebar-active-color, #0f172a)" }}>
210
+ {item.icon && <span style={{ color: "var(--sidebar-active-icon, #10b981)" }} className="text-xl drop-shadow-sm">{item.icon}</span>}
211
+ <ITText as="span" className="tracking-wide text-[15px]">{item.label}</ITText>
212
+ </div>
213
+
214
+ {item.subitems && item.subitems.length > 0 ? (
215
+ <div className="py-2">
216
+ {item.subitems.map((subitem) => (
217
+ <div
218
+ key={subitem.id}
219
+ className={`px-5 py-2.5 text-sm flex items-center gap-3 transition-colors relative`}
220
+ >
221
+ {subitem.isActive && subitemConnector === '|' && (
222
+ <div
223
+ className="absolute left-0 top-1/3 bottom-1/3 w-[2.5px] rounded-r-full"
224
+ style={{
225
+ backgroundColor: "var(--sidebar-active-icon, #10b981)",
226
+ boxShadow: "0 0 6px color-mix(in srgb, var(--sidebar-active-icon, #10b981) 25%, transparent)",
227
+ }}
228
+ />
229
+ )}
230
+ <span className={`w-1.5 h-1.5 rounded-full transition-all ${subitem.isActive ? "scale-125" : ""}`} style={{ backgroundColor: subitem.isActive ? "var(--sidebar-active-icon, #10b981)" : "var(--sidebar-icon-color, #94a3b8)" }} />
231
+ <ITText as="span" style={{ color: subitem.isActive ? "var(--sidebar-active-color, #0f172a)" : "var(--sidebar-label-color, #475569)", fontWeight: subitem.isActive ? 600 : 500 }}>{subitem.label}</ITText>
232
+ </div>
233
+ ))}
234
+ </div>
235
+ ) : (
236
+ <ITText as="div" className="px-5 py-3 text-sm italic" style={{ color: "var(--sidebar-label-color, #71717a)" }}>No hay submenú</ITText>
237
+ )}
238
+ </div>
239
+ )}
240
+
241
+ {/* Submenu - smooth height/opacity when not collapsed */}
242
+ {!isSidebarCollapsed && item.subitems && item.subitems.length > 0 && (
243
+ <div className={`overflow-hidden transition-all duration-400 ease-[cubic-bezier(0.2,0,0,1)] ${expandedItems.has(item.id) ? "max-h-[500px] opacity-100 mt-1" : "max-h-0 opacity-0"}`}>
244
+ <ul
245
+ className="ml-5 flex flex-col gap-0.5 py-1"
246
+ style={{
247
+ borderLeft: subitemConnector === '|'
248
+ ? "1px solid var(--sidebar-border, #e2e8f0)"
249
+ : 'none'
250
+ }}
251
+ >
252
+ {item.subitems.map((subitem) => (
253
+ <li key={subitem.id} className="relative">
254
+ <button
255
+ onClick={() => {
256
+ if (subitem.action) subitem.action();
257
+ if (onSubItemClick) onSubItemClick(subitem);
258
+ }}
259
+ className={`flex items-center gap-2.5 w-full text-left px-3 py-2 rounded-xl transition-all duration-300`}
260
+ style={{
261
+ color: subitem.isActive ? "var(--sidebar-active-color, #0f172a)" : "var(--sidebar-label-color, #475569)",
262
+ backgroundColor: subitem.isActive ? "var(--sidebar-active-bg, #f8fafc)" : 'transparent',
263
+ fontSize: '0.85rem',
264
+ fontWeight: subitem.isActive ? 600 : 500,
265
+ letterSpacing: '0.01em',
266
+ marginLeft: subitemConnector === '|' ? '-1px' : '0',
267
+ }}
268
+ onMouseEnter={(e) => {
269
+ if (!subitem.isActive) {
270
+ e.currentTarget.style.backgroundColor = "var(--sidebar-hover-bg, #f1f5f9)";
271
+ e.currentTarget.style.transform = 'translateX(3px)';
272
+ }
273
+ }}
274
+ onMouseLeave={(e) => {
275
+ if (!subitem.isActive) {
276
+ e.currentTarget.style.backgroundColor = 'transparent';
277
+ e.currentTarget.style.transform = 'translateX(0)';
278
+ }
279
+ }}
280
+ >
281
+ {subitem.isActive && subitemConnector === '|' && (
282
+ <div
283
+ className="absolute left-0 top-1/3 bottom-1/3 w-[2.5px] rounded-r-full transition-all"
284
+ style={{
285
+ backgroundColor: "var(--sidebar-active-icon, #10b981)",
286
+ boxShadow: "0 0 6px color-mix(in srgb, var(--sidebar-active-icon, #10b981) 25%, transparent)",
287
+ }}
288
+ />
289
+ )}
290
+ {subitemConnector === 'dot' && (
291
+ <span
292
+ className={`w-1.5 h-1.5 rounded-full flex-shrink-0 transition-all duration-300 ${subitem.isActive ? 'scale-125' : ''}`}
293
+ style={{
294
+ backgroundColor: subitem.isActive
295
+ ? "var(--sidebar-active-icon, #10b981)"
296
+ : "var(--sidebar-icon-color, #94a3b8)"
297
+ }}
298
+ />
299
+ )}
300
+ <ITText as="span" className="truncate">{subitem.label}</ITText>
301
+ </button>
302
+ </li>
303
+ ))}
304
+ </ul>
305
+ </div>
306
+ )}
307
+ </li>
308
+ ))}
309
+ </ul>
310
+ </nav>
311
+ </aside>
312
+ );
313
+ }
@@ -0,0 +1,12 @@
1
+ import { CSSProperties } from "react";
2
+
3
+ export type SkeletonVariant = "text" | "circular" | "rectangular";
4
+
5
+ export interface ITSkeletonProps {
6
+ variant?: SkeletonVariant;
7
+ width?: string | number;
8
+ height?: string | number;
9
+ count?: number;
10
+ className?: string;
11
+ style?: CSSProperties;
12
+ }
@@ -0,0 +1,30 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import ITSkeleton from "./skeleton";
3
+ import ITStack from "../stack/stack";
4
+
5
+ const meta: Meta<typeof ITSkeleton> = {
6
+ title: "Components/Feedback/ITSkeleton",
7
+ component: ITSkeleton,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof ITSkeleton>;
13
+
14
+ export const Text: Story = {
15
+ render: () => (
16
+ <ITStack spacing={2}>
17
+ <ITSkeleton variant="text" width="60%" />
18
+ <ITSkeleton variant="text" />
19
+ <ITSkeleton variant="text" width="80%" />
20
+ </ITStack>
21
+ ),
22
+ };
23
+
24
+ export const Circular: Story = {
25
+ args: { variant: "circular", width: 48, height: 48 },
26
+ };
27
+
28
+ export const Rectangular: Story = {
29
+ args: { variant: "rectangular", width: "100%", height: 160 },
30
+ };
@@ -0,0 +1,45 @@
1
+ import clsx from "clsx";
2
+ import { ITSkeletonProps, SkeletonVariant } from "./skeleton.props";
3
+
4
+ const variantClasses: Record<SkeletonVariant, string> = {
5
+ text: "rounded-md h-4 w-full",
6
+ circular: "rounded-full",
7
+ rectangular: "rounded-lg",
8
+ };
9
+
10
+ export default function ITSkeleton({
11
+ variant = "text",
12
+ width,
13
+ height,
14
+ count = 1,
15
+ className,
16
+ style,
17
+ }: ITSkeletonProps) {
18
+ const baseStyle: React.CSSProperties = {
19
+ ...(width ? { width } : variant === "text" ? {} : width ? { width } : {}),
20
+ ...(height ? { height } : {}),
21
+ ...style,
22
+ };
23
+
24
+ const items = Array.from({ length: count }, (_, i) => i);
25
+
26
+ return (
27
+ <>
28
+ {items.map((i) => (
29
+ <div
30
+ key={i}
31
+ className={clsx(
32
+ "animate-pulse bg-slate-200 dark:bg-slate-700",
33
+ variantClasses[variant],
34
+ variant === "text" && count > 1 && i < count - 1 && "mb-2",
35
+ className
36
+ )}
37
+ style={{
38
+ ...baseStyle,
39
+ width: variant === "text" && width === undefined ? `${Math.random() * 30 + 60}%` : width,
40
+ }}
41
+ />
42
+ ))}
43
+ </>
44
+ );
45
+ }
@@ -0,0 +1,45 @@
1
+ export interface ITSlideToggleProps {
2
+ /**
3
+ * Callback executed when the switch is toggled. Receives the new state.
4
+ */
5
+ onToggle?: (value: boolean) => void;
6
+
7
+ /**
8
+ * Controlled state. Use this to completely control the component externally.
9
+ */
10
+ isOn?: boolean;
11
+
12
+ /**
13
+ * Initial state for uncontrolled usage.
14
+ * Default: false
15
+ */
16
+ initialState?: boolean;
17
+
18
+ /**
19
+ * Semantic theme color when activated (e.g., 'primary', 'success').
20
+ * Default: 'success'
21
+ */
22
+ activeColor?: string;
23
+
24
+ /**
25
+ * Semantic theme color or hex when deactivated.
26
+ * Default: '#9ca3af' (gray-400)
27
+ */
28
+ inactiveColor?: string;
29
+
30
+ /**
31
+ * Whether the switch is disabled.
32
+ */
33
+ disabled?: boolean;
34
+
35
+ /**
36
+ * Size of the switch: sm, md, lg.
37
+ * Default: md
38
+ */
39
+ size?: "sm" | "md" | "lg";
40
+
41
+ /**
42
+ * Additional container classes.
43
+ */
44
+ className?: string;
45
+ }
@@ -0,0 +1,121 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import ITSlideToggle from "./slide";
3
+ import { useState } from "react";
4
+
5
+ const meta: Meta<typeof ITSlideToggle> = {
6
+ title: "Components/Form Elements/ITSlideToggle",
7
+ component: ITSlideToggle,
8
+ parameters: {
9
+ layout: "centered",
10
+ },
11
+ tags: ["autodocs"],
12
+ argTypes: {
13
+ activeColor: {
14
+ control: "select",
15
+ options: ["primary", "secondary", "success", "danger", "warning", "info", "purple"],
16
+ },
17
+ size: {
18
+ control: "select",
19
+ options: ["sm", "md", "lg"],
20
+ },
21
+ disabled: {
22
+ control: "boolean",
23
+ },
24
+ isOn: {
25
+ control: "boolean",
26
+ },
27
+ initialState: {
28
+ control: "boolean",
29
+ },
30
+ },
31
+ };
32
+
33
+ export default meta;
34
+ type Story = StoryObj<typeof ITSlideToggle>;
35
+
36
+ export const Default: Story = {
37
+ args: {
38
+ activeColor: "success",
39
+ size: "md",
40
+ },
41
+ };
42
+
43
+ export const CheckedUncontrolled: Story = {
44
+ args: {
45
+ initialState: true,
46
+ activeColor: "primary",
47
+ },
48
+ };
49
+
50
+ const ControlledSlideWrapper = (args: any) => {
51
+ const [isOn, setIsOn] = useState(false);
52
+ return (
53
+ <div className="flex flex-col gap-4 items-center">
54
+ <ITSlideToggle {...args} isOn={isOn} onToggle={setIsOn} />
55
+ <span className="text-sm font-medium text-gray-500">
56
+ External State: {isOn ? "ON" : "OFF"}
57
+ </span>
58
+ </div>
59
+ );
60
+ };
61
+
62
+ export const Controlled: Story = {
63
+ render: (args) => <ControlledSlideWrapper {...args} />,
64
+ args: {
65
+ activeColor: "info",
66
+ size: "lg",
67
+ },
68
+ };
69
+
70
+ export const Sizes: Story = {
71
+ render: (args) => (
72
+ <div className="flex flex-col gap-6 items-center">
73
+ <div className="flex items-center gap-4">
74
+ <span className="w-16 text-sm text-gray-500 font-bold">Small</span>
75
+ <ITSlideToggle {...args} size="sm" initialState={true} />
76
+ </div>
77
+ <div className="flex items-center gap-4">
78
+ <span className="w-16 text-sm text-gray-500 font-bold">Medium</span>
79
+ <ITSlideToggle {...args} size="md" initialState={true} />
80
+ </div>
81
+ <div className="flex items-center gap-4">
82
+ <span className="w-16 text-sm text-gray-500 font-bold">Large</span>
83
+ <ITSlideToggle {...args} size="lg" initialState={true} />
84
+ </div>
85
+ </div>
86
+ ),
87
+ args: {
88
+ activeColor: "primary"
89
+ }
90
+ };
91
+
92
+ export const Disabled: Story = {
93
+ render: (args) => (
94
+ <div className="flex gap-8">
95
+ <div className="flex flex-col gap-2 items-center">
96
+ <span className="text-sm text-gray-500">Off</span>
97
+ <ITSlideToggle {...args} initialState={false} />
98
+ </div>
99
+ <div className="flex flex-col gap-2 items-center">
100
+ <span className="text-sm text-gray-500">On</span>
101
+ <ITSlideToggle {...args} initialState={true} />
102
+ </div>
103
+ </div>
104
+ ),
105
+ args: {
106
+ disabled: true,
107
+ },
108
+ };
109
+
110
+ export const Colors: Story = {
111
+ render: (args) => (
112
+ <div className="flex flex-col gap-4">
113
+ {(['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'purple'] as const).map((col) => (
114
+ <div key={col} className="flex items-center gap-4">
115
+ <span className="w-24 text-sm text-gray-500 font-bold capitalize">{col}</span>
116
+ <ITSlideToggle {...args} activeColor={col} initialState={true} />
117
+ </div>
118
+ ))}
119
+ </div>
120
+ ),
121
+ };
@@ -0,0 +1,109 @@
1
+ import { useState, useEffect } from "react";
2
+ import clsx from "clsx";
3
+ import { ITSlideToggleProps } from "./slide.props";
4
+ import { theme } from "@/theme/theme";
5
+
6
+ /**
7
+ * Slide toggle switch component.
8
+ * Supports fully controlled (`isOn`) or uncontrolled (`initialState`) modes.
9
+ */
10
+ export default function ITSlideToggle({
11
+ onToggle,
12
+ isOn: controlledIsOn,
13
+ initialState = false,
14
+ activeColor = "success",
15
+ inactiveColor = "#9ca3af", // default gray-400
16
+ disabled = false,
17
+ size = "md",
18
+ className = "",
19
+ }: ITSlideToggleProps) {
20
+ // Handle internal state if not controlled
21
+ const isControlled = controlledIsOn !== undefined;
22
+ const [internalIsOn, setInternalIsOn] = useState(initialState);
23
+
24
+ // Sync internal state with controlled state if it changes externally
25
+ useEffect(() => {
26
+ if (isControlled) {
27
+ setInternalIsOn(controlledIsOn);
28
+ }
29
+ }, [controlledIsOn, isControlled]);
30
+
31
+ const isOn = isControlled ? controlledIsOn : internalIsOn;
32
+
33
+ const toggleSwitch = () => {
34
+ if (disabled) return;
35
+
36
+ const newState = !isOn;
37
+ if (!isControlled) {
38
+ setInternalIsOn(newState);
39
+ }
40
+ if (onToggle) {
41
+ onToggle(newState);
42
+ }
43
+ };
44
+
45
+ // Resolve active theme color
46
+ const isThemeColor = activeColor in theme.colors;
47
+ const resolvedActiveColor = isThemeColor
48
+ ? theme.colors[activeColor as keyof typeof theme.colors][500]
49
+ : activeColor;
50
+
51
+ // Resolve inactive color (could also be theme color, but defaulting to hex)
52
+ const isInactiveThemeColor = inactiveColor in theme.colors;
53
+ const resolvedInactiveColor = isInactiveThemeColor
54
+ ? theme.colors[inactiveColor as keyof typeof theme.colors][400]
55
+ : inactiveColor;
56
+
57
+ const backgroundColor = isOn ? resolvedActiveColor : resolvedInactiveColor;
58
+
59
+ // Sizing definitions
60
+ const sizeClasses = {
61
+ sm: {
62
+ container: "w-10 h-5",
63
+ knob: "w-3.5 h-3.5",
64
+ translate: "translate-x-5",
65
+ },
66
+ md: {
67
+ container: "w-14 h-7",
68
+ knob: "w-5 h-5",
69
+ translate: "translate-x-7",
70
+ },
71
+ lg: {
72
+ container: "w-16 h-8",
73
+ knob: "w-6 h-6",
74
+ translate: "translate-x-8",
75
+ },
76
+ };
77
+
78
+ const { container, knob, translate } = sizeClasses[size];
79
+
80
+ return (
81
+ <div
82
+ onClick={toggleSwitch}
83
+ className={clsx(
84
+ "flex items-center rounded-full p-1 transition-colors duration-300",
85
+ container,
86
+ disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
87
+ className
88
+ )}
89
+ style={{ backgroundColor }}
90
+ role="switch"
91
+ aria-checked={isOn}
92
+ tabIndex={disabled ? -1 : 0}
93
+ onKeyDown={(e) => {
94
+ if (e.key === 'Enter' || e.key === ' ') {
95
+ e.preventDefault();
96
+ toggleSwitch();
97
+ }
98
+ }}
99
+ >
100
+ <div
101
+ className={clsx(
102
+ "bg-white rounded-full shadow-md transform transition-transform duration-300 pointer-events-none",
103
+ knob,
104
+ isOn ? translate : "translate-x-0"
105
+ )}
106
+ />
107
+ </div>
108
+ );
109
+ }
@@ -0,0 +1,10 @@
1
+ export interface ITSliderProps {
2
+ value: number;
3
+ onChange: (value: number) => void;
4
+ min?: number;
5
+ max?: number;
6
+ step?: number;
7
+ label?: string;
8
+ disabled?: boolean;
9
+ className?: string;
10
+ }