@scripso-homepad/ui 0.4.35 → 0.4.37
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/{chunk-AOBKWDGW.js → chunk-4FBZM62E.js} +3 -3
- package/dist/chunk-4FBZM62E.js.map +1 -0
- package/dist/index.cjs +9 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +10 -5
- package/dist/index.js.map +1 -1
- package/dist/web/index.cjs +113 -41
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts +23 -3
- package/dist/web/index.d.ts +23 -3
- package/dist/web/index.js +113 -43
- package/dist/web/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Button.tsx +5 -1
- package/src/components/PhoneInput.tsx +5 -0
- package/src/web/components/Drawer.tsx +2 -2
- package/src/web/components/Pagination.tsx +62 -58
- package/src/web/components/table/DataTable.tsx +2 -1
- package/src/web/components/table/TableScrollArea.tsx +1 -1
- package/src/web/icons/SidebarCollapseIcon.tsx +32 -0
- package/src/web/icons/SidebarExpandIcon.tsx +32 -0
- package/src/web/index.ts +2 -0
- package/src/web/layout/DashboardLayout.stories.tsx +2 -1
- package/src/web/layout/DashboardLayout.tsx +3 -3
- package/src/web/layout/Sidebar.stories.tsx +1 -0
- package/src/web/layout/Sidebar.tsx +35 -23
- package/src/web/layout/SidebarMobileHeader.stories.tsx +20 -0
- package/src/web/layout/SidebarMobileHeader.tsx +8 -3
- package/dist/chunk-AOBKWDGW.js.map +0 -1
package/package.json
CHANGED
|
@@ -124,7 +124,11 @@ export function Button({
|
|
|
124
124
|
icon != null && typeof icon !== "boolean" ? icon : undefined;
|
|
125
125
|
const hasIcon = showIcon || icon === true || customIcon != null;
|
|
126
126
|
const resolvedContainerClassName = [
|
|
127
|
-
Platform.OS === "web"
|
|
127
|
+
Platform.OS === "web"
|
|
128
|
+
? hasIcon
|
|
129
|
+
? "max-md:pl-4! pr-2!"
|
|
130
|
+
: "max-md:px-4!"
|
|
131
|
+
: "",
|
|
128
132
|
className,
|
|
129
133
|
]
|
|
130
134
|
.filter(Boolean)
|
|
@@ -175,12 +175,15 @@ export function PhoneInput({
|
|
|
175
175
|
const styles = StyleSheet.create({
|
|
176
176
|
wrapper: {
|
|
177
177
|
width: "100%",
|
|
178
|
+
minWidth: 0,
|
|
178
179
|
gap: spacing.sm,
|
|
179
180
|
},
|
|
180
181
|
row: {
|
|
181
182
|
flexDirection: "row",
|
|
182
183
|
alignItems: "center",
|
|
183
184
|
gap: INPUT_ICON_GAP,
|
|
185
|
+
minWidth: 0,
|
|
186
|
+
width: "100%",
|
|
184
187
|
},
|
|
185
188
|
countryOutline: {
|
|
186
189
|
flexShrink: 0,
|
|
@@ -190,9 +193,11 @@ const styles = StyleSheet.create({
|
|
|
190
193
|
},
|
|
191
194
|
phoneOutline: {
|
|
192
195
|
flex: 1,
|
|
196
|
+
minWidth: 0,
|
|
193
197
|
},
|
|
194
198
|
phoneField: {
|
|
195
199
|
flex: 1,
|
|
200
|
+
minWidth: 0,
|
|
196
201
|
},
|
|
197
202
|
error: {
|
|
198
203
|
fontSize: 12,
|
|
@@ -148,7 +148,7 @@ export function Drawer({
|
|
|
148
148
|
type="button"
|
|
149
149
|
aria-label={closeAriaLabel}
|
|
150
150
|
className={cn(
|
|
151
|
-
'absolute inset-0 bg-storm-gray-850/40 backdrop-blur-[12px] transition-opacity duration-300 ease-in-out',
|
|
151
|
+
'absolute inset-0 z-0 cursor-pointer bg-storm-gray-850/40 backdrop-blur-[12px] transition-opacity duration-300 ease-in-out',
|
|
152
152
|
isAnimatedIn ? 'opacity-100' : 'opacity-0',
|
|
153
153
|
)}
|
|
154
154
|
onClick={onClose}
|
|
@@ -160,7 +160,7 @@ export function Drawer({
|
|
|
160
160
|
aria-labelledby="homepad-drawer-title"
|
|
161
161
|
{...(subtitle ? { 'aria-describedby': 'homepad-drawer-subtitle' } : {})}
|
|
162
162
|
className={cn(
|
|
163
|
-
'relative z-10 flex h-[calc(100dvh-16px)] min-h-0 flex-col overflow-hidden rounded-[16px] bg-white shadow-2xl transition-transform duration-300 ease-in-out max-md:h-dvh max-md:rounded-none',
|
|
163
|
+
'relative z-10 flex h-[calc(100dvh-16px)] min-h-0 cursor-auto flex-col overflow-hidden rounded-[16px] bg-white shadow-2xl transition-transform duration-300 ease-in-out max-md:h-dvh max-md:rounded-none',
|
|
164
164
|
// Keep GPU transform only while animating — settled translate-x-0 blurs text.
|
|
165
165
|
!isTransformSettled && 'will-change-transform',
|
|
166
166
|
isAnimatedIn
|
|
@@ -42,6 +42,7 @@ export type PaginationProps = {
|
|
|
42
42
|
*/
|
|
43
43
|
variant?: PaginationVariant;
|
|
44
44
|
className?: string;
|
|
45
|
+
/** Hide the entire bar when there is only one page. Page buttons are always hidden in that case. */
|
|
45
46
|
hideOnSinglePage?: boolean;
|
|
46
47
|
};
|
|
47
48
|
|
|
@@ -64,15 +65,16 @@ export function Pagination({
|
|
|
64
65
|
const visiblePages = getVisiblePageNumbers(safePage, totalPages, maxPageButtons);
|
|
65
66
|
const canGoPrevious = safePage > 1;
|
|
66
67
|
const canGoNext = safePage < totalPages;
|
|
68
|
+
const showPageControls = totalPages > 1;
|
|
67
69
|
|
|
68
|
-
if (hideOnSinglePage &&
|
|
70
|
+
if (hideOnSinglePage && !showPageControls) {
|
|
69
71
|
return null;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
return (
|
|
73
75
|
<div
|
|
74
76
|
className={cn(
|
|
75
|
-
'flex flex-col gap-4 px-4 pb-4 pt-4 font-[family-name:var(--font-sans)] sm:flex-row sm:items-center sm:justify-between',
|
|
77
|
+
'flex flex-col items-center gap-4 px-4 pb-4 pt-4 text-center font-[family-name:var(--font-sans)] sm:flex-row sm:items-center sm:justify-between sm:text-left',
|
|
76
78
|
variant === 'standalone' && cn('rounded-xl border bg-storm-gray-0', 'border-storm-gray-50'),
|
|
77
79
|
dataTableClassNames.pagination,
|
|
78
80
|
className,
|
|
@@ -82,66 +84,68 @@ export function Pagination({
|
|
|
82
84
|
{mergedLabels.showing({ start, end, total })}
|
|
83
85
|
</p>
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
<button
|
|
90
|
-
type="button"
|
|
91
|
-
onClick={() => onPageChange(safePage - 1)}
|
|
92
|
-
disabled={!canGoPrevious}
|
|
93
|
-
aria-label={mergedLabels.previousPage}
|
|
94
|
-
className={cn(
|
|
95
|
-
paginationControlClassName,
|
|
96
|
-
canGoPrevious ? paginationHoverClassName : 'cursor-not-allowed opacity-40',
|
|
97
|
-
)}
|
|
87
|
+
{showPageControls ? (
|
|
88
|
+
<nav
|
|
89
|
+
aria-label="Pagination"
|
|
90
|
+
className="flex items-center gap-2.5"
|
|
98
91
|
>
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
onClick={() => onPageChange(safePage - 1)}
|
|
95
|
+
disabled={!canGoPrevious}
|
|
96
|
+
aria-label={mergedLabels.previousPage}
|
|
97
|
+
className={cn(
|
|
98
|
+
paginationControlClassName,
|
|
99
|
+
canGoPrevious ? paginationHoverClassName : 'cursor-not-allowed opacity-40',
|
|
100
|
+
)}
|
|
101
|
+
>
|
|
102
|
+
<ChevronLeft
|
|
103
|
+
className="size-3.5 text-storm-gray-500"
|
|
104
|
+
strokeWidth={1.75}
|
|
105
|
+
aria-hidden
|
|
106
|
+
/>
|
|
107
|
+
</button>
|
|
105
108
|
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
{visiblePages.map((pageNumber) => {
|
|
110
|
+
const isActive = pageNumber === safePage;
|
|
108
111
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
112
|
+
return (
|
|
113
|
+
<button
|
|
114
|
+
key={pageNumber}
|
|
115
|
+
type="button"
|
|
116
|
+
onClick={() => onPageChange(pageNumber)}
|
|
117
|
+
aria-label={mergedLabels.page(pageNumber)}
|
|
118
|
+
aria-current={isActive ? 'page' : undefined}
|
|
119
|
+
className={cn(
|
|
120
|
+
paginationControlClassName,
|
|
121
|
+
isActive
|
|
122
|
+
? 'border-storm-gray-50 font-bold text-storm-gray-900'
|
|
123
|
+
: paginationHoverClassName,
|
|
124
|
+
)}
|
|
125
|
+
>
|
|
126
|
+
{pageNumber}
|
|
127
|
+
</button>
|
|
128
|
+
);
|
|
129
|
+
})}
|
|
127
130
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
131
|
+
<button
|
|
132
|
+
type="button"
|
|
133
|
+
onClick={() => onPageChange(safePage + 1)}
|
|
134
|
+
disabled={!canGoNext}
|
|
135
|
+
aria-label={mergedLabels.nextPage}
|
|
136
|
+
className={cn(
|
|
137
|
+
paginationControlClassName,
|
|
138
|
+
canGoNext ? paginationHoverClassName : 'cursor-not-allowed opacity-40',
|
|
139
|
+
)}
|
|
140
|
+
>
|
|
141
|
+
<ChevronRight
|
|
142
|
+
className="size-3.5 text-storm-gray-500"
|
|
143
|
+
strokeWidth={1.75}
|
|
144
|
+
aria-hidden
|
|
145
|
+
/>
|
|
146
|
+
</button>
|
|
147
|
+
</nav>
|
|
148
|
+
) : null}
|
|
145
149
|
</div>
|
|
146
150
|
);
|
|
147
151
|
}
|
|
@@ -86,7 +86,7 @@ export function DataTable({
|
|
|
86
86
|
|
|
87
87
|
return (
|
|
88
88
|
<DataTableClassNamesContext.Provider value={mergedClassNames}>
|
|
89
|
-
<div className={cn('flex flex-col', fillHeight && 'min-h-0 flex-1', mergedClassNames.root)}>
|
|
89
|
+
<div className={cn('flex min-w-0 flex-col', fillHeight && 'min-h-0 flex-1', mergedClassNames.root)}>
|
|
90
90
|
<div
|
|
91
91
|
className={cn(
|
|
92
92
|
tableShellClassName,
|
|
@@ -109,6 +109,7 @@ export function DataTable({
|
|
|
109
109
|
{hasFooter ? (
|
|
110
110
|
<div
|
|
111
111
|
className={cn(
|
|
112
|
+
'shrink-0',
|
|
112
113
|
tableFooterShellClassName,
|
|
113
114
|
tableBorderClassName,
|
|
114
115
|
mergedClassNames.footer,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
type SidebarCollapseIconProps = {
|
|
2
|
+
className?: string;
|
|
3
|
+
size?: number | string;
|
|
4
|
+
strokeWidth?: number | string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/** Shown when the sidebar is open — click collapses it. */
|
|
8
|
+
export function SidebarCollapseIcon({
|
|
9
|
+
className,
|
|
10
|
+
size = 20,
|
|
11
|
+
strokeWidth = 2,
|
|
12
|
+
}: SidebarCollapseIconProps) {
|
|
13
|
+
return (
|
|
14
|
+
<svg
|
|
15
|
+
width={size}
|
|
16
|
+
height={size}
|
|
17
|
+
viewBox="0 0 20 20"
|
|
18
|
+
fill="none"
|
|
19
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
20
|
+
aria-hidden
|
|
21
|
+
className={className}
|
|
22
|
+
>
|
|
23
|
+
<path
|
|
24
|
+
d="M12.5 2.5V17.5M8.33333 12.5L5.83333 10L8.33333 7.5M4.16667 2.5H15.8333C16.7538 2.5 17.5 3.24619 17.5 4.16667V15.8333C17.5 16.7538 16.7538 17.5 15.8333 17.5H4.16667C3.24619 17.5 2.5 16.7538 2.5 15.8333V4.16667C2.5 3.24619 3.24619 2.5 4.16667 2.5Z"
|
|
25
|
+
stroke="currentColor"
|
|
26
|
+
strokeWidth={strokeWidth}
|
|
27
|
+
strokeLinecap="round"
|
|
28
|
+
strokeLinejoin="round"
|
|
29
|
+
/>
|
|
30
|
+
</svg>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
type SidebarExpandIconProps = {
|
|
2
|
+
className?: string;
|
|
3
|
+
size?: number | string;
|
|
4
|
+
strokeWidth?: number | string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/** Shown when the sidebar is closed — click expands it. */
|
|
8
|
+
export function SidebarExpandIcon({
|
|
9
|
+
className,
|
|
10
|
+
size = 20,
|
|
11
|
+
strokeWidth = 2,
|
|
12
|
+
}: SidebarExpandIconProps) {
|
|
13
|
+
return (
|
|
14
|
+
<svg
|
|
15
|
+
width={size}
|
|
16
|
+
height={size}
|
|
17
|
+
viewBox="0 0 20 20"
|
|
18
|
+
fill="none"
|
|
19
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
20
|
+
aria-hidden
|
|
21
|
+
className={className}
|
|
22
|
+
>
|
|
23
|
+
<path
|
|
24
|
+
d="M12.5 2.5V17.5M6.66667 7.5L9.16667 10L6.66667 12.5M4.16667 2.5H15.8333C16.7538 2.5 17.5 3.24619 17.5 4.16667V15.8333C17.5 16.7538 16.7538 17.5 15.8333 17.5H4.16667C3.24619 17.5 2.5 16.7538 2.5 15.8333V4.16667C2.5 3.24619 3.24619 2.5 4.16667 2.5Z"
|
|
25
|
+
stroke="currentColor"
|
|
26
|
+
strokeWidth={strokeWidth}
|
|
27
|
+
strokeLinecap="round"
|
|
28
|
+
strokeLinejoin="round"
|
|
29
|
+
/>
|
|
30
|
+
</svg>
|
|
31
|
+
);
|
|
32
|
+
}
|
package/src/web/index.ts
CHANGED
|
@@ -23,6 +23,8 @@ export type { SidebarUserCardProps } from './layout/SidebarUserCard';
|
|
|
23
23
|
|
|
24
24
|
export { BellIcon } from './icons/BellIcon';
|
|
25
25
|
export { BuildingIcon } from './icons/BuildingIcon';
|
|
26
|
+
export { SidebarCollapseIcon } from './icons/SidebarCollapseIcon';
|
|
27
|
+
export { SidebarExpandIcon } from './icons/SidebarExpandIcon';
|
|
26
28
|
export { TableMoreIcon } from './icons/TableMoreIcon';
|
|
27
29
|
export { TableSortIcon } from './icons/TableSortIcon';
|
|
28
30
|
export { TableMenuCheckIcon } from './icons/TableMenuCheckIcon';
|
|
@@ -43,6 +43,7 @@ function DashboardLayoutDemo() {
|
|
|
43
43
|
user: storyUser,
|
|
44
44
|
mobileOpen: mobileMenuOpen,
|
|
45
45
|
onMobileClose: () => setMobileMenuOpen(false),
|
|
46
|
+
onMobileOpen: () => setMobileMenuOpen(true),
|
|
46
47
|
onLogout: fn(),
|
|
47
48
|
onNavItemClick: () => setMobileMenuOpen(false),
|
|
48
49
|
}}
|
|
@@ -63,7 +64,7 @@ function DashboardLayoutDemo() {
|
|
|
63
64
|
buildingOptions: storyBuildingOptions,
|
|
64
65
|
selectedBuildingId,
|
|
65
66
|
onBuildingChange: setSelectedBuildingId,
|
|
66
|
-
className: 'hidden
|
|
67
|
+
className: 'hidden lg:flex',
|
|
67
68
|
searchValue: searchQuery,
|
|
68
69
|
onSearchChange: setSearchQuery,
|
|
69
70
|
onNotificationsClick: fn(),
|
|
@@ -27,14 +27,14 @@ export function DashboardLayout({
|
|
|
27
27
|
mainClassName,
|
|
28
28
|
}: DashboardLayoutProps) {
|
|
29
29
|
return (
|
|
30
|
-
<div className={cn('flex h-screen overflow-hidden flex-row bg-storm-gray-0', className)}>
|
|
31
|
-
<div className="h-screen shrink-0">
|
|
30
|
+
<div className={cn('flex h-screen min-w-0 overflow-hidden flex-row bg-storm-gray-0', className)}>
|
|
31
|
+
<div className="max-md:contents md:h-screen md:shrink-0">
|
|
32
32
|
<Sidebar {...sidebar} />
|
|
33
33
|
</div>
|
|
34
34
|
<div className="flex h-screen min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
|
35
35
|
<SidebarMobileHeader {...mobileHeader} />
|
|
36
36
|
<AppHeader {...header} />
|
|
37
|
-
<main className={mainClassName ?? 'min-h-0 flex-1 overflow-y-auto p-4
|
|
37
|
+
<main className={mainClassName ?? 'min-h-0 min-w-0 flex-1 overflow-x-hidden overflow-y-auto p-4 xl:p-6'}>{children}</main>
|
|
38
38
|
</div>
|
|
39
39
|
</div>
|
|
40
40
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { PanelLeftClose, PanelLeftOpen, X } from 'lucide-react';
|
|
2
1
|
import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';
|
|
3
2
|
|
|
3
|
+
import { SidebarCollapseIcon } from '../icons/SidebarCollapseIcon';
|
|
4
|
+
import { SidebarExpandIcon } from '../icons/SidebarExpandIcon';
|
|
4
5
|
import { useMediaQuery } from '../hooks/useMediaQuery';
|
|
5
6
|
import { cn } from '../utils/cn';
|
|
6
7
|
import { isNavItemActive } from './nav-active';
|
|
@@ -24,6 +25,7 @@ export type SidebarProps = {
|
|
|
24
25
|
className?: string;
|
|
25
26
|
mobileOpen: boolean;
|
|
26
27
|
onMobileClose?: () => void;
|
|
28
|
+
onMobileOpen?: () => void;
|
|
27
29
|
/**
|
|
28
30
|
* Host-owned navigation. Call `navigate(item.to)` from the app router here.
|
|
29
31
|
* The sidebar never imports react-router, so this must perform the route change.
|
|
@@ -66,6 +68,7 @@ export function Sidebar({
|
|
|
66
68
|
className,
|
|
67
69
|
mobileOpen,
|
|
68
70
|
onMobileClose,
|
|
71
|
+
onMobileOpen,
|
|
69
72
|
onNavItemClick,
|
|
70
73
|
collapseIcon,
|
|
71
74
|
expandIcon,
|
|
@@ -74,13 +77,15 @@ export function Sidebar({
|
|
|
74
77
|
collapsed,
|
|
75
78
|
onCollapsedChange,
|
|
76
79
|
}: SidebarProps) {
|
|
77
|
-
const isDesktop = useMediaQuery('(min-width:
|
|
80
|
+
const isDesktop = useMediaQuery('(min-width: 1024px)');
|
|
81
|
+
const isRailLayout = useMediaQuery('(min-width: 768px)');
|
|
78
82
|
const [internalCollapsed, setInternalCollapsed] = useState(() =>
|
|
79
83
|
readInitialCollapsedState(sidebarStorageKey, initialCollapsed),
|
|
80
84
|
);
|
|
81
85
|
|
|
82
86
|
const isCollapsed = collapsed ?? internalCollapsed;
|
|
83
|
-
const isExpanded = isDesktop ? !isCollapsed :
|
|
87
|
+
const isExpanded = isDesktop ? !isCollapsed : mobileOpen;
|
|
88
|
+
const showCollapsedRail = isRailLayout && !isDesktop;
|
|
84
89
|
|
|
85
90
|
const setCollapsed = useCallback(
|
|
86
91
|
(next: boolean) => {
|
|
@@ -105,8 +110,14 @@ export function Sidebar({
|
|
|
105
110
|
return;
|
|
106
111
|
}
|
|
107
112
|
|
|
108
|
-
|
|
109
|
-
|
|
113
|
+
if (mobileOpen) {
|
|
114
|
+
onMobileClose?.();
|
|
115
|
+
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
onMobileOpen?.();
|
|
120
|
+
}, [isDesktop, mobileOpen, onMobileClose, onMobileOpen, toggleCollapsed]);
|
|
110
121
|
|
|
111
122
|
const handleNavItemNavigate = useCallback(
|
|
112
123
|
(item: AdminNavItem) => {
|
|
@@ -156,21 +167,28 @@ export function Sidebar({
|
|
|
156
167
|
aria-label={labels.closeMenu}
|
|
157
168
|
onClick={onMobileClose}
|
|
158
169
|
className={cn(
|
|
159
|
-
'fixed inset-0 z-40 bg-navy-800/60 backdrop-blur-[1px] transition-opacity duration-300 ease-in-out
|
|
170
|
+
'fixed inset-0 z-40 bg-navy-800/60 backdrop-blur-[1px] transition-opacity duration-300 ease-in-out lg:hidden',
|
|
160
171
|
mobileOpen ? 'opacity-100' : 'pointer-events-none opacity-0',
|
|
161
172
|
)}
|
|
162
173
|
/>
|
|
163
174
|
|
|
175
|
+
{showCollapsedRail && mobileOpen ? (
|
|
176
|
+
<div className="h-screen w-[76px] shrink-0" aria-hidden />
|
|
177
|
+
) : null}
|
|
178
|
+
|
|
164
179
|
<aside
|
|
165
180
|
className={cn(
|
|
166
181
|
'fixed inset-y-0 left-0 z-50 flex h-screen w-[min(300px,85vw)] flex-col gap-8 overflow-hidden bg-navy p-4 shadow-xl',
|
|
167
182
|
'transition-[transform,width,padding,gap] duration-300 ease-in-out will-change-[transform,width]',
|
|
168
|
-
mobileOpen ? 'translate-x-0' : '-translate-x-full',
|
|
169
|
-
'md:relative md:z-auto md:w-[
|
|
170
|
-
|
|
183
|
+
mobileOpen ? 'translate-x-0' : '-translate-x-full pointer-events-none',
|
|
184
|
+
'md:relative md:z-auto md:w-[76px] md:translate-x-0 md:pointer-events-auto md:shadow-none md:items-center md:gap-2 md:px-3 md:py-4',
|
|
185
|
+
mobileOpen &&
|
|
186
|
+
'md:fixed md:z-50 md:w-[min(300px,85vw)] md:items-stretch md:gap-8 md:p-4 md:shadow-xl',
|
|
187
|
+
'lg:relative lg:z-auto lg:translate-x-0 lg:pointer-events-auto lg:shadow-none',
|
|
188
|
+
isExpanded ? 'lg:w-[300px] lg:items-stretch lg:gap-8 lg:p-4' : 'lg:w-[76px] lg:items-center lg:gap-2 lg:px-3 lg:py-4',
|
|
171
189
|
className,
|
|
172
190
|
)}
|
|
173
|
-
aria-hidden={!isDesktop && !mobileOpen}
|
|
191
|
+
aria-hidden={!isDesktop && !mobileOpen && !showCollapsedRail}
|
|
174
192
|
>
|
|
175
193
|
<div
|
|
176
194
|
className={cn(
|
|
@@ -199,22 +217,16 @@ export function Sidebar({
|
|
|
199
217
|
onClick={handleHeaderAction}
|
|
200
218
|
className="shrink-0 cursor-pointer text-white transition-[opacity,transform] duration-200 hover:opacity-80 active:scale-95"
|
|
201
219
|
aria-label={
|
|
202
|
-
|
|
203
|
-
?
|
|
220
|
+
isExpanded
|
|
221
|
+
? isDesktop
|
|
204
222
|
? labels.collapse
|
|
205
|
-
: labels.
|
|
206
|
-
: labels.
|
|
223
|
+
: labels.closeMenu
|
|
224
|
+
: labels.expand
|
|
207
225
|
}
|
|
208
226
|
>
|
|
209
|
-
{
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
) : (
|
|
213
|
-
expandIcon ?? <PanelLeftOpen size={20} strokeWidth={1.75} />
|
|
214
|
-
)
|
|
215
|
-
) : (
|
|
216
|
-
closeIcon ?? <X size={20} strokeWidth={1.75} />
|
|
217
|
-
)}
|
|
227
|
+
{isExpanded
|
|
228
|
+
? (collapseIcon ?? closeIcon ?? <SidebarCollapseIcon />)
|
|
229
|
+
: (expandIcon ?? <SidebarExpandIcon />)}
|
|
218
230
|
</button>
|
|
219
231
|
</div>
|
|
220
232
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
2
|
import { fn } from '@storybook/test';
|
|
3
3
|
|
|
4
|
+
import { BellIcon } from '../icons/BellIcon';
|
|
4
5
|
import { WebLayoutCanvas } from './story-helpers';
|
|
5
6
|
import { SidebarMobileHeader } from './SidebarMobileHeader';
|
|
6
7
|
import { storyBranding, storyLabels, storyLogoIconDarkSrc } from './story-fixtures';
|
|
@@ -45,3 +46,22 @@ export const MenuOpen: Story = {
|
|
|
45
46
|
</WebLayoutCanvas>
|
|
46
47
|
),
|
|
47
48
|
};
|
|
49
|
+
|
|
50
|
+
export const WithEndAction: Story = {
|
|
51
|
+
args: {
|
|
52
|
+
end: (
|
|
53
|
+
<button
|
|
54
|
+
type="button"
|
|
55
|
+
className="relative flex size-11 shrink-0 items-center justify-center rounded-xl border border-storm-gray-50 text-navy"
|
|
56
|
+
aria-label="Notifications"
|
|
57
|
+
>
|
|
58
|
+
<BellIcon className="text-navy" />
|
|
59
|
+
</button>
|
|
60
|
+
),
|
|
61
|
+
},
|
|
62
|
+
render: (args) => (
|
|
63
|
+
<WebLayoutCanvas>
|
|
64
|
+
<SidebarMobileHeader {...args} />
|
|
65
|
+
</WebLayoutCanvas>
|
|
66
|
+
),
|
|
67
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
+
import { SidebarExpandIcon } from '../icons/SidebarExpandIcon';
|
|
3
4
|
import { cn } from '../utils/cn';
|
|
4
5
|
import type { AdminSidebarBranding } from './types';
|
|
5
6
|
|
|
@@ -9,6 +10,8 @@ export type SidebarMobileHeaderProps = {
|
|
|
9
10
|
onOpenMenu: () => void;
|
|
10
11
|
menuOpen?: boolean;
|
|
11
12
|
className?: string;
|
|
13
|
+
/** Rendered on the right side of the mobile header (e.g. notifications). */
|
|
14
|
+
end?: ReactNode;
|
|
12
15
|
};
|
|
13
16
|
|
|
14
17
|
export function SidebarMobileHeader({
|
|
@@ -17,6 +20,7 @@ export function SidebarMobileHeader({
|
|
|
17
20
|
onOpenMenu,
|
|
18
21
|
menuOpen = false,
|
|
19
22
|
className,
|
|
23
|
+
end,
|
|
20
24
|
}: SidebarMobileHeaderProps) {
|
|
21
25
|
return (
|
|
22
26
|
<header
|
|
@@ -32,9 +36,9 @@ export function SidebarMobileHeader({
|
|
|
32
36
|
aria-label={openMenuLabel}
|
|
33
37
|
aria-expanded={menuOpen}
|
|
34
38
|
>
|
|
35
|
-
<
|
|
39
|
+
<SidebarExpandIcon size={22} />
|
|
36
40
|
</button>
|
|
37
|
-
<div className="flex min-w-0 items-center gap-2">
|
|
41
|
+
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
38
42
|
<img src={branding.logoIconSrc} alt="" className="h-[30px] w-[42px] shrink-0" />
|
|
39
43
|
<div className="min-w-0 leading-none">
|
|
40
44
|
<p className="truncate text-base font-bold tracking-tight text-navy">{branding.logoTitle}</p>
|
|
@@ -43,6 +47,7 @@ export function SidebarMobileHeader({
|
|
|
43
47
|
</p>
|
|
44
48
|
</div>
|
|
45
49
|
</div>
|
|
50
|
+
{end ? <div className="shrink-0">{end}</div> : null}
|
|
46
51
|
</header>
|
|
47
52
|
);
|
|
48
53
|
}
|