shivanya-sdk 0.1.1 → 0.1.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/package.json +1 -1
- package/packages/shell/package.json +2 -2
- package/packages/shell/scripts/copy-assets.mjs +15 -5
- package/packages/shell/src/components/DrawerHeader.tsx +34 -9
- package/packages/shell/src/components/ShellBrand.tsx +12 -6
- package/packages/shell/src/components/ShellFooter.tsx +19 -6
- package/packages/shell/src/components/ShellHeader.tsx +21 -38
- package/packages/shell/src/components/ShellMobileNav.tsx +118 -29
- package/packages/shell/src/components/ShellRoot.tsx +10 -5
- package/packages/shell/src/components/ShellSidebar.tsx +102 -41
- package/packages/shell/src/components/SidebarFooter.tsx +30 -10
- package/packages/shell/src/hooks/useShell.ts +6 -4
- package/packages/shell/src/index.ts +1 -1
- package/packages/shell/src/layouts/AppShell.tsx +4 -2
- package/packages/shell/src/layouts/BlankShell.tsx +15 -6
- package/packages/shell/src/layouts/CenteredShell.tsx +26 -7
- package/packages/shell/src/layouts/DashboardShell.tsx +37 -38
- package/packages/shell/src/layouts/PageContainer.tsx +28 -9
- package/packages/shell/src/layouts/PageHeader.tsx +41 -13
- package/packages/shell/src/layouts/WebsiteShell.tsx +44 -11
- package/packages/shell/src/styles/blank-shell.css +6 -0
- package/packages/shell/src/styles/centered-shell.css +26 -0
- package/packages/shell/src/styles/dashboard-shell.css +108 -0
- package/packages/shell/src/styles/index.ts +14 -1
- package/packages/shell/src/styles/page-container.css +41 -0
- package/packages/shell/src/styles/page-header.css +71 -0
- package/packages/shell/src/styles/shell-brand.css +42 -0
- package/packages/shell/src/styles/shell-drawer-header.css +24 -0
- package/packages/shell/src/styles/shell-footer.css +65 -0
- package/packages/shell/src/styles/shell-header.css +68 -0
- package/packages/shell/src/styles/shell-main.css +17 -0
- package/packages/shell/src/styles/shell-mobile-nav.css +148 -0
- package/packages/shell/src/styles/shell-root.css +9 -0
- package/packages/shell/src/styles/shell-sidebar-footer.css +57 -0
- package/packages/shell/src/styles/shell-sidebar.css +146 -0
- package/packages/shell/src/styles/website-shell.css +51 -0
- package/packages/shell/tsconfig.json +2 -2
- package/playground/public/logo.png +0 -0
- package/playground/src/demos/shell/Components/DrawerHeaderDemo.tsx +92 -4
- package/playground/src/demos/shell/Components/PageContainerDemo.tsx +146 -4
- package/playground/src/demos/shell/Components/PageHeaderDemo.tsx +113 -5
- package/playground/src/demos/shell/Components/ShellBrandDemo.tsx +115 -6
- package/playground/src/demos/shell/Components/ShellFooterDemo.tsx +101 -4
- package/playground/src/demos/shell/Components/ShellHeaderDemo.tsx +131 -26
- package/playground/src/demos/shell/Components/ShellMainDemo.tsx +100 -5
- package/playground/src/demos/shell/Components/ShellMobileNavDemo.tsx +229 -6
- package/playground/src/demos/shell/Components/ShellRootDemo.tsx +64 -1
- package/playground/src/demos/shell/Components/ShellSidebarDemo.tsx +381 -8
- package/playground/src/demos/shell/Components/SidebarFooterDemo.tsx +107 -4
- package/playground/src/demos/shell/Layouts/AppShellDemo.tsx +123 -8
- package/playground/src/demos/shell/Layouts/BlankShellDemo.tsx +63 -6
- package/playground/src/demos/shell/Layouts/CenteredShellDemo.tsx +73 -10
- package/playground/src/demos/shell/Layouts/DashboardShellDemo.tsx +102 -15
- package/playground/src/demos/shell/Layouts/WebsiteShellDemo.tsx +143 -8
- package/playground/src/demos/shell/_demo-utils.tsx +101 -11
- package/playground/src/demos/shell/previews/AppShellPreview.tsx +29 -13
- package/playground/src/demos/shell/previews/BlankShellPreview.tsx +5 -2
- package/playground/src/demos/shell/previews/CenteredShellPreview.tsx +16 -1
- package/playground/src/demos/shell/previews/DashboardShellPreview.tsx +25 -6
- package/playground/src/demos/shell/previews/WebsiteShellPreview.tsx +92 -10
- package/playground/src/demos/shell/shell-demo.css +211 -0
- package/playground/src/pages/CategoryPage.tsx +0 -2
- package/pnpm-workspace.yaml +1 -1
- package/packages/shell/src/styles/shell.css +0 -534
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shivanya-shell",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Reusable React shell, layout, and application structure components for Shivanya applications",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"build": "tsc && node scripts/copy-assets.mjs"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"shivanya-ui": "^1.0.
|
|
22
|
+
"shivanya-ui": "^1.0.6"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -5,10 +5,20 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
const currentFile = fileURLToPath(import.meta.url);
|
|
6
6
|
const root = path.resolve(path.dirname(currentFile), "..");
|
|
7
7
|
|
|
8
|
-
const source = path.join(root, "src", "styles"
|
|
9
|
-
const target = path.join(root, "dist", "styles"
|
|
8
|
+
const source = path.join(root, "src", "styles");
|
|
9
|
+
const target = path.join(root, "dist", "styles");
|
|
10
10
|
|
|
11
|
-
fs.mkdirSync(
|
|
12
|
-
fs.copyFileSync(source, target);
|
|
11
|
+
fs.mkdirSync(target, { recursive: true });
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
for (const file of fs.readdirSync(source)) {
|
|
14
|
+
if (!file.endsWith(".css")) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fs.copyFileSync(
|
|
19
|
+
path.join(source, file),
|
|
20
|
+
path.join(target, file),
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log("Shivanya Shell CSS and assets copied.");
|
|
@@ -1,19 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CloseIcon,
|
|
3
|
+
IconButton,
|
|
4
|
+
} from "shivanya-ui";
|
|
5
|
+
|
|
1
6
|
import type { ShellBranding } from "../types/shell.js";
|
|
2
7
|
import { ShellBrand } from "./ShellBrand.js";
|
|
3
8
|
|
|
9
|
+
export interface DrawerHeaderProps {
|
|
10
|
+
branding?: ShellBranding;
|
|
11
|
+
onClose?: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
4
14
|
export function DrawerHeader({
|
|
5
15
|
branding,
|
|
6
16
|
onClose,
|
|
7
|
-
}: {
|
|
8
|
-
branding?: ShellBranding;
|
|
9
|
-
onClose?: () => void;
|
|
10
|
-
}) {
|
|
17
|
+
}: DrawerHeaderProps) {
|
|
11
18
|
return (
|
|
12
19
|
<header className="shivanya-shell-drawer-header">
|
|
13
|
-
{branding &&
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
{branding && (
|
|
21
|
+
<ShellBrand
|
|
22
|
+
branding={{
|
|
23
|
+
...branding,
|
|
24
|
+
href: undefined,
|
|
25
|
+
}}
|
|
26
|
+
compact={false}
|
|
27
|
+
/>
|
|
28
|
+
)}
|
|
29
|
+
|
|
30
|
+
<IconButton
|
|
31
|
+
size="sm"
|
|
32
|
+
variant="ghost"
|
|
33
|
+
rounded
|
|
34
|
+
iconRotateOnHover
|
|
35
|
+
iconHoverColor="var(--shivanya-color-primary)"
|
|
36
|
+
aria-label="Close menu"
|
|
37
|
+
title="Close menu"
|
|
38
|
+
onClick={onClose}
|
|
39
|
+
>
|
|
40
|
+
<CloseIcon size="sm" />
|
|
41
|
+
</IconButton>
|
|
17
42
|
</header>
|
|
18
43
|
);
|
|
19
|
-
}
|
|
44
|
+
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { ShellBranding } from "../types/shell.js";
|
|
2
2
|
|
|
3
|
+
interface ShellBrandProps {
|
|
4
|
+
branding: ShellBranding;
|
|
5
|
+
compact?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
3
8
|
export function ShellBrand({
|
|
4
9
|
branding,
|
|
5
10
|
compact = false,
|
|
6
|
-
}: {
|
|
7
|
-
branding: ShellBranding;
|
|
8
|
-
compact?: boolean;
|
|
9
|
-
}) {
|
|
11
|
+
}: ShellBrandProps) {
|
|
10
12
|
const content = (
|
|
11
13
|
<span className="shivanya-shell-brand-content">
|
|
12
14
|
{branding.src ? (
|
|
@@ -16,17 +18,21 @@ export function ShellBrand({
|
|
|
16
18
|
className="shivanya-shell-brand-image"
|
|
17
19
|
/>
|
|
18
20
|
) : null}
|
|
21
|
+
|
|
19
22
|
<span className="shivanya-shell-brand-text">
|
|
20
23
|
{branding.name && <strong>{branding.name}</strong>}
|
|
21
24
|
{!compact && branding.subtitle && <small>{branding.subtitle}</small>}
|
|
22
25
|
</span>
|
|
23
26
|
</span>
|
|
24
27
|
);
|
|
25
|
-
|
|
28
|
+
|
|
29
|
+
if (branding.href) {
|
|
26
30
|
return (
|
|
27
31
|
<a className="shivanya-shell-brand" href={branding.href}>
|
|
28
32
|
{content}
|
|
29
33
|
</a>
|
|
30
34
|
);
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
return <span className="shivanya-shell-brand">{content}</span>;
|
|
32
|
-
}
|
|
38
|
+
}
|
|
@@ -7,12 +7,25 @@ export function ShellFooter({
|
|
|
7
7
|
className = "",
|
|
8
8
|
}: ShellFooterProps) {
|
|
9
9
|
return (
|
|
10
|
-
<footer
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
<footer
|
|
11
|
+
className={[
|
|
12
|
+
"shivanya-shell-footer",
|
|
13
|
+
className,
|
|
14
|
+
]
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.join(" ")}
|
|
17
|
+
>
|
|
18
|
+
<div className="shivanya-shell-footer-brand">
|
|
19
|
+
{branding && <ShellBrand branding={branding} />}
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div className="shivanya-shell-footer-content">
|
|
23
|
+
{children}
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div className="shivanya-shell-footer-copy">
|
|
14
27
|
© {new Date().getFullYear()}
|
|
15
|
-
</
|
|
28
|
+
</div>
|
|
16
29
|
</footer>
|
|
17
30
|
);
|
|
18
|
-
}
|
|
31
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
3
|
import type { CSSProperties } from "react";
|
|
4
4
|
import type { ShellHeaderProps } from "../types/shell.js";
|
|
5
|
+
import { Button } from "shivanya-ui";
|
|
5
6
|
import { useShell } from "../hooks/useShell.js";
|
|
6
7
|
import { ShellBrand } from "./ShellBrand.js";
|
|
7
8
|
|
|
@@ -19,63 +20,45 @@ export function ShellHeader({
|
|
|
19
20
|
}: ShellHeaderProps) {
|
|
20
21
|
const shell = useShell();
|
|
21
22
|
|
|
22
|
-
const menuAction = () => {
|
|
23
|
-
if (
|
|
24
|
-
typeof window !== "undefined" &&
|
|
25
|
-
window.matchMedia("(max-width: 767px)").matches
|
|
26
|
-
) {
|
|
27
|
-
shell.toggleMobile();
|
|
28
|
-
} else {
|
|
29
|
-
shell.toggleSidebar();
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
|
|
33
23
|
return (
|
|
34
24
|
<header
|
|
35
25
|
className={`shivanya-shell-header shivanya-shell-header-${position} ${className}`.trim()}
|
|
36
26
|
style={
|
|
37
27
|
{
|
|
38
28
|
"--shivanya-shell-header-height":
|
|
39
|
-
typeof height === "number"
|
|
40
|
-
? `${height}px`
|
|
41
|
-
: height,
|
|
29
|
+
typeof height === "number" ? `${height}px` : height,
|
|
42
30
|
} as CSSProperties
|
|
43
31
|
}
|
|
44
32
|
>
|
|
45
33
|
<div className="shivanya-shell-header-inner">
|
|
46
34
|
<div className="shivanya-shell-header-start">
|
|
47
35
|
{showMenu && (
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
36
|
+
<div className="shivanya-shell-mobile-menu">
|
|
37
|
+
<Button
|
|
38
|
+
type="button"
|
|
39
|
+
variant="ghost"
|
|
40
|
+
size="sm"
|
|
41
|
+
rounded
|
|
42
|
+
onClick={shell.toggleMobile}
|
|
43
|
+
aria-label={menuLabel}
|
|
44
|
+
title={menuLabel}
|
|
45
|
+
>
|
|
46
|
+
☰
|
|
47
|
+
</Button>
|
|
48
|
+
</div>
|
|
57
49
|
)}
|
|
58
50
|
|
|
59
|
-
{
|
|
51
|
+
{branding && <ShellBrand branding={branding} compact={false} />}
|
|
60
52
|
|
|
61
|
-
{
|
|
62
|
-
<ShellBrand
|
|
63
|
-
branding={branding}
|
|
64
|
-
compact={shell.sidebarCollapsed}
|
|
65
|
-
/>
|
|
66
|
-
)}
|
|
53
|
+
{start}
|
|
67
54
|
</div>
|
|
68
55
|
|
|
69
|
-
<div className="shivanya-shell-header-center">
|
|
70
|
-
{center}
|
|
71
|
-
</div>
|
|
56
|
+
<div className="shivanya-shell-header-center">{center}</div>
|
|
72
57
|
|
|
73
|
-
<div className="shivanya-shell-header-end">
|
|
74
|
-
{end}
|
|
75
|
-
</div>
|
|
58
|
+
<div className="shivanya-shell-header-end">{end}</div>
|
|
76
59
|
</div>
|
|
77
60
|
|
|
78
61
|
{children}
|
|
79
62
|
</header>
|
|
80
63
|
);
|
|
81
|
-
}
|
|
64
|
+
}
|
|
@@ -1,29 +1,116 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import type {
|
|
5
|
+
AnimationEvent,
|
|
6
|
+
CSSProperties,
|
|
7
|
+
} from "react";
|
|
2
8
|
|
|
3
|
-
import { useEffect } from "react";
|
|
4
9
|
import type { ShellMobileNavProps } from "../types/shell.js";
|
|
10
|
+
|
|
5
11
|
import { ShellSidebar } from "./ShellSidebar.js";
|
|
6
|
-
import {
|
|
12
|
+
import { DrawerHeader } from "./DrawerHeader.js";
|
|
13
|
+
import { useOptionalShell } from "../hooks/useShell.js";
|
|
14
|
+
|
|
15
|
+
export function ShellMobileNav({
|
|
16
|
+
open: controlledOpen,
|
|
17
|
+
onClose: controlledOnClose,
|
|
18
|
+
size = 280,
|
|
19
|
+
className = "",
|
|
20
|
+
...props
|
|
21
|
+
}: ShellMobileNavProps) {
|
|
22
|
+
const shell = useOptionalShell();
|
|
23
|
+
|
|
24
|
+
const open =
|
|
25
|
+
controlledOpen ?? shell?.mobileOpen ?? false;
|
|
26
|
+
|
|
27
|
+
const close =
|
|
28
|
+
controlledOnClose ?? shell?.closeMobile ?? (() => {});
|
|
29
|
+
|
|
30
|
+
const [mounted, setMounted] = useState(open);
|
|
7
31
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (open) {
|
|
34
|
+
setMounted(true);
|
|
35
|
+
}
|
|
36
|
+
}, [open]);
|
|
12
37
|
|
|
13
38
|
useEffect(() => {
|
|
14
|
-
if (!
|
|
15
|
-
|
|
39
|
+
if (!mounted) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const previousOverflow =
|
|
44
|
+
document.body.style.overflow;
|
|
45
|
+
|
|
46
|
+
const previousTouchAction =
|
|
47
|
+
document.body.style.touchAction;
|
|
48
|
+
|
|
16
49
|
document.body.style.overflow = "hidden";
|
|
50
|
+
document.body.style.touchAction = "none";
|
|
51
|
+
|
|
52
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
53
|
+
if (event.key === "Escape") {
|
|
54
|
+
close();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
document.addEventListener(
|
|
59
|
+
"keydown",
|
|
60
|
+
handleKeyDown,
|
|
61
|
+
);
|
|
62
|
+
|
|
17
63
|
return () => {
|
|
18
|
-
document.body.style.overflow =
|
|
64
|
+
document.body.style.overflow =
|
|
65
|
+
previousOverflow;
|
|
66
|
+
|
|
67
|
+
document.body.style.touchAction =
|
|
68
|
+
previousTouchAction;
|
|
69
|
+
|
|
70
|
+
document.removeEventListener(
|
|
71
|
+
"keydown",
|
|
72
|
+
handleKeyDown,
|
|
73
|
+
);
|
|
19
74
|
};
|
|
20
|
-
}, [
|
|
75
|
+
}, [mounted, close]);
|
|
21
76
|
|
|
22
|
-
if (!
|
|
77
|
+
if (!mounted) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const panelStyle = {
|
|
82
|
+
"--shivanya-shell-mobile-width":
|
|
83
|
+
typeof size === "number"
|
|
84
|
+
? `${size}px`
|
|
85
|
+
: size,
|
|
86
|
+
} as CSSProperties;
|
|
87
|
+
|
|
88
|
+
const handleAnimationEnd = (
|
|
89
|
+
event: AnimationEvent<HTMLElement>,
|
|
90
|
+
) => {
|
|
91
|
+
if (
|
|
92
|
+
event.target !== event.currentTarget ||
|
|
93
|
+
event.animationName !==
|
|
94
|
+
"shivanya-mobile-drawer-close"
|
|
95
|
+
) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!open) {
|
|
100
|
+
setMounted(false);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
23
103
|
|
|
24
104
|
return (
|
|
25
105
|
<div
|
|
26
|
-
className=
|
|
106
|
+
className={[
|
|
107
|
+
"shivanya-shell-mobile-layer",
|
|
108
|
+
open
|
|
109
|
+
? "shivanya-shell-mobile-layer-open"
|
|
110
|
+
: "shivanya-shell-mobile-layer-closing",
|
|
111
|
+
]
|
|
112
|
+
.filter(Boolean)
|
|
113
|
+
.join(" ")}
|
|
27
114
|
role="dialog"
|
|
28
115
|
aria-modal="true"
|
|
29
116
|
aria-label="Navigation"
|
|
@@ -34,28 +121,30 @@ export function ShellMobileNav(props: ShellMobileNavProps) {
|
|
|
34
121
|
onClick={close}
|
|
35
122
|
aria-label="Close navigation"
|
|
36
123
|
/>
|
|
37
|
-
|
|
124
|
+
|
|
125
|
+
<aside
|
|
38
126
|
className="shivanya-shell-mobile-panel"
|
|
39
|
-
style={
|
|
40
|
-
|
|
41
|
-
typeof props.size === "number" ? `${props.size}px` : props.size,
|
|
42
|
-
}}
|
|
127
|
+
style={panelStyle}
|
|
128
|
+
onAnimationEnd={handleAnimationEnd}
|
|
43
129
|
>
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
>
|
|
50
|
-
×
|
|
51
|
-
</button>
|
|
130
|
+
<DrawerHeader
|
|
131
|
+
branding={props.branding}
|
|
132
|
+
onClose={close}
|
|
133
|
+
/>
|
|
134
|
+
|
|
52
135
|
<ShellSidebar
|
|
53
136
|
{...props}
|
|
54
137
|
collapsed={false}
|
|
55
138
|
variant="static"
|
|
56
|
-
|
|
139
|
+
branding={undefined}
|
|
140
|
+
className={[
|
|
141
|
+
"shivanya-shell-mobile-sidebar",
|
|
142
|
+
className,
|
|
143
|
+
]
|
|
144
|
+
.filter(Boolean)
|
|
145
|
+
.join(" ")}
|
|
57
146
|
/>
|
|
58
|
-
</
|
|
147
|
+
</aside>
|
|
59
148
|
</div>
|
|
60
149
|
);
|
|
61
|
-
}
|
|
150
|
+
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import type { CSSProperties } from "react";
|
|
2
1
|
import type { ShellRootProps } from "../types/shell.js";
|
|
3
|
-
import "../styles/shell.css";
|
|
4
2
|
|
|
5
|
-
export function ShellRoot({
|
|
3
|
+
export function ShellRoot({
|
|
4
|
+
children,
|
|
5
|
+
className = "",
|
|
6
|
+
style,
|
|
7
|
+
}: ShellRootProps) {
|
|
6
8
|
return (
|
|
7
|
-
<div
|
|
9
|
+
<div
|
|
10
|
+
className={`shivanya-shell ${className}`.trim()}
|
|
11
|
+
style={style}
|
|
12
|
+
>
|
|
8
13
|
{children}
|
|
9
14
|
</div>
|
|
10
15
|
);
|
|
11
|
-
}
|
|
16
|
+
}
|
|
@@ -1,6 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import type { CSSProperties, ElementType } from "react";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
+
import type {
|
|
5
|
+
ShellNavItem,
|
|
6
|
+
ShellSidebarProps,
|
|
7
|
+
} from "../types/shell.js";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
Sidebar,
|
|
11
|
+
SidebarItem,
|
|
12
|
+
} from "shivanya-ui";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
getShellItemKey,
|
|
16
|
+
isShellItemActive,
|
|
17
|
+
} from "../utils/navigation.js";
|
|
18
|
+
|
|
4
19
|
import { ShellBrand } from "./ShellBrand.js";
|
|
5
20
|
|
|
6
21
|
export function ShellSidebar({
|
|
@@ -20,21 +35,43 @@ export function ShellSidebar({
|
|
|
20
35
|
}: ShellSidebarProps) {
|
|
21
36
|
return (
|
|
22
37
|
<aside
|
|
23
|
-
className={
|
|
38
|
+
className={[
|
|
39
|
+
"shivanya-shell-sidebar",
|
|
40
|
+
`shivanya-shell-sidebar-${position}`,
|
|
41
|
+
`shivanya-shell-sidebar-${variant}`,
|
|
42
|
+
collapsed
|
|
43
|
+
? "shivanya-shell-sidebar-collapsed"
|
|
44
|
+
: "",
|
|
45
|
+
className,
|
|
46
|
+
]
|
|
47
|
+
.filter(Boolean)
|
|
48
|
+
.join(" ")}
|
|
24
49
|
style={
|
|
25
50
|
{
|
|
26
51
|
"--shivanya-shell-sidebar-width":
|
|
27
|
-
typeof width === "number"
|
|
52
|
+
typeof width === "number"
|
|
53
|
+
? `${width}px`
|
|
54
|
+
: width,
|
|
28
55
|
} as CSSProperties
|
|
29
56
|
}
|
|
30
57
|
>
|
|
31
58
|
{branding && (
|
|
32
59
|
<div className="shivanya-shell-sidebar-brand">
|
|
33
|
-
<ShellBrand
|
|
60
|
+
<ShellBrand
|
|
61
|
+
branding={branding}
|
|
62
|
+
compact={collapsed}
|
|
63
|
+
/>
|
|
34
64
|
</div>
|
|
35
65
|
)}
|
|
66
|
+
|
|
36
67
|
{children || (
|
|
37
|
-
<
|
|
68
|
+
<Sidebar
|
|
69
|
+
className="shivanya-shell-nav"
|
|
70
|
+
variant="ghost"
|
|
71
|
+
size="md"
|
|
72
|
+
radius="md"
|
|
73
|
+
itemPosition="start"
|
|
74
|
+
>
|
|
38
75
|
{navigation.map((item, index) => (
|
|
39
76
|
<ShellNavItemView
|
|
40
77
|
key={getShellItemKey(item, index)}
|
|
@@ -45,9 +82,14 @@ export function ShellSidebar({
|
|
|
45
82
|
onNavigate={onNavigate}
|
|
46
83
|
/>
|
|
47
84
|
))}
|
|
48
|
-
</
|
|
85
|
+
</Sidebar>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
{footer && (
|
|
89
|
+
<div className="shivanya-shell-sidebar-footer">
|
|
90
|
+
{footer}
|
|
91
|
+
</div>
|
|
49
92
|
)}
|
|
50
|
-
{footer && <div className="shivanya-shell-sidebar-footer">{footer}</div>}
|
|
51
93
|
</aside>
|
|
52
94
|
);
|
|
53
95
|
}
|
|
@@ -65,48 +107,67 @@ function ShellNavItemView({
|
|
|
65
107
|
collapsed: boolean;
|
|
66
108
|
onNavigate?: (item: ShellNavItem) => void;
|
|
67
109
|
}) {
|
|
68
|
-
if (item.divider)
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
110
|
+
if (item.divider) {
|
|
111
|
+
return (
|
|
112
|
+
<div
|
|
113
|
+
className="shivanya-shell-nav-divider"
|
|
114
|
+
role="separator"
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const handleClick = () => {
|
|
120
|
+
if (item.disabled) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
item.onClick?.();
|
|
125
|
+
onNavigate?.(item);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const rightSection = (
|
|
73
129
|
<>
|
|
74
|
-
<span className="shivanya-shell-nav-icon">{item.icon}</span>
|
|
75
|
-
<span className="shivanya-shell-nav-label">{item.label}</span>
|
|
76
130
|
{!collapsed && item.badge ? (
|
|
77
|
-
<span className="shivanya-shell-nav-badge">
|
|
131
|
+
<span className="shivanya-shell-nav-badge">
|
|
132
|
+
{item.badge}
|
|
133
|
+
</span>
|
|
78
134
|
) : null}
|
|
135
|
+
|
|
79
136
|
{!collapsed && item.endIcon ? (
|
|
80
|
-
<span className="shivanya-shell-nav-end">
|
|
137
|
+
<span className="shivanya-shell-nav-end">
|
|
138
|
+
{item.endIcon}
|
|
139
|
+
</span>
|
|
81
140
|
) : null}
|
|
82
141
|
</>
|
|
83
142
|
);
|
|
84
|
-
|
|
85
|
-
if (!item.disabled) {
|
|
86
|
-
item.onClick?.();
|
|
87
|
-
onNavigate?.(item);
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
if (Link && item.href && !item.disabled)
|
|
91
|
-
return (
|
|
92
|
-
<Link
|
|
93
|
-
className={className}
|
|
94
|
-
href={item.href}
|
|
95
|
-
onClick={handleClick}
|
|
96
|
-
title={collapsed ? String(item.label ?? "") : undefined}
|
|
97
|
-
>
|
|
98
|
-
{content}
|
|
99
|
-
</Link>
|
|
100
|
-
);
|
|
143
|
+
|
|
101
144
|
return (
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
145
|
+
<SidebarItem
|
|
146
|
+
component={Link && item.href && !item.disabled ? Link : "button"}
|
|
147
|
+
href={
|
|
148
|
+
Link && item.href && !item.disabled
|
|
149
|
+
? item.href
|
|
150
|
+
: undefined
|
|
151
|
+
}
|
|
152
|
+
icon={item.icon}
|
|
153
|
+
rightSection={rightSection}
|
|
154
|
+
active={active}
|
|
105
155
|
disabled={item.disabled}
|
|
106
156
|
onClick={handleClick}
|
|
107
|
-
|
|
157
|
+
className={[
|
|
158
|
+
"shivanya-shell-nav-item",
|
|
159
|
+
active ? "is-active" : "",
|
|
160
|
+
item.disabled ? "is-disabled" : "",
|
|
161
|
+
]
|
|
162
|
+
.filter(Boolean)
|
|
163
|
+
.join(" ")}
|
|
164
|
+
title={
|
|
165
|
+
collapsed
|
|
166
|
+
? String(item.label ?? "")
|
|
167
|
+
: undefined
|
|
168
|
+
}
|
|
108
169
|
>
|
|
109
|
-
{
|
|
110
|
-
</
|
|
170
|
+
{item.label}
|
|
171
|
+
</SidebarItem>
|
|
111
172
|
);
|
|
112
|
-
}
|
|
173
|
+
}
|