UNPKG

pagamio-frontend-commons-lib

Version:

Pagamio library for Frontend reusable components like the form engine and table container

171 lines (170 loc) 7.13 kB
'use client'; import { jsx as _jsx } from "react/jsx-runtime"; import { HiChartPie } from 'react-icons/hi'; import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { isUUIDorID } from '../shared'; /** * Context for managing breadcrumbs in the application. * Provides breadcrumbs, a function to update breadcrumb labels, and the root page icon. */ const AppBreadcrumbContext = createContext(undefined); /** * Recursively find the sidebar page item matching a given path. * Also returns the parent chain for breadcrumb construction. */ function findPageConfigWithParents(pages, path, parents = []) { for (const page of pages) { if (page.href === path) return { page, parents }; if (page.items) { const found = findPageConfigWithParents(page.items, path, [...parents, page]); if (found.page) return found; } } return { page: undefined, parents: [] }; } const buildBreadcrumbsFromParents = (parents, getManualLabel, setIcon) => { let breadcrumbs = []; let currentPath = ''; for (const parent of parents) { currentPath = parent.href ?? ''; breadcrumbs.push({ label: getManualLabel(parent.href ?? '', parent.label), path: currentPath }); setIcon(parent.icon); } return breadcrumbs; }; const addCurrentPageBreadcrumb = (foundConfig, getManualLabel, setIcon) => { if (!foundConfig) return []; const currentPath = foundConfig.href ?? ''; setIcon(foundConfig.icon); return [{ label: getManualLabel(foundConfig.href ?? '', foundConfig.label), path: currentPath }]; }; const addExtraSegmentsBreadcrumbs = (extraSegments, currentPath, newBreadcrumbs, getManualLabel, getUUIDBreadcrumb) => { const breadcrumbs = []; for (const segment of extraSegments) { currentPath += `/${segment}`; if (isUUIDorID(segment)) { const uuidBreadcrumb = getUUIDBreadcrumb(segment, currentPath, [...newBreadcrumbs, ...breadcrumbs]); if (uuidBreadcrumb) breadcrumbs.push(uuidBreadcrumb); } else { breadcrumbs.push({ label: getManualLabel(segment, segment.charAt(0).toUpperCase() + segment.slice(1)), path: currentPath, }); } } return breadcrumbs; }; /** * Provider component for the BreadcrumbContext. * Manages the state and logic for breadcrumbs based on the current pathname. * * @param children - The child components to be wrapped by the provider. * @param pathname - The pathname of the current app route. * @param pages - The app root pages of the sidebar */ const AppBreadcrumbProvider = ({ children, pathname, pages }) => { const [breadcrumbs, setBreadcrumbs] = useState([]); const manualUpdates = useRef({}); // Track manual label updates /** * Calculates the breadcrumbs based on the current pathname. * Uses the `pages` configuration to determine labels and paths. * Preserves manual updates to breadcrumb labels. */ function getManualLabel(segment, fallback) { return manualUpdates.current[segment] ?? fallback; } function shouldAddListBreadcrumb(pageConfig, currentPath, pathname) { return (!!pageConfig?.items?.length && currentPath === pathname && !pageConfig.items.some((item) => item.href === currentPath)); } function getUUIDBreadcrumb(segment, currentPath, newBreadcrumbs) { const previousLabel = newBreadcrumbs[newBreadcrumbs.length - 1]?.label; if (previousLabel) { const baseLabel = previousLabel.replace(/s$/, ''); const label = getManualLabel(segment, `${baseLabel} Details`); return { label, path: currentPath, key: segment, }; } return null; } const calculatedBreadcrumbs = useMemo(() => { const pathSegments = pathname.replace(/\/$/, '').split('/').filter(Boolean); let newBreadcrumbs = []; let icon = undefined; if (pathSegments.length === 0) { return { icon: HiChartPie, breadcrumbs: [{ label: 'Dashboard', path: '/dashboard' }], }; } let currentPath = ''; // Find the deepest matching page config and its parent chain let foundConfig = undefined; let foundParents = []; for (const segment of pathSegments) { currentPath += `/${segment}`; const { page, parents } = findPageConfigWithParents(pages, currentPath); if (page) { foundConfig = page; foundParents = parents; } } // Helper to set icon only if not already set const setIcon = (candidate) => { if (!icon && candidate) icon = candidate; }; // Build breadcrumbs from parent chain newBreadcrumbs = buildBreadcrumbsFromParents(foundParents, getManualLabel, setIcon); // Add the current page newBreadcrumbs.push(...addCurrentPageBreadcrumb(foundConfig, getManualLabel, setIcon)); // If there are extra segments (e.g. UUIDs), process them const extraSegments = pathSegments.slice(foundConfig?.href?.split('/').filter(Boolean).length ?? 0); newBreadcrumbs.push(...addExtraSegmentsBreadcrumbs(extraSegments, foundConfig?.href ?? '', newBreadcrumbs, getManualLabel, getUUIDBreadcrumb)); return { icon, breadcrumbs: newBreadcrumbs }; }, [pathname, pages]); useEffect(() => { setBreadcrumbs(calculatedBreadcrumbs.breadcrumbs); }, [calculatedBreadcrumbs.breadcrumbs]); /** * Updates the label of a specific breadcrumb identified by its key. * * @param key - The key of the breadcrumb to update. * @param newLabel - The new label to set for the breadcrumb. */ const updateBreadcrumb = useCallback((key, newLabel) => { manualUpdates.current[key] = newLabel; // Store the manual update setBreadcrumbs((current) => current.map((breadcrumb) => (breadcrumb.key === key ? { ...breadcrumb, label: newLabel } : breadcrumb))); }, []); const breadcrumbsContextValue = useMemo(() => ({ breadcrumbs, updateBreadcrumb, pathname, rootPageIcon: calculatedBreadcrumbs.icon, }), [pathname, breadcrumbs, updateBreadcrumb, calculatedBreadcrumbs.icon]); return _jsx(AppBreadcrumbContext.Provider, { value: breadcrumbsContextValue, children: children }); }; /** * Hook to access the BreadcrumbContext. * Provides access to breadcrumbs, the update function, and the root page icon. * * @throws Will throw an error if used outside a BreadcrumbProvider. */ const useAppBreadcrumbs = () => { const context = useContext(AppBreadcrumbContext); if (!context) { throw new Error('useBreadcrumbs must be used within a BreadcrumbProvider'); } return context; }; export default AppBreadcrumbProvider; export { useAppBreadcrumbs };