@scripso-homepad/ui 0.4.36 → 0.4.38
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 +25 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +26 -12
- package/dist/index.js.map +1 -1
- package/dist/web/index.cjs +55 -41
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts +6 -2
- package/dist/web/index.d.ts +6 -2
- package/dist/web/index.js +56 -42
- 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/components/Select.tsx +1 -0
- package/src/theme/select.ts +9 -1
- 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/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 +33 -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,
|
package/src/theme/select.ts
CHANGED
|
@@ -98,7 +98,10 @@ export function getSelectFieldStyles(state: InputVisualState): SelectFieldStyleS
|
|
|
98
98
|
borderColor: colors.stormGray50,
|
|
99
99
|
backgroundColor: colors.stormGray50,
|
|
100
100
|
},
|
|
101
|
-
value: {
|
|
101
|
+
value: {
|
|
102
|
+
...valueBase,
|
|
103
|
+
color: colors.stormGray300,
|
|
104
|
+
},
|
|
102
105
|
placeholder: colors.stormGray300,
|
|
103
106
|
icon: colors.stormGray150,
|
|
104
107
|
};
|
|
@@ -155,6 +158,7 @@ export const selectFieldMetrics = StyleSheet.create({
|
|
|
155
158
|
...(Platform.OS === "web"
|
|
156
159
|
? ({
|
|
157
160
|
boxSizing: "border-box",
|
|
161
|
+
cursor: "pointer",
|
|
158
162
|
} as ViewStyle)
|
|
159
163
|
: null),
|
|
160
164
|
},
|
|
@@ -169,6 +173,7 @@ export const selectFieldMetrics = StyleSheet.create({
|
|
|
169
173
|
...(Platform.OS === "web"
|
|
170
174
|
? ({
|
|
171
175
|
outlineStyle: "none",
|
|
176
|
+
cursor: "pointer",
|
|
172
177
|
} as TextStyle)
|
|
173
178
|
: null),
|
|
174
179
|
},
|
|
@@ -182,6 +187,7 @@ export const selectDropdownMetrics = StyleSheet.create({
|
|
|
182
187
|
backgroundColor: colors.white,
|
|
183
188
|
padding: SELECT_DROPDOWN_PADDING,
|
|
184
189
|
overflow: "hidden",
|
|
190
|
+
...(Platform.OS === "web" ? ({ cursor: "pointer" } as ViewStyle) : null),
|
|
185
191
|
},
|
|
186
192
|
option: {
|
|
187
193
|
width: "100%",
|
|
@@ -194,6 +200,7 @@ export const selectDropdownMetrics = StyleSheet.create({
|
|
|
194
200
|
paddingLeft: spacing.sm,
|
|
195
201
|
justifyContent: "center",
|
|
196
202
|
alignItems: "flex-start",
|
|
203
|
+
...(Platform.OS === "web" ? ({ cursor: "pointer" } as ViewStyle) : null),
|
|
197
204
|
},
|
|
198
205
|
optionLabel: {
|
|
199
206
|
fontFamily: fonts.sans,
|
|
@@ -204,6 +211,7 @@ export const selectDropdownMetrics = StyleSheet.create({
|
|
|
204
211
|
textAlign: "left",
|
|
205
212
|
color: colors.slateBlue,
|
|
206
213
|
width: "100%",
|
|
214
|
+
...(Platform.OS === "web" ? ({ cursor: "pointer" } as TextStyle) : null),
|
|
207
215
|
},
|
|
208
216
|
optionLabelSelected: {
|
|
209
217
|
fontWeight: fontWeight.medium,
|
|
@@ -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,
|
|
@@ -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,4 +1,3 @@
|
|
|
1
|
-
import { X } from 'lucide-react';
|
|
2
1
|
import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';
|
|
3
2
|
|
|
4
3
|
import { SidebarCollapseIcon } from '../icons/SidebarCollapseIcon';
|
|
@@ -26,6 +25,7 @@ export type SidebarProps = {
|
|
|
26
25
|
className?: string;
|
|
27
26
|
mobileOpen: boolean;
|
|
28
27
|
onMobileClose?: () => void;
|
|
28
|
+
onMobileOpen?: () => void;
|
|
29
29
|
/**
|
|
30
30
|
* Host-owned navigation. Call `navigate(item.to)` from the app router here.
|
|
31
31
|
* The sidebar never imports react-router, so this must perform the route change.
|
|
@@ -68,6 +68,7 @@ export function Sidebar({
|
|
|
68
68
|
className,
|
|
69
69
|
mobileOpen,
|
|
70
70
|
onMobileClose,
|
|
71
|
+
onMobileOpen,
|
|
71
72
|
onNavItemClick,
|
|
72
73
|
collapseIcon,
|
|
73
74
|
expandIcon,
|
|
@@ -76,13 +77,15 @@ export function Sidebar({
|
|
|
76
77
|
collapsed,
|
|
77
78
|
onCollapsedChange,
|
|
78
79
|
}: SidebarProps) {
|
|
79
|
-
const isDesktop = useMediaQuery('(min-width:
|
|
80
|
+
const isDesktop = useMediaQuery('(min-width: 1024px)');
|
|
81
|
+
const isRailLayout = useMediaQuery('(min-width: 768px)');
|
|
80
82
|
const [internalCollapsed, setInternalCollapsed] = useState(() =>
|
|
81
83
|
readInitialCollapsedState(sidebarStorageKey, initialCollapsed),
|
|
82
84
|
);
|
|
83
85
|
|
|
84
86
|
const isCollapsed = collapsed ?? internalCollapsed;
|
|
85
|
-
const isExpanded = isDesktop ? !isCollapsed :
|
|
87
|
+
const isExpanded = isDesktop ? !isCollapsed : mobileOpen;
|
|
88
|
+
const showCollapsedRail = isRailLayout && !isDesktop;
|
|
86
89
|
|
|
87
90
|
const setCollapsed = useCallback(
|
|
88
91
|
(next: boolean) => {
|
|
@@ -107,8 +110,14 @@ export function Sidebar({
|
|
|
107
110
|
return;
|
|
108
111
|
}
|
|
109
112
|
|
|
110
|
-
|
|
111
|
-
|
|
113
|
+
if (mobileOpen) {
|
|
114
|
+
onMobileClose?.();
|
|
115
|
+
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
onMobileOpen?.();
|
|
120
|
+
}, [isDesktop, mobileOpen, onMobileClose, onMobileOpen, toggleCollapsed]);
|
|
112
121
|
|
|
113
122
|
const handleNavItemNavigate = useCallback(
|
|
114
123
|
(item: AdminNavItem) => {
|
|
@@ -158,21 +167,28 @@ export function Sidebar({
|
|
|
158
167
|
aria-label={labels.closeMenu}
|
|
159
168
|
onClick={onMobileClose}
|
|
160
169
|
className={cn(
|
|
161
|
-
'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',
|
|
162
171
|
mobileOpen ? 'opacity-100' : 'pointer-events-none opacity-0',
|
|
163
172
|
)}
|
|
164
173
|
/>
|
|
165
174
|
|
|
175
|
+
{showCollapsedRail && mobileOpen ? (
|
|
176
|
+
<div className="h-screen w-[76px] shrink-0" aria-hidden />
|
|
177
|
+
) : null}
|
|
178
|
+
|
|
166
179
|
<aside
|
|
167
180
|
className={cn(
|
|
168
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',
|
|
169
182
|
'transition-[transform,width,padding,gap] duration-300 ease-in-out will-change-[transform,width]',
|
|
170
|
-
mobileOpen ? 'translate-x-0' : '-translate-x-full',
|
|
171
|
-
'md:relative md:z-auto md:w-[
|
|
172
|
-
|
|
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',
|
|
173
189
|
className,
|
|
174
190
|
)}
|
|
175
|
-
aria-hidden={!isDesktop && !mobileOpen}
|
|
191
|
+
aria-hidden={!isDesktop && !mobileOpen && !showCollapsedRail}
|
|
176
192
|
>
|
|
177
193
|
<div
|
|
178
194
|
className={cn(
|
|
@@ -201,22 +217,16 @@ export function Sidebar({
|
|
|
201
217
|
onClick={handleHeaderAction}
|
|
202
218
|
className="shrink-0 cursor-pointer text-white transition-[opacity,transform] duration-200 hover:opacity-80 active:scale-95"
|
|
203
219
|
aria-label={
|
|
204
|
-
|
|
205
|
-
?
|
|
220
|
+
isExpanded
|
|
221
|
+
? isDesktop
|
|
206
222
|
? labels.collapse
|
|
207
|
-
: labels.
|
|
208
|
-
: labels.
|
|
223
|
+
: labels.closeMenu
|
|
224
|
+
: labels.expand
|
|
209
225
|
}
|
|
210
226
|
>
|
|
211
|
-
{
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
) : (
|
|
215
|
-
expandIcon ?? <SidebarExpandIcon />
|
|
216
|
-
)
|
|
217
|
-
) : (
|
|
218
|
-
closeIcon ?? <X size={20} strokeWidth={1.75} />
|
|
219
|
-
)}
|
|
227
|
+
{isExpanded
|
|
228
|
+
? (collapseIcon ?? closeIcon ?? <SidebarCollapseIcon />)
|
|
229
|
+
: (expandIcon ?? <SidebarExpandIcon />)}
|
|
220
230
|
</button>
|
|
221
231
|
</div>
|
|
222
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
|
}
|