Compare commits

...

1 Commits
v0.4.4 ... main

Author SHA1 Message Date
Maksym Sadovnychyy
cd575eb926 (refactor): Breadcrumb uses react-router Link; drop linkComponent injection
Some checks failed
Storybook tests / storybook-tests (push) Has been cancelled
2026-08-12 17:01:44 +02:00
6 changed files with 35 additions and 69 deletions

View File

@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.4.5] - 2026-08-12
### Changed
- **Breaking:** `Breadcrumb` now renders links with react-router `Link` directly. Dropped injectable `linkComponent`, `href`, and `linkProps` — use `to` on trail items only. Default link/current/separator colors use slate tones.
### Removed
- `BreadcrumbLinkComponent` export (no longer needed).
## [0.4.4] - 2026-08-12
### Fixed

View File

@ -1,30 +1,12 @@
import {
type ComponentType,
type FC,
type ReactNode,
} from 'react'
import { type FC, type ReactNode } from 'react'
import { Link } from 'react-router-dom'
export interface BreadcrumbItem {
label: ReactNode
/** Used by default `<a>` and as fallback for router links. */
href?: string
/** Preferred when injecting react-router `Link`. */
/** Omit on the current page (last item). */
to?: string
/** Extra props passed to the injected link component. */
linkProps?: Record<string, unknown>
}
/**
* Injectable link surface for SPA routers.
* `to` is always provided when Breadcrumb renders a link, so react-router `Link` is assignable.
*/
export type BreadcrumbLinkComponent = ComponentType<{
to: string
href?: string
className?: string
children?: ReactNode
}>
export interface BreadcrumbProps {
items: BreadcrumbItem[]
/** Defaults to `"/"`. */
@ -33,35 +15,21 @@ export interface BreadcrumbProps {
linkClassName?: string
currentClassName?: string
separatorClassName?: string
/** Host injects `Link` from react-router (or any anchor-like component). Defaults to `<a>`. */
linkComponent?: BreadcrumbLinkComponent
/** Accessible name for the nav landmark. Defaults to `"Breadcrumb"`. */
label?: string
}
const DefaultLink: BreadcrumbLinkComponent = ({
href,
to,
children,
...rest
}) => (
<a href={href ?? to} {...rest}>
{children}
</a>
)
/**
* Presentational page trail. Use links/`span` only never headings.
* Keep a single page `h1` on `FormHeader` or the page title.
* Page trail (`nav` + `ol`). Links use react-router `Link`.
* Never uses headings keep a single page `h1` on `FormHeader` or the page title.
*/
const Breadcrumb: FC<BreadcrumbProps> = ({
items,
separator = '/',
className = '',
linkClassName = 'text-sky-700 hover:text-sky-900 hover:underline',
currentClassName = 'text-gray-700',
separatorClassName = 'text-gray-400',
linkComponent: LinkComponent = DefaultLink,
linkClassName = 'text-slate-500 hover:text-slate-800 hover:underline',
currentClassName = 'text-slate-700',
separatorClassName = 'text-slate-400',
label = 'Breadcrumb',
}) => {
if (items.length === 0)
@ -75,8 +43,7 @@ const Breadcrumb: FC<BreadcrumbProps> = ({
<ol className={'flex flex-wrap items-center gap-x-2 gap-y-1'}>
{items.map((item, index) => {
const isLast = index === items.length - 1
const target = item.to ?? item.href
const isLink = !isLast && Boolean(target)
const isLink = !isLast && Boolean(item.to)
return (
<li
@ -92,15 +59,13 @@ const Breadcrumb: FC<BreadcrumbProps> = ({
</span>
) : null}
{isLink && target ? (
<LinkComponent
href={item.href ?? (typeof item.to === 'string' ? item.to : undefined)}
to={target}
{isLink && item.to ? (
<Link
to={item.to}
className={linkClassName}
{...(item.linkProps ?? {})}
>
{item.label}
</LinkComponent>
</Link>
) : (
<span
className={currentClassName}

View File

@ -1,6 +1,2 @@
export { Breadcrumb } from './Breadcrumb'
export type {
BreadcrumbProps,
BreadcrumbItem,
BreadcrumbLinkComponent,
} from './Breadcrumb'
export type { BreadcrumbProps, BreadcrumbItem } from './Breadcrumb'

View File

@ -4,11 +4,7 @@ export { SecretComponent } from './components/editors/SecretComponent'
export type { SecretDataSource, SecretComponentProps } from './components/editors/SecretComponent'
export { FormContainer, FormContent, FormFooter, FormHeader } from './components/FormLayout'
export { Breadcrumb } from './components/Breadcrumb'
export type {
BreadcrumbProps,
BreadcrumbItem,
BreadcrumbLinkComponent,
} from './components/Breadcrumb'
export type { BreadcrumbProps, BreadcrumbItem } from './components/Breadcrumb'
export { Offcanvas } from './components/Offcanvas'
export { Modal, ConfirmDialog } from './components/Modal'
export type { ModalProps, ModalSize, ConfirmDialogProps } from './components/Modal'

View File

@ -1,6 +1,6 @@
{
"name": "@maks-it.com/webui",
"version": "0.4.4",
"version": "0.4.5",
"description": "Shared contracts, utilities, and React components for MaksIT WebUI apps",
"type": "module",
"main": "./dist/index.cjs",

View File

@ -11,7 +11,7 @@ const meta = {
docs: {
description: {
component:
'Presentational page trail (`nav` + `ol`). Never uses headings — keep a single page `h1` on `FormHeader` or the page title. Inject `linkComponent` (e.g. react-router `Link`) for SPA navigation.',
'Page trail (`nav` + `ol`) using react-router `Link`. Never uses headings — keep a single page `h1` on `FormHeader` or the page title.',
},
},
},
@ -47,15 +47,14 @@ export const SingleItem: Story = {
export const WithFormHeader: Story = {
render: () => (
<div className="space-y-0 border border-gray-200 bg-white">
<div className="bg-gray-50 px-4 py-2">
<Breadcrumb
className="bg-gray-50 px-4 py-2"
items={[
{ label: 'Admin', to: '/admin' },
{ label: 'Shop', to: '/admin/shop' },
{ label: 'Edit item' },
]}
/>
</div>
<FormHeader>Edit shop item</FormHeader>
<div className="p-4 text-sm text-gray-600">Form content</div>
</div>