mirror of
https://github.com/MAKS-IT-COM/maksit-webui.git
synced 2026-08-15 18:08:09 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd575eb926 | ||
|
|
55611f8c3b |
16
CHANGELOG.md
16
CHANGELOG.md
@ -4,6 +4,22 @@ 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).
|
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
|
## [0.4.3] - 2026-08-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@ -1,27 +1,12 @@
|
|||||||
import {
|
import { type FC, type ReactNode } from 'react'
|
||||||
type ComponentType,
|
import { Link } from 'react-router-dom'
|
||||||
type FC,
|
|
||||||
type ReactNode,
|
|
||||||
} from 'react'
|
|
||||||
|
|
||||||
export interface BreadcrumbItem {
|
export interface BreadcrumbItem {
|
||||||
label: ReactNode
|
label: ReactNode
|
||||||
/** Used by default `<a>` and as fallback for router links. */
|
/** Omit on the current page (last item). */
|
||||||
href?: string
|
|
||||||
/** Preferred when injecting react-router `Link`. */
|
|
||||||
to?: string
|
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 {
|
export interface BreadcrumbProps {
|
||||||
items: BreadcrumbItem[]
|
items: BreadcrumbItem[]
|
||||||
/** Defaults to `"/"`. */
|
/** Defaults to `"/"`. */
|
||||||
@ -30,35 +15,21 @@ export interface BreadcrumbProps {
|
|||||||
linkClassName?: string
|
linkClassName?: string
|
||||||
currentClassName?: string
|
currentClassName?: string
|
||||||
separatorClassName?: 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"`. */
|
/** Accessible name for the nav landmark. Defaults to `"Breadcrumb"`. */
|
||||||
label?: string
|
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.
|
* Page trail (`nav` + `ol`). Links use react-router `Link`.
|
||||||
* Keep a single page `h1` on `FormHeader` or the page title.
|
* Never uses headings — keep a single page `h1` on `FormHeader` or the page title.
|
||||||
*/
|
*/
|
||||||
const Breadcrumb: FC<BreadcrumbProps> = ({
|
const Breadcrumb: FC<BreadcrumbProps> = ({
|
||||||
items,
|
items,
|
||||||
separator = '/',
|
separator = '/',
|
||||||
className = '',
|
className = '',
|
||||||
linkClassName = 'text-sky-700 hover:text-sky-900 hover:underline',
|
linkClassName = 'text-slate-500 hover:text-slate-800 hover:underline',
|
||||||
currentClassName = 'text-gray-700',
|
currentClassName = 'text-slate-700',
|
||||||
separatorClassName = 'text-gray-400',
|
separatorClassName = 'text-slate-400',
|
||||||
linkComponent: LinkComponent = DefaultLink,
|
|
||||||
label = 'Breadcrumb',
|
label = 'Breadcrumb',
|
||||||
}) => {
|
}) => {
|
||||||
if (items.length === 0)
|
if (items.length === 0)
|
||||||
@ -72,8 +43,7 @@ const Breadcrumb: FC<BreadcrumbProps> = ({
|
|||||||
<ol className={'flex flex-wrap items-center gap-x-2 gap-y-1'}>
|
<ol className={'flex flex-wrap items-center gap-x-2 gap-y-1'}>
|
||||||
{items.map((item, index) => {
|
{items.map((item, index) => {
|
||||||
const isLast = index === items.length - 1
|
const isLast = index === items.length - 1
|
||||||
const target = item.to ?? item.href
|
const isLink = !isLast && Boolean(item.to)
|
||||||
const isLink = !isLast && Boolean(target)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
@ -89,15 +59,13 @@ const Breadcrumb: FC<BreadcrumbProps> = ({
|
|||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{isLink ? (
|
{isLink && item.to ? (
|
||||||
<LinkComponent
|
<Link
|
||||||
href={item.href ?? (typeof item.to === 'string' ? item.to : undefined)}
|
to={item.to}
|
||||||
to={item.to ?? item.href}
|
|
||||||
className={linkClassName}
|
className={linkClassName}
|
||||||
{...(item.linkProps ?? {})}
|
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
</LinkComponent>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
className={currentClassName}
|
className={currentClassName}
|
||||||
|
|||||||
@ -17,12 +17,15 @@ export interface CookieConsentLink {
|
|||||||
linkProps?: Record<string, unknown>
|
linkProps?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConsentLinkComponent = ComponentType<{
|
/**
|
||||||
|
* 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
|
||||||
href?: string
|
href?: string
|
||||||
to?: string
|
|
||||||
className?: string
|
className?: string
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
[key: string]: unknown
|
|
||||||
}>
|
}>
|
||||||
|
|
||||||
export interface CookieConsentProps {
|
export interface CookieConsentProps {
|
||||||
@ -33,13 +36,13 @@ export interface CookieConsentProps {
|
|||||||
cookieName?: string
|
cookieName?: string
|
||||||
cookieDays?: number
|
cookieDays?: number
|
||||||
/** Host injects `Link` from react-router (or any anchor-like component). Defaults to `<a>`. */
|
/** Host injects `Link` from react-router (or any anchor-like component). Defaults to `<a>`. */
|
||||||
linkComponent?: ConsentLinkComponent
|
linkComponent?: CookieConsentLinkComponent
|
||||||
onAccept?: () => void
|
onAccept?: () => void
|
||||||
onDismiss?: () => void
|
onDismiss?: () => void
|
||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const DefaultLink: ConsentLinkComponent = ({
|
const DefaultLink: CookieConsentLinkComponent = ({
|
||||||
href,
|
href,
|
||||||
to,
|
to,
|
||||||
children,
|
children,
|
||||||
@ -108,18 +111,24 @@ const CookieConsent: FC<CookieConsentProps> = ({
|
|||||||
{message}
|
{message}
|
||||||
{links.length > 0 ? (
|
{links.length > 0 ? (
|
||||||
<ul className={'mt-2 flex flex-wrap gap-x-3 gap-y-1'}>
|
<ul className={'mt-2 flex flex-wrap gap-x-3 gap-y-1'}>
|
||||||
{links.map((link, index) => (
|
{links.map((link, index) => {
|
||||||
<li key={index}>
|
const target = link.to ?? link.href
|
||||||
<LinkComponent
|
if (!target)
|
||||||
href={link.href ?? (typeof link.to === 'string' ? link.to : undefined)}
|
return null
|
||||||
to={link.to ?? link.href}
|
|
||||||
className={'text-sky-700 underline hover:text-sky-900'}
|
return (
|
||||||
{...(link.linkProps ?? {})}
|
<li key={index}>
|
||||||
>
|
<LinkComponent
|
||||||
{link.label}
|
href={link.href ?? (typeof link.to === 'string' ? link.to : undefined)}
|
||||||
</LinkComponent>
|
to={target}
|
||||||
</li>
|
className={'text-sky-700 underline hover:text-sky-900'}
|
||||||
))}
|
{...(link.linkProps ?? {})}
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</LinkComponent>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
export { CookieConsent } from './CookieConsent'
|
export { CookieConsent } from './CookieConsent'
|
||||||
export type { CookieConsentProps, CookieConsentLink } from './CookieConsent'
|
export type {
|
||||||
|
CookieConsentProps,
|
||||||
|
CookieConsentLink,
|
||||||
|
CookieConsentLinkComponent,
|
||||||
|
} from './CookieConsent'
|
||||||
export { getCookie, setCookie } from './cookies'
|
export { getCookie, setCookie } from './cookies'
|
||||||
|
|||||||
@ -22,7 +22,11 @@ export type { MasonryProps } from './components/Masonry'
|
|||||||
export { LightBox } from './components/LightBox'
|
export { LightBox } from './components/LightBox'
|
||||||
export type { LightBoxProps, LightBoxSlide } from './components/LightBox'
|
export type { LightBoxProps, LightBoxSlide } from './components/LightBox'
|
||||||
export { CookieConsent, getCookie, setCookie } from './components/CookieConsent'
|
export { CookieConsent, getCookie, setCookie } from './components/CookieConsent'
|
||||||
export type { CookieConsentProps, CookieConsentLink } from './components/CookieConsent'
|
export type {
|
||||||
|
CookieConsentProps,
|
||||||
|
CookieConsentLink,
|
||||||
|
CookieConsentLinkComponent,
|
||||||
|
} from './components/CookieConsent'
|
||||||
export {
|
export {
|
||||||
WhatsAppButton,
|
WhatsAppButton,
|
||||||
buildWhatsAppHref,
|
buildWhatsAppHref,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@maks-it.com/webui",
|
"name": "@maks-it.com/webui",
|
||||||
"version": "0.4.3",
|
"version": "0.4.5",
|
||||||
"description": "Shared contracts, utilities, and React components for MaksIT WebUI apps",
|
"description": "Shared contracts, utilities, and React components for MaksIT WebUI apps",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.cjs",
|
"main": "./dist/index.cjs",
|
||||||
|
|||||||
@ -11,7 +11,7 @@ const meta = {
|
|||||||
docs: {
|
docs: {
|
||||||
description: {
|
description: {
|
||||||
component:
|
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 = {
|
export const WithFormHeader: Story = {
|
||||||
render: () => (
|
render: () => (
|
||||||
<div className="space-y-0 border border-gray-200 bg-white">
|
<div className="space-y-0 border border-gray-200 bg-white">
|
||||||
<div className="bg-gray-50 px-4 py-2">
|
<Breadcrumb
|
||||||
<Breadcrumb
|
className="bg-gray-50 px-4 py-2"
|
||||||
items={[
|
items={[
|
||||||
{ label: 'Admin', to: '/admin' },
|
{ label: 'Admin', to: '/admin' },
|
||||||
{ label: 'Shop', to: '/admin/shop' },
|
{ label: 'Shop', to: '/admin/shop' },
|
||||||
{ label: 'Edit item' },
|
{ label: 'Edit item' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<FormHeader>Edit shop item</FormHeader>
|
<FormHeader>Edit shop item</FormHeader>
|
||||||
<div className="p-4 text-sm text-gray-600">Form content</div>
|
<div className="p-4 text-sm text-gray-600">Form content</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user