@alepha/ui 0.11.3 → 0.11.5
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/AlephaMantineProvider-Ba88lMeq.js +3 -0
- package/dist/AlephaMantineProvider-Be0DAazb.js +150 -0
- package/dist/AlephaMantineProvider-Be0DAazb.js.map +1 -0
- package/dist/index.d.ts +289 -225
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +650 -729
- package/dist/index.js.map +1 -1
- package/package.json +14 -12
- package/src/RootRouter.ts +1 -1
- package/src/components/buttons/ActionButton.tsx +542 -0
- package/src/components/buttons/BurgerButton.tsx +20 -0
- package/src/components/{DarkModeButton.tsx → buttons/DarkModeButton.tsx} +27 -14
- package/src/components/buttons/LanguageButton.tsx +28 -0
- package/src/components/buttons/OmnibarButton.tsx +32 -0
- package/src/components/buttons/ToggleSidebarButton.tsx +28 -0
- package/src/components/dialogs/AlertDialog.tsx +10 -10
- package/src/components/dialogs/ConfirmDialog.tsx +18 -18
- package/src/components/dialogs/PromptDialog.tsx +5 -3
- package/src/components/{Control.tsx → form/Control.tsx} +6 -3
- package/src/components/{ControlDate.tsx → form/ControlDate.tsx} +4 -1
- package/src/components/{ControlSelect.tsx → form/ControlSelect.tsx} +4 -1
- package/src/components/{TypeForm.tsx → form/TypeForm.tsx} +8 -6
- package/src/components/layout/AdminShell.tsx +97 -0
- package/src/components/{AlephaMantineProvider.tsx → layout/AlephaMantineProvider.tsx} +30 -10
- package/src/components/layout/AppBar.tsx +133 -0
- package/src/components/layout/Omnibar.tsx +43 -0
- package/src/components/layout/Sidebar.tsx +410 -0
- package/src/components/table/DataTable.tsx +63 -0
- package/src/constants/ui.ts +8 -0
- package/src/index.ts +89 -24
- package/src/services/DialogService.tsx +13 -32
- package/src/services/ToastService.tsx +16 -4
- package/src/utils/parseInput.ts +1 -1
- package/dist/AlephaMantineProvider-DDbIijPF.js +0 -96
- package/dist/AlephaMantineProvider-DDbIijPF.js.map +0 -1
- package/dist/AlephaMantineProvider-pOu8hOzK.js +0 -3
- package/src/components/Action.tsx +0 -345
- package/src/components/DataTable.css +0 -199
- package/src/components/DataTable.tsx +0 -724
- package/src/components/Omnibar.tsx +0 -77
- package/src/components/Sidebar.css +0 -217
- package/src/components/Sidebar.tsx +0 -255
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { Spotlight, type SpotlightActionData } from "@mantine/spotlight";
|
|
2
|
-
import {
|
|
3
|
-
IconDashboard,
|
|
4
|
-
IconFileText,
|
|
5
|
-
IconHome,
|
|
6
|
-
IconSearch,
|
|
7
|
-
IconSettings,
|
|
8
|
-
IconUser,
|
|
9
|
-
} from "@tabler/icons-react";
|
|
10
|
-
// biome-ignore lint/correctness/noUnusedImports: required
|
|
11
|
-
import React, { type ReactNode } from "react";
|
|
12
|
-
|
|
13
|
-
export interface OmnibarProps {
|
|
14
|
-
actions?: SpotlightActionData[];
|
|
15
|
-
shortcut?: string | string[];
|
|
16
|
-
searchPlaceholder?: string;
|
|
17
|
-
nothingFound?: ReactNode;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const defaultActions: SpotlightActionData[] = [
|
|
21
|
-
{
|
|
22
|
-
id: "home",
|
|
23
|
-
label: "Home",
|
|
24
|
-
description: "Go to home page",
|
|
25
|
-
onClick: () => console.log("Home"),
|
|
26
|
-
leftSection: <IconHome size={20} />,
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
id: "dashboard",
|
|
30
|
-
label: "Dashboard",
|
|
31
|
-
description: "View your dashboard",
|
|
32
|
-
onClick: () => console.log("Dashboard"),
|
|
33
|
-
leftSection: <IconDashboard size={20} />,
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
id: "documents",
|
|
37
|
-
label: "Documents",
|
|
38
|
-
description: "Browse all documents",
|
|
39
|
-
onClick: () => console.log("Documents"),
|
|
40
|
-
leftSection: <IconFileText size={20} />,
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
id: "profile",
|
|
44
|
-
label: "Profile",
|
|
45
|
-
description: "View and edit your profile",
|
|
46
|
-
onClick: () => console.log("Profile"),
|
|
47
|
-
leftSection: <IconUser size={20} />,
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
id: "settings",
|
|
51
|
-
label: "Settings",
|
|
52
|
-
description: "Manage application settings",
|
|
53
|
-
onClick: () => console.log("Settings"),
|
|
54
|
-
leftSection: <IconSettings size={20} />,
|
|
55
|
-
},
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
const Omnibar = (props: OmnibarProps) => {
|
|
59
|
-
const actions = props.actions ?? defaultActions;
|
|
60
|
-
const shortcut = props.shortcut ?? "mod+K";
|
|
61
|
-
const searchPlaceholder = props.searchPlaceholder ?? "Search...";
|
|
62
|
-
const nothingFound = props.nothingFound ?? "Nothing found...";
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<Spotlight
|
|
66
|
-
actions={actions}
|
|
67
|
-
shortcut={shortcut}
|
|
68
|
-
searchProps={{
|
|
69
|
-
leftSection: <IconSearch size={20} />,
|
|
70
|
-
placeholder: searchPlaceholder,
|
|
71
|
-
}}
|
|
72
|
-
nothingFound={nothingFound}
|
|
73
|
-
/>
|
|
74
|
-
);
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
export default Omnibar;
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
/* Sidebar Component Styles */
|
|
2
|
-
|
|
3
|
-
.alepha-sidebar {
|
|
4
|
-
width: 100%;
|
|
5
|
-
padding: 8px;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/* Search button styles */
|
|
9
|
-
.alepha-sidebar-search-button {
|
|
10
|
-
display: block;
|
|
11
|
-
width: 100%;
|
|
12
|
-
padding: 8px 12px;
|
|
13
|
-
margin-bottom: 12px;
|
|
14
|
-
background-color: var(--alepha-surface);
|
|
15
|
-
border: 1px solid var(--alepha-border);
|
|
16
|
-
border-radius: 8px;
|
|
17
|
-
transition: all 150ms ease;
|
|
18
|
-
cursor: pointer;
|
|
19
|
-
text-decoration: none;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
.alepha-sidebar-search-button:hover {
|
|
23
|
-
background-color: var(--alepha-elevated-hover);
|
|
24
|
-
border-color: var(--mantine-color-blue-6);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.alepha-sidebar-search-button-content {
|
|
28
|
-
display: flex;
|
|
29
|
-
align-items: center;
|
|
30
|
-
justify-content: space-between;
|
|
31
|
-
width: 100%;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.alepha-sidebar-search-button-left {
|
|
35
|
-
display: flex;
|
|
36
|
-
align-items: center;
|
|
37
|
-
gap: 10px;
|
|
38
|
-
color: var(--alepha-text-muted);
|
|
39
|
-
font-size: 14px;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.alepha-sidebar-search-button-shortcut {
|
|
43
|
-
display: flex;
|
|
44
|
-
align-items: center;
|
|
45
|
-
gap: 2px;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.alepha-sidebar-search-button-shortcut kbd {
|
|
49
|
-
padding: 2px 6px;
|
|
50
|
-
background-color: var(--alepha-elevated);
|
|
51
|
-
border: 1px solid var(--alepha-border);
|
|
52
|
-
border-radius: 4px;
|
|
53
|
-
font-size: 11px;
|
|
54
|
-
font-family: inherit;
|
|
55
|
-
color: var(--alepha-text-muted);
|
|
56
|
-
box-shadow: 0 1px 2px var(--alepha-shadow);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.alepha-sidebar-item-wrapper {
|
|
60
|
-
position: relative;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.alepha-sidebar-item {
|
|
64
|
-
display: block;
|
|
65
|
-
width: 100%;
|
|
66
|
-
padding: 8px 12px;
|
|
67
|
-
text-decoration: none;
|
|
68
|
-
color: var(--alepha-text);
|
|
69
|
-
border-radius: 24px;
|
|
70
|
-
transition: background-color 150ms ease;
|
|
71
|
-
cursor: pointer;
|
|
72
|
-
font-size: 14px;
|
|
73
|
-
line-height: 1.5;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/* Full width for level 0 items */
|
|
77
|
-
.alepha-sidebar-level-0 {
|
|
78
|
-
margin-left: 0;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/* Adjusted width for indented items */
|
|
82
|
-
.alepha-sidebar-level-1 {
|
|
83
|
-
width: calc(100% - 24px);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.alepha-sidebar-level-2 {
|
|
87
|
-
width: calc(100% - 48px);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/* Active state for current page */
|
|
91
|
-
.alepha-sidebar-item-active {
|
|
92
|
-
background-color: var(--mantine-color-blue-0);
|
|
93
|
-
color: var(--mantine-color-blue-7);
|
|
94
|
-
font-weight: 500;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.alepha-sidebar-item:hover:not(.alepha-sidebar-item-active) {
|
|
98
|
-
background-color: var(--alepha-elevated-hover);
|
|
99
|
-
color: var(--alepha-text) !important;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
[data-mantine-color-scheme="dark"] .alepha-sidebar-item-active {
|
|
103
|
-
background-color: rgba(34, 139, 230, 0.15);
|
|
104
|
-
color: var(--mantine-color-blue-4);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.alepha-sidebar-item-active .alepha-sidebar-item-icon {
|
|
108
|
-
color: var(--mantine-color-blue-6);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
[data-mantine-color-scheme="dark"]
|
|
112
|
-
.alepha-sidebar-item-active
|
|
113
|
-
.alepha-sidebar-item-icon {
|
|
114
|
-
color: var(--mantine-color-blue-4);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
.alepha-sidebar-item-content {
|
|
118
|
-
flex: 1;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
.alepha-sidebar-item-icon {
|
|
122
|
-
flex-shrink: 0;
|
|
123
|
-
color: var(--alepha-text-muted);
|
|
124
|
-
display: flex;
|
|
125
|
-
align-items: center;
|
|
126
|
-
justify-content: center;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.alepha-sidebar-item-label {
|
|
130
|
-
flex: 1;
|
|
131
|
-
font-weight: 400;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.alepha-sidebar-item-caret {
|
|
135
|
-
flex-shrink: 0;
|
|
136
|
-
color: var(--alepha-text-muted);
|
|
137
|
-
transition: transform 150ms ease;
|
|
138
|
-
display: flex;
|
|
139
|
-
align-items: center;
|
|
140
|
-
justify-content: center;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/* Level-specific indentation (now in the width calc above) */
|
|
144
|
-
.alepha-sidebar-level-1 {
|
|
145
|
-
margin-left: 24px;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
.alepha-sidebar-level-2 {
|
|
149
|
-
margin-left: 48px;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/* Children container */
|
|
153
|
-
.alepha-sidebar-children {
|
|
154
|
-
position: relative;
|
|
155
|
-
margin-top: 2px;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.alepha-sidebar-children-items {
|
|
159
|
-
position: relative;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/* Vertical bar for nested items */
|
|
163
|
-
.alepha-sidebar-vertical-bar {
|
|
164
|
-
position: absolute;
|
|
165
|
-
top: 0;
|
|
166
|
-
bottom: 0;
|
|
167
|
-
width: 1px;
|
|
168
|
-
background: linear-gradient(
|
|
169
|
-
to bottom,
|
|
170
|
-
var(--alepha-border) 0%,
|
|
171
|
-
var(--alepha-border) 90%,
|
|
172
|
-
transparent 100%
|
|
173
|
-
);
|
|
174
|
-
opacity: 0.5;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/* Vertical bar positioning based on parent level */
|
|
178
|
-
.alepha-sidebar-children[data-parent-level="0"] .alepha-sidebar-vertical-bar {
|
|
179
|
-
left: 20px; /* Position for level 1 children (level 2 items) */
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
.alepha-sidebar-children[data-parent-level="1"] .alepha-sidebar-vertical-bar {
|
|
183
|
-
left: 45px; /* Position for level 2 children (level 3 items) */
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/* Smooth animations */
|
|
187
|
-
.alepha-sidebar-children {
|
|
188
|
-
animation: alepha-sidebar-slideDown 150ms ease-out;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
@keyframes alepha-sidebar-slideDown {
|
|
192
|
-
from {
|
|
193
|
-
opacity: 0;
|
|
194
|
-
transform: translateY(-4px);
|
|
195
|
-
}
|
|
196
|
-
to {
|
|
197
|
-
opacity: 1;
|
|
198
|
-
transform: translateY(0);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/* Remove default focus outline and add custom */
|
|
203
|
-
.alepha-sidebar-item:focus-visible {
|
|
204
|
-
outline: none;
|
|
205
|
-
box-shadow: 0 0 0 2px var(--alepha-focus-ring, #0969da);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/* Active state */
|
|
209
|
-
.alepha-sidebar-item:active {
|
|
210
|
-
transform: scale(0.98);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/* Optional: Add subtle hover effect for icons (but not for active items) */
|
|
214
|
-
.alepha-sidebar-item:hover:not(.alepha-sidebar-item-active)
|
|
215
|
-
.alepha-sidebar-item-icon {
|
|
216
|
-
color: var(--alepha-text);
|
|
217
|
-
}
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import { useActive } from "@alepha/react";
|
|
2
|
-
import { Box, Flex, UnstyledButton } from "@mantine/core";
|
|
3
|
-
import {
|
|
4
|
-
IconChevronDown,
|
|
5
|
-
IconChevronRight,
|
|
6
|
-
IconCircle,
|
|
7
|
-
IconSearch,
|
|
8
|
-
} from "@tabler/icons-react";
|
|
9
|
-
import { type FC, type ReactNode, useState } from "react";
|
|
10
|
-
|
|
11
|
-
export interface MenuItem {
|
|
12
|
-
id: string;
|
|
13
|
-
label: string;
|
|
14
|
-
icon?: ReactNode;
|
|
15
|
-
href?: string;
|
|
16
|
-
activeStartsWith?: boolean; // Use startWith matching for active state
|
|
17
|
-
onClick?: () => void;
|
|
18
|
-
children?: MenuItem[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface SidebarProps {
|
|
22
|
-
menu: MenuItem[];
|
|
23
|
-
defaultOpenIds?: string[];
|
|
24
|
-
onItemClick?: (item: MenuItem) => void;
|
|
25
|
-
showSearchButton?: boolean;
|
|
26
|
-
onSearchClick?: () => void;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const Sidebar: FC<SidebarProps> = ({
|
|
30
|
-
menu,
|
|
31
|
-
defaultOpenIds = [],
|
|
32
|
-
onItemClick,
|
|
33
|
-
showSearchButton = false,
|
|
34
|
-
onSearchClick,
|
|
35
|
-
}) => {
|
|
36
|
-
const [openIds, setOpenIds] = useState<Set<string>>(new Set(defaultOpenIds));
|
|
37
|
-
|
|
38
|
-
const toggleOpen = (id: string) => {
|
|
39
|
-
setOpenIds((prev) => {
|
|
40
|
-
const newSet = new Set(prev);
|
|
41
|
-
if (newSet.has(id)) {
|
|
42
|
-
newSet.delete(id);
|
|
43
|
-
} else {
|
|
44
|
-
newSet.add(id);
|
|
45
|
-
}
|
|
46
|
-
return newSet;
|
|
47
|
-
});
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<Box component="nav" className="alepha-sidebar">
|
|
52
|
-
{showSearchButton && (
|
|
53
|
-
<UnstyledButton
|
|
54
|
-
className="alepha-sidebar-search-button"
|
|
55
|
-
onClick={onSearchClick}
|
|
56
|
-
>
|
|
57
|
-
<Box className="alepha-sidebar-search-button-content">
|
|
58
|
-
<Box className="alepha-sidebar-search-button-left">
|
|
59
|
-
<IconSearch size={16} />
|
|
60
|
-
<span>Search...</span>
|
|
61
|
-
</Box>
|
|
62
|
-
<Box className="alepha-sidebar-search-button-shortcut">
|
|
63
|
-
<kbd>⌘+K</kbd>
|
|
64
|
-
</Box>
|
|
65
|
-
</Box>
|
|
66
|
-
</UnstyledButton>
|
|
67
|
-
)}
|
|
68
|
-
{menu.map((item) => (
|
|
69
|
-
<SidebarItem
|
|
70
|
-
key={item.id}
|
|
71
|
-
item={item}
|
|
72
|
-
level={0}
|
|
73
|
-
openIds={openIds}
|
|
74
|
-
onToggle={toggleOpen}
|
|
75
|
-
onItemClick={onItemClick}
|
|
76
|
-
/>
|
|
77
|
-
))}
|
|
78
|
-
</Box>
|
|
79
|
-
);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
83
|
-
// SidebarItem - Main component that decides which variant to render
|
|
84
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
85
|
-
|
|
86
|
-
export interface SidebarItemProps {
|
|
87
|
-
item: MenuItem;
|
|
88
|
-
level: number;
|
|
89
|
-
openIds: Set<string>;
|
|
90
|
-
onToggle: (id: string) => void;
|
|
91
|
-
onItemClick?: (item: MenuItem) => void;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export const SidebarItem: React.FC<SidebarItemProps> = (props) => {
|
|
95
|
-
const { item, level } = props;
|
|
96
|
-
const maxLevel = 2; // 0, 1, 2 = 3 levels total
|
|
97
|
-
|
|
98
|
-
if (level > maxLevel) return null;
|
|
99
|
-
|
|
100
|
-
// Render different components based on whether item has href or not
|
|
101
|
-
if (item.href) {
|
|
102
|
-
return <SidebarItemHref {...props} />;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return <SidebarItemButton {...props} />;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
109
|
-
// SidebarItemHref - Component for items with href (navigation)
|
|
110
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
111
|
-
|
|
112
|
-
const SidebarItemHref: React.FC<SidebarItemProps> = ({
|
|
113
|
-
item,
|
|
114
|
-
level,
|
|
115
|
-
openIds,
|
|
116
|
-
onToggle,
|
|
117
|
-
onItemClick,
|
|
118
|
-
}) => {
|
|
119
|
-
const hasChildren = item.children && item.children.length > 0;
|
|
120
|
-
const isOpen = openIds.has(item.id);
|
|
121
|
-
|
|
122
|
-
// Use the useActive hook for navigation
|
|
123
|
-
const { isActive, anchorProps } = useActive({
|
|
124
|
-
href: item.href!,
|
|
125
|
-
startWith: item.activeStartsWith,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const handleItemClick = (e: React.MouseEvent) => {
|
|
129
|
-
if (hasChildren) {
|
|
130
|
-
e.preventDefault();
|
|
131
|
-
onToggle(item.id);
|
|
132
|
-
}
|
|
133
|
-
// anchorProps.onClick handles navigation automatically
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
return (
|
|
137
|
-
<Box className="alepha-sidebar-item-wrapper">
|
|
138
|
-
<UnstyledButton
|
|
139
|
-
component="a"
|
|
140
|
-
{...anchorProps}
|
|
141
|
-
className={`alepha-sidebar-item alepha-sidebar-level-${level} ${isActive ? "alepha-sidebar-item-active" : ""}`}
|
|
142
|
-
onClick={hasChildren ? handleItemClick : anchorProps.onClick}
|
|
143
|
-
>
|
|
144
|
-
<Flex justify="space-between" align="center" w="100%">
|
|
145
|
-
<Flex className="alepha-sidebar-item-content" align="center" gap={10}>
|
|
146
|
-
<Box className="alepha-sidebar-item-icon">
|
|
147
|
-
{item.icon || <IconCircle size={16} />}
|
|
148
|
-
</Box>
|
|
149
|
-
<Box className="alepha-sidebar-item-label">{item.label}</Box>
|
|
150
|
-
</Flex>
|
|
151
|
-
{hasChildren && (
|
|
152
|
-
<Box className="alepha-sidebar-item-caret">
|
|
153
|
-
{isOpen ? (
|
|
154
|
-
<IconChevronDown size={14} />
|
|
155
|
-
) : (
|
|
156
|
-
<IconChevronRight size={14} />
|
|
157
|
-
)}
|
|
158
|
-
</Box>
|
|
159
|
-
)}
|
|
160
|
-
</Flex>
|
|
161
|
-
</UnstyledButton>
|
|
162
|
-
{hasChildren && isOpen && (
|
|
163
|
-
<Box className="alepha-sidebar-children" data-parent-level={level}>
|
|
164
|
-
{(level === 0 || level === 1) && (
|
|
165
|
-
<Box className="alepha-sidebar-vertical-bar" />
|
|
166
|
-
)}
|
|
167
|
-
<Box className="alepha-sidebar-children-items">
|
|
168
|
-
{item.children!.map((child) => (
|
|
169
|
-
<SidebarItem
|
|
170
|
-
key={child.id}
|
|
171
|
-
item={child}
|
|
172
|
-
level={level + 1}
|
|
173
|
-
openIds={openIds}
|
|
174
|
-
onToggle={onToggle}
|
|
175
|
-
onItemClick={onItemClick}
|
|
176
|
-
/>
|
|
177
|
-
))}
|
|
178
|
-
</Box>
|
|
179
|
-
</Box>
|
|
180
|
-
)}
|
|
181
|
-
</Box>
|
|
182
|
-
);
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
186
|
-
// SidebarItemButton - Component for items without href (buttons with onClick)
|
|
187
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
188
|
-
|
|
189
|
-
const SidebarItemButton: React.FC<SidebarItemProps> = ({
|
|
190
|
-
item,
|
|
191
|
-
level,
|
|
192
|
-
openIds,
|
|
193
|
-
onToggle,
|
|
194
|
-
onItemClick,
|
|
195
|
-
}) => {
|
|
196
|
-
const hasChildren = item.children && item.children.length > 0;
|
|
197
|
-
const isOpen = openIds.has(item.id);
|
|
198
|
-
|
|
199
|
-
const handleItemClick = (e: React.MouseEvent) => {
|
|
200
|
-
e.preventDefault();
|
|
201
|
-
if (hasChildren) {
|
|
202
|
-
onToggle(item.id);
|
|
203
|
-
} else {
|
|
204
|
-
onItemClick?.(item);
|
|
205
|
-
item.onClick?.();
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
return (
|
|
210
|
-
<Box className="alepha-sidebar-item-wrapper">
|
|
211
|
-
<UnstyledButton
|
|
212
|
-
component="button"
|
|
213
|
-
className={`alepha-sidebar-item alepha-sidebar-level-${level}`}
|
|
214
|
-
onClick={handleItemClick}
|
|
215
|
-
>
|
|
216
|
-
<Flex justify="space-between" align="center" w="100%">
|
|
217
|
-
<Flex className="alepha-sidebar-item-content" align="center" gap={10}>
|
|
218
|
-
<Box className="alepha-sidebar-item-icon">
|
|
219
|
-
{item.icon || <IconCircle size={16} />}
|
|
220
|
-
</Box>
|
|
221
|
-
<Box className="alepha-sidebar-item-label">{item.label}</Box>
|
|
222
|
-
</Flex>
|
|
223
|
-
{hasChildren && (
|
|
224
|
-
<Box className="alepha-sidebar-item-caret">
|
|
225
|
-
{isOpen ? (
|
|
226
|
-
<IconChevronDown size={14} />
|
|
227
|
-
) : (
|
|
228
|
-
<IconChevronRight size={14} />
|
|
229
|
-
)}
|
|
230
|
-
</Box>
|
|
231
|
-
)}
|
|
232
|
-
</Flex>
|
|
233
|
-
</UnstyledButton>
|
|
234
|
-
{hasChildren && isOpen && (
|
|
235
|
-
<Box className="alepha-sidebar-children" data-parent-level={level}>
|
|
236
|
-
{(level === 0 || level === 1) && (
|
|
237
|
-
<Box className="alepha-sidebar-vertical-bar" />
|
|
238
|
-
)}
|
|
239
|
-
<Box className="alepha-sidebar-children-items">
|
|
240
|
-
{item.children!.map((child) => (
|
|
241
|
-
<SidebarItem
|
|
242
|
-
key={child.id}
|
|
243
|
-
item={child}
|
|
244
|
-
level={level + 1}
|
|
245
|
-
openIds={openIds}
|
|
246
|
-
onToggle={onToggle}
|
|
247
|
-
onItemClick={onItemClick}
|
|
248
|
-
/>
|
|
249
|
-
))}
|
|
250
|
-
</Box>
|
|
251
|
-
</Box>
|
|
252
|
-
)}
|
|
253
|
-
</Box>
|
|
254
|
-
);
|
|
255
|
-
};
|