@scripso-homepad/ui 0.4.25 → 0.4.26
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/web/index.cjs +127 -53
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.js +127 -53
- package/dist/web/index.js.map +1 -1
- package/package.json +1 -1
- package/src/web/layout/Sidebar.tsx +20 -21
- package/src/web/layout/SidebarNavItem.tsx +2 -2
- package/src/web/layout/SidebarUserCard.tsx +99 -20
package/package.json
CHANGED
|
@@ -218,7 +218,7 @@ export function Sidebar({
|
|
|
218
218
|
</button>
|
|
219
219
|
</div>
|
|
220
220
|
|
|
221
|
-
<nav className="flex flex-1 flex-col gap-2 overflow-
|
|
221
|
+
<nav className="flex w-full min-h-0 flex-1 flex-col gap-2 overflow-x-hidden overflow-y-auto">
|
|
222
222
|
{visibleNavItems.map((item) => (
|
|
223
223
|
<SidebarNavItem
|
|
224
224
|
key={item.to}
|
|
@@ -228,27 +228,26 @@ export function Sidebar({
|
|
|
228
228
|
onNavigate={handleNavItemNavigate}
|
|
229
229
|
/>
|
|
230
230
|
))}
|
|
231
|
+
<div className="mt-auto flex w-full flex-col gap-2">
|
|
232
|
+
{visibleFooterNavItems.map((item) => (
|
|
233
|
+
<SidebarNavItem
|
|
234
|
+
key={item.to}
|
|
235
|
+
item={item}
|
|
236
|
+
isOpen={isExpanded}
|
|
237
|
+
isActive={isNavItemActive(pathname, item)}
|
|
238
|
+
onNavigate={handleNavItemNavigate}
|
|
239
|
+
/>
|
|
240
|
+
))}
|
|
241
|
+
{user ? (
|
|
242
|
+
<SidebarUserCard
|
|
243
|
+
user={user}
|
|
244
|
+
isOpen={isExpanded}
|
|
245
|
+
logoutLabel={labels.logout}
|
|
246
|
+
onLogout={onLogout}
|
|
247
|
+
/>
|
|
248
|
+
) : null}
|
|
249
|
+
</div>
|
|
231
250
|
</nav>
|
|
232
|
-
|
|
233
|
-
<div className="flex w-full flex-col gap-4">
|
|
234
|
-
{visibleFooterNavItems.map((item) => (
|
|
235
|
-
<SidebarNavItem
|
|
236
|
-
key={item.to}
|
|
237
|
-
item={item}
|
|
238
|
-
isOpen={isExpanded}
|
|
239
|
-
isActive={isNavItemActive(pathname, item)}
|
|
240
|
-
onNavigate={handleNavItemNavigate}
|
|
241
|
-
/>
|
|
242
|
-
))}
|
|
243
|
-
{user ? (
|
|
244
|
-
<SidebarUserCard
|
|
245
|
-
user={user}
|
|
246
|
-
isOpen={isExpanded}
|
|
247
|
-
logoutLabel={labels.logout}
|
|
248
|
-
onLogout={onLogout}
|
|
249
|
-
/>
|
|
250
|
-
) : null}
|
|
251
|
-
</div>
|
|
252
251
|
</aside>
|
|
253
252
|
</>
|
|
254
253
|
);
|
|
@@ -36,9 +36,9 @@ export function SidebarNavItem({ item, isOpen, isActive, onNavigate }: SidebarNa
|
|
|
36
36
|
tabIndex={item.disabled ? -1 : undefined}
|
|
37
37
|
className={cn(
|
|
38
38
|
'relative flex items-center rounded-xl px-4 py-3 transition-[background-color,color,gap,padding] duration-300 ease-in-out',
|
|
39
|
-
isOpen ? 'w-full gap-4' : 'justify-center gap-0 px-3',
|
|
39
|
+
isOpen ? 'w-full gap-4' : 'w-full justify-center gap-0 px-3',
|
|
40
40
|
item.disabled && 'pointer-events-none opacity-50',
|
|
41
|
-
isActive ? 'bg-black/12 text-white' : 'text-navy-100 hover:bg-white/5',
|
|
41
|
+
isActive ? 'bg-black/12 text-white shadow-none' : 'text-navy-100 shadow-none hover:bg-white/5',
|
|
42
42
|
)}
|
|
43
43
|
>
|
|
44
44
|
<span
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LogOut } from 'lucide-react';
|
|
2
|
+
import { useEffect, useId, useRef, useState } from 'react';
|
|
2
3
|
|
|
3
4
|
import { cn } from '../utils/cn';
|
|
4
5
|
import type { AdminSidebarUser } from './types';
|
|
@@ -23,6 +24,9 @@ function getInitials(fullName: string, email: string): string {
|
|
|
23
24
|
return email.slice(0, 2).toUpperCase();
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
const avatarCircleClassName =
|
|
28
|
+
'flex size-11 shrink-0 items-center justify-center rounded-full bg-white/5';
|
|
29
|
+
|
|
26
30
|
export function SidebarUserCard({
|
|
27
31
|
user,
|
|
28
32
|
isOpen,
|
|
@@ -31,37 +35,112 @@ export function SidebarUserCard({
|
|
|
31
35
|
className,
|
|
32
36
|
}: SidebarUserCardProps) {
|
|
33
37
|
const initials = user.initials ?? getInitials(user.fullName, user.email);
|
|
38
|
+
const [logoutOpen, setLogoutOpen] = useState(false);
|
|
39
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
40
|
+
const logoutMenuId = useId();
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (isOpen) {
|
|
44
|
+
setLogoutOpen(false);
|
|
45
|
+
}
|
|
46
|
+
}, [isOpen]);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (isOpen || !logoutOpen) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function handlePointerDown(event: MouseEvent) {
|
|
54
|
+
if (rootRef.current && !rootRef.current.contains(event.target as Node)) {
|
|
55
|
+
setLogoutOpen(false);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
60
|
+
if (event.key === 'Escape') {
|
|
61
|
+
setLogoutOpen(false);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
document.addEventListener('mousedown', handlePointerDown);
|
|
66
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
67
|
+
|
|
68
|
+
return () => {
|
|
69
|
+
document.removeEventListener('mousedown', handlePointerDown);
|
|
70
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
71
|
+
};
|
|
72
|
+
}, [isOpen, logoutOpen]);
|
|
73
|
+
|
|
74
|
+
const avatar = (
|
|
36
75
|
<div
|
|
37
|
-
className={cn(
|
|
38
|
-
|
|
39
|
-
isOpen ? 'justify-between gap-3 bg-black/12 p-3' : 'justify-center px-3 py-3',
|
|
40
|
-
className,
|
|
41
|
-
)}
|
|
76
|
+
className={cn(avatarCircleClassName, 'typography-14 font-bold text-white')}
|
|
77
|
+
aria-hidden
|
|
42
78
|
>
|
|
79
|
+
{initials}
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (!isOpen) {
|
|
84
|
+
return (
|
|
43
85
|
<div
|
|
44
|
-
|
|
45
|
-
|
|
86
|
+
ref={rootRef}
|
|
87
|
+
className={cn('flex w-full flex-col items-center gap-2', className)}
|
|
46
88
|
>
|
|
47
|
-
{
|
|
48
|
-
</div>
|
|
49
|
-
{isOpen ? (
|
|
50
|
-
<>
|
|
51
|
-
<div className="flex min-w-0 flex-1 flex-col overflow-hidden">
|
|
52
|
-
<p className="truncate typography-14 font-bold text-white">{user.fullName}</p>
|
|
53
|
-
<p className="truncate typography-12 text-storm-gray-100">{user.email}</p>
|
|
54
|
-
</div>
|
|
89
|
+
{logoutOpen ? (
|
|
55
90
|
<button
|
|
56
91
|
type="button"
|
|
57
|
-
|
|
58
|
-
|
|
92
|
+
id={logoutMenuId}
|
|
93
|
+
onClick={() => {
|
|
94
|
+
setLogoutOpen(false);
|
|
95
|
+
onLogout?.();
|
|
96
|
+
}}
|
|
97
|
+
className={cn(
|
|
98
|
+
avatarCircleClassName,
|
|
99
|
+
'cursor-pointer bg-black/20 text-white',
|
|
100
|
+
'transition-[background-color,transform] duration-300 ease-in-out',
|
|
101
|
+
'hover:bg-white/10 active:scale-95',
|
|
102
|
+
)}
|
|
59
103
|
aria-label={logoutLabel}
|
|
60
104
|
>
|
|
61
105
|
<LogOut size={20} strokeWidth={1.75} />
|
|
62
106
|
</button>
|
|
63
|
-
|
|
64
|
-
|
|
107
|
+
) : null}
|
|
108
|
+
<button
|
|
109
|
+
type="button"
|
|
110
|
+
onClick={() => setLogoutOpen((open) => !open)}
|
|
111
|
+
className="flex cursor-pointer items-center justify-center transition-transform duration-300 ease-in-out active:scale-95"
|
|
112
|
+
aria-expanded={logoutOpen}
|
|
113
|
+
aria-haspopup="true"
|
|
114
|
+
aria-controls={logoutOpen ? logoutMenuId : undefined}
|
|
115
|
+
aria-label={user.fullName}
|
|
116
|
+
>
|
|
117
|
+
{avatar}
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
className={cn(
|
|
126
|
+
'flex w-full items-center justify-between gap-3 rounded-xl bg-black/12 p-3',
|
|
127
|
+
'transition-[background-color,justify-content,gap,padding] duration-300 ease-in-out',
|
|
128
|
+
className,
|
|
129
|
+
)}
|
|
130
|
+
>
|
|
131
|
+
{avatar}
|
|
132
|
+
<div className="flex min-w-0 flex-1 flex-col overflow-hidden">
|
|
133
|
+
<p className="truncate typography-14 font-bold text-white">{user.fullName}</p>
|
|
134
|
+
<p className="truncate typography-12 text-storm-gray-100">{user.email}</p>
|
|
135
|
+
</div>
|
|
136
|
+
<button
|
|
137
|
+
type="button"
|
|
138
|
+
onClick={() => onLogout?.()}
|
|
139
|
+
className="shrink-0 cursor-pointer text-navy-150 transition-[opacity,transform] duration-300 ease-in-out hover:opacity-80 active:scale-95"
|
|
140
|
+
aria-label={logoutLabel}
|
|
141
|
+
>
|
|
142
|
+
<LogOut size={20} strokeWidth={1.75} />
|
|
143
|
+
</button>
|
|
65
144
|
</div>
|
|
66
145
|
);
|
|
67
146
|
}
|