Compare commits

..

No commits in common. "main" and "v0.4.3" have entirely different histories.
main ... v0.4.3

7 changed files with 75 additions and 75 deletions

View File

@ -4,22 +4,6 @@ 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
- `Breadcrumb` / `CookieConsent` `linkComponent` typing: `to` is required on the injected link props (and always passed when rendering links), so react-router `Link` is assignable without a host adapter. Exports: `BreadcrumbLinkComponent`, `CookieConsentLinkComponent`.
## [0.4.3] - 2026-08-06
### Added

View File

@ -1,12 +1,27 @@
import { type FC, type ReactNode } from 'react'
import { Link } from 'react-router-dom'
import {
type ComponentType,
type FC,
type ReactNode,
} from 'react'
export interface BreadcrumbItem {
label: ReactNode
/** Omit on the current page (last item). */
/** Used by default `<a>` and as fallback for router links. */
href?: string
/** Preferred when injecting react-router `Link`. */
to?: string
/** Extra props passed to the injected link component. */
linkProps?: Record<string, unknown>
}
type BreadcrumbLinkComponent = ComponentType<{
href?: string
to?: string
className?: string
children?: ReactNode
[key: string]: unknown
}>
export interface BreadcrumbProps {
items: BreadcrumbItem[]
/** Defaults to `"/"`. */
@ -15,21 +30,35 @@ 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>
)
/**
* Page trail (`nav` + `ol`). Links use react-router `Link`.
* Never uses headings keep a single page `h1` on `FormHeader` or the page title.
* Presentational page trail. Use links/`span` only never headings.
* Keep a single page `h1` on `FormHeader` or the page title.
*/
const Breadcrumb: FC<BreadcrumbProps> = ({
items,
separator = '/',
className = '',
linkClassName = 'text-slate-500 hover:text-slate-800 hover:underline',
currentClassName = 'text-slate-700',
separatorClassName = 'text-slate-400',
linkClassName = 'text-sky-700 hover:text-sky-900 hover:underline',
currentClassName = 'text-gray-700',
separatorClassName = 'text-gray-400',
linkComponent: LinkComponent = DefaultLink,
label = 'Breadcrumb',
}) => {
if (items.length === 0)
@ -43,7 +72,8 @@ 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 isLink = !isLast && Boolean(item.to)
const target = item.to ?? item.href
const isLink = !isLast && Boolean(target)
return (
<li
@ -59,13 +89,15 @@ const Breadcrumb: FC<BreadcrumbProps> = ({
</span>
) : null}
{isLink && item.to ? (
<Link
to={item.to}
{isLink ? (
<LinkComponent
href={item.href ?? (typeof item.to === 'string' ? item.to : undefined)}
to={item.to ?? item.href}
className={linkClassName}
{...(item.linkProps ?? {})}
>
{item.label}
</Link>
</LinkComponent>
) : (
<span
className={currentClassName}

View File

@ -17,15 +17,12 @@ export interface CookieConsentLink {
linkProps?: Record<string, unknown>
}
/**
* Injectable link surface for SPA routers.
* `to` is always provided when CookieConsent renders a link, so react-router `Link` is assignable.
*/
export type CookieConsentLinkComponent = ComponentType<{
to: string
type ConsentLinkComponent = ComponentType<{
href?: string
to?: string
className?: string
children?: ReactNode
[key: string]: unknown
}>
export interface CookieConsentProps {
@ -36,13 +33,13 @@ export interface CookieConsentProps {
cookieName?: string
cookieDays?: number
/** Host injects `Link` from react-router (or any anchor-like component). Defaults to `<a>`. */
linkComponent?: CookieConsentLinkComponent
linkComponent?: ConsentLinkComponent
onAccept?: () => void
onDismiss?: () => void
className?: string
}
const DefaultLink: CookieConsentLinkComponent = ({
const DefaultLink: ConsentLinkComponent = ({
href,
to,
children,
@ -111,24 +108,18 @@ const CookieConsent: FC<CookieConsentProps> = ({
{message}
{links.length > 0 ? (
<ul className={'mt-2 flex flex-wrap gap-x-3 gap-y-1'}>
{links.map((link, index) => {
const target = link.to ?? link.href
if (!target)
return null
return (
{links.map((link, index) => (
<li key={index}>
<LinkComponent
href={link.href ?? (typeof link.to === 'string' ? link.to : undefined)}
to={target}
to={link.to ?? link.href}
className={'text-sky-700 underline hover:text-sky-900'}
{...(link.linkProps ?? {})}
>
{link.label}
</LinkComponent>
</li>
)
})}
))}
</ul>
) : null}
</div>

View File

@ -1,7 +1,3 @@
export { CookieConsent } from './CookieConsent'
export type {
CookieConsentProps,
CookieConsentLink,
CookieConsentLinkComponent,
} from './CookieConsent'
export type { CookieConsentProps, CookieConsentLink } from './CookieConsent'
export { getCookie, setCookie } from './cookies'

View File

@ -22,11 +22,7 @@ export type { MasonryProps } from './components/Masonry'
export { LightBox } from './components/LightBox'
export type { LightBoxProps, LightBoxSlide } from './components/LightBox'
export { CookieConsent, getCookie, setCookie } from './components/CookieConsent'
export type {
CookieConsentProps,
CookieConsentLink,
CookieConsentLinkComponent,
} from './components/CookieConsent'
export type { CookieConsentProps, CookieConsentLink } from './components/CookieConsent'
export {
WhatsAppButton,
buildWhatsAppHref,

View File

@ -1,6 +1,6 @@
{
"name": "@maks-it.com/webui",
"version": "0.4.5",
"version": "0.4.3",
"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:
'Page trail (`nav` + `ol`) using react-router `Link`. Never uses headings — keep a single page `h1` on `FormHeader` or the page title.',
'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.',
},
},
},
@ -47,14 +47,15 @@ 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>