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
JavaScript
'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 };