@mohasinac/appkit 4.42.0 → 4.42.1
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/_internal/client/features/layout/DashboardLayoutClient.js +2 -30
- package/dist/features/layout/AppLayoutShell.js +2 -2
- package/dist/features/layout/BottomNavLayout.d.ts +9 -3
- package/dist/features/layout/BottomNavLayout.js +9 -3
- package/dist/features/tester/seed-data/authored/buying__user-dashboard-navigation.js +6 -5
- package/dist/features/tester/seed-data/authored/design-ux__hand-mode-layout.js +3 -3
- package/dist/features/tester/seed-data/authored/selling__store-dashboard-navigation.js +6 -5
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +3 -3
- package/package.json +1 -1
|
@@ -34,8 +34,7 @@ import { filterNavItems } from "./filterNavItems";
|
|
|
34
34
|
import { useSiteSettings } from "../../../../core/hooks/useSiteSettings";
|
|
35
35
|
import { useTheme } from "../../theme";
|
|
36
36
|
import { useHandMode } from "../../hand-mode";
|
|
37
|
-
import { BackgroundRenderer, Div,
|
|
38
|
-
import { BottomNavLayout } from "../../../../features/layout/BottomNavLayout";
|
|
37
|
+
import { BackgroundRenderer, Div, Nav } from "../../../../ui";
|
|
39
38
|
import { useToast } from "../../../../ui/components/Toast";
|
|
40
39
|
import { useSession } from "../../../../react/contexts/SessionContext";
|
|
41
40
|
import { ROUTES } from "../../../../next/routing/route-map";
|
|
@@ -147,33 +146,6 @@ function filterGroups(groups, navConfig, permissions) {
|
|
|
147
146
|
}))
|
|
148
147
|
.filter((group) => group.items.length > 0);
|
|
149
148
|
}
|
|
150
|
-
const MAX_BOTTOM_NAV_ITEMS = 5;
|
|
151
|
-
/**
|
|
152
|
-
* Mobile bottom tab bar (<lg) for the dashboard shells. Admin/store/user
|
|
153
|
-
* previously had no bottom-nav — this was the source of "hard to navigate
|
|
154
|
-
* on mobile" (the sidebar's only mobile affordance was an off-canvas drawer
|
|
155
|
-
* behind a hamburger toggle). Pattern lifted from the unused
|
|
156
|
-
* `DashboardScaffold` scaffold, which has since been retired in favour of
|
|
157
|
-
* wiring it directly here where the sidebars already live.
|
|
158
|
-
*
|
|
159
|
-
* Renders through the shared `<BottomNavLayout>` rather than hand-rolling its
|
|
160
|
-
* own `fixed bottom-0` chrome. That is not cosmetic: `BottomNavLayout` is what
|
|
161
|
-
* publishes `--bottom-nav-height`, which every bottom-anchored surface above it
|
|
162
|
-
* (the CTA tier, the pagination bar, BackToTop, the footer's clearance) offsets
|
|
163
|
-
* against. The hand-rolled version published nothing, so on dashboard routes
|
|
164
|
-
* that whole stack was positioned against the PUBLIC nav's height instead —
|
|
165
|
-
* which was itself a bug, since both bars were rendering on the same pixels.
|
|
166
|
-
* `AppLayoutShell` is now told to suppress the public one here via
|
|
167
|
-
* `showBottomNav={!isDashboard}`; exactly one nav must be mounted at a time.
|
|
168
|
-
*/
|
|
169
|
-
function DashboardBottomNav({ items, activeHref }) {
|
|
170
|
-
if (items.length === 0)
|
|
171
|
-
return null;
|
|
172
|
-
return (_jsx(BottomNavLayout, { id: "dashboard-bottom-navbar", ariaLabel: "Dashboard bottom navigation", children: items.slice(0, MAX_BOTTOM_NAV_ITEMS).map((item) => {
|
|
173
|
-
const isActive = activeHref === item.href || activeHref.startsWith(`${item.href}/`);
|
|
174
|
-
return (_jsx(Li, { className: "flex-1", children: _jsxs(Link, { href: item.href, className: `flex h-full flex-col items-center justify-center gap-[var(--appkit-space-0-5)] py-[var(--appkit-space-2)] ${isActive ? "text-[var(--appkit-color-primary)]" : "text-[var(--appkit-color-text-muted)]"}`, "aria-current": isActive ? "page" : undefined, children: [item.icon && _jsx(Span, { size: "base", children: item.icon }), _jsx(Span, { size: "xs", className: "max-w-full truncate", children: item.label })] }) }, item.href));
|
|
175
|
-
}) }));
|
|
176
|
-
}
|
|
177
149
|
// Asymmetric padding clears the persistent sidebar rail — right-heavy by
|
|
178
150
|
// default (rail docks left), left-heavy in left-hand mode (rail docks right).
|
|
179
151
|
const DEFAULT_CONTENT_PADDING_RIGHT = "px-[var(--appkit-space-5)] py-[var(--appkit-space-8)] lg:pl-14 lg:pr-6 xl:pl-16 xl:pr-10";
|
|
@@ -227,5 +199,5 @@ responsive: _responsive, crossNav, className, contentPadding, contentSurface, co
|
|
|
227
199
|
"pb-[var(--appkit-space-16)] lg:pb-[0]", // clears the fixed mobile bottom-nav
|
|
228
200
|
contentPadding ?? (hand === "left" ? DEFAULT_CONTENT_PADDING_LEFT : DEFAULT_CONTENT_PADDING_RIGHT),
|
|
229
201
|
contentSurface,
|
|
230
|
-
].filter(Boolean).join(" "), children: _jsx(Div, { className: ["w-full flex-1 mx-auto", contentMaxWidth ?? DEFAULT_CONTENT_MAX_WIDTH].filter(Boolean).join(" "), children: children }) })
|
|
202
|
+
].filter(Boolean).join(" "), children: _jsx(Div, { className: ["w-full flex-1 mx-auto", contentMaxWidth ?? DEFAULT_CONTENT_MAX_WIDTH].filter(Boolean).join(" "), children: children }) })] }));
|
|
231
203
|
}
|
|
@@ -139,7 +139,7 @@ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarS
|
|
|
139
139
|
const pathname = usePathname();
|
|
140
140
|
useEffect(() => { setSidebarOpen(false); }, [pathname]);
|
|
141
141
|
const { theme, toggleTheme, activeTheme } = useTheme();
|
|
142
|
-
const { closeNav: closeDashboardNav
|
|
142
|
+
const { closeNav: closeDashboardNav } = useDashboardNav();
|
|
143
143
|
const { user: authUser } = useAuth();
|
|
144
144
|
const headerRef = useRef(null);
|
|
145
145
|
useEffect(() => {
|
|
@@ -200,5 +200,5 @@ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarS
|
|
|
200
200
|
// main-to-footer — it never clears the bottom of the page, which is
|
|
201
201
|
// what the fixed nav + tier actually overlap. That reservation now
|
|
202
202
|
// lives on the footer's own padding-bottom.
|
|
203
|
-
className: "w-full flex-1 flex flex-col", children: _jsx(Div, { padding: "y-lg", className: `flex-1 ${contentClassName ?? "mx-auto w-full max-w-screen-xl px-[var(--appkit-space-5)] md:px-[var(--appkit-space-6)] lg:px-[var(--appkit-space-8)]"}`, children: children }) })] }), _jsx(BackToTop, {}), _jsx(FooterLayout, { ...footer }), _jsx(BottomChrome, { children: _jsx(BottomActions, {}) }), showBottomNav && (_jsx(BottomNavbar, { user: user, userId: userId, homeHref: homeHref, shopHref: shopHref, cartHref: cartHref, wishlistHref: wishlistHref, profileHref: profileHref, loginHref: loginHref, onSearchToggle: () => setSearchOpen((prev) => !prev), navItems: navItems, onMoreToggle:
|
|
203
|
+
className: "w-full flex-1 flex flex-col", children: _jsx(Div, { padding: "y-lg", className: `flex-1 ${contentClassName ?? "mx-auto w-full max-w-screen-xl px-[var(--appkit-space-5)] md:px-[var(--appkit-space-6)] lg:px-[var(--appkit-space-8)]"}`, children: children }) })] }), _jsx(BackToTop, {}), _jsx(FooterLayout, { ...footer }), _jsx(BottomChrome, { children: _jsx(BottomActions, {}) }), showBottomNav && (_jsx(BottomNavbar, { user: user, userId: userId, homeHref: homeHref, shopHref: shopHref, cartHref: cartHref, wishlistHref: wishlistHref, profileHref: profileHref, loginHref: loginHref, onSearchToggle: () => setSearchOpen((prev) => !prev), navItems: navItems, onMoreToggle: handleTogglePublicSidebar })), _jsx(UnsavedChangesModal, {})] }) }));
|
|
204
204
|
}
|
|
@@ -18,8 +18,14 @@ export interface BottomNavLayoutProps {
|
|
|
18
18
|
*
|
|
19
19
|
* Hand mode: the item strip carries `appkit-hand-mirror`, so slot order
|
|
20
20
|
* reverses in left-hand mode (CSS-only via `[data-hand]` — applies on the
|
|
21
|
-
* first paint, no flash).
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* first paint, no flash).
|
|
22
|
+
*
|
|
23
|
+
* `BottomNavbar` (`./BottomNavbar.tsx`) is the sole consumer, and it renders
|
|
24
|
+
* unconditionally on every route including admin/store/user dashboards —
|
|
25
|
+
* there is deliberately no second, dashboard-specific bottom bar (removed
|
|
26
|
+
* 2026-09-28; dashboard section navigation lives on the header hamburger
|
|
27
|
+
* instead, via `DashboardNavContext`). Never add a second component that
|
|
28
|
+
* mounts through this shell — exactly one bottom nav may exist per route,
|
|
29
|
+
* since this is the sole publisher of `--bottom-nav-height`.
|
|
24
30
|
*/
|
|
25
31
|
export declare function BottomNavLayout({ ariaLabel, children, id, className, }: BottomNavLayoutProps): React.JSX.Element;
|
|
@@ -16,9 +16,15 @@ import { useVisualViewportInset } from "../../react/hooks/useVisualViewportInset
|
|
|
16
16
|
*
|
|
17
17
|
* Hand mode: the item strip carries `appkit-hand-mirror`, so slot order
|
|
18
18
|
* reverses in left-hand mode (CSS-only via `[data-hand]` — applies on the
|
|
19
|
-
* first paint, no flash).
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* first paint, no flash).
|
|
20
|
+
*
|
|
21
|
+
* `BottomNavbar` (`./BottomNavbar.tsx`) is the sole consumer, and it renders
|
|
22
|
+
* unconditionally on every route including admin/store/user dashboards —
|
|
23
|
+
* there is deliberately no second, dashboard-specific bottom bar (removed
|
|
24
|
+
* 2026-09-28; dashboard section navigation lives on the header hamburger
|
|
25
|
+
* instead, via `DashboardNavContext`). Never add a second component that
|
|
26
|
+
* mounts through this shell — exactly one bottom nav may exist per route,
|
|
27
|
+
* since this is the sole publisher of `--bottom-nav-height`.
|
|
22
28
|
*/
|
|
23
29
|
export function BottomNavLayout({ ariaLabel, children, id = "bottom-navbar", className, }) {
|
|
24
30
|
const isKeyboardOpen = useVisualViewportInset();
|
|
@@ -58,15 +58,16 @@ export const authored = {
|
|
|
58
58
|
steps: [
|
|
59
59
|
"Sign in as rehan.sheikh@gmail.com / TempPass123!.",
|
|
60
60
|
"Resize the browser window to 390 pixels wide and open /user.",
|
|
61
|
-
"
|
|
62
|
-
"
|
|
61
|
+
"Open the sidebar using the header's hamburger icon.",
|
|
62
|
+
"Read its entries, and navigate somewhere.",
|
|
63
63
|
"Check whether it closed on navigation.",
|
|
64
|
-
"Count the fixed bars at the bottom of the screen.",
|
|
64
|
+
"Count the fixed bars at the bottom of the screen and read every one of its slots, including the last ('More').",
|
|
65
|
+
"Tap the bottom bar's 'More' slot and read what it opens.",
|
|
65
66
|
"Try to scroll the page sideways.",
|
|
66
67
|
],
|
|
67
68
|
inputs: { mobileWidth: 390 },
|
|
68
|
-
expectedBehaviour: "
|
|
69
|
-
expectedUiState: "The sidebar
|
|
69
|
+
expectedBehaviour: "BEFORE (fixed 2026-09-28): /user rendered a dashboard-specific bottom bar — up to 5 flattened sidebar links, text-only, no icons — while the public bottom bar was suppressed on this route. AFTER: the dashboard sidebar opens from the header hamburger, exactly as on every dashboard route, and closes after navigating. The bottom tab bar itself is now the SAME shared bar every public page uses (same items, same order, same icons) — not a dashboard-specific one — and its own 'More' slot opens the public site menu, not this sidebar. Exactly one bottom bar is mounted either way, since it is the sole publisher of its own height.",
|
|
70
|
+
expectedUiState: "The header hamburger opens the dashboard sidebar, collapsed by default, closing after navigation. The bottom bar matches the public site's bar item-for-item, and tapping its 'More' slot opens the public menu rather than the dashboard sidebar. The page does not scroll sideways.",
|
|
70
71
|
expectedData: { bottomNavCount: 1 },
|
|
71
72
|
endResult: "Read-only; restore the window width afterwards.",
|
|
72
73
|
},
|
|
@@ -227,12 +227,12 @@ export const authored = {
|
|
|
227
227
|
"Resize the browser window to 390 pixels wide and open /user.",
|
|
228
228
|
"Look at the bottom tab bar and write down its items in order.",
|
|
229
229
|
"Count the bottom bars present.",
|
|
230
|
+
"Compare the item order against the public bar from the sibling case (checklist-design-ux-hand-mode-layout-bottom-nav-mirror, on /).",
|
|
230
231
|
"Turn 'Left-hand mode' off and check both again.",
|
|
231
|
-
"Compare the dashboard's bar against the public one from the previous case.",
|
|
232
232
|
],
|
|
233
233
|
inputs: { leftHandMode: true, mobileWidth: 390 },
|
|
234
|
-
expectedBehaviour: "
|
|
235
|
-
expectedUiState: "
|
|
234
|
+
expectedBehaviour: "BEFORE (fixed 2026-09-28): /user rendered its own dashboard-specific bottom bar, so this case was testing whether a second, separate implementation happened to mirror the same way as the public one. AFTER: /user renders the exact same shared bottom bar component as every public page — there is only one implementation, so hand-mode mirroring is inherited for free rather than needing its own logic. Exactly one bottom bar is mounted.",
|
|
235
|
+
expectedUiState: "The /user bottom bar's items and order match the public bar's from the sibling case, in both hand-mode states, because it is literally the same bar. Exactly one bottom bar is present throughout.",
|
|
236
236
|
expectedData: { bottomNavCount: 1 },
|
|
237
237
|
endResult: "The mode is off again by the last step; restore the window width.",
|
|
238
238
|
},
|
|
@@ -56,15 +56,16 @@ export const authored = {
|
|
|
56
56
|
steps: [
|
|
57
57
|
"Sign in as tyson@beybladearena.in / TempPass123!.",
|
|
58
58
|
"Resize the browser window to 390 pixels wide and open /store.",
|
|
59
|
-
"
|
|
60
|
-
"
|
|
59
|
+
"Open the sidebar using the header's hamburger icon.",
|
|
60
|
+
"Read its entries, and navigate somewhere.",
|
|
61
61
|
"Check whether it closed on navigation.",
|
|
62
|
-
"Count the fixed bars at the bottom of the screen.",
|
|
62
|
+
"Count the fixed bars at the bottom of the screen and read every one of its slots, including the last ('More').",
|
|
63
|
+
"Tap the bottom bar's 'More' slot and read what it opens.",
|
|
63
64
|
"Try to scroll the page sideways.",
|
|
64
65
|
],
|
|
65
66
|
inputs: { mobileWidth: 390 },
|
|
66
|
-
expectedBehaviour: "
|
|
67
|
-
expectedUiState: "The sidebar
|
|
67
|
+
expectedBehaviour: "BEFORE (fixed 2026-09-28): /store rendered a dashboard-specific bottom bar — up to 5 flattened sidebar links, text-only, no icons — while the public bottom bar was suppressed on this route. AFTER: the dashboard sidebar opens from the header hamburger, exactly as on every dashboard route, and closes after navigating. The bottom tab bar itself is now the SAME shared bar every public page uses (same items, same order, same icons) — not a dashboard-specific one — and its own 'More' slot opens the public site menu, not this sidebar. Exactly one bottom bar is mounted either way.",
|
|
68
|
+
expectedUiState: "The header hamburger opens the dashboard sidebar, collapsed by default, closing after navigation. The bottom bar matches the public site's bar item-for-item, and tapping its 'More' slot opens the public menu rather than the dashboard sidebar. The page does not scroll sideways.",
|
|
68
69
|
expectedData: { bottomNavCount: 1 },
|
|
69
70
|
endResult: "Read-only; restore the window width afterwards.",
|
|
70
71
|
},
|
|
@@ -2054,7 +2054,7 @@ const rawTesterChecklistItems = [
|
|
|
2054
2054
|
cases: [
|
|
2055
2055
|
{ key: "sidebar-all-links-work", label: "Every item in the user dashboard sidebar navigates to its page without a 404 or broken layout", href: "/user" },
|
|
2056
2056
|
{ key: "sidebar-active-highlight", label: "The sidebar correctly highlights the currently active section as you navigate between pages" },
|
|
2057
|
-
{ key: "sidebar-mobile-collapse", label: "
|
|
2057
|
+
{ key: "sidebar-mobile-collapse", label: "On small screens, the user dashboard sidebar opens from the header hamburger as a collapsible menu; the bottom tab bar is the exact same one shown on every other page, not a dashboard-specific bar" },
|
|
2058
2058
|
{ key: "deep-link-direct-load", label: "Directly loading a deep user dashboard URL (e.g. /user/orders/view/[id]) works without first visiting the dashboard home" },
|
|
2059
2059
|
{ key: "become-seller-crossnav", label: "\"Become a Seller\" (buyer) vs \"Go to my Store\" (seller) shows the correct one based on account state and navigates correctly" },
|
|
2060
2060
|
{ key: "browser-back-forward", label: "Browser back/forward buttons move correctly between dashboard sub-pages without breaking the layout" },
|
|
@@ -2734,7 +2734,7 @@ const rawTesterChecklistItems = [
|
|
|
2734
2734
|
cases: [
|
|
2735
2735
|
{ key: "store-sidebar-all-links-work", label: "Every item in the store/seller dashboard sidebar navigates to its page without a 404 or broken layout", href: "/store" },
|
|
2736
2736
|
{ key: "store-sidebar-active-highlight", label: "The store sidebar correctly highlights the currently active section as you navigate between pages" },
|
|
2737
|
-
{ key: "store-sidebar-mobile-collapse", label: "
|
|
2737
|
+
{ key: "store-sidebar-mobile-collapse", label: "On small screens, the store dashboard sidebar opens from the header hamburger as a collapsible menu; the bottom tab bar is the exact same one shown on every other page, not a dashboard-specific bar" },
|
|
2738
2738
|
{ key: "store-deep-link-direct-load", label: "Directly loading a deep store dashboard URL (e.g. /store/products/[id]/edit) works without first visiting the dashboard home" },
|
|
2739
2739
|
{ key: "store-user-crossnav", label: "The store dashboard's cross-nav link back to the buyer dashboard works and lands on the correct page" },
|
|
2740
2740
|
{ key: "store-browser-back-forward", label: "Browser back/forward buttons move correctly between store dashboard sub-pages without breaking the layout" },
|
|
@@ -3890,7 +3890,7 @@ const rawTesterChecklistItems = [
|
|
|
3890
3890
|
{ key: "titlebar-row2-mirror", label: "On a phone-width viewport with Left-hand mode ON, the top bar's SECOND row (notifications / wishlist / cart / profile icons) packs against the LEFT edge in mirrored order with profile leftmost; with it OFF it packs against the right edge with notifications leftmost", href: "/" },
|
|
3891
3891
|
{ key: "titlebar-actions-no-overflow-narrow", label: "With Left-hand mode ON at the narrowest phone width (~320px), no top-bar icon is clipped or pushed off-screen — the mirrored row fits exactly as well as the default row does", href: "/" },
|
|
3892
3892
|
{ key: "bottom-nav-mirror", label: "With Left-hand mode ON, the mobile bottom tab bar's slots appear in reverse order (Profile leftmost, Home rightmost); with it OFF, Home is leftmost. Slot widths stay equal in both modes.", href: "/" },
|
|
3893
|
-
{ key: "dashboard-bottom-nav-mirror", label: "
|
|
3893
|
+
{ key: "dashboard-bottom-nav-mirror", label: "On admin/store/user routes, the mobile bottom tab bar is the exact same shared bar the public site uses — with Left-hand mode ON it mirrors identically, because it is literally the same component", href: "/user" },
|
|
3894
3894
|
{ key: "count-badges-mirror", label: "With Left-hand mode ON, the red count bubbles on the cart / wishlist / notification icons sit on the TOP-LEFT corner of their icon instead of top-right — in BOTH the top bar and the mobile bottom tab bar — and neither is clipped by the icon's edge", href: "/" },
|
|
3895
3895
|
{ key: "nav-scroll-arrows-unaffected", label: "Left-hand mode does NOT flip the main navigation bar's overflow scroll chevrons — the left chevron still scrolls the nav left and the right chevron still scrolls it right, in both modes", href: "/" },
|
|
3896
3896
|
{ key: "header-tab-order-sane", label: "With Left-hand mode ON, pressing Tab repeatedly from the top of the page still reaches every top-bar control with none skipped or trapped", description: "The visual left-to-right order will NOT match the tab order in left-hand mode — that is expected and accepted (the mirror is visual only, DOM order is deliberately unchanged so screen-reader reading order stays stable). What must not happen is a control becoming unreachable.", href: "/" },
|