@pagamio/frontend-commons-lib 0.8.218 → 0.8.219

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.
@@ -6,16 +6,34 @@ import { useAppSidebarContext } from '../../context';
6
6
  import { useLibTranslations, useTranslation } from '../../translations';
7
7
  const AppSidebarMenu = () => {
8
8
  const { pages, groupByItem } = useAppSidebarContext();
9
+ // Helper to generate unique key for sidebar items
10
+ const getItemKey = (item, index) => item.key || item.href || `${item.label}-${index}`;
9
11
  // If groupByItem is true each item gets its own ItemGroup
10
12
  if (groupByItem) {
11
- return (_jsx(Sidebar.Items, { children: pages.map((item) => (_jsx(Sidebar.ItemGroup, { children: _jsx(AppSidebarItem, { ...item }) }, item.label))) }));
13
+ return (_jsx(Sidebar.Items, { children: pages.map((item, index) => {
14
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
15
+ const { key, ...itemProps } = item;
16
+ return (_jsx(Sidebar.ItemGroup, { children: _jsx(AppSidebarItem, { ...itemProps }) }, getItemKey(item, index)));
17
+ }) }));
12
18
  }
13
19
  // Default behavior all items in one ItemGroup
14
- return (_jsx(Sidebar.Items, { children: _jsx(Sidebar.ItemGroup, { children: pages.map((item) => (_jsx(AppSidebarItem, { ...item }, item.label))) }) }));
20
+ return (_jsx(Sidebar.Items, { children: _jsx(Sidebar.ItemGroup, { children: pages.map((item, index) => {
21
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
22
+ const { key, ...itemProps } = item;
23
+ return _jsx(AppSidebarItem, { ...itemProps }, getItemKey(item, index));
24
+ }) }) }));
15
25
  };
16
- const AppSidebarItem = ({ href, target, icon, label, items, badge, forceDropdown, collapsible = true, showSeparator = false, }) => {
17
- const { pathname, linkComponent: Link } = useAppSidebarContext();
26
+ const AppSidebarItem = ({ href, target, icon, label, items, badge, forceDropdown, collapsible = true, showSeparator = false, isSectionHeader = false, }) => {
27
+ const { pathname, linkComponent: Link, desktop } = useAppSidebarContext();
18
28
  const { t } = useTranslation();
29
+ // Handle section headers - render as a non-interactive label (hide when collapsed)
30
+ if (isSectionHeader) {
31
+ // Don't render section headers when sidebar is collapsed
32
+ if (desktop.isCollapsed) {
33
+ return null;
34
+ }
35
+ return (_jsxs("div", { className: "pt-4 pb-1 px-3 first:pt-0", children: [showSeparator && _jsx("hr", { className: "mb-3 border-t border-gray-200 dark:border-gray-700" }), _jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400", children: t(label) })] }));
36
+ }
19
37
  // Check if current path matches this item or any of its descendants
20
38
  const isParentActive = href ? pathname === href || pathname.startsWith(`${href}/`) : false;
21
39
  // Recursive function to check if any nested child is active
@@ -40,14 +58,18 @@ const AppSidebarItem = ({ href, target, icon, label, items, badge, forceDropdown
40
58
  }
41
59
  // Handle non-collapsible sections - render as section header with always-visible links
42
60
  if (items?.length && collapsible === false) {
43
- return (_jsxs("div", { className: "space-y-1", children: [showSeparator && _jsx("hr", { className: "my-3 border-t border-gray-200 dark:border-gray-700" }), _jsxs("div", { className: twMerge('flex items-center gap-3 px-3 py-2 text-sm font-semibold uppercase tracking-wider', 'text-gray-500 dark:text-gray-400'), children: [icon && React.createElement(icon, { className: 'h-5 w-5' }), _jsx("span", { children: t(label) })] }), _jsx("div", { className: "space-y-1 pl-2", children: items.map((child) => (_jsx(React.Fragment, { children: child.items?.length ? (
44
- // Recursively render nested items
45
- _jsx("div", { className: "pl-3", children: _jsx(AppSidebarItem, { ...child }) })) : child.href ? (
46
- // Render leaf item with link
47
- _jsx(Sidebar.Item, { as: Link, href: child.href, target: child.target, icon: child.icon, className: twMerge('justify-center [&>*]:font-normal', 'text-gray-600 hover:text-primary-700 hover:bg-primary-50/70', 'dark:text-gray-400 dark:hover:text-primary-300 dark:hover:bg-primary-900/30', pathname === child.href &&
48
- 'text-primary-700 bg-primary-100/80 hover:bg-primary-100/80 dark:text-primary-300 dark:bg-primary-900/40 dark:hover:bg-primary-900/40'), children: t(child.label) })) : (
49
- // Render leaf item without link
50
- _jsx(Sidebar.Item, { icon: child.icon, className: twMerge('justify-center [&>*]:font-normal', 'text-gray-600 cursor-default', 'dark:text-gray-400'), children: t(child.label) })) }, child.label))) })] }));
61
+ return (_jsxs("div", { className: "space-y-1", children: [showSeparator && _jsx("hr", { className: "my-3 border-t border-gray-200 dark:border-gray-700" }), _jsxs("div", { className: twMerge('flex items-center gap-3 px-3 py-2 text-sm font-semibold uppercase tracking-wider', 'text-gray-500 dark:text-gray-400'), children: [icon && React.createElement(icon, { className: 'h-5 w-5' }), _jsx("span", { children: t(label) })] }), _jsx("div", { className: "space-y-1 pl-2", children: items.map((child, childIndex) => {
62
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
63
+ const { key, ...childProps } = child;
64
+ return (_jsx(React.Fragment, { children: child.items?.length ? (
65
+ // Recursively render nested items
66
+ _jsx("div", { className: "pl-3", children: _jsx(AppSidebarItem, { ...childProps }) })) : child.href ? (
67
+ // Render leaf item with link
68
+ _jsx(Sidebar.Item, { as: Link, href: child.href, target: child.target, icon: child.icon, className: twMerge('justify-center [&>*]:font-normal', 'text-gray-600 hover:text-primary-700 hover:bg-primary-50/70', 'dark:text-gray-400 dark:hover:text-primary-300 dark:hover:bg-primary-900/30', pathname === child.href &&
69
+ 'text-primary-700 bg-primary-100/80 hover:bg-primary-100/80 dark:text-primary-300 dark:bg-primary-900/40 dark:hover:bg-primary-900/40'), children: t(child.label) })) : (
70
+ // Render leaf item without link
71
+ _jsx(Sidebar.Item, { icon: child.icon, className: twMerge('justify-center [&>*]:font-normal', 'text-gray-600 cursor-default', 'dark:text-gray-400'), children: t(child.label) })) }, child.key || child.href || `${child.label}-${childIndex}`));
72
+ }) })] }));
51
73
  }
52
74
  if (items?.length) {
53
75
  const isOpen = isParentActive || hasActiveChild(items);
@@ -57,14 +79,18 @@ const AppSidebarItem = ({ href, target, icon, label, items, badge, forceDropdown
57
79
  // Parent styling when it's directly active
58
80
  isParentActive &&
59
81
  !hasActiveChild(items) &&
60
- 'text-primary-700 bg-primary-100/80 hover:bg-primary-100/80 dark:text-primary-300 dark:bg-primary-900/40 dark:hover:bg-primary-900/40'), theme: { list: 'space-y-2 py-2 [&>li>div]:w-full' }, children: items.map((child) => (_jsx(React.Fragment, { children: child.items?.length ? (
61
- // Recursively render nested items
62
- _jsx("div", { className: "pl-3", children: _jsx(AppSidebarItem, { ...child }) })) : child.href ? (
63
- // Render leaf item with link
64
- _jsx(Sidebar.Item, { as: Link, href: child.href, target: child.target, icon: child.icon, className: twMerge('justify-center [&>*]:font-normal', 'text-gray-600 hover:text-primary-700 hover:bg-primary-50/70', 'dark:text-gray-400 dark:hover:text-primary-300 dark:hover:bg-primary-900/30', pathname === child.href &&
65
- 'text-primary-700 bg-primary-100/80 hover:bg-primary-100/80 dark:text-primary-300 dark:bg-primary-900/40 dark:hover:bg-primary-900/40'), children: t(child.label) })) : (
66
- // Render leaf item without link
67
- _jsx(Sidebar.Item, { icon: child.icon, className: twMerge('justify-center [&>*]:font-normal', 'text-gray-600 cursor-default', 'dark:text-gray-400'), children: t(child.label) })) }, child.label))) }));
82
+ 'text-primary-700 bg-primary-100/80 hover:bg-primary-100/80 dark:text-primary-300 dark:bg-primary-900/40 dark:hover:bg-primary-900/40'), theme: { list: 'space-y-2 py-2 [&>li>div]:w-full' }, children: items.map((child, childIndex) => {
83
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
84
+ const { key, ...childProps } = child;
85
+ return (_jsx(React.Fragment, { children: child.items?.length ? (
86
+ // Recursively render nested items
87
+ _jsx("div", { className: "pl-3", children: _jsx(AppSidebarItem, { ...childProps }) })) : child.href ? (
88
+ // Render leaf item with link
89
+ _jsx(Sidebar.Item, { as: Link, href: child.href, target: child.target, icon: child.icon, className: twMerge('justify-center [&>*]:font-normal', 'text-gray-600 hover:text-primary-700 hover:bg-primary-50/70', 'dark:text-gray-400 dark:hover:text-primary-300 dark:hover:bg-primary-900/30', pathname === child.href &&
90
+ 'text-primary-700 bg-primary-100/80 hover:bg-primary-100/80 dark:text-primary-300 dark:bg-primary-900/40 dark:hover:bg-primary-900/40'), children: t(child.label) })) : (
91
+ // Render leaf item without link
92
+ _jsx(Sidebar.Item, { icon: child.icon, className: twMerge('justify-center [&>*]:font-normal', 'text-gray-600 cursor-default', 'dark:text-gray-400'), children: t(child.label) })) }, child.key || child.href || `${child.label}-${childIndex}`));
93
+ }) }));
68
94
  }
69
95
  // Render leaf item
70
96
  // If no href, render as a non-link item
@@ -1,6 +1,11 @@
1
1
  import type { FC, HTMLAttributeAnchorTarget, PropsWithChildren } from 'react';
2
2
  import React from 'react';
3
3
  interface AppSidebarPageItem {
4
+ /**
5
+ * Unique key for React rendering. If not provided, href or label will be used.
6
+ * Use this to avoid duplicate key warnings when multiple items have the same label.
7
+ */
8
+ key?: string;
4
9
  href?: string;
5
10
  target?: HTMLAttributeAnchorTarget;
6
11
  icon?: FC;
@@ -19,6 +24,11 @@ interface AppSidebarPageItem {
19
24
  * Useful for visually separating non-collapsible sections. Defaults to false.
20
25
  */
21
26
  showSeparator?: boolean;
27
+ /**
28
+ * When true, renders as a flat inline section header (non-interactive label).
29
+ * Used for visual grouping in flat sidebar structures.
30
+ */
31
+ isSectionHeader?: boolean;
22
32
  }
23
33
  /**
24
34
  * Props for the AppSidebarContext
package/lib/styles.css CHANGED
@@ -2928,6 +2928,9 @@ input[type="range"]::-ms-fill-lower {
2928
2928
  .pb-0 {
2929
2929
  padding-bottom: 0px;
2930
2930
  }
2931
+ .pb-1 {
2932
+ padding-bottom: 0.25rem;
2933
+ }
2931
2934
  .pb-2\.5 {
2932
2935
  padding-bottom: 0.625rem;
2933
2936
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pagamio/frontend-commons-lib",
3
3
  "description": "Pagamio library for Frontend reusable components like the form engine and table container",
4
- "version": "0.8.218",
4
+ "version": "0.8.219",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "provenance": false