@pagamio/frontend-commons-lib 0.8.249 → 0.8.251

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.
@@ -31,13 +31,16 @@ const MenuItemRenderer = ({ item, depth = 0, iconResolver = defaultIconResolver,
31
31
  const [isExpanded, setIsExpanded] = useState(false);
32
32
  // Resolve icon
33
33
  const IconComponent = resolveIcon(item.icon, iconResolver);
34
- // Check active state
35
- const isActive = item.href ? pathname === item.href || pathname.startsWith(`${item.href}/`) : false;
34
+ // Check active state — exact match only for leaf items, prefix match for parents with children
35
+ const hasChildren = item.items && item.items.length > 0;
36
+ const isActive = item.href
37
+ ? pathname === item.href || (hasChildren && pathname.startsWith(`${item.href}/`))
38
+ : false;
36
39
  // Check if any child is active
37
40
  const hasActiveChild = useMemo(() => {
38
41
  const checkActive = (items = []) => {
39
42
  return items.some((child) => {
40
- if (child.href && pathname.startsWith(child.href))
43
+ if (child.href && (pathname === child.href || pathname.startsWith(`${child.href}/`)))
41
44
  return true;
42
45
  if (child.items)
43
46
  return checkActive(child.items);
@@ -20,12 +20,12 @@ const SidebarContainer = forwardRef(({ className, side = 'left', variant = 'defa
20
20
  // Mobile sidebar (offcanvas)
21
21
  if (isMobile) {
22
22
  const { setOpenMobile } = useSidebarV2();
23
- return (_jsxs(_Fragment, { children: [openMobile && (_jsx("div", { className: "fixed inset-0 z-40 bg-black/50 backdrop-blur-sm transition-opacity cursor-pointer", onClick: () => setOpenMobile(false), "aria-hidden": "true" })), _jsx("aside", { ref: ref, "data-state": openMobile ? 'open' : 'closed', "data-side": side, className: cn('fixed top-16 bottom-0 z-[60] flex flex-col', 'bg-background', 'text-foreground', 'transition-transform duration-300 ease-in-out', side === 'left' ? 'left-0' : 'right-0', openMobile ? 'translate-x-0' : side === 'left' ? '-translate-x-full' : 'translate-x-full', className), style: {
23
+ return (_jsxs(_Fragment, { children: [openMobile && (_jsx("div", { className: "fixed inset-0 z-40 bg-black/50 backdrop-blur-sm transition-opacity cursor-pointer", onClick: () => setOpenMobile(false), "aria-hidden": "true" })), _jsx("aside", { ref: ref, "data-state": openMobile ? 'open' : 'closed', "data-side": side, className: cn('fixed top-16 bottom-0 z-[60] flex flex-col', 'bg-card', 'text-foreground', 'transition-transform duration-300 ease-in-out', side === 'left' ? 'left-0' : 'right-0', openMobile ? 'translate-x-0' : side === 'left' ? '-translate-x-full' : 'translate-x-full', className), style: {
24
24
  width: config.mobileWidth,
25
25
  }, ...props, children: children })] }));
26
26
  }
27
27
  // Desktop sidebar - Fixed position, starts below navbar
28
- return (_jsx("aside", { ref: ref, "data-state": state, "data-side": side, "data-variant": variant, className: cn('group/sidebar fixed top-16 left-0 z-50', 'hidden md:flex flex-col', 'bg-background', 'text-foreground', 'border-border', 'transition-all duration-200 ease-in-out', side === 'left' ? 'border-r' : 'border-l', variant === 'floating' && 'rounded-lg border m-2 shadow-lg', variant === 'inset' && 'rounded-lg', className), style: {
28
+ return (_jsx("aside", { ref: ref, "data-state": state, "data-side": side, "data-variant": variant, className: cn('group/sidebar fixed top-16 left-0 z-50', 'hidden md:flex flex-col', 'bg-card', 'text-foreground', 'border-border', 'transition-all duration-200 ease-in-out', side === 'left' ? 'border-r' : 'border-l', variant === 'floating' && 'rounded-lg border m-2 shadow-lg', variant === 'inset' && 'rounded-lg', className), style: {
29
29
  width: open ? config.width : config.collapsedWidth,
30
30
  height: 'calc(100vh - 4rem)', // Full height minus navbar (64px)
31
31
  }, ...props, children: children }));
@@ -35,7 +35,7 @@ SidebarContainer.displayName = 'SidebarContainer';
35
35
  * Sticky header at the top of sidebar
36
36
  */
37
37
  const SidebarHeader = forwardRef(({ className, children, ...props }, ref) => {
38
- return (_jsx("div", { ref: ref, className: cn('flex flex-col gap-2 p-4 border-b border-sidebar-border z-[60] relative bg-sidebar', className), ...props, children: children }));
38
+ return (_jsx("div", { ref: ref, className: cn('flex flex-col gap-2 p-4 border-b border-sidebar-border z-[60] relative bg-card', className), ...props, children: children }));
39
39
  });
40
40
  SidebarHeader.displayName = 'SidebarHeader';
41
41
  /**
@@ -64,7 +64,7 @@ const FilterComponent = ({ filters, selectedFilters, showApplyFilterButton = tru
64
64
  resetFilters();
65
65
  }
66
66
  };
67
- return (_jsxs(FilterWrapper, { isNarrow: isNarrow, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [showSearch && (_jsx("div", { className: "w-full sm:w-[300px]", children: _jsx(Input, { leftSection: _jsx(IconSearch, { size: 16 }), placeholder: searctInputPlaceHolder, value: searchQuery, onChange: (event) => onSearch(event), onKeyDown: handleSearchKeyDown, className: "w-full", styles: { input: { height: 39 } }, "aria-label": "Search" }) })), filters.map((filter) => {
67
+ return (_jsxs(FilterWrapper, { isNarrow: isNarrow, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [showSearch && (_jsx("div", { className: "w-full sm:w-[300px]", children: _jsx(Input, { leftSection: _jsx(IconSearch, { size: 16 }), placeholder: searctInputPlaceHolder, value: searchQuery, onChange: (event) => onSearch(event), onKeyDown: handleSearchKeyDown, className: "w-full", styles: { input: { height: 39, backgroundColor: 'hsl(var(--muted))' } }, "aria-label": "Search" }) })), filters.map((filter) => {
68
68
  const { name, type, options } = filter;
69
69
  const value = selectedFilters[name];
70
70
  const renderFilterInput = () => {
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  const FilterWrapper = ({ isNarrow, children }) => {
3
- return (_jsx("div", { className: "bg-muted shadow-xl rounded-lg p-4 mb-2", children: _jsx("div", { className: isNarrow
3
+ return (_jsx("div", { className: "bg-card shadow-xl rounded-lg p-4 mb-2", children: _jsx("div", { className: isNarrow
4
4
  ? 'flex flex-col w-full space-y-4'
5
5
  : 'flex flex-row justify-between items-start w-full space-y-0 space-x-0 sm:space-x-4', children: children }) }));
6
6
  };
@@ -6,7 +6,7 @@
6
6
  * ISP: Only the props you use matter — everything is optional.
7
7
  *
8
8
  * Supports ALL Flowbite tab variants (default, underline, pills, fullWidth)
9
- * plus vertical orientation with matching styling.
9
+ * plus vertical orientation using the same Flowbite Tabs with a vertical theme.
10
10
  */
11
11
  import { type TabsTheme } from 'flowbite-react';
12
12
  import type { ComponentProps, FC, ReactNode } from 'react';
@@ -21,7 +21,7 @@ export interface PagamioTabsProps {
21
21
  tabs: PagamioTabItem[];
22
22
  /** Flowbite tab variant */
23
23
  variant?: 'default' | 'underline' | 'pills' | 'fullWidth';
24
- /** Tab orientation — horizontal uses Flowbite, vertical uses custom layout */
24
+ /** Tab orientation — horizontal or vertical, both use Flowbite Tabs */
25
25
  orientation?: 'horizontal' | 'vertical';
26
26
  /** Controlled active tab index (0-based) */
27
27
  activeTab?: number;
@@ -39,7 +39,7 @@ export interface PagamioTabsProps {
39
39
  description?: string;
40
40
  /** Container className */
41
41
  className?: string;
42
- /** Tab list className (horizontal only) */
42
+ /** Tab list className */
43
43
  tabListClassName?: string;
44
44
  /** Tab panel className */
45
45
  tabPanelClassName?: string;
@@ -7,12 +7,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
7
  * ISP: Only the props you use matter — everything is optional.
8
8
  *
9
9
  * Supports ALL Flowbite tab variants (default, underline, pills, fullWidth)
10
- * plus vertical orientation with matching styling.
10
+ * plus vertical orientation using the same Flowbite Tabs with a vertical theme.
11
11
  */
12
12
  import { TabItem as FlowbiteTabItem, Tabs } from 'flowbite-react';
13
13
  import { useRef, useState } from 'react';
14
- import Button from './Button';
15
- // ─── Contained Tab Theme (matches old ContainedTab look) ─────────────
14
+ // ─── Horizontal Tab Theme (matches old ContainedTab look) ────────────
16
15
  const containedTheme = {
17
16
  base: 'flex flex-col gap-2',
18
17
  tablist: {
@@ -69,26 +68,63 @@ const containedTheme = {
69
68
  },
70
69
  tabpanel: 'p-0',
71
70
  };
72
- function VerticalTabs({ tabs, activeIndex, onSelect, variant, className, tabPanelClassName, }) {
73
- const getItemClasses = (isActive) => {
74
- const base = 'inline-flex items-center px-4 py-3 rounded-lg w-full text-left transition-colors duration-200 focus:outline-none whitespace-nowrap';
75
- if (variant === 'pills') {
76
- return isActive
77
- ? `${base} text-white bg-primary hover:bg-primary/90`
78
- : `${base} text-foreground/70 bg-muted hover:text-foreground hover:bg-accent`;
79
- }
80
- if (variant === 'underline') {
81
- return isActive
82
- ? `${base} text-primary border-l-2 border-primary bg-primary/10`
83
- : `${base} text-foreground/70 border-l-2 border-transparent hover:text-foreground hover:bg-accent`;
84
- }
85
- // default / fullWidth
86
- return isActive
87
- ? `${base} text-foreground bg-card shadow-sm border border-border`
88
- : `${base} text-foreground/70 bg-muted hover:text-foreground hover:bg-accent`;
89
- };
90
- return (_jsxs("div", { className: `flex flex-col md:flex-row gap-4 ${className ?? ''}`, children: [_jsx("div", { className: "md:w-64 flex-shrink-0", children: _jsx("ul", { className: "flex flex-row md:flex-col space-x-2 md:space-x-0 md:space-y-2 text-sm font-medium text-muted-foreground overflow-x-auto md:overflow-x-visible", children: tabs.map((tab, index) => (_jsx("li", { className: "flex-shrink-0", children: _jsxs(Button, { type: "button", variant: "ghost", onClick: () => !tab.disabled && onSelect(index), disabled: tab.disabled, className: getItemClasses(activeIndex === index), children: [tab.icon && (_jsx("span", { className: `w-4 h-4 me-2 ${activeIndex === index ? 'text-current' : 'text-muted-foreground'}`, children: _jsx(tab.icon, {}) })), tab.title] }) }, tab.id))) }) }), _jsx("div", { className: "flex-1 min-w-0", children: _jsx("div", { className: `bg-card border border-border rounded-lg p-6 ${tabPanelClassName ?? ''}`, children: tabs[activeIndex]?.content }) })] }));
91
- }
71
+ // ─── Vertical Tab Theme ──────────────────────────────────────────────
72
+ const verticalTheme = {
73
+ base: 'flex flex-col md:flex-row gap-2',
74
+ tablist: {
75
+ base: 'flex flex-row md:flex-col md:w-44 flex-shrink-0 space-x-2 md:space-x-0 md:space-y-1 text-sm font-medium overflow-x-auto md:overflow-x-visible p-[5px] rounded-lg bg-card',
76
+ variant: {
77
+ default: '',
78
+ fullWidth: '',
79
+ pills: '',
80
+ underline: '',
81
+ },
82
+ tabitem: {
83
+ base: 'flex items-center justify-center px-4 py-2 rounded-lg w-full text-center text-sm font-medium whitespace-nowrap first:ml-0 disabled:cursor-not-allowed disabled:text-muted-foreground focus:outline-none transition-colors duration-200',
84
+ variant: {
85
+ default: {
86
+ base: '',
87
+ active: {
88
+ on: 'text-foreground bg-card shadow-sm border border-border',
89
+ off: 'text-foreground/70 bg-muted hover:text-foreground hover:bg-accent',
90
+ },
91
+ },
92
+ fullWidth: {
93
+ base: '',
94
+ active: {
95
+ on: 'text-foreground bg-card shadow-sm border border-border',
96
+ off: 'text-foreground/70 bg-muted hover:text-foreground hover:bg-accent',
97
+ },
98
+ },
99
+ pills: {
100
+ base: '',
101
+ active: {
102
+ on: 'text-white bg-primary',
103
+ off: 'text-foreground/70 hover:text-foreground hover:bg-accent',
104
+ },
105
+ },
106
+ underline: {
107
+ base: '',
108
+ active: {
109
+ on: 'text-primary border-l-2 border-primary bg-primary/10',
110
+ off: 'text-foreground/70 border-l-2 border-transparent hover:text-foreground hover:bg-accent',
111
+ },
112
+ },
113
+ },
114
+ icon: 'mr-2 h-5 w-5',
115
+ },
116
+ },
117
+ tabitemcontainer: {
118
+ base: 'flex-1 min-w-0',
119
+ variant: {
120
+ default: '',
121
+ fullWidth: '',
122
+ pills: '',
123
+ underline: '',
124
+ },
125
+ },
126
+ tabpanel: 'p-0',
127
+ };
92
128
  // ─── PagamioTabs Component ───────────────────────────────────────────
93
129
  const PagamioTabs = ({ tabs, variant = 'default', orientation = 'horizontal', activeTab: controlledTab, defaultTab = 0, onTabChange, isSticky = false, isFullWidth = true, title, description, className = '', tabListClassName = '', tabPanelClassName = '', theme: customTheme, }) => {
94
130
  const [internalTab, setInternalTab] = useState(defaultTab);
@@ -103,24 +139,21 @@ const PagamioTabs = ({ tabs, variant = 'default', orientation = 'horizontal', ac
103
139
  };
104
140
  // ── Header ───────────────────────────────────────────────────────
105
141
  const header = title || description ? (_jsxs("div", { className: "mb-2", children: [title && _jsx("h2", { className: "text-lg font-semibold", children: title }), description && _jsx("p", { className: "text-sm text-muted-foreground", children: description })] })) : null;
106
- // ── Vertical orientation ─────────────────────────────────────────
107
- if (orientation === 'vertical') {
108
- return (_jsxs("div", { className: `w-full ${className}`, children: [header, _jsx(VerticalTabs, { tabs: tabs, activeIndex: currentTab, onSelect: handleTabChange, variant: variant, tabPanelClassName: tabPanelClassName })] }));
109
- }
110
- // ── Horizontal orientation (Flowbite Tabs) ───────────────────────
111
- const stickyClass = isSticky ? 'sticky top-0 z-10 bg-background' : '';
112
- const widthClass = isFullWidth ? 'w-full' : 'w-fit';
113
- // Merge themes: containedTheme base + customTheme overrides
142
+ const isVertical = orientation === 'vertical';
143
+ const baseTheme = isVertical ? verticalTheme : containedTheme;
144
+ // ── Build merged theme ─────────────────────────────────────────
145
+ const stickyClass = !isVertical && isSticky ? 'sticky top-0 z-10 bg-background' : '';
146
+ const widthClass = !isVertical && isFullWidth ? 'w-full' : '';
114
147
  const mergedTheme = {
115
- ...containedTheme,
148
+ ...baseTheme,
116
149
  ...customTheme,
117
150
  tablist: {
118
- ...containedTheme.tablist,
151
+ ...baseTheme.tablist,
119
152
  ...customTheme?.tablist,
120
- base: `${stickyClass} ${containedTheme.tablist?.base ?? ''} ${widthClass} ${tabListClassName}`.trim(),
153
+ base: `${stickyClass} ${baseTheme.tablist?.base ?? ''} ${widthClass} ${tabListClassName}`.trim(),
121
154
  },
122
- tabpanel: tabPanelClassName || containedTheme.tabpanel || 'p-0',
155
+ tabpanel: tabPanelClassName || baseTheme.tabpanel || 'p-0',
123
156
  };
124
- return (_jsxs("div", { className: `w-full ${className}`, children: [header, _jsx(Tabs, { ref: tabsRef, "aria-label": "Tabs", variant: variant, onActiveTabChange: handleTabChange, theme: mergedTheme, className: "flex-nowrap", style: { display: 'flex', flexDirection: 'row' }, children: tabs.map((tab) => (_jsx(FlowbiteTabItem, { title: tab.title, icon: tab.icon, disabled: tab.disabled, active: currentTab === tabs.indexOf(tab), children: tab.content }, tab.id))) })] }));
157
+ return (_jsxs("div", { className: `w-full ${className}`, children: [header, _jsx(Tabs, { ref: tabsRef, "aria-label": "Tabs", variant: variant, onActiveTabChange: handleTabChange, theme: mergedTheme, children: tabs.map((tab) => (_jsx(FlowbiteTabItem, { title: tab.title, icon: tab.icon, disabled: tab.disabled, active: currentTab === tabs.indexOf(tab), children: tab.content }, tab.id))) })] }));
125
158
  };
126
159
  export default PagamioTabs;
package/lib/styles.css CHANGED
@@ -6143,12 +6143,12 @@ video {
6143
6143
  max-height: 100%;
6144
6144
  }
6145
6145
 
6146
- .md\:w-48 {
6147
- width: 12rem;
6146
+ .md\:w-44 {
6147
+ width: 11rem;
6148
6148
  }
6149
6149
 
6150
- .md\:w-64 {
6151
- width: 16rem;
6150
+ .md\:w-48 {
6151
+ width: 12rem;
6152
6152
  }
6153
6153
 
6154
6154
  .md\:w-96 {
@@ -6211,10 +6211,10 @@ video {
6211
6211
  margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse)));
6212
6212
  }
6213
6213
 
6214
- .md\:space-y-2 > :not([hidden]) ~ :not([hidden]) {
6214
+ .md\:space-y-1 > :not([hidden]) ~ :not([hidden]) {
6215
6215
  --tw-space-y-reverse: 0;
6216
- margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
6217
- margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
6216
+ margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
6217
+ margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
6218
6218
  }
6219
6219
 
6220
6220
  .md\:overflow-x-visible {
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.249",
4
+ "version": "0.8.251",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "provenance": false