@axzydev/axzy_ui_system 1.2.1 → 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.
- package/dist/index.css +82 -1
- package/dist/index.css.map +1 -1
- package/package.json +2 -2
- package/src/App.tsx +354 -0
- package/src/assets/logo.png +0 -0
- package/src/assets/react.svg +1 -0
- package/src/components/alert/alert.props.ts +13 -0
- package/src/components/alert/alert.stories.tsx +41 -0
- package/src/components/alert/alert.tsx +53 -0
- package/src/components/avatar/avatar.props.ts +14 -0
- package/src/components/avatar/avatar.stories.tsx +46 -0
- package/src/components/avatar/avatar.tsx +53 -0
- package/src/components/badget/badget.props.ts +12 -0
- package/src/components/badget/badget.stories.tsx +76 -0
- package/src/components/badget/badget.tsx +61 -0
- package/src/components/breadcrumbs/breadcrumbs.props.ts +13 -0
- package/src/components/breadcrumbs/breadcrumbs.stories.tsx +21 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +34 -0
- package/src/components/button/button.props.ts +18 -0
- package/src/components/button/button.stories.tsx +174 -0
- package/src/components/button/button.tsx +117 -0
- package/src/components/calendar/calendar.props.ts +33 -0
- package/src/components/calendar/calendar.stories.tsx +91 -0
- package/src/components/calendar/calendar.tsx +608 -0
- package/src/components/calendar/index.ts +3 -0
- package/src/components/card/card.props.ts +13 -0
- package/src/components/card/card.stories.tsx +58 -0
- package/src/components/card/card.tsx +79 -0
- package/src/components/checkbox/checkbox.props.ts +11 -0
- package/src/components/checkbox/checkbox.stories.tsx +54 -0
- package/src/components/checkbox/checkbox.tsx +52 -0
- package/src/components/confirm-dialog/confirm-dialog.props.ts +14 -0
- package/src/components/confirm-dialog/confirm-dialog.stories.tsx +33 -0
- package/src/components/confirm-dialog/confirm-dialog.tsx +45 -0
- package/src/components/data-table/ITDataTable.stories.tsx +213 -0
- package/src/components/data-table/dataTable.props.ts +69 -0
- package/src/components/data-table/dataTable.tsx +313 -0
- package/src/components/date-picker/date-picker.props.ts +30 -0
- package/src/components/date-picker/date-picker.stories.tsx +90 -0
- package/src/components/date-picker/datePicker.tsx +307 -0
- package/src/components/dialog/dialog.props.ts +9 -0
- package/src/components/dialog/dialog.stories.tsx +80 -0
- package/src/components/dialog/dialog.tsx +88 -0
- package/src/components/divider/divider.props.ts +8 -0
- package/src/components/divider/divider.stories.tsx +34 -0
- package/src/components/divider/divider.tsx +21 -0
- package/src/components/drawer/drawer.props.ts +14 -0
- package/src/components/drawer/drawer.stories.tsx +41 -0
- package/src/components/drawer/drawer.tsx +53 -0
- package/src/components/dropfile/dropfile.stories.tsx +75 -0
- package/src/components/dropfile/dropfile.tsx +407 -0
- package/src/components/empty-state/empty-state.props.ts +9 -0
- package/src/components/empty-state/empty-state.stories.tsx +20 -0
- package/src/components/empty-state/empty-state.tsx +21 -0
- package/src/components/flex/flex.props.ts +22 -0
- package/src/components/flex/flex.stories.tsx +71 -0
- package/src/components/flex/flex.tsx +79 -0
- package/src/components/form-builder/fieldRenderer.tsx +218 -0
- package/src/components/form-builder/formBuilder.context.tsx +70 -0
- package/src/components/form-builder/formBuilder.props.ts +43 -0
- package/src/components/form-builder/formBuilder.stories.tsx +317 -0
- package/src/components/form-builder/formBuilder.tsx +186 -0
- package/src/components/form-builder/useFormBuilder.ts +80 -0
- package/src/components/form-header/form-header.props.ts +5 -0
- package/src/components/form-header/form-header.tsx +38 -0
- package/src/components/grid/grid.props.ts +17 -0
- package/src/components/grid/grid.stories.tsx +72 -0
- package/src/components/grid/grid.tsx +69 -0
- package/src/components/image/image.props.ts +7 -0
- package/src/components/image/image.tsx +38 -0
- package/src/components/input/input.props.ts +49 -0
- package/src/components/input/input.stories.tsx +115 -0
- package/src/components/input/input.tsx +615 -0
- package/src/components/layout/layout.props.ts +10 -0
- package/src/components/layout/layout.stories.tsx +114 -0
- package/src/components/layout/layout.tsx +80 -0
- package/src/components/loader/loader.props.ts +8 -0
- package/src/components/loader/loader.stories.tsx +105 -0
- package/src/components/loader/loader.tsx +108 -0
- package/src/components/navbar/navbar.props.ts +37 -0
- package/src/components/navbar/navbar.tsx +328 -0
- package/src/components/page/page.props.ts +19 -0
- package/src/components/page/page.stories.tsx +98 -0
- package/src/components/page/page.tsx +90 -0
- package/src/components/page-header/page-header.props.ts +11 -0
- package/src/components/page-header/page-header.stories.tsx +61 -0
- package/src/components/page-header/page-header.tsx +62 -0
- package/src/components/pagination/pagination.props.ts +53 -0
- package/src/components/pagination/pagination.stories.tsx +111 -0
- package/src/components/pagination/pagination.tsx +241 -0
- package/src/components/popover/popover.props.ts +12 -0
- package/src/components/popover/popover.stories.tsx +25 -0
- package/src/components/popover/popover.tsx +45 -0
- package/src/components/progress/progress.props.ts +12 -0
- package/src/components/progress/progress.stories.tsx +40 -0
- package/src/components/progress/progress.tsx +52 -0
- package/src/components/radio/radio.props.ts +16 -0
- package/src/components/radio/radio.stories.tsx +50 -0
- package/src/components/radio/radio.tsx +58 -0
- package/src/components/search-select/index.ts +2 -0
- package/src/components/search-select/search-select.props.ts +46 -0
- package/src/components/search-select/search-select.stories.tsx +129 -0
- package/src/components/search-select/search-select.tsx +229 -0
- package/src/components/searchTable/components/EditableCell.tsx +149 -0
- package/src/components/searchTable/components/PaginationControls.tsx +86 -0
- package/src/components/searchTable/components/PaginationInfo.tsx +20 -0
- package/src/components/searchTable/components/SearchAndSortBar.tsx +53 -0
- package/src/components/searchTable/components/SearchInput.tsx +33 -0
- package/src/components/searchTable/components/SortButton.tsx +50 -0
- package/src/components/searchTable/components/TableEmptyState.tsx +22 -0
- package/src/components/searchTable/components/TableHeader.tsx +35 -0
- package/src/components/searchTable/components/TableHeaderCell.tsx +43 -0
- package/src/components/searchTable/components/TableRow.tsx +144 -0
- package/src/components/searchTable/searchTable.props.ts +56 -0
- package/src/components/searchTable/searchTable.tsx +187 -0
- package/src/components/segmented-control/segmented-control.props.ts +18 -0
- package/src/components/segmented-control/segmented-control.stories.tsx +63 -0
- package/src/components/segmented-control/segmented-control.tsx +52 -0
- package/src/components/select/select.props.ts +25 -0
- package/src/components/select/select.stories.tsx +86 -0
- package/src/components/select/select.tsx +150 -0
- package/src/components/sidebar/sidebar.props.ts +28 -0
- package/src/components/sidebar/sidebar.stories.tsx +117 -0
- package/src/components/sidebar/sidebar.tsx +313 -0
- package/src/components/skeleton/skeleton.props.ts +12 -0
- package/src/components/skeleton/skeleton.stories.tsx +30 -0
- package/src/components/skeleton/skeleton.tsx +45 -0
- package/src/components/slide/slide.props.ts +45 -0
- package/src/components/slide/slide.stories.tsx +121 -0
- package/src/components/slide/slide.tsx +109 -0
- package/src/components/slider/slider.props.ts +10 -0
- package/src/components/slider/slider.stories.tsx +30 -0
- package/src/components/slider/slider.tsx +49 -0
- package/src/components/stack/stack.props.ts +19 -0
- package/src/components/stack/stack.stories.tsx +79 -0
- package/src/components/stack/stack.tsx +79 -0
- package/src/components/stat-card/stat-card.props.ts +13 -0
- package/src/components/stat-card/stat-card.stories.tsx +41 -0
- package/src/components/stat-card/stat-card.tsx +44 -0
- package/src/components/stepper/stepper.css +26 -0
- package/src/components/stepper/stepper.props.ts +29 -0
- package/src/components/stepper/stepper.stories.tsx +155 -0
- package/src/components/stepper/stepper.tsx +227 -0
- package/src/components/table/table.props.ts +43 -0
- package/src/components/table/table.stories.tsx +189 -0
- package/src/components/table/table.tsx +376 -0
- package/src/components/tabs/tabs.props.ts +18 -0
- package/src/components/tabs/tabs.stories.tsx +32 -0
- package/src/components/tabs/tabs.tsx +74 -0
- package/src/components/text/text.props.ts +9 -0
- package/src/components/text/text.tsx +20 -0
- package/src/components/textarea/textarea.props.ts +15 -0
- package/src/components/textarea/textarea.stories.tsx +27 -0
- package/src/components/textarea/textarea.tsx +55 -0
- package/src/components/theme-provider/themeProvider.props.ts +28 -0
- package/src/components/theme-provider/themeProvider.tsx +1854 -0
- package/src/components/time-picker/timePicker.props.ts +16 -0
- package/src/components/time-picker/timePicker.stories.tsx +131 -0
- package/src/components/time-picker/timePicker.tsx +317 -0
- package/src/components/toast/toast.css +32 -0
- package/src/components/toast/toast.props.ts +13 -0
- package/src/components/toast/toast.stories.tsx +138 -0
- package/src/components/toast/toast.tsx +87 -0
- package/src/components/tooltip/tooltip.props.ts +11 -0
- package/src/components/tooltip/tooltip.stories.tsx +20 -0
- package/src/components/tooltip/tooltip.tsx +55 -0
- package/src/components/topbar/topbar.props.ts +21 -0
- package/src/components/topbar/topbar.stories.tsx +80 -0
- package/src/components/topbar/topbar.tsx +205 -0
- package/src/components/triple-filter/tripleFilter.props.ts +15 -0
- package/src/components/triple-filter/tripleFilter.stories.tsx +32 -0
- package/src/components/triple-filter/tripleFilter.tsx +50 -0
- package/src/hooks/useClickOutside.ts +21 -0
- package/src/hooks/useDebouncedSearch.ts +55 -0
- package/src/hooks/useEditableRow.ts +157 -0
- package/src/hooks/useTableState.ts +122 -0
- package/src/index.css +168 -0
- package/src/index.ts +165 -0
- package/src/main.tsx +9 -0
- package/src/showcases/DataShowcases.tsx +260 -0
- package/src/showcases/FeedbackShowcases.tsx +268 -0
- package/src/showcases/FormShowcases.tsx +1159 -0
- package/src/showcases/HomeShowcase.tsx +324 -0
- package/src/showcases/LayoutPrimitivesShowcases.tsx +569 -0
- package/src/showcases/NavigationShowcases.tsx +193 -0
- package/src/showcases/PageShowcases.tsx +207 -0
- package/src/showcases/ShowcaseLayout.tsx +139 -0
- package/src/showcases/StructureShowcases.tsx +152 -0
- package/src/types/badget.types.ts +37 -0
- package/src/types/button.types.ts +16 -0
- package/src/types/colors.types.ts +3 -0
- package/src/types/field.types.ts +103 -0
- package/src/types/formik.types.ts +15 -0
- package/src/types/input.types.ts +14 -0
- package/src/types/loader.types.ts +9 -0
- package/src/types/sizes.types.ts +1 -0
- package/src/types/table.types.ts +15 -0
- package/src/types/toast.types.ts +8 -0
- package/src/types/yup.types.ts +11 -0
- package/src/utils/color.utils.ts +99 -0
- package/src/utils/styles.ts +120 -0
- package/src/utils/table.utils.ts +10 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import useClickOutside from "@/hooks/useClickOutside";
|
|
2
|
+
import { useRef, useState } from "react";
|
|
3
|
+
import { FaChevronDown, FaChevronRight, FaUserCircle } from "react-icons/fa";
|
|
4
|
+
import { ITNavbarProps, ITNavigationItem } from "./navbar.props";
|
|
5
|
+
import ITText from "@/components/text/text";
|
|
6
|
+
|
|
7
|
+
export default function ITNavbar({
|
|
8
|
+
logo,
|
|
9
|
+
logoText,
|
|
10
|
+
navigationItems = [],
|
|
11
|
+
userMenu,
|
|
12
|
+
children,
|
|
13
|
+
// Legacy props for backward compatibility
|
|
14
|
+
navItems,
|
|
15
|
+
showSidebar = false,
|
|
16
|
+
showSidebarOnMobile = false,
|
|
17
|
+
sidebarItems,
|
|
18
|
+
}: ITNavbarProps) {
|
|
19
|
+
const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
|
|
20
|
+
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
|
|
21
|
+
|
|
22
|
+
const userMenuRef = useRef<HTMLDivElement>(null);
|
|
23
|
+
|
|
24
|
+
useClickOutside(userMenuRef, () => setIsUserMenuOpen(false));
|
|
25
|
+
|
|
26
|
+
const toggleUserMenu = () => {
|
|
27
|
+
setIsUserMenuOpen(!isUserMenuOpen);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const toggleExpanded = (itemId: string) => {
|
|
31
|
+
const newExpanded = new Set(expandedItems);
|
|
32
|
+
if (newExpanded.has(itemId)) {
|
|
33
|
+
newExpanded.delete(itemId);
|
|
34
|
+
} else {
|
|
35
|
+
newExpanded.add(itemId);
|
|
36
|
+
}
|
|
37
|
+
setExpandedItems(newExpanded);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const handleItemClick = (item: ITNavigationItem) => {
|
|
41
|
+
if (item.subitems && item.subitems.length > 0) {
|
|
42
|
+
toggleExpanded(item.id);
|
|
43
|
+
} else if (item.action) {
|
|
44
|
+
item.action();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Use new navigation items if provided, otherwise fall back to legacy
|
|
49
|
+
const shouldUseLegacy = !navigationItems.length && (navItems || sidebarItems);
|
|
50
|
+
|
|
51
|
+
if (shouldUseLegacy) {
|
|
52
|
+
// Legacy behavior - original navbar implementation
|
|
53
|
+
return (
|
|
54
|
+
<div className="flex flex-col h-screen">
|
|
55
|
+
<nav className="bg-white border-b border-gray-200">
|
|
56
|
+
<div className="flex items-center justify-between mx-auto p-4">
|
|
57
|
+
<div className="flex items-center space-x-3 rtl:space-x-reverse">
|
|
58
|
+
{logo && <div className="h-8">{logo}</div>}
|
|
59
|
+
{logoText && (
|
|
60
|
+
<ITText as="span" className="self-center text-2xl font-semibold whitespace-nowrap text-gray-900">
|
|
61
|
+
{logoText}
|
|
62
|
+
</ITText>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div className="flex items-center justify-end w-full md:w-auto md:order-2">
|
|
67
|
+
<div className="flex items-center space-x-4 md:order-2">
|
|
68
|
+
<ul className="hidden md:flex space-x-4">{navItems}</ul>
|
|
69
|
+
|
|
70
|
+
{userMenu && (
|
|
71
|
+
<div className="relative">
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
className="flex text-sm bg-gray-200 rounded-full md:me-0 focus:ring-4 focus:ring-gray-300"
|
|
75
|
+
onClick={toggleUserMenu}
|
|
76
|
+
>
|
|
77
|
+
{userMenu.userImage ? (
|
|
78
|
+
<img
|
|
79
|
+
className="w-8 h-8 rounded-full"
|
|
80
|
+
src={userMenu.userImage}
|
|
81
|
+
alt="user photo"
|
|
82
|
+
/>
|
|
83
|
+
) : (
|
|
84
|
+
<FaUserCircle className="w-8 h-8 text-gray-500" />
|
|
85
|
+
)}
|
|
86
|
+
</button>
|
|
87
|
+
|
|
88
|
+
{isUserMenuOpen && (
|
|
89
|
+
<div
|
|
90
|
+
ref={userMenuRef}
|
|
91
|
+
className="z-50 absolute right-0 mt-2 text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow-sm"
|
|
92
|
+
>
|
|
93
|
+
<div className="px-4 py-3">
|
|
94
|
+
<ITText as="span" className="block text-sm text-gray-900">
|
|
95
|
+
{userMenu.userName}
|
|
96
|
+
</ITText>
|
|
97
|
+
<ITText as="span" className="block text-sm text-gray-500 truncate">
|
|
98
|
+
{userMenu.userEmail}
|
|
99
|
+
</ITText>
|
|
100
|
+
</div>
|
|
101
|
+
<ul className="py-2">
|
|
102
|
+
{userMenu.menuItems.map((item, index) => (
|
|
103
|
+
<li key={index}>
|
|
104
|
+
<button
|
|
105
|
+
onClick={() => {
|
|
106
|
+
item.onClick();
|
|
107
|
+
setIsUserMenuOpen(false);
|
|
108
|
+
}}
|
|
109
|
+
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 w-full text-left"
|
|
110
|
+
>
|
|
111
|
+
<ITText as="span">{item.label}</ITText>
|
|
112
|
+
</button>
|
|
113
|
+
</li>
|
|
114
|
+
))}
|
|
115
|
+
</ul>
|
|
116
|
+
</div>
|
|
117
|
+
)}
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</nav>
|
|
124
|
+
|
|
125
|
+
<div className="flex-1 flex overflow-hidden relative">
|
|
126
|
+
{(showSidebar || showSidebarOnMobile) && (
|
|
127
|
+
<aside className="fixed inset-y-0 left-0 w-64 bg-gray-50 transform transition-transform duration-300 ease-in-out z-50 shadow-lg md:static md:transform-none md:shadow-none md:border-r md:border-gray-200">
|
|
128
|
+
<div className="h-full overflow-y-auto py-4 px-3">
|
|
129
|
+
<ul className="space-y-2 font-medium">{sidebarItems}</ul>
|
|
130
|
+
</div>
|
|
131
|
+
</aside>
|
|
132
|
+
)}
|
|
133
|
+
<main className="flex-1 bg-gray-100 overflow-y-auto">
|
|
134
|
+
{children}
|
|
135
|
+
</main>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// New sidebar design
|
|
142
|
+
return (
|
|
143
|
+
<div className="flex h-screen font-sans" style={{ backgroundColor: "var(--layout-bg, #f8fafc)" }}>
|
|
144
|
+
{/* Sidebar */}
|
|
145
|
+
<aside className="w-72 shadow-xl flex flex-col transition-all duration-300 ease-in-out" style={{ backgroundColor: "var(--sidebar-bg, #0f172a)", borderRight: "1px solid var(--sidebar-border, #1e293b)" }}>
|
|
146
|
+
{/* Logo Section */}
|
|
147
|
+
<div className="p-6 flex items-center gap-3" style={{ borderBottom: "1px solid var(--sidebar-border, #1e293b)" }}>
|
|
148
|
+
{logo && <div className="h-8 w-auto object-contain transition-transform hover:scale-105">{logo}</div>}
|
|
149
|
+
{logoText && (
|
|
150
|
+
<ITText as="span" className="text-lg font-bold tracking-wide" style={{ color: "var(--sidebar-active-color, #ffffff)" }}>
|
|
151
|
+
{logoText}
|
|
152
|
+
</ITText>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
{/* Navigation Items */}
|
|
157
|
+
<nav className="flex-1 px-4 py-6 overflow-y-auto custom-scrollbar">
|
|
158
|
+
<ul className="flex flex-col gap-1.5">
|
|
159
|
+
{navigationItems.map((item) => (
|
|
160
|
+
<li key={item.id}>
|
|
161
|
+
<div
|
|
162
|
+
className={`group flex items-center justify-between px-4 py-3 rounded-xl cursor-pointer transition-all duration-200 border-l-4 ${
|
|
163
|
+
item.isActive
|
|
164
|
+
? 'shadow-sm'
|
|
165
|
+
: 'hover:shadow-sm'
|
|
166
|
+
}`}
|
|
167
|
+
onClick={() => handleItemClick(item)}
|
|
168
|
+
style={{
|
|
169
|
+
backgroundColor: item.isActive ? "var(--sidebar-active-bg, rgba(255,255,255,0.1))" : "transparent",
|
|
170
|
+
borderColor: item.isActive ? "var(--sidebar-active-icon, #3b82f6)" : "transparent",
|
|
171
|
+
color: item.isActive ? "var(--sidebar-active-color, #ffffff)" : "var(--sidebar-label-color, #94a3b8)"
|
|
172
|
+
}}
|
|
173
|
+
onMouseEnter={(e) => {
|
|
174
|
+
if (!item.isActive) {
|
|
175
|
+
e.currentTarget.style.backgroundColor = "var(--sidebar-hover-bg, rgba(255,255,255,0.05))";
|
|
176
|
+
e.currentTarget.style.color = "var(--sidebar-active-color, #ffffff)";
|
|
177
|
+
}
|
|
178
|
+
}}
|
|
179
|
+
onMouseLeave={(e) => {
|
|
180
|
+
if (!item.isActive) {
|
|
181
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
182
|
+
e.currentTarget.style.color = "var(--sidebar-label-color, #94a3b8)";
|
|
183
|
+
}
|
|
184
|
+
}}
|
|
185
|
+
>
|
|
186
|
+
<div className="flex items-center gap-3">
|
|
187
|
+
{/* Icon */}
|
|
188
|
+
{item.icon && (
|
|
189
|
+
<div className="text-xl transition-colors" style={{
|
|
190
|
+
color: item.isActive ? "var(--sidebar-active-icon, #3b82f6)" : "var(--sidebar-icon-color, #64748b)"
|
|
191
|
+
}}>
|
|
192
|
+
{item.icon}
|
|
193
|
+
</div>
|
|
194
|
+
)}
|
|
195
|
+
|
|
196
|
+
{/* Label */}
|
|
197
|
+
<ITText as="span" className={`font-medium text-sm ${item.isActive ? 'font-semibold' : ''}`}>{item.label}</ITText>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
{/* Chevron for expandable items */}
|
|
201
|
+
{item.subitems && item.subitems.length > 0 && (
|
|
202
|
+
<div className="transition-transform" style={{ color: "var(--sidebar-icon-color, #64748b)" }}>
|
|
203
|
+
{expandedItems.has(item.id) ? (
|
|
204
|
+
<FaChevronDown className="w-3 h-3" />
|
|
205
|
+
) : (
|
|
206
|
+
<FaChevronRight className="w-3 h-3" />
|
|
207
|
+
)}
|
|
208
|
+
</div>
|
|
209
|
+
)}
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
{/* Submenu */}
|
|
213
|
+
{item.subitems &&
|
|
214
|
+
item.subitems.length > 0 &&
|
|
215
|
+
expandedItems.has(item.id) && (
|
|
216
|
+
<ul className="mt-1 ml-4 pl-4 space-y-1" style={{ borderLeft: "1px solid var(--sidebar-border, #1e293b)" }}>
|
|
217
|
+
{item.subitems.map((subitem) => (
|
|
218
|
+
<li key={subitem.id}>
|
|
219
|
+
<button
|
|
220
|
+
onClick={subitem.action}
|
|
221
|
+
className="block w-full text-left px-4 py-2.5 rounded-lg text-sm transition-all duration-200"
|
|
222
|
+
style={{
|
|
223
|
+
color: subitem.isActive ? "var(--sidebar-active-color, #ffffff)" : "var(--sidebar-label-color, #94a3b8)",
|
|
224
|
+
backgroundColor: subitem.isActive ? "var(--sidebar-active-bg, rgba(255,255,255,0.1))" : "transparent"
|
|
225
|
+
}}
|
|
226
|
+
onMouseEnter={(e) => {
|
|
227
|
+
if (!subitem.isActive) {
|
|
228
|
+
e.currentTarget.style.backgroundColor = "var(--sidebar-hover-bg, rgba(255,255,255,0.05))";
|
|
229
|
+
}
|
|
230
|
+
}}
|
|
231
|
+
onMouseLeave={(e) => {
|
|
232
|
+
if (!subitem.isActive) {
|
|
233
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
234
|
+
}
|
|
235
|
+
}}
|
|
236
|
+
>
|
|
237
|
+
<ITText as="span">{subitem.label}</ITText>
|
|
238
|
+
</button>
|
|
239
|
+
</li>
|
|
240
|
+
))}
|
|
241
|
+
</ul>
|
|
242
|
+
)}
|
|
243
|
+
</li>
|
|
244
|
+
))}
|
|
245
|
+
</ul>
|
|
246
|
+
</nav>
|
|
247
|
+
|
|
248
|
+
{/* User Menu */}
|
|
249
|
+
{userMenu && (
|
|
250
|
+
<div className="p-4" style={{ borderTop: "1px solid var(--sidebar-border, #1e293b)" }}>
|
|
251
|
+
<div className="relative">
|
|
252
|
+
<button
|
|
253
|
+
type="button"
|
|
254
|
+
className="flex items-center gap-3 w-full p-3 rounded-xl transition-colors duration-200 group"
|
|
255
|
+
style={{ color: "var(--sidebar-label-color, #94a3b8)" }}
|
|
256
|
+
onMouseEnter={(e) => { e.currentTarget.style.backgroundColor = "var(--sidebar-hover-bg, rgba(255,255,255,0.05))"; e.currentTarget.style.color = "var(--sidebar-active-color, #ffffff)"; }}
|
|
257
|
+
onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = "transparent"; e.currentTarget.style.color = "var(--sidebar-label-color, #94a3b8)"; }}
|
|
258
|
+
onClick={toggleUserMenu}
|
|
259
|
+
>
|
|
260
|
+
{userMenu.userImage ? (
|
|
261
|
+
<img
|
|
262
|
+
className="w-10 h-10 rounded-full border-2 transition-colors"
|
|
263
|
+
src={userMenu.userImage}
|
|
264
|
+
alt="user photo"
|
|
265
|
+
style={{ borderColor: "var(--sidebar-border, #1e293b)" }}
|
|
266
|
+
/>
|
|
267
|
+
) : (
|
|
268
|
+
<div className="w-10 h-10 rounded-full flex items-center justify-center transition-colors" style={{ backgroundColor: "var(--sidebar-hover-bg, rgba(255,255,255,0.1))", color: "var(--sidebar-icon-color, #64748b)" }}>
|
|
269
|
+
<FaUserCircle className="w-6 h-6" />
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
<div className="flex-1 text-left overflow-hidden">
|
|
273
|
+
<ITText as="div" className="font-medium text-sm truncate" style={{ color: "var(--sidebar-active-color, #ffffff)" }}>
|
|
274
|
+
{userMenu.userName}
|
|
275
|
+
</ITText>
|
|
276
|
+
<ITText as="div" className="text-xs truncate" style={{ color: "var(--sidebar-label-color, #94a3b8)" }}>
|
|
277
|
+
{userMenu.userEmail}
|
|
278
|
+
</ITText>
|
|
279
|
+
</div>
|
|
280
|
+
<FaChevronRight className="w-3 h-3" style={{ color: "var(--sidebar-icon-color, #64748b)" }} />
|
|
281
|
+
</button>
|
|
282
|
+
|
|
283
|
+
{isUserMenuOpen && (
|
|
284
|
+
<div
|
|
285
|
+
ref={userMenuRef}
|
|
286
|
+
className="absolute bottom-full left-0 mb-3 w-full rounded-xl shadow-2xl overflow-hidden transform transition-all duration-200 origin-bottom"
|
|
287
|
+
style={{ backgroundColor: "var(--topbar-user-dropdown-bg, #ffffff)", border: "1px solid var(--topbar-user-dropdown-border, #e2e8f0)" }}
|
|
288
|
+
>
|
|
289
|
+
<div className="px-4 py-3" style={{ backgroundColor: "var(--topbar-user-bg, #f8fafc)", borderBottom: "1px solid var(--topbar-user-dropdown-border, #e2e8f0)" }}>
|
|
290
|
+
<ITText as="span" className="block text-sm font-semibold" style={{ color: "var(--topbar-user-text, #0f172a)" }}>
|
|
291
|
+
{userMenu.userName}
|
|
292
|
+
</ITText>
|
|
293
|
+
<ITText as="span" className="block text-xs truncate" style={{ color: "var(--topbar-user-subtitle, #64748b)" }}>
|
|
294
|
+
{userMenu.userEmail}
|
|
295
|
+
</ITText>
|
|
296
|
+
</div>
|
|
297
|
+
<ul className="py-1">
|
|
298
|
+
{userMenu.menuItems.map((item, index) => (
|
|
299
|
+
<li key={index}>
|
|
300
|
+
<button
|
|
301
|
+
onClick={() => {
|
|
302
|
+
item.onClick();
|
|
303
|
+
setIsUserMenuOpen(false);
|
|
304
|
+
}}
|
|
305
|
+
className="flex items-center w-full px-4 py-2.5 text-sm transition-colors"
|
|
306
|
+
style={{ color: "var(--topbar-user-text, #334155)" }}
|
|
307
|
+
onMouseEnter={(e) => { e.currentTarget.style.backgroundColor = "var(--topbar-user-item-hover, #f8fafc)"; }}
|
|
308
|
+
onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = "transparent"; }}
|
|
309
|
+
>
|
|
310
|
+
<ITText as="span">{item.label}</ITText>
|
|
311
|
+
</button>
|
|
312
|
+
</li>
|
|
313
|
+
))}
|
|
314
|
+
</ul>
|
|
315
|
+
</div>
|
|
316
|
+
)}
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
)}
|
|
320
|
+
</aside>
|
|
321
|
+
|
|
322
|
+
{/* Main Content */}
|
|
323
|
+
<main className="flex-1 overflow-y-auto relative" style={{ backgroundColor: "var(--layout-bg, #f8fafc)" }}>
|
|
324
|
+
{children}
|
|
325
|
+
</main>
|
|
326
|
+
</div>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { ITBreadcrumbItem } from "../breadcrumbs/breadcrumbs.props";
|
|
3
|
+
|
|
4
|
+
export interface ITPageProps {
|
|
5
|
+
title?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
breadcrumbs?: ITBreadcrumbItem[];
|
|
8
|
+
actions?: ReactNode;
|
|
9
|
+
backAction?: () => void;
|
|
10
|
+
loading?: boolean;
|
|
11
|
+
error?: string | null;
|
|
12
|
+
onRetry?: () => void;
|
|
13
|
+
empty?: boolean;
|
|
14
|
+
emptyTitle?: string;
|
|
15
|
+
emptyDescription?: string;
|
|
16
|
+
emptyAction?: ReactNode;
|
|
17
|
+
className?: string;
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import ITPage from "./page";
|
|
3
|
+
import ITButton from "../button/button";
|
|
4
|
+
import ITCard from "../card/card";
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof ITPage> = {
|
|
7
|
+
title: "Components/Layout/ITPage",
|
|
8
|
+
component: ITPage,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
type Story = StoryObj<typeof ITPage>;
|
|
14
|
+
|
|
15
|
+
export const Default: Story = {
|
|
16
|
+
args: {
|
|
17
|
+
title: "Usuarios",
|
|
18
|
+
description: "Gestión de usuarios del sistema",
|
|
19
|
+
children: (
|
|
20
|
+
<ITCard title="Contenido">
|
|
21
|
+
<p className="text-sm text-slate-600 dark:text-slate-400">
|
|
22
|
+
Aquí va el contenido de la página.
|
|
23
|
+
</p>
|
|
24
|
+
</ITCard>
|
|
25
|
+
),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const WithBreadcrumbsAndActions: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
title: "Editar Producto",
|
|
32
|
+
description: "Modifica los datos del producto",
|
|
33
|
+
breadcrumbs: [
|
|
34
|
+
{ label: "Productos", href: "#" },
|
|
35
|
+
{ label: "Editar Producto" },
|
|
36
|
+
],
|
|
37
|
+
actions: (
|
|
38
|
+
<>
|
|
39
|
+
<ITButton label="Cancelar" variant="outlined" size="small" />
|
|
40
|
+
<ITButton label="Guardar" size="small" />
|
|
41
|
+
</>
|
|
42
|
+
),
|
|
43
|
+
children: (
|
|
44
|
+
<ITCard title="Información General">
|
|
45
|
+
<p className="text-sm text-slate-600 dark:text-slate-400">
|
|
46
|
+
Contenido del formulario aquí.
|
|
47
|
+
</p>
|
|
48
|
+
</ITCard>
|
|
49
|
+
),
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const Loading: Story = {
|
|
54
|
+
args: {
|
|
55
|
+
title: "Dashboard",
|
|
56
|
+
loading: true,
|
|
57
|
+
children: null,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const WithError: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
title: "Órdenes",
|
|
64
|
+
error: "No se pudieron cargar las órdenes. Verifica tu conexión.",
|
|
65
|
+
onRetry: () => alert("Retry clicked"),
|
|
66
|
+
children: null,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const Empty: Story = {
|
|
71
|
+
args: {
|
|
72
|
+
title: "Notificaciones",
|
|
73
|
+
empty: true,
|
|
74
|
+
emptyTitle: "Sin notificaciones",
|
|
75
|
+
emptyDescription: "No tienes notificaciones pendientes.",
|
|
76
|
+
emptyAction: <ITButton label="Recargar" size="small" onClick={() => alert("Refresh")} />,
|
|
77
|
+
children: null,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const WithBackAction: Story = {
|
|
82
|
+
args: {
|
|
83
|
+
title: "Detalle del Usuario",
|
|
84
|
+
description: "Información completa del usuario",
|
|
85
|
+
backAction: () => alert("Back"),
|
|
86
|
+
breadcrumbs: [
|
|
87
|
+
{ label: "Usuarios", href: "#" },
|
|
88
|
+
{ label: "Detalle" },
|
|
89
|
+
],
|
|
90
|
+
children: (
|
|
91
|
+
<ITCard title="Perfil">
|
|
92
|
+
<p className="text-sm text-slate-600 dark:text-slate-400">
|
|
93
|
+
Contenido del detalle aquí.
|
|
94
|
+
</p>
|
|
95
|
+
</ITCard>
|
|
96
|
+
),
|
|
97
|
+
},
|
|
98
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { ITPageProps } from "./page.props";
|
|
3
|
+
import ITPageHeader from "../page-header/page-header";
|
|
4
|
+
import ITSkeleton from "../skeleton/skeleton";
|
|
5
|
+
import ITEmptyState from "../empty-state/empty-state";
|
|
6
|
+
import ITButton from "../button/button";
|
|
7
|
+
import ITStack from "../stack/stack";
|
|
8
|
+
|
|
9
|
+
export default function ITPage({
|
|
10
|
+
title,
|
|
11
|
+
description,
|
|
12
|
+
breadcrumbs,
|
|
13
|
+
actions,
|
|
14
|
+
backAction,
|
|
15
|
+
loading = false,
|
|
16
|
+
error = null,
|
|
17
|
+
onRetry,
|
|
18
|
+
empty = false,
|
|
19
|
+
emptyTitle,
|
|
20
|
+
emptyDescription,
|
|
21
|
+
emptyAction,
|
|
22
|
+
className,
|
|
23
|
+
children,
|
|
24
|
+
}: ITPageProps) {
|
|
25
|
+
if (loading) {
|
|
26
|
+
return (
|
|
27
|
+
<div className={className}>
|
|
28
|
+
{title && (
|
|
29
|
+
<ITPageHeader title={title} />
|
|
30
|
+
)}
|
|
31
|
+
<div className="mt-6">
|
|
32
|
+
<ITStack spacing={4}>
|
|
33
|
+
<ITSkeleton variant="rectangular" height={40} width="40%" />
|
|
34
|
+
<ITSkeleton variant="rectangular" height={200} />
|
|
35
|
+
<ITSkeleton variant="rectangular" height={200} />
|
|
36
|
+
</ITStack>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (error) {
|
|
43
|
+
return (
|
|
44
|
+
<div className={className}>
|
|
45
|
+
{title && (
|
|
46
|
+
<ITPageHeader title={title} />
|
|
47
|
+
)}
|
|
48
|
+
<ITEmptyState
|
|
49
|
+
title="Error"
|
|
50
|
+
description={error}
|
|
51
|
+
action={
|
|
52
|
+
onRetry ? (
|
|
53
|
+
<ITButton label="Reintentar" onClick={onRetry} size="small" />
|
|
54
|
+
) : undefined
|
|
55
|
+
}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (empty) {
|
|
62
|
+
return (
|
|
63
|
+
<div className={className}>
|
|
64
|
+
{title && (
|
|
65
|
+
<ITPageHeader title={title} />
|
|
66
|
+
)}
|
|
67
|
+
<ITEmptyState
|
|
68
|
+
title={emptyTitle || "Sin datos"}
|
|
69
|
+
description={emptyDescription || "No hay información para mostrar"}
|
|
70
|
+
action={emptyAction}
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div className={clsx("space-y-6", className)}>
|
|
78
|
+
{(title || breadcrumbs || actions || backAction) && (
|
|
79
|
+
<ITPageHeader
|
|
80
|
+
title={title || ""}
|
|
81
|
+
description={description}
|
|
82
|
+
breadcrumbs={breadcrumbs}
|
|
83
|
+
actions={actions}
|
|
84
|
+
backAction={backAction}
|
|
85
|
+
/>
|
|
86
|
+
)}
|
|
87
|
+
{children}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { ITBreadcrumbItem } from "../breadcrumbs/breadcrumbs.props";
|
|
3
|
+
|
|
4
|
+
export interface ITPageHeaderProps {
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
breadcrumbs?: ITBreadcrumbItem[];
|
|
8
|
+
actions?: ReactNode;
|
|
9
|
+
backAction?: () => void;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import ITPageHeader from "./page-header";
|
|
3
|
+
import ITButton from "../button/button";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof ITPageHeader> = {
|
|
6
|
+
title: "Components/Layout/ITPageHeader",
|
|
7
|
+
component: ITPageHeader,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof ITPageHeader>;
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
args: {
|
|
16
|
+
title: "Usuarios",
|
|
17
|
+
description: "Gestiona los usuarios del sistema",
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const WithBreadcrumbs: Story = {
|
|
22
|
+
args: {
|
|
23
|
+
title: "Editar Usuario",
|
|
24
|
+
description: "Modifica los datos del usuario seleccionado",
|
|
25
|
+
breadcrumbs: [
|
|
26
|
+
{ label: "Inicio", href: "#" },
|
|
27
|
+
{ label: "Usuarios", href: "#" },
|
|
28
|
+
{ label: "Editar Usuario" },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const WithActions: Story = {
|
|
34
|
+
args: {
|
|
35
|
+
title: "Órdenes de Compra",
|
|
36
|
+
description: "Listado de órdenes activas",
|
|
37
|
+
breadcrumbs: [
|
|
38
|
+
{ label: "Dashboard", href: "#" },
|
|
39
|
+
{ label: "Órdenes" },
|
|
40
|
+
],
|
|
41
|
+
actions: (
|
|
42
|
+
<>
|
|
43
|
+
<ITButton label="Exportar" variant="outlined" size="small" />
|
|
44
|
+
<ITButton label="Nueva Orden" size="small" />
|
|
45
|
+
</>
|
|
46
|
+
),
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const WithBackAction: Story = {
|
|
51
|
+
args: {
|
|
52
|
+
title: "Detalle del Producto",
|
|
53
|
+
description: "Información completa del producto",
|
|
54
|
+
backAction: () => alert("Back clicked"),
|
|
55
|
+
breadcrumbs: [
|
|
56
|
+
{ label: "Productos", href: "#" },
|
|
57
|
+
{ label: "Detalle" },
|
|
58
|
+
],
|
|
59
|
+
actions: <ITButton label="Editar" size="small" />,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { FaChevronLeft } from "react-icons/fa";
|
|
3
|
+
import { ITPageHeaderProps } from "./page-header.props";
|
|
4
|
+
import ITBreadcrumbs from "../breadcrumbs/breadcrumbs";
|
|
5
|
+
import ITText from "@/components/text/text";
|
|
6
|
+
|
|
7
|
+
export default function ITPageHeader({
|
|
8
|
+
title,
|
|
9
|
+
description,
|
|
10
|
+
breadcrumbs,
|
|
11
|
+
actions,
|
|
12
|
+
backAction,
|
|
13
|
+
className,
|
|
14
|
+
}: ITPageHeaderProps) {
|
|
15
|
+
const showTopRow = breadcrumbs?.length || backAction;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className={clsx(className)}>
|
|
19
|
+
{showTopRow && (
|
|
20
|
+
<div className="flex items-center justify-between gap-4 mb-1">
|
|
21
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
22
|
+
{backAction && (
|
|
23
|
+
<button
|
|
24
|
+
onClick={backAction}
|
|
25
|
+
className="p-1.5 rounded-lg text-slate-400 hover:text-slate-600 hover:bg-slate-100 dark:text-slate-500 dark:hover:text-slate-200 dark:hover:bg-slate-800 transition-colors flex-shrink-0"
|
|
26
|
+
aria-label="Volver"
|
|
27
|
+
>
|
|
28
|
+
<FaChevronLeft size={14} />
|
|
29
|
+
</button>
|
|
30
|
+
)}
|
|
31
|
+
{breadcrumbs && breadcrumbs.length > 0 && (
|
|
32
|
+
<ITBreadcrumbs items={breadcrumbs} />
|
|
33
|
+
)}
|
|
34
|
+
</div>
|
|
35
|
+
{actions && (
|
|
36
|
+
<div className="flex items-center gap-2 flex-shrink-0">
|
|
37
|
+
{actions}
|
|
38
|
+
</div>
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
)}
|
|
42
|
+
|
|
43
|
+
<div className="flex items-start justify-between gap-4">
|
|
44
|
+
<div className="min-w-0">
|
|
45
|
+
<ITText as="h1" className="text-2xl font-extrabold text-slate-800 dark:text-white tracking-tight">
|
|
46
|
+
{title}
|
|
47
|
+
</ITText>
|
|
48
|
+
{description && (
|
|
49
|
+
<ITText as="p" className="text-sm text-slate-500 dark:text-slate-400 mt-0.5">
|
|
50
|
+
{description}
|
|
51
|
+
</ITText>
|
|
52
|
+
)}
|
|
53
|
+
</div>
|
|
54
|
+
{!showTopRow && actions && (
|
|
55
|
+
<div className="flex items-center gap-2 flex-shrink-0">
|
|
56
|
+
{actions}
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|