@axzydev/axzy_ui_system 1.2.6 → 1.2.7
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.cjs +246 -182
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +246 -182
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/grid/grid.tsx +7 -2
- package/src/components/layout/layout.tsx +14 -17
- package/src/components/navbar/navbar.tsx +51 -46
- package/src/components/page/page.props.ts +4 -0
- package/src/components/page/page.tsx +17 -7
- package/src/components/page-header/page-header.props.ts +2 -0
- package/src/components/page-header/page-header.stories.tsx +14 -0
- package/src/components/page-header/page-header.tsx +35 -15
- package/src/index.css +15 -0
- package/src/showcases/PageShowcases.tsx +465 -45
- package/src/showcases/StructureShowcases.tsx +205 -36
package/package.json
CHANGED
|
@@ -13,6 +13,11 @@ function breakpointSpan(span: number | undefined, bp: string, cols: number) {
|
|
|
13
13
|
return `${bp}:col-span-${clamped}`;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function gridColsClass(cols: number) {
|
|
17
|
+
const clamped = Math.min(Math.max(cols, 1), 12);
|
|
18
|
+
return `grid-cols-${clamped}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
export default function ITGrid({
|
|
17
22
|
children,
|
|
18
23
|
container,
|
|
@@ -31,9 +36,8 @@ export default function ITGrid({
|
|
|
31
36
|
if (container) {
|
|
32
37
|
return (
|
|
33
38
|
<Component
|
|
34
|
-
className={clsx("grid", className)}
|
|
39
|
+
className={clsx("grid", gridColsClass(columns), className)}
|
|
35
40
|
style={{
|
|
36
|
-
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
|
|
37
41
|
gap: spacing > 0 ? `${spacing * 0.25}rem` : undefined,
|
|
38
42
|
...style,
|
|
39
43
|
}}
|
|
@@ -47,6 +51,7 @@ export default function ITGrid({
|
|
|
47
51
|
return (
|
|
48
52
|
<Component
|
|
49
53
|
className={clsx(
|
|
54
|
+
"col-span-full",
|
|
50
55
|
colSpanClass(xs ?? sm, columns),
|
|
51
56
|
sm !== undefined && breakpointSpan(sm, "sm", columns),
|
|
52
57
|
md !== undefined && breakpointSpan(md, "md", columns),
|
|
@@ -3,6 +3,8 @@ import clsx from "clsx";
|
|
|
3
3
|
import ITTopBar from "../topbar/topbar";
|
|
4
4
|
import ITSidebar from "../sidebar/sidebar";
|
|
5
5
|
import { ITLayoutProps } from "./layout.props";
|
|
6
|
+
import { theme } from "@/theme/theme";
|
|
7
|
+
|
|
6
8
|
export default function ITLayout({
|
|
7
9
|
topBar,
|
|
8
10
|
sidebar,
|
|
@@ -10,37 +12,33 @@ export default function ITLayout({
|
|
|
10
12
|
className = "",
|
|
11
13
|
contentClassName = "",
|
|
12
14
|
}: ITLayoutProps) {
|
|
13
|
-
|
|
14
|
-
// Desktop states
|
|
15
|
-
const [desktopCollapsed, setDesktopCollapsed] = useState(true); // Default to collapsed
|
|
16
|
-
|
|
17
|
-
// Mobile drawer state
|
|
15
|
+
const [desktopCollapsed, setDesktopCollapsed] = useState(true);
|
|
18
16
|
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
|
19
17
|
|
|
18
|
+
const layoutTokens = theme.layout;
|
|
19
|
+
|
|
20
20
|
return (
|
|
21
21
|
<div className={`flex flex-col h-screen overflow-hidden w-full ${className}`}>
|
|
22
|
-
|
|
23
|
-
{/* TOPBAR - Full Width at Top */}
|
|
24
22
|
<ITTopBar
|
|
25
23
|
{...topBar}
|
|
26
24
|
showMobileMenuButton
|
|
27
25
|
onToggleMobileMenu={() => setMobileSidebarOpen(v => !v)}
|
|
28
26
|
/>
|
|
29
27
|
|
|
30
|
-
<div
|
|
31
|
-
|
|
32
|
-
{
|
|
28
|
+
<div
|
|
29
|
+
className="flex flex-1 overflow-hidden relative"
|
|
30
|
+
style={{ backgroundColor: layoutTokens.backgroundColor }}
|
|
31
|
+
>
|
|
32
|
+
{/* DESKTOP SIDEBAR */}
|
|
33
33
|
<div className="hidden lg:block relative z-40 h-full">
|
|
34
|
-
{/* Spacer to keep main content from shifting. Width matches collapsed sidebar. */}
|
|
35
34
|
<div className="w-[88px] h-full flex-shrink-0" />
|
|
36
|
-
|
|
37
35
|
<div className="absolute top-0 left-0 h-full">
|
|
38
36
|
<ITSidebar
|
|
39
37
|
{...sidebar}
|
|
40
38
|
isCollapsed={desktopCollapsed}
|
|
41
39
|
onToggleCollapse={() => setDesktopCollapsed(v => !v)}
|
|
42
40
|
visibleOnMobile={false}
|
|
43
|
-
className=
|
|
41
|
+
className="h-full drop-shadow-2xl transition-all duration-400 ease-[cubic-bezier(0.2,0,0,1)] flex-shrink-0"
|
|
44
42
|
/>
|
|
45
43
|
</div>
|
|
46
44
|
</div>
|
|
@@ -51,11 +49,11 @@ export default function ITLayout({
|
|
|
51
49
|
className="lg:hidden fixed inset-0 z-50 transition-opacity duration-300 backdrop-blur-sm bg-black/40"
|
|
52
50
|
onClick={() => setMobileSidebarOpen(false)}
|
|
53
51
|
>
|
|
54
|
-
<div
|
|
52
|
+
<div
|
|
55
53
|
className="h-full w-fit flex transform transition-transform duration-300 ease-[cubic-bezier(0.2,0,0,1)]"
|
|
56
|
-
onClick={(e) => e.stopPropagation()}
|
|
54
|
+
onClick={(e) => e.stopPropagation()}
|
|
57
55
|
>
|
|
58
|
-
|
|
56
|
+
<ITSidebar
|
|
59
57
|
{...sidebar}
|
|
60
58
|
isCollapsed={false}
|
|
61
59
|
visibleOnMobile={true}
|
|
@@ -74,7 +72,6 @@ export default function ITLayout({
|
|
|
74
72
|
{children}
|
|
75
73
|
</div>
|
|
76
74
|
</main>
|
|
77
|
-
|
|
78
75
|
</div>
|
|
79
76
|
</div>
|
|
80
77
|
);
|
|
@@ -3,6 +3,7 @@ import { useRef, useState } from "react";
|
|
|
3
3
|
import { FaChevronDown, FaChevronRight, FaUserCircle } from "react-icons/fa";
|
|
4
4
|
import { ITNavbarProps, ITNavigationItem } from "./navbar.props";
|
|
5
5
|
import ITText from "@/components/text/text";
|
|
6
|
+
import { theme } from "@/theme/theme";
|
|
6
7
|
|
|
7
8
|
export default function ITNavbar({
|
|
8
9
|
logo,
|
|
@@ -139,15 +140,21 @@ export default function ITNavbar({
|
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
// New sidebar design
|
|
143
|
+
const sidebar = theme.sidebar;
|
|
144
|
+
const topbar = theme.topbar;
|
|
145
|
+
|
|
142
146
|
return (
|
|
143
|
-
<div className="flex h-screen font-sans" style={{ backgroundColor:
|
|
147
|
+
<div className="flex h-screen font-sans" style={{ backgroundColor: theme.layout.backgroundColor }}>
|
|
144
148
|
{/* Sidebar */}
|
|
145
|
-
<aside
|
|
149
|
+
<aside
|
|
150
|
+
className="w-72 shadow-xl flex flex-col transition-all duration-300 ease-in-out"
|
|
151
|
+
style={{ backgroundColor: sidebar.backgroundColor, borderRight: `1px solid ${sidebar.borderColor}` }}
|
|
152
|
+
>
|
|
146
153
|
{/* Logo Section */}
|
|
147
|
-
<div className="p-6 flex items-center gap-3" style={{ borderBottom:
|
|
154
|
+
<div className="p-6 flex items-center gap-3" style={{ borderBottom: `1px solid ${sidebar.borderColor}` }}>
|
|
148
155
|
{logo && <div className="h-8 w-auto object-contain transition-transform hover:scale-105">{logo}</div>}
|
|
149
156
|
{logoText && (
|
|
150
|
-
<ITText as="span" className="text-lg font-bold tracking-wide" style={{ color:
|
|
157
|
+
<ITText as="span" className="text-lg font-bold tracking-wide" style={{ color: sidebar.active.color }}>
|
|
151
158
|
{logoText}
|
|
152
159
|
</ITText>
|
|
153
160
|
)}
|
|
@@ -160,46 +167,42 @@ export default function ITNavbar({
|
|
|
160
167
|
<li key={item.id}>
|
|
161
168
|
<div
|
|
162
169
|
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'
|
|
170
|
+
item.isActive
|
|
171
|
+
? 'shadow-sm'
|
|
165
172
|
: 'hover:shadow-sm'
|
|
166
173
|
}`}
|
|
167
174
|
onClick={() => handleItemClick(item)}
|
|
168
175
|
style={{
|
|
169
|
-
backgroundColor: item.isActive ?
|
|
170
|
-
borderColor: item.isActive ?
|
|
171
|
-
color: item.isActive ?
|
|
176
|
+
backgroundColor: item.isActive ? sidebar.active.backgroundColor : "transparent",
|
|
177
|
+
borderColor: item.isActive ? sidebar.active.iconColor : "transparent",
|
|
178
|
+
color: item.isActive ? sidebar.active.color : sidebar.label.color
|
|
172
179
|
}}
|
|
173
180
|
onMouseEnter={(e) => {
|
|
174
181
|
if (!item.isActive) {
|
|
175
|
-
e.currentTarget.style.backgroundColor =
|
|
176
|
-
e.currentTarget.style.color =
|
|
182
|
+
e.currentTarget.style.backgroundColor = sidebar.hover.backgroundColor;
|
|
183
|
+
e.currentTarget.style.color = sidebar.active.color;
|
|
177
184
|
}
|
|
178
185
|
}}
|
|
179
186
|
onMouseLeave={(e) => {
|
|
180
187
|
if (!item.isActive) {
|
|
181
188
|
e.currentTarget.style.backgroundColor = "transparent";
|
|
182
|
-
e.currentTarget.style.color =
|
|
189
|
+
e.currentTarget.style.color = sidebar.label.color;
|
|
183
190
|
}
|
|
184
191
|
}}
|
|
185
192
|
>
|
|
186
193
|
<div className="flex items-center gap-3">
|
|
187
|
-
{/* Icon */}
|
|
188
194
|
{item.icon && (
|
|
189
195
|
<div className="text-xl transition-colors" style={{
|
|
190
|
-
color: item.isActive ?
|
|
196
|
+
color: item.isActive ? sidebar.active.iconColor : sidebar.icon.color
|
|
191
197
|
}}>
|
|
192
198
|
{item.icon}
|
|
193
199
|
</div>
|
|
194
200
|
)}
|
|
195
|
-
|
|
196
|
-
{/* Label */}
|
|
197
201
|
<ITText as="span" className={`font-medium text-sm ${item.isActive ? 'font-semibold' : ''}`}>{item.label}</ITText>
|
|
198
202
|
</div>
|
|
199
203
|
|
|
200
|
-
{/* Chevron for expandable items */}
|
|
201
204
|
{item.subitems && item.subitems.length > 0 && (
|
|
202
|
-
<div className="transition-transform" style={{ color:
|
|
205
|
+
<div className="transition-transform" style={{ color: sidebar.icon.color }}>
|
|
203
206
|
{expandedItems.has(item.id) ? (
|
|
204
207
|
<FaChevronDown className="w-3 h-3" />
|
|
205
208
|
) : (
|
|
@@ -209,23 +212,22 @@ export default function ITNavbar({
|
|
|
209
212
|
)}
|
|
210
213
|
</div>
|
|
211
214
|
|
|
212
|
-
{
|
|
213
|
-
|
|
214
|
-
item.subitems.length > 0 &&
|
|
215
|
+
{item.subitems &&
|
|
216
|
+
item.subitems.length > 0 &&
|
|
215
217
|
expandedItems.has(item.id) && (
|
|
216
|
-
<ul className="mt-1 ml-4 pl-4 space-y-1" style={{ borderLeft:
|
|
218
|
+
<ul className="mt-1 ml-4 pl-4 space-y-1" style={{ borderLeft: `1px solid ${sidebar.borderColor}` }}>
|
|
217
219
|
{item.subitems.map((subitem) => (
|
|
218
220
|
<li key={subitem.id}>
|
|
219
221
|
<button
|
|
220
222
|
onClick={subitem.action}
|
|
221
223
|
className="block w-full text-left px-4 py-2.5 rounded-lg text-sm transition-all duration-200"
|
|
222
224
|
style={{
|
|
223
|
-
color: subitem.isActive ?
|
|
224
|
-
backgroundColor: subitem.isActive ?
|
|
225
|
+
color: subitem.isActive ? sidebar.active.color : sidebar.label.color,
|
|
226
|
+
backgroundColor: subitem.isActive ? sidebar.active.backgroundColor : "transparent"
|
|
225
227
|
}}
|
|
226
228
|
onMouseEnter={(e) => {
|
|
227
229
|
if (!subitem.isActive) {
|
|
228
|
-
e.currentTarget.style.backgroundColor =
|
|
230
|
+
e.currentTarget.style.backgroundColor = sidebar.hover.backgroundColor;
|
|
229
231
|
}
|
|
230
232
|
}}
|
|
231
233
|
onMouseLeave={(e) => {
|
|
@@ -247,14 +249,14 @@ export default function ITNavbar({
|
|
|
247
249
|
|
|
248
250
|
{/* User Menu */}
|
|
249
251
|
{userMenu && (
|
|
250
|
-
<div className="p-4" style={{ borderTop:
|
|
252
|
+
<div className="p-4" style={{ borderTop: `1px solid ${sidebar.borderColor}` }}>
|
|
251
253
|
<div className="relative">
|
|
252
254
|
<button
|
|
253
255
|
type="button"
|
|
254
256
|
className="flex items-center gap-3 w-full p-3 rounded-xl transition-colors duration-200 group"
|
|
255
|
-
style={{ color:
|
|
256
|
-
onMouseEnter={(e) => { e.currentTarget.style.backgroundColor =
|
|
257
|
-
onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = "transparent"; e.currentTarget.style.color =
|
|
257
|
+
style={{ color: sidebar.label.color }}
|
|
258
|
+
onMouseEnter={(e) => { e.currentTarget.style.backgroundColor = sidebar.hover.backgroundColor; e.currentTarget.style.color = sidebar.active.color; }}
|
|
259
|
+
onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = "transparent"; e.currentTarget.style.color = sidebar.label.color; }}
|
|
258
260
|
onClick={toggleUserMenu}
|
|
259
261
|
>
|
|
260
262
|
{userMenu.userImage ? (
|
|
@@ -262,37 +264,40 @@ export default function ITNavbar({
|
|
|
262
264
|
className="w-10 h-10 rounded-full border-2 transition-colors"
|
|
263
265
|
src={userMenu.userImage}
|
|
264
266
|
alt="user photo"
|
|
265
|
-
style={{ borderColor:
|
|
267
|
+
style={{ borderColor: sidebar.borderColor }}
|
|
266
268
|
/>
|
|
267
269
|
) : (
|
|
268
|
-
<div
|
|
269
|
-
|
|
270
|
+
<div
|
|
271
|
+
className="w-10 h-10 rounded-full flex items-center justify-center transition-colors"
|
|
272
|
+
style={{ backgroundColor: sidebar.hover.backgroundColor, color: sidebar.icon.color }}
|
|
273
|
+
>
|
|
274
|
+
<FaUserCircle className="w-6 h-6" />
|
|
270
275
|
</div>
|
|
271
276
|
)}
|
|
272
277
|
<div className="flex-1 text-left overflow-hidden">
|
|
273
|
-
<ITText as="div" className="font-medium text-sm truncate" style={{ color:
|
|
278
|
+
<ITText as="div" className="font-medium text-sm truncate" style={{ color: sidebar.active.color }}>
|
|
274
279
|
{userMenu.userName}
|
|
275
280
|
</ITText>
|
|
276
|
-
<ITText as="div" className="text-xs truncate" style={{ color:
|
|
281
|
+
<ITText as="div" className="text-xs truncate" style={{ color: sidebar.label.color }}>
|
|
277
282
|
{userMenu.userEmail}
|
|
278
283
|
</ITText>
|
|
279
284
|
</div>
|
|
280
|
-
<FaChevronRight className="w-3 h-3" style={{ color:
|
|
285
|
+
<FaChevronRight className="w-3 h-3" style={{ color: sidebar.icon.color }} />
|
|
281
286
|
</button>
|
|
282
287
|
|
|
283
288
|
{isUserMenuOpen && (
|
|
284
289
|
<div
|
|
285
290
|
ref={userMenuRef}
|
|
286
291
|
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:
|
|
292
|
+
style={{ backgroundColor: topbar.userMenu.dropdown.backgroundColor, border: `1px solid ${topbar.userMenu.dropdown.borderColor}` }}
|
|
288
293
|
>
|
|
289
|
-
<div className="px-4 py-3" style={{ backgroundColor:
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
294
|
+
<div className="px-4 py-3" style={{ backgroundColor: topbar.userMenu.backgroundColor, borderBottom: `1px solid ${topbar.userMenu.dropdown.borderColor}` }}>
|
|
295
|
+
<ITText as="span" className="block text-sm font-semibold" style={{ color: topbar.userMenu.textColor }}>
|
|
296
|
+
{userMenu.userName}
|
|
297
|
+
</ITText>
|
|
298
|
+
<ITText as="span" className="block text-xs truncate" style={{ color: topbar.userMenu.subtitleColor }}>
|
|
299
|
+
{userMenu.userEmail}
|
|
300
|
+
</ITText>
|
|
296
301
|
</div>
|
|
297
302
|
<ul className="py-1">
|
|
298
303
|
{userMenu.menuItems.map((item, index) => (
|
|
@@ -303,8 +308,8 @@ export default function ITNavbar({
|
|
|
303
308
|
setIsUserMenuOpen(false);
|
|
304
309
|
}}
|
|
305
310
|
className="flex items-center w-full px-4 py-2.5 text-sm transition-colors"
|
|
306
|
-
style={{ color:
|
|
307
|
-
onMouseEnter={(e) => { e.currentTarget.style.backgroundColor =
|
|
311
|
+
style={{ color: topbar.userMenu.textColor }}
|
|
312
|
+
onMouseEnter={(e) => { e.currentTarget.style.backgroundColor = topbar.userMenu.dropdown.itemHoverBackground; }}
|
|
308
313
|
onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = "transparent"; }}
|
|
309
314
|
>
|
|
310
315
|
<ITText as="span">{item.label}</ITText>
|
|
@@ -320,7 +325,7 @@ export default function ITNavbar({
|
|
|
320
325
|
</aside>
|
|
321
326
|
|
|
322
327
|
{/* Main Content */}
|
|
323
|
-
<main className="flex-1 overflow-y-auto relative" style={{ backgroundColor:
|
|
328
|
+
<main className="flex-1 overflow-y-auto relative" style={{ backgroundColor: theme.layout.backgroundColor }}>
|
|
324
329
|
{children}
|
|
325
330
|
</main>
|
|
326
331
|
</div>
|
|
@@ -7,6 +7,8 @@ export interface ITPageProps {
|
|
|
7
7
|
breadcrumbs?: ITBreadcrumbItem[];
|
|
8
8
|
actions?: ReactNode;
|
|
9
9
|
backAction?: () => void;
|
|
10
|
+
icon?: ReactNode;
|
|
11
|
+
iconColor?: string;
|
|
10
12
|
loading?: boolean;
|
|
11
13
|
error?: string | null;
|
|
12
14
|
errorTitle?: string;
|
|
@@ -18,4 +20,6 @@ export interface ITPageProps {
|
|
|
18
20
|
emptyAction?: ReactNode;
|
|
19
21
|
className?: string;
|
|
20
22
|
children: ReactNode;
|
|
23
|
+
maxWidth?: "2xl" | "3xl" | "4xl" | "5xl" | "6xl" | "7xl";
|
|
24
|
+
noPadding?: boolean;
|
|
21
25
|
}
|
|
@@ -7,7 +7,7 @@ import ITButton from "../button/button";
|
|
|
7
7
|
import ITStack from "../stack/stack";
|
|
8
8
|
|
|
9
9
|
const hasHeader = (props: ITPageProps) =>
|
|
10
|
-
!!(props.title || props.description || props.breadcrumbs || props.actions || props.backAction);
|
|
10
|
+
!!(props.title || props.description || props.breadcrumbs || props.actions || props.backAction || props.icon);
|
|
11
11
|
|
|
12
12
|
const renderHeader = (props: ITPageProps) => {
|
|
13
13
|
if (!hasHeader(props)) return null;
|
|
@@ -18,6 +18,8 @@ const renderHeader = (props: ITPageProps) => {
|
|
|
18
18
|
breadcrumbs={props.breadcrumbs}
|
|
19
19
|
actions={props.actions}
|
|
20
20
|
backAction={props.backAction}
|
|
21
|
+
icon={props.icon}
|
|
22
|
+
iconColor={props.iconColor}
|
|
21
23
|
/>
|
|
22
24
|
);
|
|
23
25
|
};
|
|
@@ -35,18 +37,26 @@ export default function ITPage(props: ITPageProps) {
|
|
|
35
37
|
emptyAction,
|
|
36
38
|
className,
|
|
37
39
|
children,
|
|
40
|
+
maxWidth = "7xl",
|
|
41
|
+
noPadding = false,
|
|
38
42
|
} = props;
|
|
39
43
|
|
|
40
|
-
const wrapperClass = clsx(
|
|
44
|
+
const wrapperClass = clsx(
|
|
45
|
+
"mx-auto w-full",
|
|
46
|
+
!noPadding && "px-4 sm:px-6 lg:px-8 py-6",
|
|
47
|
+
`max-w-${maxWidth}`,
|
|
48
|
+
"space-y-8",
|
|
49
|
+
className
|
|
50
|
+
);
|
|
41
51
|
|
|
42
52
|
if (loading) {
|
|
43
53
|
return (
|
|
44
54
|
<div className={wrapperClass}>
|
|
45
55
|
{renderHeader(props)}
|
|
46
|
-
<ITStack spacing={
|
|
47
|
-
<ITSkeleton variant="rectangular" height={40} width="40%" />
|
|
48
|
-
<ITSkeleton variant="rectangular" height={200} />
|
|
49
|
-
<ITSkeleton variant="rectangular" height={200} />
|
|
56
|
+
<ITStack spacing={6}>
|
|
57
|
+
<ITSkeleton variant="rectangular" height={40} width="40%" className="rounded-lg" />
|
|
58
|
+
<ITSkeleton variant="rectangular" height={200} className="rounded-lg" />
|
|
59
|
+
<ITSkeleton variant="rectangular" height={200} className="rounded-lg" />
|
|
50
60
|
</ITStack>
|
|
51
61
|
</div>
|
|
52
62
|
);
|
|
@@ -92,4 +102,4 @@ export default function ITPage(props: ITPageProps) {
|
|
|
92
102
|
{children}
|
|
93
103
|
</div>
|
|
94
104
|
);
|
|
95
|
-
}
|
|
105
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { FaUsers } from "react-icons/fa";
|
|
2
3
|
import ITPageHeader from "./page-header";
|
|
3
4
|
import ITButton from "../button/button";
|
|
4
5
|
|
|
@@ -59,3 +60,16 @@ export const WithBackAction: Story = {
|
|
|
59
60
|
actions: <ITButton label="Editar" size="small" />,
|
|
60
61
|
},
|
|
61
62
|
};
|
|
63
|
+
|
|
64
|
+
export const WithIcon: Story = {
|
|
65
|
+
args: {
|
|
66
|
+
title: "Usuarios",
|
|
67
|
+
description: "Gestiona los usuarios del sistema",
|
|
68
|
+
icon: <FaUsers size={20} />,
|
|
69
|
+
iconColor: "#6366f1",
|
|
70
|
+
breadcrumbs: [
|
|
71
|
+
{ label: "Inicio", href: "#" },
|
|
72
|
+
{ label: "Usuarios" },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -9,19 +9,22 @@ export default function ITPageHeader({
|
|
|
9
9
|
breadcrumbs,
|
|
10
10
|
actions,
|
|
11
11
|
backAction,
|
|
12
|
+
icon,
|
|
13
|
+
iconColor,
|
|
12
14
|
className,
|
|
13
15
|
}: ITPageHeaderProps) {
|
|
14
16
|
const showTopRow = breadcrumbs?.length || backAction;
|
|
15
17
|
|
|
16
18
|
return (
|
|
17
|
-
<div className={className}>
|
|
19
|
+
<div className={`${className} space-y-3`}>
|
|
20
|
+
{/* Fila superior: breadcrumbs + back + acciones */}
|
|
18
21
|
{showTopRow && (
|
|
19
|
-
<div className="flex items-center justify-between gap-4
|
|
22
|
+
<div className="flex items-center justify-between gap-4">
|
|
20
23
|
<div className="flex items-center gap-2 min-w-0">
|
|
21
24
|
{backAction && (
|
|
22
25
|
<button
|
|
23
26
|
onClick={backAction}
|
|
24
|
-
className="
|
|
27
|
+
className="flex items-center justify-center w-9 h-9 rounded-full border border-slate-200 dark:border-slate-700 text-slate-400 hover:text-slate-700 dark:hover:text-slate-200 hover:border-slate-300 dark:hover:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-800/60 transition-all duration-200 flex-shrink-0"
|
|
25
28
|
aria-label="Volver"
|
|
26
29
|
>
|
|
27
30
|
<FaChevronLeft size={14} />
|
|
@@ -32,30 +35,47 @@ export default function ITPageHeader({
|
|
|
32
35
|
)}
|
|
33
36
|
</div>
|
|
34
37
|
{actions && (
|
|
35
|
-
<div className="flex items-center gap-
|
|
38
|
+
<div className="flex items-center gap-3 flex-shrink-0">
|
|
36
39
|
{actions}
|
|
37
40
|
</div>
|
|
38
41
|
)}
|
|
39
42
|
</div>
|
|
40
43
|
)}
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
{/* Fila principal: icono + título + descripción + (acciones si no hay top row) */}
|
|
46
|
+
<div className="flex items-start justify-between gap-6">
|
|
47
|
+
<div className="flex items-start gap-4 min-w-0">
|
|
48
|
+
{icon && (
|
|
49
|
+
<div
|
|
50
|
+
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"
|
|
51
|
+
style={iconColor ? { color: iconColor } : undefined}
|
|
52
|
+
>
|
|
53
|
+
{icon}
|
|
54
|
+
</div>
|
|
51
55
|
)}
|
|
56
|
+
<div className="min-w-0">
|
|
57
|
+
<ITText
|
|
58
|
+
as="h1"
|
|
59
|
+
className="text-3xl font-bold text-slate-800 dark:text-white tracking-tight leading-tight"
|
|
60
|
+
>
|
|
61
|
+
{title}
|
|
62
|
+
</ITText>
|
|
63
|
+
{description && (
|
|
64
|
+
<ITText
|
|
65
|
+
as="p"
|
|
66
|
+
className="text-sm font-light text-slate-500 dark:text-slate-400 mt-1 leading-relaxed"
|
|
67
|
+
>
|
|
68
|
+
{description}
|
|
69
|
+
</ITText>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
52
72
|
</div>
|
|
53
73
|
{!showTopRow && actions && (
|
|
54
|
-
<div className="flex items-center gap-
|
|
74
|
+
<div className="flex items-center gap-3 flex-shrink-0">
|
|
55
75
|
{actions}
|
|
56
76
|
</div>
|
|
57
77
|
)}
|
|
58
78
|
</div>
|
|
59
79
|
</div>
|
|
60
80
|
);
|
|
61
|
-
}
|
|
81
|
+
}
|
package/src/index.css
CHANGED
|
@@ -99,6 +99,21 @@
|
|
|
99
99
|
to { opacity: 1; transform: translateY(0); }
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
|
103
|
+
.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
104
|
+
.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
105
|
+
.grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
|
106
|
+
.grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
|
107
|
+
.grid-cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
|
108
|
+
.grid-cols-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); }
|
|
109
|
+
.grid-cols-8 { grid-template-columns: repeat(8, minmax(0, 1fr)); }
|
|
110
|
+
.grid-cols-9 { grid-template-columns: repeat(9, minmax(0, 1fr)); }
|
|
111
|
+
.grid-cols-10 { grid-template-columns: repeat(10, minmax(0, 1fr)); }
|
|
112
|
+
.grid-cols-11 { grid-template-columns: repeat(11, minmax(0, 1fr)); }
|
|
113
|
+
.grid-cols-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); }
|
|
114
|
+
|
|
115
|
+
.col-span-full { grid-column: 1 / -1; }
|
|
116
|
+
|
|
102
117
|
.col-span-1 { grid-column: span 1 / span 1; }
|
|
103
118
|
.col-span-2 { grid-column: span 2 / span 2; }
|
|
104
119
|
.col-span-3 { grid-column: span 3 / span 3; }
|