@axzydev/axzy_ui_system 1.4.1 → 1.4.3

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.
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "packageManager": "pnpm@10.34.0",
5
5
  "private": false,
6
6
  "license": "MIT",
7
- "version": "1.4.1",
7
+ "version": "1.4.3",
8
8
  "type": "module",
9
9
  "sideEffects": [
10
10
  "*.css"
package/src/App.tsx CHANGED
@@ -14,6 +14,7 @@ import "./index.css";
14
14
  // Import Showcases
15
15
  import { HomeShowcase } from "./showcases/HomeShowcase";
16
16
  import { GettingStartedShowcase } from "./showcases/GettingStartedShowcase";
17
+ import { HelpShowcase } from "./showcases/HelpShowcase";
17
18
  import { SizesShowcase } from "./showcases/SizesShowcase";
18
19
  import {
19
20
  CardShowcase,
@@ -233,6 +234,8 @@ function App() {
233
234
  switch (itemId) {
234
235
  case "getting-started":
235
236
  return <GettingStartedShowcase />;
237
+ case "help":
238
+ return <HelpShowcase />;
236
239
  // Structure
237
240
  case "layout":
238
241
  return <LayoutShowcase />;
@@ -2,6 +2,7 @@ import clsx from "clsx";
2
2
  import { ITCardProps } from "./card.props";
3
3
  import { useState } from "react";
4
4
  import ITText from "@/components/atoms/text/text";
5
+ import { useITFlatAppearance } from "@/theme/theme-context";
5
6
 
6
7
  /**
7
8
  * Versatile card container with optional image, title, body content, and action footer. Supports interactive hover states.
@@ -30,21 +31,33 @@ export default function ITCard({
30
31
  onClick,
31
32
  }: ITCardProps) {
32
33
  const [isHovered, setIsHovered] = useState(false);
34
+ const flat = useITFlatAppearance();
33
35
 
34
36
  const containerStyle: React.CSSProperties = {
35
37
  backgroundColor: "var(--it-card-bg, #ffffff)",
36
- borderColor: "var(--it-card-border, rgba(15, 23, 42, 0.06))",
38
+ borderColor: flat
39
+ ? "var(--it-card-flat-border, var(--color-secondary-200))"
40
+ : "var(--it-card-border, rgba(15, 23, 42, 0.06))",
37
41
  borderWidth: "1px",
38
- borderRadius: "var(--it-card-radius, var(--radius-2xl))",
42
+ borderStyle: "solid",
43
+ borderRadius: flat
44
+ ? "var(--it-card-radius, var(--radius-lg))"
45
+ : "var(--it-card-radius, var(--radius-2xl))",
39
46
  // Soft depth: every card floats a little at rest; interactive cards
40
47
  // gain visual weight on hover instead of appearing flat until clicked.
41
- boxShadow: onClick
42
- ? isHovered
43
- ? "var(--shadow-lg)"
44
- : "var(--shadow-sm)"
45
- : "var(--shadow-sm)",
48
+ // Flat appearance: the hairline border carries the surface, so a card
49
+ // only gets a shadow as hover feedback when it is clickable.
50
+ boxShadow: flat
51
+ ? onClick && isHovered
52
+ ? "var(--shadow-sm)"
53
+ : "none"
54
+ : onClick
55
+ ? isHovered
56
+ ? "var(--shadow-lg)"
57
+ : "var(--shadow-sm)"
58
+ : "var(--shadow-sm)",
46
59
  transition: onClick ? "box-shadow 0.2s ease-in-out, transform 0.2s ease-in-out" : "none",
47
- transform: onClick && isHovered ? "translateY(-2px)" : "translateY(0)",
60
+ transform: onClick && isHovered && !flat ? "translateY(-2px)" : "translateY(0)",
48
61
  cursor: onClick ? "pointer" : "default",
49
62
  };
50
63
 
@@ -72,7 +85,7 @@ export default function ITCard({
72
85
  {title && (
73
86
  <ITText
74
87
  as="h3"
75
- className={clsx("text-xl font-semibold mb-2", titleClassName)}
88
+ className={clsx(flat ? "text-base font-semibold mb-3" : "text-xl font-semibold mb-2", titleClassName)}
76
89
  >
77
90
  {title}
78
91
  </ITText>
@@ -2,6 +2,7 @@ import { FaChevronLeft } from "react-icons/fa";
2
2
  import { ITPageHeaderProps } from "./page-header.props";
3
3
  import ITBreadcrumbs from "@/components/molecules/breadcrumbs/breadcrumbs";
4
4
  import ITText from "@/components/atoms/text/text";
5
+ import { useITFlatAppearance } from "@/theme/theme-context";
5
6
 
6
7
  /**
7
8
  * Page title bar with breadcrumbs, description, back button, and action buttons.
@@ -29,6 +30,8 @@ export default function ITPageHeader({
29
30
  className,
30
31
  }: ITPageHeaderProps) {
31
32
  const showTopRow = breadcrumbs?.length || backAction;
33
+ // Flat appearance: compact title, no decorative icon tile.
34
+ const flat = useITFlatAppearance();
32
35
 
33
36
  return (
34
37
  <div className={`${className} space-y-3`}>
@@ -60,7 +63,7 @@ export default function ITPageHeader({
60
63
  {/* Fila principal: icono + título + descripción + (acciones si no hay top row) */}
61
64
  <div className="flex items-start justify-between gap-6">
62
65
  <div className="flex items-start gap-4 min-w-0">
63
- {icon && (
66
+ {icon && !flat && (
64
67
  <div
65
68
  className="flex items-center justify-center w-12 h-12 rounded-full border border-slate-200 dark:border-slate-700 bg-slate-50/50 dark:bg-slate-800/50 text-slate-600 dark:text-slate-300 flex-shrink-0 transition-all duration-200 hover:border-slate-300 dark:hover:border-slate-500 hover:shadow-sm"
66
69
  style={iconColor ? { color: iconColor } : undefined}
@@ -71,14 +74,22 @@ export default function ITPageHeader({
71
74
  <div className="min-w-0">
72
75
  <ITText
73
76
  as="h1"
74
- className="text-3xl font-bold text-slate-800 dark:text-white tracking-tight leading-tight"
77
+ className={
78
+ flat
79
+ ? "text-[1.625rem] font-bold text-slate-900 dark:text-white tracking-tight leading-tight"
80
+ : "text-3xl font-bold text-slate-800 dark:text-white tracking-tight leading-tight"
81
+ }
75
82
  >
76
83
  {title}
77
84
  </ITText>
78
85
  {description && (
79
86
  <ITText
80
87
  as="p"
81
- className="text-sm font-light text-slate-500 dark:text-slate-400 mt-1 leading-relaxed"
88
+ className={
89
+ flat
90
+ ? "text-sm font-normal text-slate-500 dark:text-slate-400 mt-0.5 leading-relaxed"
91
+ : "text-sm font-light text-slate-500 dark:text-slate-400 mt-1 leading-relaxed"
92
+ }
82
93
  >
83
94
  {description}
84
95
  </ITText>
@@ -20,6 +20,8 @@ Vertical navigation sidebar with collapsible submenus, tooltips when collapsed,
20
20
  | `subitemConnector` | `"dot" \| "\|" \| "none"` | `"dot"` | Visual connector style for sub-items. |
21
21
  | `className` | `string` | — | Additional CSS classes on the sidebar `<aside>`. |
22
22
  | `header` | `ReactNode` | — | Optional content rendered above the navigation list (e.g. a wordmark). Hidden while the sidebar is collapsed, so it only appears when the hover-expanded rail is shown. |
23
+ | `notification` | `ITSidebarNotification` | — | Optional notification entry (icon + label + `ITBadget` counter) pinned above the navigation. Unlike `header`, it stays visible while collapsed, showing only the icon and counter. |
24
+ | `brand` | `ITSidebarBrand` | — | Brand (`logo` + `text`) pinned in a 64px row at the very top. The logo stays visible while collapsed; the text does not. `ITLayout` fills it from `topBar.logo` / `topBar.logoText` when `sidebarFullHeight` is set. |
23
25
 
24
26
  ### ITNavigationItem
25
27
 
@@ -31,7 +33,18 @@ Vertical navigation sidebar with collapsible submenus, tooltips when collapsed,
31
33
  | `action` | `() => void` | Click handler for top-level items without submenus. |
32
34
  | `isActive` | `boolean` | Whether this item is active/highlighted. |
33
35
  | `subitems` | `ITNavigationSubItemEntry[]` | Array of entries: plain sub-items or titled groups (`ITNavigationSubItemGroup`). |
34
- | `badge` | `string` | Badge text (e.g. notification count). |
36
+ | `badge` | `number \| string` | Notification content rendered with `ITBadget` (e.g. an unread count). |
37
+ | `badgeProps` | `ITBadgetProps` | Styling forwarded to the item badge (`color`, `size`, `variant`, `className` for padding). |
38
+
39
+ ### ITSidebarNotification
40
+
41
+ | Prop | Type | Default | Description |
42
+ |------|------|---------|-------------|
43
+ | `count` | `number` | — | Count displayed inside the notification `ITBadget`. |
44
+ | `icon` | `ReactNode` | — | Icon rendered left of the label (e.g. a bell). Omit for a count-only pill. |
45
+ | `label` | `string` | `"Notificaciones"` | Accessible label and tooltip for the notification button. |
46
+ | `onClick` | `() => void` | — | Click handler for the notification button. |
47
+ | `badgeProps` | `ITBadgetProps` | `{ color: "danger", size: "sm" }` | Styling forwarded to the internal `ITBadget`; use `className` for padding. |
35
48
 
36
49
  ### ITNavigationSubItem
37
50
 
@@ -105,13 +118,46 @@ const items = [
105
118
 
106
119
  Discriminate entries with `"items" in entry`. The heading style (uppercase, `0.68rem`, weight 700, `0.08em` tracking, `var(--it-sidebar-icon-color, var(--color-secondary-400))`) is applied by the component and cannot be overridden per group.
107
120
 
121
+ ## Notifications and badges
122
+
123
+ Item badges and the notification row both render the `ITBadget` atom. Use `badge` for a per-item counter and `notification` for a top-level entry (e.g. a bell):
124
+
125
+ ```tsx
126
+ import { ITSidebar } from '@axzydev/axzy_ui_system';
127
+ import { FaBell } from 'react-icons/fa';
128
+
129
+ const items = [
130
+ { id: 'dashboard', label: 'Dashboard', isActive: true, action: () => {} },
131
+ { id: 'inbox', label: 'Inbox', badge: 12, action: () => {} },
132
+ {
133
+ id: 'alerts',
134
+ label: 'Alerts',
135
+ badge: 3,
136
+ badgeProps: { color: 'warning', variant: 'outlined' },
137
+ action: () => {},
138
+ },
139
+ ];
140
+
141
+ <ITSidebar
142
+ navigationItems={items}
143
+ notification={{
144
+ count: 5,
145
+ icon: <FaBell />,
146
+ label: 'Notifications',
147
+ onClick: () => {},
148
+ // Tweak the ITBadget, incl. padding via className.
149
+ badgeProps: { color: 'danger', className: 'px-2' },
150
+ }}
151
+ />
152
+ ```
153
+
108
154
  ## Notes
109
155
 
110
156
  - There is no collapse toggle button: the collapsed 88px rail expands and collapses automatically on pointer hover (300 ms leave delay).
111
157
  - Collapsed mode shrinks to icon-only; a glassmorphism panel shows the item's sub-items on hover. For an item whose `subitems` contain groups, that panel **flattens** every group into one plain list and renders **no** group headings.
112
158
  - Submenus expand/collapse with smooth transitions when a parent item is clicked; the expanded submenu is animated with a `max-h` transition (cap `1000px`).
113
159
  - Active item highlighting uses a colored left-border accent and subtle background tint.
114
- - Badge support renders a notification-style counter on the item.
160
+ - Badge support renders a notification-style counter on the item using the `ITBadget` atom; customize it with `badgeProps` (`color`, `size`, `variant`, `className`). The sidebar also accepts a `notification` entry (icon + label + `ITBadget` counter) pinned above the navigation, which stays visible while collapsed as icon + counter.
115
161
  - `subitemConnector` styles the visual link between parent and sub-items: `dot` (default), `|` (vertical bar), or `none`. Grouping does not change it: the `|` guide is a single `border-left` on the outer submenu `<ul>` only.
116
162
  - An active sub-item inside a group still auto-expands its parent, because auto-expand flattens groups the same way the collapsed flyout does.
117
163
  - `visibleOnMobile` forces the sidebar visible at every breakpoint and disables the collapsed rail, so it stays fully expanded.
@@ -1,3 +1,5 @@
1
+ import type { ITBadgetProps } from "@/components/atoms/badget/badget.props";
2
+
1
3
  /** A sub-navigation item nested under a parent menu. */
2
4
  export interface ITNavigationSubItem {
3
5
  /** Unique identifier for the sub-item. */
@@ -50,8 +52,49 @@ export interface ITNavigationItem {
50
52
  * The field `items` is reserved for {@link ITNavigationSubItemGroup}.
51
53
  */
52
54
  subitems?: ITNavigationSubItemEntry[];
53
- /** Badge text displayed on the item (e.g. notification count). */
54
- badge?: string;
55
+ /**
56
+ * Notification badge content rendered with the {@link ITBadget} atom
57
+ * (e.g. an unread count). Omit it to render no badge.
58
+ */
59
+ badge?: number | string;
60
+ /**
61
+ * Styling forwarded to the `ITBadget` rendered for {@link ITNavigationItem.badge}.
62
+ * `color`, `size`, `variant` and `className` are supported; use `className`
63
+ * to tweak the badge padding/positioning.
64
+ */
65
+ badgeProps?: ITBadgetProps;
66
+ }
67
+
68
+ /**
69
+ * Notification row pinned above the navigation in {@link ITSidebarProps}. It
70
+ * renders an optional icon, a label and an `ITBadget` counter — e.g. a bell with
71
+ * a number. When the rail is collapsed only the icon + counter are shown.
72
+ */
73
+ export interface ITSidebarNotification {
74
+ /** Count displayed inside the notification `ITBadget`. */
75
+ count: number;
76
+ /** Icon rendered left of the label (e.g. a bell). Omit for a count-only pill. */
77
+ icon?: React.ReactNode;
78
+ /** Accessible label and tooltip for the notification button. @default "Notificaciones" */
79
+ label?: string;
80
+ /** Click handler for the notification button. */
81
+ onClick?: () => void;
82
+ /**
83
+ * Styling forwarded to the internal `ITBadget`. Defaults to `color: "danger"`
84
+ * and `size: "sm"`; override `color`/`variant`/`size` or use `className` for padding.
85
+ */
86
+ badgeProps?: ITBadgetProps;
87
+ }
88
+
89
+ /**
90
+ * Brand block pinned at the top of {@link ITSidebarProps}. The logo stays
91
+ * visible while the rail is collapsed; the text only shows when expanded.
92
+ */
93
+ export interface ITSidebarBrand {
94
+ /** Logo element (e.g. an `<img>` or an SVG mark). Always visible. */
95
+ logo?: React.ReactNode;
96
+ /** Wordmark rendered next to the logo. Hidden while the sidebar is collapsed. */
97
+ text?: string;
55
98
  }
56
99
 
57
100
  /** Props for the ITSidebar vertical navigation component. */
@@ -77,5 +120,17 @@ export interface ITSidebarProps {
77
120
  * Hidden while the sidebar is collapsed, so it only shows on hover.
78
121
  */
79
122
  header?: React.ReactNode;
123
+ /**
124
+ * Optional notification entry pinned above the navigation (icon + label +
125
+ * `ITBadget` counter). Unlike {@link ITSidebarProps.header} it stays visible
126
+ * while collapsed, showing just the icon and the counter.
127
+ */
128
+ notification?: ITSidebarNotification;
129
+ /**
130
+ * Brand (logo + wordmark) pinned at the very top of the sidebar, in a 64px
131
+ * row that lines up with the top bar. Unlike {@link ITSidebarProps.header}
132
+ * the logo stays visible while collapsed. `ITLayout` fills it from
133
+ * `topBar.logo` / `topBar.logoText` when `sidebarFullHeight` is set.
134
+ */
135
+ brand?: ITSidebarBrand;
80
136
  }
81
-
@@ -1,6 +1,6 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react';
2
2
  import ITSidebar from '@/components/organisms/sidebar/sidebar';
3
- import { FaHome, FaUsers, FaCog, FaChartBar, FaShieldAlt, FaShoppingCart } from 'react-icons/fa';
3
+ import { FaHome, FaUsers, FaCog, FaChartBar, FaShieldAlt, FaShoppingCart, FaRegBell } from 'react-icons/fa';
4
4
  import { expect, fn, userEvent, waitFor } from 'storybook/test';
5
5
 
6
6
  const meta: Meta<typeof ITSidebar> = {
@@ -174,3 +174,61 @@ export const Grouped: Story = {
174
174
  await expect(groupedSubItemActionSpy).toHaveBeenCalled();
175
175
  },
176
176
  };
177
+
178
+ export const WithNotificationBadges: Story = {
179
+ args: {
180
+ navigationItems: [
181
+ { id: 'dashboard', label: 'Dashboard', icon: <FaHome />, isActive: true },
182
+ { id: 'inbox', label: 'Bandeja', icon: <FaUsers />, badge: 12 },
183
+ {
184
+ id: 'alerts',
185
+ label: 'Alertas',
186
+ icon: <FaShieldAlt />,
187
+ badge: 3,
188
+ badgeProps: { color: 'warning', variant: 'outlined' },
189
+ },
190
+ { id: 'settings', label: 'Configuración', icon: <FaCog />, action: fn() },
191
+ ],
192
+ notification: {
193
+ count: 5,
194
+ icon: <FaRegBell />,
195
+ label: 'Notificaciones',
196
+ onClick: fn(),
197
+ },
198
+ isCollapsed: false,
199
+ visibleOnMobile: true,
200
+ },
201
+ render: (args) => (
202
+ <div className="h-screen bg-gray-50 flex">
203
+ <ITSidebar {...args} />
204
+ <div className="flex-1 p-8 text-zinc-500 font-medium">
205
+ Cada ítem puede llevar un ITBadget con número (`badge` + `badgeProps`) y el sidebar admite una fila de notificación con contador (`notification`).
206
+ </div>
207
+ </div>
208
+ ),
209
+ };
210
+
211
+ export const CollapsedNotifications: Story = {
212
+ args: {
213
+ navigationItems: [
214
+ { id: 'dashboard', label: 'Dashboard', icon: <FaHome />, isActive: true },
215
+ { id: 'inbox', label: 'Bandeja', icon: <FaUsers />, badge: 12 },
216
+ { id: 'settings', label: 'Configuración', icon: <FaCog /> },
217
+ ],
218
+ notification: {
219
+ count: 9,
220
+ icon: <FaRegBell />,
221
+ label: 'Notificaciones',
222
+ },
223
+ isCollapsed: true,
224
+ visibleOnMobile: true,
225
+ },
226
+ render: (args) => (
227
+ <div className="h-screen bg-gray-50 flex">
228
+ <ITSidebar {...args} />
229
+ <div className="flex-1 p-8 text-zinc-500 font-medium">
230
+ Al colapsar, la fila de notificación queda como icono + contador, y los badges de cada ítem siguen visibles como píldoras con número.
231
+ </div>
232
+ </div>
233
+ ),
234
+ };
@@ -1,4 +1,5 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
+ import clsx from "clsx";
2
3
  import { FaChevronDown } from "react-icons/fa";
3
4
  import {
4
5
  ITNavigationItem,
@@ -7,7 +8,9 @@ import {
7
8
  ITNavigationSubItemGroup,
8
9
  ITSidebarProps,
9
10
  } from "./sidebar.props";
11
+ import ITBadget from "@/components/atoms/badget/badget";
10
12
  import ITText from "@/components/atoms/text/text";
13
+ import { useITFlatAppearance } from "@/theme/theme-context";
11
14
 
12
15
  /** Narrows a submenu entry to a titled group. */
13
16
  function isSubItemGroup(
@@ -42,7 +45,14 @@ function flattenSubItems(
42
45
  * { id: "billing", label: "Billing", isActive: true },
43
46
  * ],
44
47
  * },
48
+ * { id: "inbox", label: "Inbox", icon: <FaEnvelope />, badge: 12 },
45
49
  * ]}
50
+ * notification={{
51
+ * count: 5,
52
+ * icon: <FaBell />,
53
+ * label: "Notificaciones",
54
+ * onClick: () => navigate("/notifications"),
55
+ * }}
46
56
  * isCollapsed
47
57
  * />
48
58
  * ```
@@ -56,7 +66,11 @@ export default function ITSidebar({
56
66
  onSubItemClick,
57
67
  subitemConnector = 'dot',
58
68
  header,
69
+ notification,
70
+ brand,
59
71
  }: ITSidebarProps) {
72
+ // Flat appearance: plain surface, no glow/accent bar, narrower panel.
73
+ const flat = useITFlatAppearance();
60
74
  const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
61
75
  const [isHovering, setIsHovering] = useState(false);
62
76
  const sidebarRef = useRef<HTMLDivElement>(null);
@@ -135,7 +149,7 @@ export default function ITSidebar({
135
149
  };
136
150
 
137
151
  const isSidebarCollapsed = visibleOnMobile ? false : (!isHovering && isCollapsed);
138
- const sidebarWidth = isSidebarCollapsed ? "w-[88px]" : "w-[280px]";
152
+ const sidebarWidth = isSidebarCollapsed ? "w-[88px]" : flat ? "w-[248px]" : "w-[280px]";
139
153
 
140
154
  const renderSubItem = (subitem: ITNavigationSubItem) => (
141
155
  <li key={subitem.id} className="relative">
@@ -217,6 +231,88 @@ export default function ITSidebar({
217
231
  );
218
232
  };
219
233
 
234
+ const renderNotification = () => {
235
+ if (!notification) return null;
236
+
237
+ const {
238
+ count,
239
+ icon,
240
+ label = "Notificaciones",
241
+ onClick,
242
+ badgeProps,
243
+ } = notification;
244
+ const { className: badgeClassName, ...badgeRest } = badgeProps ?? {};
245
+
246
+ const badge = (
247
+ <ITBadget
248
+ color="danger"
249
+ size="sm"
250
+ {...badgeRest}
251
+ className={clsx("shadow-md", badgeClassName)}
252
+ >
253
+ {String(count)}
254
+ </ITBadget>
255
+ );
256
+
257
+ if (isSidebarCollapsed) {
258
+ return (
259
+ <button
260
+ type="button"
261
+ aria-label={label}
262
+ title={label}
263
+ onClick={onClick}
264
+ className="relative flex items-center justify-center w-full p-2 mb-1 rounded-xl transition-all duration-300"
265
+ style={{ color: "var(--it-sidebar-icon-color, var(--color-secondary-500))" }}
266
+ onMouseEnter={(e) => {
267
+ e.currentTarget.style.backgroundColor = "var(--it-sidebar-hover-bg, var(--color-secondary-100))";
268
+ }}
269
+ onMouseLeave={(e) => {
270
+ e.currentTarget.style.backgroundColor = "transparent";
271
+ }}
272
+ >
273
+ {icon && (
274
+ <span className="flex items-center justify-center text-[1.05rem]">{icon}</span>
275
+ )}
276
+ <span className="absolute top-0.5 right-0.5 pointer-events-none">{badge}</span>
277
+ </button>
278
+ );
279
+ }
280
+
281
+ return (
282
+ <button
283
+ type="button"
284
+ aria-label={label}
285
+ title={label}
286
+ onClick={onClick}
287
+ className="flex items-center gap-2.5 w-full text-left px-3 py-2 mb-1 rounded-xl transition-all duration-300"
288
+ style={{ color: "var(--it-sidebar-label-color, var(--color-secondary-600))" }}
289
+ onMouseEnter={(e) => {
290
+ e.currentTarget.style.backgroundColor = "var(--it-sidebar-hover-bg, var(--color-secondary-100))";
291
+ }}
292
+ onMouseLeave={(e) => {
293
+ e.currentTarget.style.backgroundColor = "transparent";
294
+ }}
295
+ >
296
+ {icon && (
297
+ <span
298
+ className="flex-shrink-0 flex items-center justify-center text-[1.05rem]"
299
+ style={{ color: "var(--it-sidebar-icon-color, #9ca3af)" }}
300
+ >
301
+ {icon}
302
+ </span>
303
+ )}
304
+ <ITText
305
+ as="span"
306
+ className="truncate tracking-wide"
307
+ style={{ fontSize: "0.8rem", fontWeight: 500 }}
308
+ >
309
+ {label}
310
+ </ITText>
311
+ <span className="ml-auto flex-shrink-0">{badge}</span>
312
+ </button>
313
+ );
314
+ };
315
+
220
316
  return (
221
317
  <aside
222
318
  ref={sidebarRef}
@@ -226,26 +322,47 @@ export default function ITSidebar({
226
322
  ${sidebarWidth}
227
323
  ${className}
228
324
  ${!visibleOnMobile ? "hidden lg:flex" : "flex"}
229
- shadow-[4px_0_32px_rgba(15,23,42,0.04)]
325
+ ${flat ? "" : "shadow-[4px_0_32px_rgba(15,23,42,0.04)]"}
230
326
  `}
231
327
  style={{
232
328
  zIndex: 50,
233
329
  backgroundColor: "var(--it-sidebar-bg, rgba(255, 255, 255, 0.90))",
234
330
  borderRight: "1px solid var(--it-sidebar-border, var(--color-secondary-200))",
235
- WebkitBackdropFilter: 'blur(12px)',
236
- backdropFilter: 'blur(12px)',
331
+ ...(flat ? {} : { WebkitBackdropFilter: 'blur(12px)', backdropFilter: 'blur(12px)' }),
237
332
  }}
238
333
  >
334
+ {brand && (brand.logo || brand.text) && (
335
+ <div
336
+ className={`flex items-center flex-shrink-0 h-[64px] ${isSidebarCollapsed ? "justify-center px-2" : "gap-2.5 px-7"}`}
337
+ >
338
+ {brand.logo && <div className="flex-shrink-0 flex items-center">{brand.logo}</div>}
339
+ {brand.text && !isSidebarCollapsed && (
340
+ <ITText
341
+ as="span"
342
+ className="text-[1.05rem] font-bold tracking-tight truncate"
343
+ style={{ color: "var(--it-sidebar-brand-color, var(--it-sidebar-label-color, var(--color-secondary-900)))" }}
344
+ >
345
+ {brand.text}
346
+ </ITText>
347
+ )}
348
+ </div>
349
+ )}
350
+
239
351
  {header && !isSidebarCollapsed && (
240
352
  <div className="px-4 pt-4 pb-1 flex-shrink-0">{header}</div>
241
353
  )}
242
354
 
355
+ {notification && (
356
+ <div className="px-4 pt-3 flex-shrink-0">{renderNotification()}</div>
357
+ )}
358
+
243
359
  {/* Navigation Items */}
244
360
  <nav aria-label="Componentes de la librería" className="flex-1 py-4 overflow-y-auto overflow-x-hidden custom-scrollbar px-4">
245
361
  <ul className="space-y-1">
246
362
  {navigationItems.map((item) => {
247
363
  const hasSubmenu = !!item.subitems && item.subitems.length > 0;
248
364
  const submenuId = `it-sidebar-submenu-${item.id}`;
365
+ const { className: badgeClassName, ...badgeRest } = item.badgeProps ?? {};
249
366
  return (
250
367
  <li key={item.id} className="relative group/navitem">
251
368
  <div
@@ -261,8 +378,8 @@ export default function ITSidebar({
261
378
  `}
262
379
  style={{
263
380
  backgroundColor: item.isActive ? "var(--it-sidebar-active-bg, var(--color-secondary-50))" : 'transparent',
264
- boxShadow: item.isActive ? 'var(--shadow-xs)' : 'none',
265
- border: item.isActive ? "1px solid var(--it-sidebar-border, var(--color-secondary-200))" : '1px solid 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'
266
383
  }}
267
384
  onMouseEnter={(e) => {
268
385
  if (!item.isActive) e.currentTarget.style.backgroundColor = "var(--it-sidebar-hover-bg, var(--color-secondary-100))";
@@ -278,7 +395,7 @@ export default function ITSidebar({
278
395
  }
279
396
  }}
280
397
  >
281
- {item.isActive && !isSidebarCollapsed && (
398
+ {item.isActive && !isSidebarCollapsed && !flat && (
282
399
  <div
283
400
  className="absolute left-0 top-1/4 bottom-1/4 w-[3px] rounded-r-full transition-all"
284
401
  style={{ backgroundColor: "var(--it-sidebar-active-icon, var(--color-primary-500))", boxShadow: "0 0 10px var(--it-sidebar-active-icon, var(--color-primary-500))" }}
@@ -292,8 +409,8 @@ export default function ITSidebar({
292
409
  style={{
293
410
  color: item.isActive ? "var(--it-sidebar-active-icon, var(--color-primary-500))" : "var(--it-sidebar-icon-color, #9ca3af)",
294
411
  opacity: item.isActive ? 1 : 0.8,
295
- fontSize: item.isActive ? '1.12rem' : '1.05rem',
296
- filter: item.isActive ? 'drop-shadow(0 0 8px rgba(255,255,255,0.2))' : 'none'
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'
297
414
  }}
298
415
  >
299
416
  {item.icon}
@@ -305,7 +422,7 @@ export default function ITSidebar({
305
422
  className={`transition-all duration-300 truncate tracking-wide`}
306
423
  style={{
307
424
  color: item.isActive ? "var(--it-sidebar-active-color, #ffffff)" : "var(--it-sidebar-label-color, var(--color-secondary-300))",
308
- fontSize: '0.8rem',
425
+ fontSize: flat ? '0.875rem' : '0.8rem',
309
426
  fontWeight: item.isActive ? '600' : '500'
310
427
  }}
311
428
  >
@@ -321,22 +438,21 @@ export default function ITSidebar({
321
438
  </div>
322
439
  )}
323
440
 
324
- {item.badge && (
325
- <span
326
- className={`
327
- absolute flex items-center justify-center font-bold shadow-md
328
- ${isSidebarCollapsed
329
- ? "top-1 right-1 w-2.5 h-2.5 rounded-full ring-2 ring-white"
330
- : "right-3 top-1/2 transform -translate-y-1/2 px-1.5 py-0 text-[9px] rounded-full backdrop-blur-sm"}
331
- `}
332
- style={{
333
- backgroundColor: "var(--it-sidebar-badge-bg, var(--color-primary-500))",
334
- color: "var(--it-sidebar-badge-color, #ffffff)",
335
- boxShadow: isSidebarCollapsed ? "0 0 0 2px var(--it-sidebar-bg, var(--color-secondary-900))" : 'none'
336
- }}
441
+ {item.badge != null && item.badge !== "" && (
442
+ <ITBadget
443
+ color="primary"
444
+ size="sm"
445
+ {...badgeRest}
446
+ className={clsx(
447
+ "absolute shadow-md",
448
+ isSidebarCollapsed
449
+ ? "top-0.5 right-0.5"
450
+ : "right-3 top-1/2 -translate-y-1/2",
451
+ badgeClassName,
452
+ )}
337
453
  >
338
- {isSidebarCollapsed ? "" : item.badge}
339
- </span>
454
+ {String(item.badge)}
455
+ </ITBadget>
340
456
  )}
341
457
  </div>
342
458
 
@@ -49,4 +49,11 @@ 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
+ * Hides the logo and `logoText` from the `lg` breakpoint up and left-aligns
54
+ * `centerContent`. Use it when the brand already lives somewhere else (e.g.
55
+ * at the top of a full-height sidebar); `ITLayout` sets it for you when
56
+ * `sidebarFullHeight` is on. Below `lg` the logo is still shown. @default false
57
+ */
58
+ hideBrandOnDesktop?: boolean;
52
59
  }