mirror of
https://github.com/MAKS-IT-COM/maksit-webui.git
synced 2026-08-15 18:08:09 +02:00
Compare commits
No commits in common. "main" and "v0.4.3" have entirely different histories.
16
CHANGELOG.md
16
CHANGELOG.md
@ -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
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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 (
|
||||
<li key={index}>
|
||||
<LinkComponent
|
||||
href={link.href ?? (typeof link.to === 'string' ? link.to : undefined)}
|
||||
to={target}
|
||||
className={'text-sky-700 underline hover:text-sky-900'}
|
||||
{...(link.linkProps ?? {})}
|
||||
>
|
||||
{link.label}
|
||||
</LinkComponent>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
{links.map((link, index) => (
|
||||
<li key={index}>
|
||||
<LinkComponent
|
||||
href={link.href ?? (typeof link.to === 'string' ? link.to : undefined)}
|
||||
to={link.to ?? link.href}
|
||||
className={'text-sky-700 underline hover:text-sky-900'}
|
||||
{...(link.linkProps ?? {})}
|
||||
>
|
||||
{link.label}
|
||||
</LinkComponent>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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">
|
||||
<Breadcrumb
|
||||
className="bg-gray-50 px-4 py-2"
|
||||
items={[
|
||||
{ label: 'Admin', to: '/admin' },
|
||||
{ label: 'Shop', to: '/admin/shop' },
|
||||
{ label: 'Edit item' },
|
||||
]}
|
||||
/>
|
||||
<div className="bg-gray-50 px-4 py-2">
|
||||
<Breadcrumb
|
||||
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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user