From d6459e95161fdb450619595270c22932b336b352 Mon Sep 17 00:00:00 2001 From: Amir Meshkin Date: Sat, 1 Nov 2025 22:31:46 -0400 Subject: [PATCH] Version 0.1.1 --- README.md | 322 ++++++++++++++----------------------------- docs/next-router.md | 170 +++++++++++++++++++++++ docs/react-router.md | 132 ++++++++++++++++++ docs/vanilla-json.md | 131 ++++++++++++++++++ package.json | 2 +- 5 files changed, 540 insertions(+), 217 deletions(-) diff --git a/README.md b/README.md index eae87e5..85ac8d2 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,56 @@ -# Next Crumb +# NextCrumbs -> MUI 7 Breadcrumbs with **Next.js Link** support, optional **URL-based generation**, and **SEO microdata**. Router-agnostic, zero runtime deps. +> MUI 7 Breadcrumbs with **Next.js Link** support, optional **URL-based generation**, and **built-in SEO microdata**. Router-agnostic with zero runtime deps. - -| Item | Value | Badge | -|-----------------|-------------------------------------------------------------------------------------------|-------| -| **GitHub repo** | [github.com/ameshkin/nextcrumbs](https://github.com/ameshkin/nextcrumbs) | — | -| **CI (main)** | `.github/workflows/ci-main.yml` | [![CI — main](https://img.shields.io/github/actions/workflow/status/ameshkin/nextcrumbs/ci-main.yml?branch=main&label=ci%20(main))](https://github.com/ameshkin/nextcrumbs/actions/workflows/ci-main.yml) | -| **CI (dev)** | `.github/workflows/ci-dev.yml` | [![CI — dev](https://img.shields.io/github/actions/workflow/status/ameshkin/nextcrumbs/ci-dev.yml?branch=dev&label=ci%20(dev))](https://github.com/ameshkin/nextcrumbs/actions/workflows/ci-dev.yml) | -| **npm package** | [@ameshkin/nextcrumbs](https://www.npmjs.com/package/@ameshkin/nextcrumbs) | [![npm](https://img.shields.io/npm/v/@ameshkin/nextcrumbs.svg)](https://www.npmjs.com/package/@ameshkin/nextcrumbs) | -| **Install** | `npm i @ameshkin/nextcrumbs` | [![Install test — main](https://img.shields.io/github/actions/workflow/status/ameshkin/nextcrumbs/install.yml?branch=main&label=install%20test)](https://github.com/ameshkin/nextcrumbs/actions/workflows/install.yml) | -| **Peer deps** | `@mui/material@^7`, `@mui/icons-material@^7`, `react@>=18`, `react-dom@>=18` | ![peer deps](https://img.shields.io/badge/peer_deps-MUI%207%20%7C%20Icons%207%20%7C%20React%2018-blue) | -| **Types** | TypeScript | ![types](https://img.shields.io/badge/types-TypeScript-blue.svg) | -| **License** | MIT | [![license](https://img.shields.io/badge/license-MIT-black.svg)](LICENSE) | -| **Bundle size** | bundlephobia | [![bundle size](https://img.shields.io/bundlephobia/minzip/%40ameshkin%2Fnextcrumbs?label=bundle%20size)](https://bundlephobia.com/package/@ameshkin/nextcrumbs) | -| **Deps status** | libraries.io | [![deps](https://img.shields.io/librariesio/release/npm/%40ameshkin%2Fnextcrumbs)](https://libraries.io/npm/%40ameshkin%2Fnextcrumbs) | +| Item | Value | Badge | +| --------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **GitHub repo** | [github.com/ameshkin/nextcrumbs](https://github.com/ameshkin/nextcrumbs) | — | +| **CI** | `.github/workflows/ci.yml` | [![CI — main](https://img.shields.io/github/actions/workflow/status/ameshkin/nextcrumbs/ci.yml?branch=main\&label=ci%20\(main\))](https://github.com/ameshkin/nextcrumbs/actions/workflows/ci.yml) [![CI — dev](https://img.shields.io/github/actions/workflow/status/ameshkin/nextcrumbs/ci.yml?branch=dev\&label=ci%20\(dev\))](https://github.com/ameshkin/nextcrumbs/actions/workflows/ci.yml) | +| **npm package** | [@ameshkin/nextcrumbs](https://www.npmjs.com/package/@ameshkin/nextcrumbs) | [![npm](https://img.shields.io/npm/v/@ameshkin/nextcrumbs.svg)](https://www.npmjs.com/package/@ameshkin/nextcrumbs) | +| **Install** | `npm i @ameshkin/nextcrumbs` | [![install test](https://img.shields.io/github/actions/workflow/status/ameshkin/nextcrumbs/install.yml?branch=main\&label=install%20test)](https://github.com/ameshkin/nextcrumbs/actions/workflows/install.yml) | +| **Peer deps** | `@mui/material@^7`, `@mui/icons-material@^7`, `react@>=18`, `react-dom@>=18` | ![peer deps](https://img.shields.io/badge/peer_deps-MUI%207%20%7C%20Icons%207%20%7C%20React%2018-blue) | +| **Types** | TypeScript | ![types](https://img.shields.io/badge/types-TypeScript-blue.svg) | +| **License** | MIT | [![license](https://img.shields.io/badge/license-MIT-black.svg)](LICENSE) | +| **Bundle size** | bundlephobia | [![bundle size](https://img.shields.io/bundlephobia/minzip/%40ameshkin%2Fnextcrumbs?label=bundle%20size)](https://bundlephobia.com/package/@ameshkin/nextcrumbs) | +| **Deps status** | libraries.io | [![deps](https://img.shields.io/librariesio/release/npm/%40ameshkin%2Fnextcrumbs)](https://libraries.io/npm/%40ameshkin%2Fnextcrumbs) | --- ## Menu -- [Features](#features) -- [Requirements](#requirements) -- [Install](#install) -- [Quick Start (Next.js)](#quick-start-nextjs) -- [Auto from URL (Next App Router)](#auto-from-url-next-app-router) -- [Props](#props) -- [Icon & Separator Examples](#icon--separator-examples) -- [Accessibility & SEO](#accessibility--seo) -- [Dependency Checks](#dependency-checks) -- [FAQ](#faq) -- [Contributing](#contributing) -- [License](#license) +* [Features](#features) +* [Requirements](#requirements) +* [Install](#install) +* [Quick Start (Next.js)](#quick-start-nextjs) +* [Auto from URL (Next App Router)](#auto-from-url-next-app-router) +* [React Router](#react-router) +* [Props](#props) +* [Icon & Separator Examples](#icon--separator-examples) +* [Accessibility & SEO](#accessibility--seo) +* [Dependency Checks](#dependency-checks) +* [FAQ](#faq) +* [Contributing](#contributing) +* [License](#license) --- ## Features -- ✅ **MUI** Breadcrumbs under the hood (accessible, stable) -- ✅ **Next.js** ready via pluggable `LinkComponent` (e.g., `next/link`) -- ✅ Optional **URL → items** helper (works with App Router `usePathname()`) -- ✅ **SEO microdata** (`schema.org/BreadcrumbList`) -- ✅ Customize **separator** and per-item **icons** (e.g., Home) +* ✅ **MUI** Breadcrumbs under the hood (accessible, stable) +* ✅ **Next.js** ready via pluggable `LinkComponent` (e.g., `next/link`) +* ✅ Optional **URL → items** helper for App Router (`usePathBreadcrumbs`) +* ✅ **React Router** helper hook (`useReactRouterBreadcrumbs`) +* ✅ Built-in **schema.org/BreadcrumbList** microdata via `withSchema` --- ## Requirements -- React **18+** -- @mui/material **7+** -- (Optional) @mui/icons-material **7+** for built-in icons -- Next.js **13.4+** (if using Next + App Router) +* React **18+** +* @mui/material **7+** +* (Optional) @mui/icons-material **7+** for icons +* Next.js **13.4+** if using App Router examples +* (Optional) react-router-dom **6+ or 7+** for the React Router hook --- @@ -60,16 +60,14 @@ npm i @ameshkin/nextcrumbs # peer deps npm i @mui/material @mui/icons-material react react-dom -```` - -> Using pnpm or yarn? Replace `npm i` with your package manager’s command. +# optional for React Router examples +npm i react-router-dom +``` --- ## Quick Start (Next.js) -Manual items with `next/link`: - ```tsx "use client"; import Link from "next/link"; @@ -93,7 +91,7 @@ export default function HeaderTrail() { ## Auto from URL (Next App Router) -Generate items from the current pathname: +See full guide: [`docs/next-router.md`](./docs/next-router.md) ```tsx "use client"; @@ -115,37 +113,77 @@ export default function AutoTrail() { --- +## React Router + +See full guide: [`docs/react-router.md`](./docs/react-router.md) + +```tsx +import { MemoryRouter, Routes, Route } from "react-router-dom"; +import { Breadcrumbs } from "@ameshkin/nextcrumbs"; +import useReactRouterBreadcrumbs from "@ameshkin/nextcrumbs"; + +function Trail() { + const items = useReactRouterBreadcrumbs({ + rootLabel: "Home", + exclude: [/^_/] + }); + return ; +} + +export default function Demo() { + return ( + + + } /> + } /> + + + ); +} +``` + +> `react-router-dom` is an optional peer. Install it only if you use the router hook. + +--- + ## Props ### `` -| Prop | Type | Default | Description | -| --------------- | ------------------------------------------------------ | -------------------- | ---------------------------------------------------------------- | -| `items` | `{ label: string; href?: string; icon?: ReactNode }[]` | **required** | The breadcrumb trail; last item usually has no `href`. | -| `LinkComponent` | `ElementType` | `@mui/material/Link` | Custom link component (e.g., Next.js `Link`). | -| `muiProps` | `Omit` | — | Pass-through props to MUI ``. | -| `separatorIcon` | `ReactNode` | `ChevronRightIcon` | Icon/node placed between items. | -| `homeLabel` | `string` | `"Dashboard"` | If an item’s label matches this, it gets a Home icon by default. | -| `withSchema` | `boolean` | `true` | Adds `schema.org/BreadcrumbList` microdata. | +| Prop | Type | Default | Description | +| --------------- | ------------------------------------------------------------------------------------------------ | -------------------- | --------------------------------------------------------- | +| `items` | `{ label: string; href?: string; icon?: React.ReactNode; title?: string; external?: boolean }[]` | **required** | The breadcrumb trail; last item usually has no `href`. | +| `LinkComponent` | `React.ElementType` | `@mui/material/Link` | Custom link component (e.g., Next.js `Link`). | +| `muiProps` | `Omit` | — | Props passed through to MUI ``. | +| `separatorIcon` | `React.ReactNode` | `ChevronRightIcon` | Icon/node placed between items. | +| `homeLabel` | `string` | `"Dashboard"` | If a crumb’s label matches, it can receive a Home icon. | +| `withSchema` | `boolean` | `true` | Adds `schema.org/BreadcrumbList` microdata to the markup. | ### `usePathBreadcrumbs(pathname, options?)` | Option | Type | Default | Description | | ---------- | ----------------------- | ------- | ---------------------------------- | | `baseHref` | `string` | `"/"` | Root href for the first crumb. | -| `labelMap` | `Record` | `{}` | Override labels by segment. | -| `exclude` | `string[]` | `[]` | Skip specific path segments. | +| `labelMap` | `Record` | `{}` | Override labels by URL segment. | +| `exclude` | `string[]` | `[]` | Skip specific segments. | | `decode` | `boolean` | `true` | `decodeURIComponent` each segment. | +### `useReactRouterBreadcrumbs(options?)` + +| Option | Type | Default | Description | +| ----------------- | ---------------------------------------------- | ------- | ----------------------------------------------------- | +| `rootLabel` | `string` | — | Optional first crumb label linking to `/`. | +| `basePath` | `string` | — | Strip a leading base path when building crumbs. | +| `decode` | `boolean` | `true` | Decode each segment before labeling. | +| `exclude` | `(string \| RegExp)[]` | `[]` | Skip segments by string or regex. | +| `mapSegmentLabel` | `(segment, index, segments) => string \| null` | — | Customize a label or return `null` to hide a segment. | + --- ## Icon & Separator Examples -Change the separator and home icon behavior: - ```tsx import Link from "next/link"; -import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import NavigateNextIcon from "@mui/icons-material/NavigateNext"; import HomeOutlinedIcon from "@mui/icons-material/HomeOutlined"; import { Breadcrumbs } from "@ameshkin/nextcrumbs"; @@ -154,140 +192,50 @@ import { Breadcrumbs } from "@ameshkin/nextcrumbs"; LinkComponent={Link} separatorIcon={} items={[ - { label: "Dashboard", href: "/", icon: }, + { label: "Home", href: "/", icon: }, { label: "Products", href: "/products" }, { label: "Gadgets" } ]} />; ``` -* Any MUI icon works for `separatorIcon` or per-item `icon`. -* If you set `homeLabel="Home"`, the component shows a Home icon automatically when a crumb’s label is `"Home"`. - ---- - -## Minimal AutoTrail Example - -> Simple usage with default behavior (no `labelMap`, `exclude`, or `baseHref`) - -```tsx -"use client"; -import { usePathname } from "next/navigation"; -import { Breadcrumbs, usePathBreadcrumbs } from "@ameshkin/nextcrumbs"; -import Link from "next/link"; - -export default function AutoTrail() { - const pathname = usePathname(); - const items = usePathBreadcrumbs(pathname); - - return ; -} -``` - -✅ Great for dashboards or quick scaffolded layouts. Automatically capitalizes, cleans up slugs, and converts URL segments into breadcrumb labels. - - --- ## Accessibility & SEO * Uses MUI’s accessible ``. -* Adds **schema.org** `BreadcrumbList` microdata by default (set `withSchema={false}` to disable). -* Minimal DOM/no extra wrappers (uses `display: contents` for the list wrapper). +* **SEO microdata is enabled by default** via `withSchema`. Set `withSchema={false}` to disable. +* Minimal DOM footprint; sensible defaults for current page vs. links. + +> Prefer **page-level JSON-LD** for static SEO markup. See: [`docs/vanilla-json.md`](./docs/vanilla-json.md) --- ## Dependency Checks -Make sure peer deps resolve cleanly: - ```bash -# in your app npm ls @mui/material @mui/icons-material react react-dom +# optional router +npm ls react-router-dom ``` -Optional size/security checks: - -* Size: [https://bundlephobia.com/package/@ameshkin/nextcrumbs](https://bundlephobia.com/package/@ameshkin/nextcrumbs) -* Vulnerabilities (example): `npx snyk test` or GitHub Advanced Security (CodeQL) - --- ## FAQ **Does it work with React Router?** -Yes—pass your router’s `` as `LinkComponent`. The UI is MUI; navigation is your router. +Yes. Use `useReactRouterBreadcrumbs()` or pass your router’s `` via `LinkComponent`. **How do I change the last crumb’s style?** -The last item usually has no `href`. You can add conditional styles by checking `href` presence in your `items` or wrap this component with your own style logic. +The last item usually has no `href`. Style it as the current page using your theme or conditional props. **Can I disable icons entirely?** -Yes—don’t pass `icon` and set `homeLabel` to a non-matching value. +Yes—don’t pass `icon`, and set `homeLabel` to a non-matching value. --- -## Accessibility & SEO - -This component previously supported `schema.org` microdata via a `withSchema` and `withJsonLd` prop. These have been **removed** for the following reasons: - -- ⚠️ **Google no longer consistently indexes client-side microdata** (e.g. via React or `dangerouslySetInnerHTML`) unless it's rendered server-side. -- 🧼 Breadcrumb JSON-LD tags added via `