@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.
@@ -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, Li, Nav, Span } from "../../../../ui";
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 }) }), _jsx(DashboardBottomNav, { items: filteredGroups.flatMap((g) => g.items), activeHref: activeHref })] }));
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, hasNav: hasDashboardNav, toggleNav: toggleDashboardNav } = useDashboardNav();
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: hasDashboardNav ? toggleDashboardNav : handleTogglePublicSidebar })), _jsx(UnsavedChangesModal, {})] }) }));
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). This deliberately covers BOTH bottom bars: the
22
- * public `BottomNavbar` and `DashboardLayoutClient`'s `dashboard-bottom-navbar`
23
- * both render through here, and they must behave identically.
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). This deliberately covers BOTH bottom bars: the
20
- * public `BottomNavbar` and `DashboardLayoutClient`'s `dashboard-bottom-navbar`
21
- * both render through here, and they must behave identically.
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
- "Read how the sidebar is presented.",
62
- "Open it, read its entries, and navigate somewhere.",
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: "On a phone the sidebar collapses behind a control and closes after navigating. The dashboard renders its own bottom tab bar and the public one is suppressed on these routes, so exactly one is mounted — two would be two bars on the same pixels with a height nobody owns, and everything above them would be positioned against the wrong one.",
69
- expectedUiState: "The sidebar is collapsed by default, opens on demand and closes after navigation. Exactly one bottom bar is present, carrying dashboard destinations rather than the public ones. The page does not scroll sideways.",
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: "The dashboard renders its own bottom bar and the public one is suppressed on those routes, so exactly one is mounted. Two would be two bars on the same pixels with a height nobody owns, and everything above would be positioned against the wrong one.",
235
- expectedUiState: "Exactly one bottom bar on /user in both modes, carrying dashboard destinations rather than the public ones. Item order is unchanged between modes, matching the public bar's behaviour.",
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
- "Read how the sidebar is presented.",
60
- "Open it, read its entries, and navigate somewhere.",
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: "On a phone the sidebar collapses behind a control and closes after navigating. The dashboard renders its own bottom tab bar and the public one is suppressed on these routes, so exactly one is mounted — two would be two bars on the same pixels with a height nobody owns.",
67
- expectedUiState: "The sidebar is collapsed by default, opens on demand, and closes after a navigation. Exactly one bottom bar is present, carrying dashboard destinations. The page does not scroll sideways.",
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: "The user dashboard sidebar collapses into a mobile-friendly menu/bottom bar on small screens" },
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: "The store dashboard sidebar collapses into a mobile-friendly menu/bottom bar on small screens" },
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: "With Left-hand mode ON, the dashboard's own mobile bottom tab bar (on admin/store/user routes) mirrors the same way the public one does — both bars behave identically", href: "/user" },
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: "/" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "4.42.0",
3
+ "version": "4.42.1",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"