diff --git a/.gitignore b/.gitignore index 7d2f2e8..5e200a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ .idea dist node_modules +.cursor +.cursorrules +.nvmrc +.progress +.vscode +.orchestrator diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index 1d9b783..0000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -22.12.0 diff --git a/README.md b/README.md index 7db3e88..09a91fa 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ | **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/ci-main.yml?branch=main\&label=install%20test)](https://github.com/ameshkin/nextcrumbs/actions/workflows/ci-main.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) | +| **Peer deps** | `@mui/material@^7`, `@mui/icons-material@^7`, `react@>=18`, `react-dom@>=18`
(Optional: `react-router-dom@>=6`) | ![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) | @@ -27,7 +27,9 @@ * [Props](#props) * [Icon & Separator Examples](#icon--separator-examples) * [Accessibility & SEO](#accessibility--seo) +* [Docs](#docs) * [Dependency Checks](#dependency-checks) +* [Troubleshooting](#troubleshooting) * [FAQ](#faq) * [Contributing](#contributing) * [License](#license) @@ -39,8 +41,8 @@ * ✅ **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` +* ✅ **React Router** helper hook (`useReactRouterBreadcrumbs`) - optional dependency +* ✅ Fully typed with TypeScript --- @@ -60,10 +62,14 @@ npm i @ameshkin/nextcrumbs # peer deps npm i @mui/material @mui/icons-material react react-dom -# optional for React Router examples +# optional: only needed if using useReactRouterBreadcrumbs hook npm i react-router-dom ``` +> **Note for Next.js projects:** If you're using Next.js and don't need React Router, you can either: +> - Install `react-router-dom` as a dev dependency: `npm i -D react-router-dom` +> - Or configure webpack to ignore it (see [Troubleshooting](#troubleshooting)) + --- ## Quick Start (Next.js) @@ -71,11 +77,12 @@ npm i react-router-dom ```tsx "use client"; import Link from "next/link"; -import { Breadcrumbs } from "@ameshkin/nextcrumbs"; +import { NextCrumb } from "@ameshkin/nextcrumbs"; +// Also available as: import { Breadcrumbs } from "@ameshkin/nextcrumbs"; export default function HeaderTrail() { return ( - `react-router-dom` is an optional peer. Install it only if you use the router hook. +> **Important:** `react-router-dom` is an **optional peer dependency**. You only need to install it if you use the `useReactRouterBreadcrumbs` hook. The package uses lazy loading, so importing other parts of the library won't require `react-router-dom` to be installed. --- @@ -156,17 +163,18 @@ export default function Demo() { | `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. | +| `homeLabel` | `string` | `"Home"` | If a crumb's label matches, it can receive a Home icon. | ### `usePathBreadcrumbs(pathname, options?)` -| Option | Type | Default | Description | -| ---------- | ----------------------- | ------- | ---------------------------------- | -| `baseHref` | `string` | `"/"` | Root href for the first crumb. | -| `labelMap` | `Record` | `{}` | Override labels by URL segment. | -| `exclude` | `string[]` | `[]` | Skip specific segments. | -| `decode` | `boolean` | `true` | `decodeURIComponent` each segment. | +| Option | Type | Default | Description | +| -------------- | ----------------------- | ------------ | ---------------------------------- | +| `baseHref` | `string` | `"/"` | Root href for the first crumb. | +| `labelMap` | `Record` | `{}` | Override labels by URL segment. | +| `exclude` | `string[]` | `[]` | Skip specific segments. | +| `decode` | `boolean` | `true` | `decodeURIComponent` each segment. | +| `rootLabel` | `string` | `"Dashboard"`| Label for the root/home breadcrumb. | +| `transformLabel` | `(segment: string) => string` | — | Custom label formatter function. | ### `useReactRouterBreadcrumbs(options?)` @@ -203,11 +211,64 @@ import { Breadcrumbs } from "@ameshkin/nextcrumbs"; ## Accessibility & SEO -* Uses MUI’s accessible ``. -* **SEO microdata is enabled by default** via `withSchema`. Set `withSchema={false}` to disable. +* Uses MUI's accessible ``. * Minimal DOM footprint; sensible defaults for current page vs. links. +* Follows ARIA best practices for breadcrumb navigation. + +### JSON-LD Structured Data + +For SEO, you can add JSON-LD structured data using the built-in utilities: + +```tsx +import { Breadcrumbs, BreadcrumbJsonLd, breadcrumbsToJsonLd } from "@ameshkin/nextcrumbs"; + +function MyPage() { + const items = [ + { label: "Home", href: "/" }, + { label: "Products", href: "/products" }, + { label: "New Product" } + ]; + + return ( + <> + + + + ); +} +``` + +Or use the `toJsonLd` function for custom JSON-LD generation: + +```tsx +import { toJsonLd } from "@ameshkin/nextcrumbs"; + +const jsonLd = toJsonLd( + [{ name: "Home", href: "/" }, { name: "Products" }], + { origin: "https://example.com" } +); +``` + +> See [`docs/vanilla-json.md`](./docs/vanilla-json.md) for more examples. -> Prefer **page-level JSON-LD** for static SEO markup. See: [`docs/vanilla-json.md`](./docs/vanilla-json.md) +--- + +## Docs + +- **Next.js App Router guide**: `docs/next-router.md` + Deep dive into `usePathBreadcrumbs` with Next App Router, theming, and testing tips. + +- **React Router guide**: `docs/react-router.md` + How to use `useReactRouterBreadcrumbs`, wire up `` for SPA navigation, and test with RTL. + +- **Vanilla React + JSON-LD**: `docs/vanilla-json.md` + Using `` without any router and adding SEO JSON-LD at the page level. + +- **GitLab / subpath exports**: `docs/gitlab.md` + How to consume `@ameshkin/nextcrumbs` (and its subpath exports) in GitLab and other CI/CD setups with good tree shaking. + +- **Export & API changes**: `docs/changes.md` + Notes on the current export layout, subpath exports, and dependency cleanup. --- @@ -215,22 +276,55 @@ import { Breadcrumbs } from "@ameshkin/nextcrumbs"; ```bash npm ls @mui/material @mui/icons-material react react-dom -# optional router +# optional router (only needed if using useReactRouterBreadcrumbs) npm ls react-router-dom ``` +## Troubleshooting + +### Webpack Error: "Can't resolve 'react-router-dom'" + +If you're using Next.js (or another webpack-based bundler) and see this error even though you're not using the React Router hook, you have two options: + +**Option 1: Install as dev dependency (Recommended)** +```bash +npm install --save-dev react-router-dom +``` + +**Option 2: Configure webpack to ignore it** + +For Next.js, add this to your `next.config.js`: + +```js +/** @type {import('next').NextConfig} */ +const nextConfig = { + webpack: (config) => { + config.externals = config.externals || {} + config.externals['react-router-dom'] = 'commonjs react-router-dom' + return config + } +} + +module.exports = nextConfig +``` + +> **Note:** The `useReactRouterBreadcrumbs` hook uses lazy loading, so `react-router-dom` is only accessed when the hook is actually called. However, webpack may still try to resolve it during static analysis. The solutions above prevent webpack from failing when the module isn't installed. + --- ## FAQ **Does it work with React Router?** -Yes. Use `useReactRouterBreadcrumbs()` or pass your router’s `` via `LinkComponent`. +Yes. Use `useReactRouterBreadcrumbs()` or pass your router's `` via `LinkComponent`. Note that `react-router-dom` is optional and only needed if you use the hook. + +**Do I need to install react-router-dom if I'm using Next.js?** +No, you only need it if you're using the `useReactRouterBreadcrumbs` hook. If webpack complains about it, install it as a dev dependency or configure webpack to ignore it (see [Troubleshooting](#troubleshooting)). -**How do I change the last crumb’s style?** -The last item usually has no `href`. Style it as the current page using your theme or conditional props. +**How do I change the last crumb's style?** +The last item usually has no `href`. Style it as the current page using your theme or conditional props via the `currentSx` prop. **Can I disable icons entirely?** -Yes—don’t pass `icon`, and set `homeLabel` to a non-matching value. +Yes—don't pass `icon` in your items, and set `homeLabel` to a non-matching value if you don't want the home icon. --- diff --git a/docs/changes.md b/docs/changes.md new file mode 100644 index 0000000..e0c2a35 --- /dev/null +++ b/docs/changes.md @@ -0,0 +1,29 @@ +## Docs & API Changes + +### Export layout & tree shaking + +- **New:** multiple ESM entrypoints are now built: + - `src/index.ts` + - `src/Breadcrumbs.tsx` + - `src/hooks/usePathBreadcrumbs.ts` + - `src/hooks/useReactRouterBreadcrumbs.ts` + - `src/utils/jsonld.ts` +- **New:** subpath exports in `package.json`: + - `@ameshkin/nextcrumbs/breadcrumbs` + - `@ameshkin/nextcrumbs/hooks/usePathBreadcrumbs` + - `@ameshkin/nextcrumbs/hooks/useReactRouterBreadcrumbs` + - `@ameshkin/nextcrumbs/utils/jsonld` +- **Unchanged (backward compatible):** + - Top-level exports from `@ameshkin/nextcrumbs` still include: + - `Breadcrumbs`, `NextCrumb` + - `usePathBreadcrumbs` + - `useReactRouterBreadcrumbs` and `useRRBreadcrumbs` + - `toJsonLd`, `BreadcrumbJsonLd`, `breadcrumbsToJsonLd` + - `BreadcrumbsProps`, `BreadcrumbItem`, `PathCrumbOptions`, `PathBreadcrumb`, `ReactRouterBreadcrumbsOptions`, `JsonLdBreadcrumb`, `JsonLdOptions` + +### Dependency cleanup + +- **Removed:** runtime dependency on `@ameshkin/orchestrator` from `@ameshkin/nextcrumbs`. + - This avoids install issues in external projects and keeps the public package focused on breadcrumbs only. + + diff --git a/docs/gitlab.md b/docs/gitlab.md new file mode 100644 index 0000000..f155f1d --- /dev/null +++ b/docs/gitlab.md @@ -0,0 +1,53 @@ +## Using `@ameshkin/nextcrumbs` in GitLab projects + +This package is published as a modern ESM bundle with tree-shakable entrypoints and subpath exports. + +### Install + +```bash +npm install @ameshkin/nextcrumbs +``` + +### Top-level (backward-compatible) imports + +These continue to work and are the recommended default: + +```ts +import { + Breadcrumbs, + NextCrumb, // alias of Breadcrumbs + usePathBreadcrumbs, + useReactRouterBreadcrumbs, + toJsonLd, + BreadcrumbJsonLd, + breadcrumbsToJsonLd, +} from "@ameshkin/nextcrumbs"; +``` + +### Tree-shakable subpath imports + +For GitLab (and other) projects that prefer explicit per-feature imports, you can use the new subpath exports: + +```ts +// Component only +import { Breadcrumbs } from "@ameshkin/nextcrumbs/breadcrumbs"; + +// Next.js App Router hook +import { usePathBreadcrumbs } from "@ameshkin/nextcrumbs/hooks/usePathBreadcrumbs"; + +// React Router hook +import useReactRouterBreadcrumbs, { + ReactRouterBreadcrumbsOptions, +} from "@ameshkin/nextcrumbs/hooks/useReactRouterBreadcrumbs"; + +// JSON-LD helpers +import { + toJsonLd, + BreadcrumbJsonLd, + breadcrumbsToJsonLd, +} from "@ameshkin/nextcrumbs/utils/jsonld"; +``` + +All of these entrypoints are pure ESM and are safe for bundlers that perform tree shaking. + + diff --git a/docs/next-router.md b/docs/next-router.md index 46b66cd..ea056c0 100644 --- a/docs/next-router.md +++ b/docs/next-router.md @@ -105,8 +105,7 @@ type PathOptions = { ## Accessibility & SEO * `` for screen readers -* `withSchema` is enabled by default to add `schema.org/BreadcrumbList` -* For JSON-LD, add page-level `