From 86361741309df785589712769f849263e6a0f811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kuna?= <1282324+codecalm@users.noreply.github.com> Date: Fri, 18 Sep 2026 16:16:30 +0200 Subject: [PATCH] Fix chart options, marketing paths and silent failures in shared components (#3093) --- .build/copy-assets.ts | 11 +++++++++++ .build/markup-classes-baseline.txt | 2 -- .build/vite.config.helper.ts | 8 -------- .changeset/fix-shared-component-audit-bugs.md | 6 ++++++ docs/content/ui/plugins/dropzone.mdx | 2 +- docs/middleware.ts | 4 +++- preview/pages/all-elements.astro | 2 +- preview/pages/dashboard-crypto.astro | 2 +- preview/pages/form-elements.astro | 2 +- preview/pages/pay.astro | 2 +- preview/pages/prose.astro | 2 +- preview/pages/screenshot.astro | 4 ++-- preview/pages/stars-rating.astro | 2 +- preview/pages/text-features.astro | 8 ++++---- preview/pages/typography.astro | 2 +- shared/components/cards/CrmStats.astro | 10 +++++++--- shared/components/demo/DataSelect.astro | 12 ++++++++++-- .../components/marketing/MarketingNavbar.astro | 6 +++--- shared/components/marketing/hero/Browser.astro | 5 ++++- .../marketing/sections/Testimonials.astro | 9 ++++++--- shared/components/navbar/NavbarMenu.astro | 8 ++++---- shared/components/navbar/NavbarMenuItem.astro | 4 ++-- .../navbar/NavbarSideNotifications.astro | 6 ++++-- shared/layouts/BaseLayout.astro | 4 ++++ shared/layouts/MarketingLayout.astro | 3 ++- shared/lib/chart-script.test.ts | 11 +++++++++++ shared/lib/chart-script.ts | 6 ++++-- shared/lib/date-format.test.ts | 10 +++++++--- shared/lib/date-format.ts | 9 ++++----- shared/lib/svg.test.ts | 4 ++++ shared/lib/svg.ts | 5 ++++- shared/lib/url.test.ts | 17 ++++++++++++++++- shared/lib/url.ts | 9 +++++++++ shared/ui/Breadcrumb.astro | 4 ++-- shared/ui/Dropzone.astro | 4 ++-- shared/ui/Empty.astro | 4 ++-- shared/ui/Select.astro | 5 +---- shared/ui/Trending.astro | 14 ++++++++++---- turbo.json | 2 +- 39 files changed, 157 insertions(+), 73 deletions(-) create mode 100644 .changeset/fix-shared-component-audit-bugs.md diff --git a/.build/copy-assets.ts b/.build/copy-assets.ts index 0b2f8ecb9..17dbb9689 100644 --- a/.build/copy-assets.ts +++ b/.build/copy-assets.ts @@ -145,8 +145,19 @@ export function copyAssets({ repo, publicDir, copies, syncDirs = [] }: CopyAsset return } } + // A file deleted from a source dir (a renamed bundle, a cleaned dist) must + // leave public/ too, or the dev server keeps serving it until a restart. + const remove = (file: string) => { + for (const { from, to } of syncDirs) { + if (!file.startsWith(from + sep)) continue + rmSync(join(to, relative(from, file)), { force: true }) + scheduleReload(file) + return + } + } server.watcher.on('add', sync) server.watcher.on('change', sync) + server.watcher.on('unlink', remove) }, }, } diff --git a/.build/markup-classes-baseline.txt b/.build/markup-classes-baseline.txt index 090026cf6..af8f12fdb 100644 --- a/.build/markup-classes-baseline.txt +++ b/.build/markup-classes-baseline.txt @@ -10,8 +10,6 @@ docs-classnames docs-page-lead docs-search docs-theme-toggle -dropzone-msg-desc -dropzone-msg-title fallback github-dark icon-tabler diff --git a/.build/vite.config.helper.ts b/.build/vite.config.helper.ts index b06d9c670..ffa80f785 100644 --- a/.build/vite.config.helper.ts +++ b/.build/vite.config.helper.ts @@ -46,14 +46,6 @@ export function createViteConfig({ entry, name, fileName, formats, outDir, banne define: { 'process.env.NODE_ENV': '"production"', }, - esbuild: { - target: 'es2015', - tsconfigRaw: { - compilerOptions: { - target: 'ES2015', - }, - }, - }, } return defineConfig(config) diff --git a/.changeset/fix-shared-component-audit-bugs.md b/.changeset/fix-shared-component-audit-bugs.md new file mode 100644 index 000000000..0540f620c --- /dev/null +++ b/.changeset/fix-shared-component-audit-bugs.md @@ -0,0 +1,6 @@ +--- +"@tabler/preview": patch +"@tabler/docs": patch +--- + +Fixed `Trending` signs, ApexCharts 7 legend markers, marketing asset paths, `pageLibs` checks and the notifications close button. diff --git a/docs/content/ui/plugins/dropzone.mdx b/docs/content/ui/plugins/dropzone.mdx index 5c761570d..8afc543d4 100644 --- a/docs/content/ui/plugins/dropzone.mdx +++ b/docs/content/ui/plugins/dropzone.mdx @@ -78,7 +78,7 @@ Add the `multiple` attribute to the input to accept several files at once, for e You can replace the default message in the drop area with your own markup, for example an icon and a hint about the accepted file types: -

Your text here

Your custom description here
+

Your text here

Your custom description here
Custom content replaces the button Dropzone renders by default, and that button is the only part of the drop area a keyboard can reach. Keep a focusable control - such as the one above - inside your own message. diff --git a/docs/middleware.ts b/docs/middleware.ts index 936750939..a91a17449 100644 --- a/docs/middleware.ts +++ b/docs/middleware.ts @@ -22,10 +22,12 @@ const alternate = (path: string, type: string) => `<${path}>; rel="alternate"; t export default function middleware(request: Request) { const url = new URL(request.url) + // redirect keys have no trailing slash, so "/ui/base/markdown/" must match "/ui/base/markdown" + const path = url.pathname.length > 1 ? url.pathname.replace(/\/+$/, '') : url.pathname // renamed pages fall through to their 301; negotiation reruns on the target // not Object.hasOwn — Vercel's middleware type-check runs with a pre-es2022 lib - if (Object.prototype.hasOwnProperty.call(redirects, url.pathname)) return next() + if (Object.prototype.hasOwnProperty.call(redirects, path)) return next() const format = preferredFormat(request.headers.get('accept')) diff --git a/preview/pages/all-elements.astro b/preview/pages/all-elements.astro index 0314b0160..bc9d2ebf5 100644 --- a/preview/pages/all-elements.astro +++ b/preview/pages/all-elements.astro @@ -70,7 +70,7 @@ function greetUser(name) { }` --- - +
diff --git a/preview/pages/dashboard-crypto.astro b/preview/pages/dashboard-crypto.astro index f99ca0a36..87dfb6461 100644 --- a/preview/pages/dashboard-crypto.astro +++ b/preview/pages/dashboard-crypto.astro @@ -192,7 +192,7 @@ const operationCurrencies = currencies.slice(0, 20) {market.price} {market.volume} - + )) diff --git a/preview/pages/form-elements.astro b/preview/pages/form-elements.astro index c4e9a1af4..0e20af2b5 100644 --- a/preview/pages/form-elements.astro +++ b/preview/pages/form-elements.astro @@ -52,7 +52,7 @@ const rows = [ ] --- - +
diff --git a/preview/pages/pay.astro b/preview/pages/pay.astro index 4ec7fd61d..9814923c1 100644 --- a/preview/pages/pay.astro +++ b/preview/pages/pay.astro @@ -7,7 +7,7 @@ import FormGroup from '@ui/FormGroup.astro' import Icon from '@ui/Icon.astro' --- - +
diff --git a/preview/pages/prose.astro b/preview/pages/prose.astro index 52b5f7918..bbd9afbcc 100644 --- a/preview/pages/prose.astro +++ b/preview/pages/prose.astro @@ -102,7 +102,7 @@ import DocsLink from '@ui/DocsLink.astro'

Supporting images

Images should support the text and provide useful context, not just decorate the page.

- Open book and coffee + Open book and coffee
A quiet spot makes for better writing sessions.
diff --git a/preview/pages/screenshot.astro b/preview/pages/screenshot.astro index a7a3c6722..3f8bba2e8 100644 --- a/preview/pages/screenshot.astro +++ b/preview/pages/screenshot.astro @@ -4,7 +4,7 @@ import Logo from '@shared/components/navbar/Logo.astro' import { site } from '@shared/lib/site' --- - + diff --git a/preview/pages/stars-rating.astro b/preview/pages/stars-rating.astro index 3d4f85be4..3db98c93f 100644 --- a/preview/pages/stars-rating.astro +++ b/preview/pages/stars-rating.astro @@ -8,7 +8,7 @@ import CardSubtitle from '@ui/CardSubtitle.astro' import DocsLink from '@ui/DocsLink.astro' --- - +
diff --git a/preview/pages/text-features.astro b/preview/pages/text-features.astro index bfefed449..4318def12 100644 --- a/preview/pages/text-features.astro +++ b/preview/pages/text-features.astro @@ -57,7 +57,7 @@ const homepage = siteData.homepage Heading {i} by{' '} <> - + @ JohnDoe @@ -76,9 +76,9 @@ const homepage = siteData.homepage

- Hey @JohnDoe, have you seen the latest updates on #WebDevelopment16? @JaneSmith just shared an interesting article about Messenger and Netflix! + Hey @JohnDoe, have you seen the latest updates on #WebDevelopment16? @JaneSmith just shared an interesting article about Messenger and Netflix!

diff --git a/preview/pages/typography.astro b/preview/pages/typography.astro index 79f15d689..813f1d2f9 100644 --- a/preview/pages/typography.astro +++ b/preview/pages/typography.astro @@ -15,7 +15,7 @@ import DocsLink from '@ui/DocsLink.astro'

Good coffee isn't about expensive gear — it's about consistency. Get the ratio, the grind, and the water temperature right, and almost any method will taste better.

- A cup of coffee next to an open book + A cup of coffee next to an open book
Ten minutes of prep, and the rest of the morning is yours.
diff --git a/shared/components/cards/CrmStats.astro b/shared/components/cards/CrmStats.astro index acc2e5eb6..545c7b322 100644 --- a/shared/components/cards/CrmStats.astro +++ b/shared/components/cards/CrmStats.astro @@ -44,9 +44,13 @@ const avatarClass = [color ? `bg-${color}${lt ? '-lt' : ' text-white'}` : '', 'a
-
- -
+ { + changeValue !== undefined && ( +
+ +
+ ) + }
diff --git a/shared/components/demo/DataSelect.astro b/shared/components/demo/DataSelect.astro index 7c81ea01a..bc82290ed 100644 --- a/shared/components/demo/DataSelect.astro +++ b/shared/components/demo/DataSelect.astro @@ -31,8 +31,16 @@ const { } = Astro.props; const id = idProp ?? keyProp; +if (!id) { + throw new Error('DataSelect: pass an `id` or a `key` — the select needs one for its id'); +} const key = keyProp ?? 'people'; -const data = (selects as Record)[key] ?? {}; +let data = (selects as Record)[key]; +if (!data && !values) { + throw new Error(`DataSelect: unknown key "${key}" — not in @data/selects.json`); +} +// `values` alone needs no selects.json entry. +data ??= {}; const isMultiple = Boolean(data.multiple); @@ -77,4 +85,4 @@ if (values) { } --- -{id && diff --git a/shared/components/marketing/MarketingNavbar.astro b/shared/components/marketing/MarketingNavbar.astro index 8b6ffc38c..925a3adb6 100644 --- a/shared/components/marketing/MarketingNavbar.astro +++ b/shared/components/marketing/MarketingNavbar.astro @@ -1,8 +1,8 @@ --- -// Relative base is "../" for one-level-deep marketing pages. import Logo from '../navbar/Logo.astro' +import { relativeBase } from '@shared/lib/url' -const base = '..' +const base = relativeBase(Astro.url.pathname) --- @@ -16,7 +16,7 @@ const base = '..'
- +
diff --git a/shared/components/marketing/sections/Testimonials.astro b/shared/components/marketing/sections/Testimonials.astro index 5ee665333..7761fda4e 100644 --- a/shared/components/marketing/sections/Testimonials.astro +++ b/shared/components/marketing/sections/Testimonials.astro @@ -1,8 +1,8 @@ --- -// Marketing pages live one level deep, so the avatar asset base is "..", passed to Avatar via `base`. import testimonials from '@data/testimonials.json' import { people } from '@shared/lib/people' import Avatar from '@ui/Avatar.astro' +import { relativeBase } from '@shared/lib/url' interface Props { class?: string @@ -12,12 +12,15 @@ interface Props { const { class: className, limit = 99, hideHeader } = Astro.props +const base = relativeBase(Astro.url.pathname) + const sectionClass = ['section', className] // Slice testimonials and split into 3 columns. const list = (testimonials as string[]).slice(0, limit) const n = 3 -const chunkSize = Math.round(list.length / n) +// At least 1, or a limit below 2 would never advance the loop below. +const chunkSize = Math.max(1, Math.round(list.length / n)) const groups: string[][] = [] for (let i = 0; i < list.length; i += chunkSize) { groups.push(list.slice(i, i + chunkSize)) @@ -56,7 +59,7 @@ const cards = groups.map((group) =>
- +

{person.full_name}

diff --git a/shared/components/navbar/NavbarMenu.astro b/shared/components/navbar/NavbarMenu.astro index e5b9bd0c0..d1273fccf 100644 --- a/shared/components/navbar/NavbarMenu.astro +++ b/shared/components/navbar/NavbarMenu.astro @@ -1,6 +1,6 @@ --- import Icon from '@ui/Icon.astro' -import NavbarMenuItem from './NavbarMenuItem.astro' +import NavbarMenuItem, { type Level2 } from './NavbarMenuItem.astro' import menu from '@data/menu.json' import menuSample from '@data/menu-sample.json' @@ -16,7 +16,7 @@ interface Level1 { 'columns'?: number /** section label rendered before this item (sidebar only, needs the `sections` prop) */ 'section'?: string - 'children'?: Record> + 'children'?: Record } interface Props { @@ -84,13 +84,13 @@ const chunk = (arr: T[], size: number): T[][] => { {columns.map((column) => ( ))}
) : ( - children.map(([childKey, child]) => ) + children.map(([childKey, child]) => ) )}
)} diff --git a/shared/components/navbar/NavbarMenuItem.astro b/shared/components/navbar/NavbarMenuItem.astro index 1e0a50c35..59d916204 100644 --- a/shared/components/navbar/NavbarMenuItem.astro +++ b/shared/components/navbar/NavbarMenuItem.astro @@ -3,13 +3,13 @@ import { isExternal } from '@shared/lib/url' import Icon from '@ui/Icon.astro' -interface Level3 { +export interface Level3 { title: string url?: string badge?: string } -interface Level2 extends Level3 { +export interface Level2 extends Level3 { icon?: string color?: string children?: Record diff --git a/shared/components/navbar/NavbarSideNotifications.astro b/shared/components/navbar/NavbarSideNotifications.astro index 6b207f2b7..9d56eb382 100644 --- a/shared/components/navbar/NavbarSideNotifications.astro +++ b/shared/components/navbar/NavbarSideNotifications.astro @@ -99,11 +99,13 @@ import CaptureScript from '../CaptureScript.astro' diff --git a/shared/layouts/BaseLayout.astro b/shared/layouts/BaseLayout.astro index e1a74e816..0ba1aa0d8 100644 --- a/shared/layouts/BaseLayout.astro +++ b/shared/layouts/BaseLayout.astro @@ -36,6 +36,10 @@ const min = isDevelopment ? '' : '.min' const rtlSuffix = rtl ? '.rtl' : '' type Lib = { npm?: string; js?: string[]; css?: string[]; head?: boolean } +const unknownLibs = pageLibs.filter((name) => !(name in libs)) +if (unknownLibs.length > 0) { + throw new Error(`BaseLayout: unknown pageLibs ${unknownLibs.map((name) => `"${name}"`).join(', ')} — use a key from core/libs.json`) +} const pageLibEntries = Object.entries(libs as Record).filter(([name]) => pageLibs.includes(name)) // Google Maps API key comes from the environment (GOOGLE_MAPS_KEY in .env or the // host's settings), never from the source. Without it the script loads keyless. diff --git a/shared/layouts/MarketingLayout.astro b/shared/layouts/MarketingLayout.astro index be193e83d..20c143728 100644 --- a/shared/layouts/MarketingLayout.astro +++ b/shared/layouts/MarketingLayout.astro @@ -5,6 +5,7 @@ import MarketingNavbar from '@shared/components/marketing/MarketingNavbar.astro' import Payment from '@ui/Payment.astro' import Icon from '@ui/Icon.astro' import { site } from '@shared/lib/site' +import { relativeBase } from '@shared/lib/url' interface Props { title?: string @@ -14,7 +15,7 @@ interface Props { const { title, pageLibs } = Astro.props -const base = '..' +const base = relativeBase(Astro.url.pathname) --- diff --git a/shared/lib/chart-script.test.ts b/shared/lib/chart-script.test.ts index 8512e9c62..2836718b2 100644 --- a/shared/lib/chart-script.test.ts +++ b/shared/lib/chart-script.test.ts @@ -136,4 +136,15 @@ describe('chartConfig', () => { expect(xFormatterExpr).toBe('val + "K"') expect(JSON.stringify(config)).not.toContain('val + "K"') }) + + it('uses the ApexCharts 7 legend marker options', () => { + const { config } = chartConfig({ id: 'demo', data: { series: [], legend: true }, height: 10 }) + expect(config.legend).toMatchObject({ markers: { size: 5, strokeWidth: 0, shape: 'circle' } }) + }) + + it('hides line points, including the hover dot, with hide-points', () => { + const { config } = chartConfig({ id: 'demo', data: { 'series': [], 'hide-points': true }, height: 10 }) + expect(config.markers).toEqual({ size: 0, hover: { sizeOffset: 0 } }) + expect(config).not.toHaveProperty('point') + }) }) diff --git a/shared/lib/chart-script.ts b/shared/lib/chart-script.ts index 1f41b8ca7..9e15dae3d 100644 --- a/shared/lib/chart-script.ts +++ b/shared/lib/chart-script.ts @@ -444,7 +444,8 @@ export function chartConfig(opts: { id: string; data: ChartData; height: number show: true, position: 'bottom', offsetY: 12, - markers: { width: 10, height: 10, radius: 100 }, + // v7 sizes the legend dot as (size + strokeWidth) * 2 — a 10px circle. + markers: { size: 5, strokeWidth: 0, shape: 'circle' }, itemMargin: { horizontal: 8, vertical: 8 }, } : { show: false } @@ -458,7 +459,8 @@ export function chartConfig(opts: { id: string; data: ChartData; height: number } if (data['hide-points']) { - config.point = { show: false } + // hover.size 0 counts as unset in v7, so the hover dot is removed through sizeOffset. + config.markers = { size: 0, hover: { sizeOffset: 0 } } } if (data['show-markers']) { diff --git a/shared/lib/date-format.test.ts b/shared/lib/date-format.test.ts index dab64b60b..629612657 100644 --- a/shared/lib/date-format.test.ts +++ b/shared/lib/date-format.test.ts @@ -12,9 +12,13 @@ describe('formatCommitDate', () => { }) describe('toUnixSeconds', () => { - it('shifts the wall-clock epoch by the local timezone offset', () => { - const date = new Date(Date.UTC(2024, 0, 1, 0, 0, 0)) - expect(toUnixSeconds(date)).toBe(Math.floor(date.getTime() / 1000) + date.getTimezoneOffset() * 60) + it('returns the true epoch seconds', () => { + expect(toUnixSeconds(new Date(Date.UTC(2024, 0, 1, 0, 0, 0)))).toBe(1704067200) + }) + + it('does not depend on DST (summer and winter dates)', () => { + expect(toUnixSeconds(new Date(Date.UTC(2024, 6, 1, 12, 0, 0)))).toBe(1719835200) + expect(toUnixSeconds(new Date(Date.UTC(2024, 0, 1, 12, 0, 0, 999)))).toBe(1704110400) }) }) diff --git a/shared/lib/date-format.ts b/shared/lib/date-format.ts index 0a8b7137c..7c7dd82dc 100644 --- a/shared/lib/date-format.ts +++ b/shared/lib/date-format.ts @@ -1,12 +1,11 @@ -// Demo data date formatting quirks: -// - unix seconds are shifted by local timezone offset (not the true epoch) -// - long-date format uses UTC wall-clock components +// Demo data is rendered at build time, so every formatter reads UTC components +// and never the local timezone — output must match between CI and local builds. const SHORT_MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] -/** Unix seconds shifted by the local timezone offset (not the true epoch). */ +/** Unix epoch seconds, independent of the build machine's timezone. */ export function toUnixSeconds(date: Date): number { - return Math.floor(date.getTime() / 1000) + date.getTimezoneOffset() * 60 + return Math.floor(date.getTime() / 1000) } /** Formats a date as "Month DD, YYYY" using its UTC components. */ diff --git a/shared/lib/svg.test.ts b/shared/lib/svg.test.ts index c23a5c279..11404e395 100644 --- a/shared/lib/svg.test.ts +++ b/shared/lib/svg.test.ts @@ -42,6 +42,10 @@ describe('freeIllustrationSource', () => { it('returns the auto-dark source of a bundled illustration', () => { expect(freeIllustrationSource('not-found')).toContain(' { + expect(() => freeIllustrationSource('no-such-illustration' as never)).toThrow(/Unknown illustration/) + }) }) describe('illustrationSvg', () => { diff --git a/shared/lib/svg.ts b/shared/lib/svg.ts index 0359f75b5..77be63910 100644 --- a/shared/lib/svg.ts +++ b/shared/lib/svg.ts @@ -11,7 +11,10 @@ export type IllustrationImage = FreeIllustration | `${FreeIllustration}.svg` /** Source SVG of a bundled illustration, in its auto-dark variant. */ export function freeIllustrationSource(image: IllustrationImage): string { - return freeIllustrations.autodark[image.replaceAll('.svg', '') as FreeIllustration] ?? '' + const name = image.replace(/\.svg$/, '') + const source = (freeIllustrations.autodark as Record)[name] + if (!source) throw new Error(`Unknown illustration "${image}" — not in @data/free-illustrations.json`) + return source } type IconRecord = { svg: Record } diff --git a/shared/lib/url.test.ts b/shared/lib/url.test.ts index cb6740261..f232fdfa0 100644 --- a/shared/lib/url.test.ts +++ b/shared/lib/url.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { isExternal } from './url' +import { isExternal, relativeBase } from './url' describe('isExternal', () => { it('is true for absolute http(s) urls', () => { @@ -24,3 +24,18 @@ describe('isExternal (anchored)', () => { expect(isExternal('/redirect?to=https://example.com')).toBe(false) }) }) + +describe('relativeBase', () => { + it('is "." for top-level pages, including a folder index built as a root file', () => { + expect(relativeBase('/')).toBe('.') + expect(relativeBase('/marketing')).toBe('.') + expect(relativeBase('/marketing/')).toBe('.') + expect(relativeBase('/marketing.html')).toBe('.') + }) + + it('climbs one level per folder', () => { + expect(relativeBase('/marketing/about')).toBe('..') + expect(relativeBase('/marketing/about.html')).toBe('..') + expect(relativeBase('/a/b/c.html')).toBe('../..') + }) +}) diff --git a/shared/lib/url.ts b/shared/lib/url.ts index 8c3a85d10..cbb5124d0 100644 --- a/shared/lib/url.ts +++ b/shared/lib/url.ts @@ -4,3 +4,12 @@ export function isExternal(url?: string): boolean { return !!url && /^https?:\/\//.test(url) } + +/** + * Relative path from a page back to the site root: "." for a top-level page, ".." one level deep. + * Pages are built with `format: 'file'`, so "/marketing" (or "/marketing.html") is a root file. + */ +export function relativeBase(pathname: string): string { + const depth = pathname.split('/').filter(Boolean).length - 1 + return depth > 0 ? Array.from({ length: depth }, () => '..').join('/') : '.' +} diff --git a/shared/ui/Breadcrumb.astro b/shared/ui/Breadcrumb.astro index c73eae0f7..3d36973b6 100644 --- a/shared/ui/Breadcrumb.astro +++ b/shared/ui/Breadcrumb.astro @@ -32,8 +32,8 @@ const classes = ['breadcrumb', className, separator && `breadcrumb-${separator}` ) : (