@axzydev/axzy_ui_system 1.4.3 → 1.4.5

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.
@@ -68,6 +68,8 @@ export default function ITSidebar({
68
68
  header,
69
69
  notification,
70
70
  brand,
71
+ onToggleCollapse,
72
+ expandOnHover = true,
71
73
  }: ITSidebarProps) {
72
74
  // Flat appearance: plain surface, no glow/accent bar, narrower panel.
73
75
  const flat = useITFlatAppearance();
@@ -132,6 +134,13 @@ export default function ITSidebar({
132
134
  }
133
135
  }, [navigationItems]);
134
136
 
137
+ const isSidebarCollapsed = visibleOnMobile ? false : isCollapsed && !(expandOnHover && isHovering);
138
+ const sidebarWidth = isSidebarCollapsed
139
+ ? flat ? "w-[72px]" : "w-[88px]"
140
+ : flat ? "w-[248px]" : "w-[280px]";
141
+ // Icon-only rail that never expands on hover: names show in a tooltip.
142
+ const fixedRail = isSidebarCollapsed && !expandOnHover;
143
+
135
144
  const toggleExpanded = (itemId: string) => {
136
145
  const newExpanded = new Set(expandedItems);
137
146
  if (newExpanded.has(itemId)) newExpanded.delete(itemId);
@@ -141,15 +150,20 @@ export default function ITSidebar({
141
150
 
142
151
  const handleItemClick = (item: ITNavigationItem) => {
143
152
  if (item.subitems && item.subitems.length > 0) {
144
- toggleExpanded(item.id);
153
+ if (isSidebarCollapsed && !expandOnHover) {
154
+ // Icon-only rail: there is no room for the submenu, so open the
155
+ // sidebar with this group expanded.
156
+ setExpandedItems((prev) => new Set(prev).add(item.id));
157
+ onToggleCollapse?.();
158
+ } else {
159
+ toggleExpanded(item.id);
160
+ }
145
161
  } else {
146
162
  if (item.action) item.action();
147
163
  if (onItemClick) onItemClick(item);
148
164
  }
149
165
  };
150
166
 
151
- const isSidebarCollapsed = visibleOnMobile ? false : (!isHovering && isCollapsed);
152
- const sidebarWidth = isSidebarCollapsed ? "w-[88px]" : flat ? "w-[248px]" : "w-[280px]";
153
167
 
154
168
  const renderSubItem = (subitem: ITNavigationSubItem) => (
155
169
  <li key={subitem.id} className="relative">
@@ -163,7 +177,7 @@ export default function ITSidebar({
163
177
  style={{
164
178
  color: subitem.isActive ? "var(--it-sidebar-active-color, var(--color-secondary-900))" : "var(--it-sidebar-label-color, var(--color-secondary-600))",
165
179
  backgroundColor: subitem.isActive ? "var(--it-sidebar-active-bg, var(--color-secondary-50))" : 'transparent',
166
- fontSize: '0.78rem',
180
+ fontSize: flat ? '0.84rem' : '0.78rem',
167
181
  fontWeight: subitem.isActive ? 600 : 500,
168
182
  letterSpacing: '0.01em',
169
183
  marginLeft: subitemConnector === '|' ? '-1px' : '0',
@@ -181,7 +195,7 @@ export default function ITSidebar({
181
195
  }
182
196
  }}
183
197
  >
184
- {subitem.isActive && subitemConnector === '|' && (
198
+ {subitem.isActive && subitemConnector === '|' && !flat && (
185
199
  <div
186
200
  className="absolute left-0 top-1/3 bottom-1/3 w-[2.5px] rounded-r-full transition-all"
187
201
  style={{
@@ -357,12 +371,15 @@ export default function ITSidebar({
357
371
  )}
358
372
 
359
373
  {/* Navigation Items */}
360
- <nav aria-label="Componentes de la librería" className="flex-1 py-4 overflow-y-auto overflow-x-hidden custom-scrollbar px-4">
374
+ <nav aria-label="Componentes de la librería" className={`flex-1 py-4 custom-scrollbar ${fixedRail ? "overflow-visible" : "overflow-y-auto overflow-x-hidden"} ${flat && isSidebarCollapsed ? "px-3" : "px-4"}`}>
361
375
  <ul className="space-y-1">
362
376
  {navigationItems.map((item) => {
363
377
  const hasSubmenu = !!item.subitems && item.subitems.length > 0;
364
378
  const submenuId = `it-sidebar-submenu-${item.id}`;
365
379
  const { className: badgeClassName, ...badgeRest } = item.badgeProps ?? {};
380
+ // Flat + expanded: a parent whose subitem is active stays plain; the
381
+ // subitem carries the highlight. Collapsed, the parent icon shows it.
382
+ const active = !!item.isActive && !(flat && hasSubmenu && !isSidebarCollapsed);
366
383
  return (
367
384
  <li key={item.id} className="relative group/navitem">
368
385
  <div
@@ -374,18 +391,18 @@ export default function ITSidebar({
374
391
  className={`flex items-center cursor-pointer
375
392
  transition-all duration-300 ease-[cubic-bezier(0.2,0,0,1)]
376
393
  rounded-xl relative overflow-visible
377
- ${isSidebarCollapsed ? "justify-center p-2 mb-1" : "justify-between px-3 py-2 mb-0.5"}
394
+ ${isSidebarCollapsed ? (flat ? "justify-center h-10 mb-1" : "justify-center p-2 mb-1") : "justify-between px-3 py-2 mb-0.5"}
378
395
  `}
379
396
  style={{
380
- backgroundColor: item.isActive ? "var(--it-sidebar-active-bg, var(--color-secondary-50))" : 'transparent',
381
- boxShadow: item.isActive && !flat ? 'var(--shadow-xs)' : 'none',
382
- border: item.isActive && !flat ? "1px solid var(--it-sidebar-border, var(--color-secondary-200))" : '1px solid transparent'
397
+ backgroundColor: active ? "var(--it-sidebar-active-bg, var(--color-secondary-50))" : 'transparent',
398
+ boxShadow: active && !flat ? 'var(--shadow-xs)' : 'none',
399
+ border: active && !flat ? "1px solid var(--it-sidebar-border, var(--color-secondary-200))" : '1px solid transparent'
383
400
  }}
384
401
  onMouseEnter={(e) => {
385
- if (!item.isActive) e.currentTarget.style.backgroundColor = "var(--it-sidebar-hover-bg, var(--color-secondary-100))";
402
+ if (!active) e.currentTarget.style.backgroundColor = "var(--it-sidebar-hover-bg, var(--color-secondary-100))";
386
403
  }}
387
404
  onMouseLeave={(e) => {
388
- if (!item.isActive) e.currentTarget.style.backgroundColor = 'transparent';
405
+ if (!active) e.currentTarget.style.backgroundColor = 'transparent';
389
406
  }}
390
407
  onClick={() => handleItemClick(item)}
391
408
  onKeyDown={(e) => {
@@ -395,7 +412,7 @@ export default function ITSidebar({
395
412
  }
396
413
  }}
397
414
  >
398
- {item.isActive && !isSidebarCollapsed && !flat && (
415
+ {active && !isSidebarCollapsed && !flat && (
399
416
  <div
400
417
  className="absolute left-0 top-1/4 bottom-1/4 w-[3px] rounded-r-full transition-all"
401
418
  style={{ backgroundColor: "var(--it-sidebar-active-icon, var(--color-primary-500))", boxShadow: "0 0 10px var(--it-sidebar-active-icon, var(--color-primary-500))" }}
@@ -407,10 +424,10 @@ export default function ITSidebar({
407
424
  <div
408
425
  className={`transition-all duration-300 flex-shrink-0 flex items-center justify-center`}
409
426
  style={{
410
- color: item.isActive ? "var(--it-sidebar-active-icon, var(--color-primary-500))" : "var(--it-sidebar-icon-color, #9ca3af)",
411
- opacity: item.isActive ? 1 : 0.8,
412
- fontSize: flat ? '1rem' : item.isActive ? '1.12rem' : '1.05rem',
413
- filter: item.isActive && !flat ? 'drop-shadow(0 0 8px rgba(255,255,255,0.2))' : 'none'
427
+ color: active ? "var(--it-sidebar-active-icon, var(--color-primary-500))" : "var(--it-sidebar-icon-color, #9ca3af)",
428
+ opacity: active ? 1 : 0.8,
429
+ fontSize: flat ? '1rem' : active ? '1.12rem' : '1.05rem',
430
+ filter: active && !flat ? 'drop-shadow(0 0 8px rgba(255,255,255,0.2))' : 'none'
414
431
  }}
415
432
  >
416
433
  {item.icon}
@@ -421,9 +438,9 @@ export default function ITSidebar({
421
438
  <ITText as="span"
422
439
  className={`transition-all duration-300 truncate tracking-wide`}
423
440
  style={{
424
- color: item.isActive ? "var(--it-sidebar-active-color, #ffffff)" : "var(--it-sidebar-label-color, var(--color-secondary-300))",
441
+ color: active ? "var(--it-sidebar-active-color, #ffffff)" : "var(--it-sidebar-label-color, var(--color-secondary-300))",
425
442
  fontSize: flat ? '0.875rem' : '0.8rem',
426
- fontWeight: item.isActive ? '600' : '500'
443
+ fontWeight: active ? '600' : '500'
427
444
  }}
428
445
  >
429
446
  {item.label}
@@ -433,7 +450,7 @@ export default function ITSidebar({
433
450
 
434
451
  {!isSidebarCollapsed && item.subitems && item.subitems.length > 0 && (
435
452
  <div className={`flex-shrink-0 transition-transform duration-300 ease-[cubic-bezier(0.2,0,0,1)] ${expandedItems.has(item.id) ? "rotate-180" : ""}`}
436
- style={{ color: item.isActive ? "var(--it-sidebar-active-color, var(--color-secondary-900))" : "var(--it-sidebar-icon-color, var(--color-secondary-500))", opacity: 0.7 }}>
453
+ style={{ color: active ? "var(--it-sidebar-active-color, var(--color-secondary-900))" : "var(--it-sidebar-icon-color, var(--color-secondary-500))", opacity: 0.7 }}>
437
454
  <FaChevronDown className="w-3 h-3" />
438
455
  </div>
439
456
  )}
@@ -457,7 +474,21 @@ export default function ITSidebar({
457
474
  </div>
458
475
 
459
476
  {/* Glassmorphism Collapsed Tooltip / Submenu */}
460
- {isSidebarCollapsed && (
477
+ {isSidebarCollapsed && flat && (
478
+ <div
479
+ role="tooltip"
480
+ className="absolute left-full top-1/2 -translate-y-1/2 ml-3 px-2.5 py-1.5 rounded-lg whitespace-nowrap opacity-0 invisible group-hover/navitem:opacity-100 group-hover/navitem:visible transition-opacity duration-150 pointer-events-none z-[70]"
481
+ style={{
482
+ backgroundColor: "var(--it-sidebar-tooltip-bg, var(--color-secondary-900))",
483
+ color: "var(--it-sidebar-tooltip-color, #ffffff)",
484
+ fontSize: "0.78rem",
485
+ fontWeight: 500,
486
+ }}
487
+ >
488
+ {item.label}
489
+ </div>
490
+ )}
491
+ {isSidebarCollapsed && !flat && (
461
492
  <div
462
493
  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-[70] 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)]"
463
494
  style={{
@@ -503,7 +534,7 @@ export default function ITSidebar({
503
534
  {!isSidebarCollapsed && item.subitems && item.subitems.length > 0 && (
504
535
  <div id={submenuId} role="group" aria-label={item.label} className={`overflow-hidden transition-all duration-400 ease-[cubic-bezier(0.2,0,0,1)] ${expandedItems.has(item.id) ? "max-h-[1000px] opacity-100 mt-1" : "max-h-0 opacity-0"}`}>
505
536
  <ul
506
- className="ml-4 flex flex-col gap-0 py-0.5"
537
+ className={`${flat && subitemConnector === '|' ? "ml-[21px] pl-2" : "ml-4"} flex flex-col gap-0 py-0.5`}
507
538
  style={{
508
539
  borderLeft: subitemConnector === '|'
509
540
  ? "1px solid var(--it-sidebar-border, var(--color-secondary-200))"
@@ -49,6 +49,12 @@ export interface ITTopBarProps {
49
49
  showMobileMenuButton?: boolean;
50
50
  /** Callback fired when the mobile menu toggle button is clicked. */
51
51
  onToggleMobileMenu?: () => void;
52
+ /**
53
+ * Keeps the menu toggle button visible from the `lg` breakpoint up as well
54
+ * (it is mobile-only by default). `ITLayout` turns it on when the sidebar
55
+ * collapses by button instead of hover (`sidebar.expandOnHover === false`). @default false
56
+ */
57
+ showDesktopMenuButton?: boolean;
52
58
  /**
53
59
  * Hides the logo and `logoText` from the `lg` breakpoint up and left-aligns
54
60
  * `centerContent`. Use it when the brand already lives somewhere else (e.g.
@@ -69,6 +69,7 @@ export default function ITTopBar({
69
69
  navItems,
70
70
  onNavItemClick,
71
71
  hideBrandOnDesktop = false,
72
+ showDesktopMenuButton = false,
72
73
  }: ITTopBarProps) {
73
74
  // Flat appearance: plain bar (no shadow/blur), initials avatar, name before it.
74
75
  const flat = useITFlatAppearance();
@@ -100,7 +101,9 @@ export default function ITTopBar({
100
101
  {/* Mobile Sidebar Toggle */}
101
102
  {showMobileMenuButton && (
102
103
  <button
103
- className="lg:hidden p-2.5 rounded-xl transition-colors duration-200"
104
+ type="button"
105
+ aria-label="Menú"
106
+ className={`${showDesktopMenuButton ? "" : "lg:hidden"} p-2.5 -ml-2.5 rounded-xl transition-colors duration-200`}
104
107
  style={{
105
108
  color: "var(--it-topbar-icon, #64748b)",
106
109
  }}
@@ -56,8 +56,17 @@ export default function ITLayout({
56
56
  const handleToggleCollapse = isControlled ? (sidebar.onToggleCollapse ?? (() => {})) : () => setInternalCollapsed(v => !v);
57
57
 
58
58
  const layoutTokens = theme.layout;
59
- // Must match ITSidebar's own at-rest width (88px rail / 280px, 248px when flat).
60
- const reservedWidth = desktopCollapsed ? "w-[88px]" : flat ? "w-[248px]" : "w-[280px]";
59
+ // Must match ITSidebar's own at-rest width (88px rail / 280px; 72px / 248px when flat).
60
+ const reservedWidth = desktopCollapsed
61
+ ? flat ? "w-[72px]" : "w-[88px]"
62
+ : flat ? "w-[248px]" : "w-[280px]";
63
+ // The sidebar collapses by button instead of hover: the top bar's menu
64
+ // button toggles the rail on desktop and still opens the drawer on mobile.
65
+ const toggleByButton = sidebar.expandOnHover === false;
66
+ const handleMenuButton = () => {
67
+ if (toggleByButton && window.matchMedia("(min-width: 1024px)").matches) handleToggleCollapse();
68
+ else setMobileSidebarOpen(v => !v);
69
+ };
61
70
  // With a full-height sidebar the brand moves from the top bar to the sidebar.
62
71
  const brand = sidebarFullHeight
63
72
  ? (sidebar.brand ?? { logo: topBar.logo, text: topBar.logoText })
@@ -68,7 +77,8 @@ export default function ITLayout({
68
77
  {...topBar}
69
78
  hideBrandOnDesktop={sidebarFullHeight || topBar.hideBrandOnDesktop}
70
79
  showMobileMenuButton
71
- onToggleMobileMenu={() => setMobileSidebarOpen(v => !v)}
80
+ showDesktopMenuButton={toggleByButton || topBar.showDesktopMenuButton}
81
+ onToggleMobileMenu={handleMenuButton}
72
82
  />
73
83
  );
74
84
 
package/src/index.ts CHANGED
@@ -54,6 +54,7 @@ import ITSlideToggle from "./components/atoms/slide/slide";
54
54
  import ITSlider from "./components/atoms/slider/slider";
55
55
  import ITStack from "./components/atoms/stack/stack";
56
56
  import ITStatCard from "./components/molecules/stat-card/stat-card";
57
+ import ITLineBarChart from "./components/molecules/line-bar-chart/line-bar-chart";
57
58
  import ITTable from "./components/molecules/table/table";
58
59
  import ITText from "./components/atoms/text/text";
59
60
  import ITTextarea from "./components/atoms/textarea/textarea";
@@ -109,6 +110,7 @@ import type { ITSlideToggleProps } from "./components/atoms/slide/slide.props";
109
110
  import type { ITSliderProps } from "./components/atoms/slider/slider.props";
110
111
  import type { ITStackProps } from "./components/atoms/stack/stack.props";
111
112
  import type { ITStatCardProps } from "./components/molecules/stat-card/stat-card.props";
113
+ import type { ITLineBarChartProps, ITLineBarDatum } from "./components/molecules/line-bar-chart/line-bar-chart.props";
112
114
  import type { ITTableProps, Column, ColumnFilterMode } from "./components/molecules/table/table.props";
113
115
  import type { ITTextProps } from "./components/atoms/text/text.props";
114
116
  import type { ITTextareaProps } from "./components/atoms/textarea/textarea.props";
@@ -151,7 +153,7 @@ export {
151
153
  ITLoader, ITNavbar, ITPage, ITPageHeader, ITPagination, ITPopover, ITProgress,
152
154
  ITRadioGroup, ITSelect, ITSearchSelect,
153
155
  ITSearchTable,
154
- ITSegmentedControl, ITSidebar, ITSkeleton, ITSlideToggle, ITSlider, ITStack, ITStatCard, ITStepper,
156
+ ITSegmentedControl, ITSidebar, ITSkeleton, ITSlideToggle, ITSlider, ITStack, ITStatCard, ITStepper, ITLineBarChart,
155
157
  ITTable, ITTabs, ITText, ITTextarea, ITTripleFilter, ITThemeProvider,
156
158
  useITTheme, useITThemeSafe, useITFlatAppearance, ITTimePicker, ITToast, ITWysiwyg,
157
159
  UploadStatus, FileTypeEnum,
@@ -181,7 +183,7 @@ export {
181
183
  ITRadioGroupProps, ITRadioOption, ITSelectProps, ITSearchSelectProps,
182
184
  ITSearchTableProps,
183
185
  ITSegmentedControlProps, ITSidebarProps, ITSkeletonProps, ITSlideToggleProps, ITSliderProps,
184
- ITStackProps, ITStatCardProps, ITStepperProps,
186
+ ITStackProps, ITStatCardProps, ITStepperProps, ITLineBarChartProps, ITLineBarDatum,
185
187
  ITTableProps, ITTabsProps, ITTabItem, ITTextProps, ITTextareaProps,
186
188
  TableDensity,
187
189
  ITTripleFilterProps, ITTripleFilterOption, ITThemeConfig, ITThemeProviderProps, ITThemePalette, ITThemeAppearance,