@moontra/moonui 6.7.0 → 6.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moontra/moonui",
3
- "version": "6.7.0",
3
+ "version": "6.8.0",
4
4
  "description": "Premium React component library with modern design and customization",
5
5
  "author": "MoonUI",
6
6
  "license": "MIT",
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Grup C — a11y / geçersiz işaretleme düzeltmeleri (#309 breadcrumb, #315 pagination,
3
+ * #322 tabs).
4
+ *
5
+ * Bu testlerin ortak amacı: düzeltilen kusurların HER BİRİ, düzeltme geri alındığında
6
+ * kırmızıya dönecek biçimde sabitlensin. Mevcut suite'ler bu davranışların bir kısmını
7
+ * YANLIŞ yönde sabitliyordu (ör. `aria-current`'ı <li>'de, `aria-hidden`'ı ellipsis
8
+ * sarmalayıcısında doğruluyorlardı) — onlar da bu PR'da gerçeğe çekildi.
9
+ */
10
+ import React from 'react'
11
+ import { render, screen } from '@testing-library/react'
12
+ import '@testing-library/jest-dom'
13
+
14
+ import {
15
+ Breadcrumb,
16
+ BreadcrumbList,
17
+ BreadcrumbItem,
18
+ BreadcrumbEllipsis,
19
+ BreadcrumbSeparator,
20
+ } from '../breadcrumb'
21
+ import {
22
+ Pagination,
23
+ PaginationContent,
24
+ PaginationItem,
25
+ PaginationLink,
26
+ PaginationPrevious,
27
+ PaginationNext,
28
+ PaginationEllipsis,
29
+ } from '../pagination'
30
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '../tabs'
31
+
32
+ /**
33
+ * Bir elemanın erişilebilirlik ağacından düşüp düşmediğini ata zincirine bakarak belirler.
34
+ * `getByText` aria-hidden'ı UMURSAMAZ — bu yüzden "metin DOM'da mı" testi, metnin ekran
35
+ * okuyucuya ULAŞTIĞINI kanıtlamaz. Grup C'nin iki kusuru da tam bu boşlukta saklanmıştı.
36
+ */
37
+ function isHiddenFromAssistiveTech(el: Element | null): boolean {
38
+ let node: Element | null = el
39
+ while (node) {
40
+ if (node.getAttribute('aria-hidden') === 'true') return true
41
+ node = node.parentElement
42
+ }
43
+ return false
44
+ }
45
+
46
+ describe('#309 breadcrumb', () => {
47
+ it('asChild: className KAYBOLMAZ ve React geçersiz-prop uyarısı basılmaz', () => {
48
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
49
+
50
+ render(
51
+ <BreadcrumbItem asChild>
52
+ <a href="/docs" data-testid="child-link">
53
+ Docs
54
+ </a>
55
+ </BreadcrumbItem>
56
+ )
57
+
58
+ const link = screen.getByTestId('child-link')
59
+ // Radix Slot, kendi className'ini çocuğunkiyle BİRLEŞTİRİR. React.Fragment yolunda
60
+ // className tümüyle düşüyordu.
61
+ expect(link.className).toContain('transition-colors')
62
+ expect(link).toHaveAttribute('href', '/docs')
63
+ expect(errorSpy).not.toHaveBeenCalled()
64
+
65
+ errorSpy.mockRestore()
66
+ })
67
+
68
+ it('asChild + isCurrent: aria-current çocuğa iner', () => {
69
+ render(
70
+ <BreadcrumbItem asChild isCurrent>
71
+ <span data-testid="child-page">Current</span>
72
+ </BreadcrumbItem>
73
+ )
74
+ expect(screen.getByTestId('child-page')).toHaveAttribute('aria-current', 'page')
75
+ })
76
+
77
+ it('ellipsis: "More pages" metni erişilebilirlik ağacında', () => {
78
+ render(<BreadcrumbEllipsis />)
79
+ const srText = screen.getByText('More pages')
80
+ expect(isHiddenFromAssistiveTech(srText)).toBe(false)
81
+ })
82
+
83
+ it('ellipsis: dekoratif ikon gizli KALIR', () => {
84
+ const { container } = render(<BreadcrumbEllipsis />)
85
+ const svg = container.querySelector('svg')
86
+ expect(svg).not.toBeNull()
87
+ expect(isHiddenFromAssistiveTech(svg)).toBe(true)
88
+ })
89
+
90
+ it('separator/ellipsis: `icon` prop\'u okunur, DOM\'a sızmaz', () => {
91
+ const { container: sep } = render(
92
+ <BreadcrumbSeparator icon={<i data-testid="sep-icon" />} />
93
+ )
94
+ expect(sep.querySelector('[data-testid="sep-icon"]')).not.toBeNull()
95
+ expect(sep.querySelector('[icon]')).toBeNull()
96
+
97
+ const { container: ell } = render(
98
+ <BreadcrumbEllipsis icon={<i data-testid="ell-icon" />} />
99
+ )
100
+ expect(ell.querySelector('[data-testid="ell-icon"]')).not.toBeNull()
101
+ expect(ell.querySelector('[icon]')).toBeNull()
102
+ })
103
+
104
+ it('kök `separator` alt ayraçlara iner; yerel icon/children onu EZER', () => {
105
+ const { container } = render(
106
+ <Breadcrumb separator={<i data-testid="root-sep" />}>
107
+ <BreadcrumbList>
108
+ <BreadcrumbItem>Home</BreadcrumbItem>
109
+ <BreadcrumbSeparator />
110
+ <BreadcrumbItem>Docs</BreadcrumbItem>
111
+ <BreadcrumbSeparator icon={<i data-testid="local-sep" />} />
112
+ <BreadcrumbItem isCurrent>Breadcrumb</BreadcrumbItem>
113
+ </BreadcrumbList>
114
+ </Breadcrumb>
115
+ )
116
+
117
+ // Kök ayraç context ile indi
118
+ expect(container.querySelectorAll('[data-testid="root-sep"]')).toHaveLength(1)
119
+ // Yerel `icon` kökü ezdi
120
+ expect(container.querySelectorAll('[data-testid="local-sep"]')).toHaveLength(1)
121
+ })
122
+
123
+ it('separator/showHomeIcon `<nav>` DOM\'una SIZMAZ', () => {
124
+ const { container } = render(
125
+ <Breadcrumb separator={<i />} showHomeIcon>
126
+ <BreadcrumbList>
127
+ <BreadcrumbItem>Home</BreadcrumbItem>
128
+ </BreadcrumbList>
129
+ </Breadcrumb>
130
+ )
131
+
132
+ const nav = container.querySelector('nav')!
133
+ // Eskiden ikisi de destructure edilmiyor, `{...props}` ile öznitelik olarak basılıyordu.
134
+ expect(nav.hasAttribute('separator')).toBe(false)
135
+ expect(nav.hasAttribute('showhomeicon')).toBe(false)
136
+ })
137
+ })
138
+
139
+ describe('#315 pagination', () => {
140
+ it('ellipsis: "More pages" metni erişilebilirlik ağacında', () => {
141
+ render(<PaginationEllipsis />)
142
+ const srText = screen.getByText('More pages')
143
+ expect(isHiddenFromAssistiveTech(srText)).toBe(false)
144
+ })
145
+
146
+ it('Previous/Next etiketi küçük ekranda gizlenir, erişilebilir ad korunur', () => {
147
+ render(
148
+ <Pagination>
149
+ <PaginationContent>
150
+ <PaginationItem>
151
+ <PaginationPrevious href="#" />
152
+ </PaginationItem>
153
+ <PaginationItem>
154
+ <PaginationNext href="#" />
155
+ </PaginationItem>
156
+ </PaginationContent>
157
+ </Pagination>
158
+ )
159
+
160
+ // Görsel etiket `sm` altında display:none — 375px'te 88px'lik taşmanın kaynağıydı.
161
+ expect(screen.getByText('Previous')).toHaveClass('hidden', 'sm:inline')
162
+ expect(screen.getByText('Next')).toHaveClass('hidden', 'sm:inline')
163
+
164
+ // Ad `aria-label`'dan geldiği için ekran-okuyucu deneyimi değişmez.
165
+ expect(screen.getByLabelText('Go to previous page')).toBeInTheDocument()
166
+ expect(screen.getByLabelText('Go to next page')).toBeInTheDocument()
167
+ })
168
+
169
+ it('beş alt-component ref iletir', () => {
170
+ const refs = {
171
+ nav: React.createRef<HTMLElement>(),
172
+ link: React.createRef<HTMLAnchorElement>(),
173
+ prev: React.createRef<HTMLAnchorElement>(),
174
+ next: React.createRef<HTMLAnchorElement>(),
175
+ ellipsis: React.createRef<HTMLSpanElement>(),
176
+ }
177
+
178
+ render(
179
+ <Pagination ref={refs.nav}>
180
+ <PaginationContent>
181
+ <PaginationItem>
182
+ <PaginationLink ref={refs.link} href="#">
183
+ 1
184
+ </PaginationLink>
185
+ </PaginationItem>
186
+ <PaginationItem>
187
+ <PaginationPrevious ref={refs.prev} href="#" />
188
+ </PaginationItem>
189
+ <PaginationItem>
190
+ <PaginationNext ref={refs.next} href="#" />
191
+ </PaginationItem>
192
+ <PaginationItem>
193
+ <PaginationEllipsis ref={refs.ellipsis} />
194
+ </PaginationItem>
195
+ </PaginationContent>
196
+ </Pagination>
197
+ )
198
+
199
+ expect(refs.nav.current?.tagName).toBe('NAV')
200
+ expect(refs.link.current?.tagName).toBe('A')
201
+ expect(refs.prev.current?.tagName).toBe('A')
202
+ expect(refs.next.current?.tagName).toBe('A')
203
+ expect(refs.ellipsis.current?.tagName).toBe('SPAN')
204
+ })
205
+ })
206
+
207
+ describe('#322 tabs (free)', () => {
208
+ const renderTabs = (props: React.ComponentProps<typeof Tabs> = {}) =>
209
+ render(
210
+ <Tabs defaultValue="a" {...props}>
211
+ <TabsList data-testid="list">
212
+ <TabsTrigger value="a" data-testid="trigger">
213
+ A
214
+ </TabsTrigger>
215
+ </TabsList>
216
+ <TabsContent value="a">içerik</TabsContent>
217
+ </Tabs>
218
+ )
219
+
220
+ it('orientation="vertical" Root + List + Trigger\'ın ÜÇÜNE birden iner', () => {
221
+ const { container } = renderTabs({ orientation: 'vertical' })
222
+
223
+ // Root: Radix'in kendi data-özniteliği
224
+ expect(container.querySelector('[data-orientation="vertical"]')).not.toBeNull()
225
+ // List ve Trigger: CVA dikey sınıfları — eskiden bunlar Root'tan HİÇ beslenmiyordu,
226
+ // sonuç: klavye yönü dikey, görünüm yatay.
227
+ expect(screen.getByTestId('list').className).toContain('flex-col')
228
+ expect(screen.getByTestId('trigger').className).toContain('justify-start')
229
+ })
230
+
231
+ it('vertical={true} (deprecated) hâlâ çalışır', () => {
232
+ renderTabs({ vertical: true })
233
+ expect(screen.getByTestId('list').className).toContain('flex-col')
234
+ })
235
+
236
+ it('açık orientation, vertical prop\'unu EZER', () => {
237
+ renderTabs({ vertical: true, orientation: 'horizontal' })
238
+ expect(screen.getByTestId('list').className).toContain('flex-row')
239
+ expect(screen.getByTestId('list').className).not.toContain('flex-col')
240
+ })
241
+
242
+ it('alt-component kendi orientation\'ını verirse context\'i ezer', () => {
243
+ render(
244
+ <Tabs defaultValue="a" orientation="vertical">
245
+ <TabsList data-testid="list" orientation="horizontal">
246
+ <TabsTrigger value="a">A</TabsTrigger>
247
+ </TabsList>
248
+ </Tabs>
249
+ )
250
+ expect(screen.getByTestId('list').className).toContain('flex-row')
251
+ })
252
+
253
+ it('Root className verildiğinde `moonui-theme` KAYBOLMAZ', () => {
254
+ const { container } = render(
255
+ <Tabs defaultValue="a" className="custom-root" data-testid="root">
256
+ <TabsList>
257
+ <TabsTrigger value="a">A</TabsTrigger>
258
+ </TabsList>
259
+ </Tabs>
260
+ )
261
+ const root = container.querySelector('[data-testid="root"]')!
262
+ // Eskiden `{...props}` cn() sonucunu tümüyle eziyordu → tema kapsam sınıfı düşüyordu.
263
+ expect(root.className).toContain('moonui-theme')
264
+ expect(root.className).toContain('custom-root')
265
+ })
266
+ })
@@ -333,15 +333,24 @@ describe('Breadcrumb Components', () => {
333
333
  expect(button).toHaveTextContent('Custom Button')
334
334
  })
335
335
 
336
- it('sets aria-current when isCurrent is true', () => {
336
+ // #309: `aria-current` sarmalayıcı <li>'den, geçerli sayfayı TEMSİL EDEN elemana taşındı
337
+ // (APG breadcrumb deseni). Bu test eskiden <li>'yi doğruluyordu — yani daha az doğru olan
338
+ // yerleşimi sabitliyordu.
339
+ it('sets aria-current on the page element (not the wrapper li)', () => {
337
340
  render(
338
341
  <BreadcrumbItem isCurrent data-testid="breadcrumb-item">
339
342
  Current Page
340
343
  </BreadcrumbItem>
341
344
  )
342
-
343
- const item = screen.getByTestId('breadcrumb-item')
344
- expect(item).toHaveAttribute('aria-current', 'page')
345
+
346
+ const wrapper = screen.getByTestId('breadcrumb-item')
347
+ expect(wrapper.tagName).toBe('LI')
348
+ expect(wrapper).not.toHaveAttribute('aria-current')
349
+
350
+ const pageEl = screen.getByText('Current Page')
351
+ expect(pageEl).toHaveAttribute('aria-current', 'page')
352
+ // Çift duyuruyu önlemek için tam olarak BİR eleman taşımalı
353
+ expect(document.querySelectorAll('[aria-current="page"]')).toHaveLength(1)
345
354
  })
346
355
 
347
356
  it('does not set aria-current when isCurrent is false', () => {
@@ -467,7 +476,11 @@ describe('Breadcrumb Components', () => {
467
476
  // Ellipsis de <span> olarak render ediliyor.
468
477
  expect(ellipsis.tagName).toBe('SPAN')
469
478
  expect(ellipsis).toHaveAttribute('role', 'presentation')
470
- expect(ellipsis).toHaveAttribute('aria-hidden', 'true')
479
+ // #309: bu satır eskiden `aria-hidden="true"` DOĞRULUYORDU — yani component'in
480
+ // "More pages" metnini ekran okuyucudan gizleyen kusurunu sabitliyordu. Gizleme
481
+ // artık yalnız dekoratif ikonun sarmalayıcısında.
482
+ expect(ellipsis).not.toHaveAttribute('aria-hidden')
483
+ expect(ellipsis.querySelector('[aria-hidden="true"]')).not.toBeNull()
471
484
  })
472
485
 
473
486
  it('applies custom className', () => {
@@ -539,11 +552,13 @@ describe('Breadcrumb Components', () => {
539
552
 
540
553
  const homeLink = screen.getByText('Home').closest('a')
541
554
  const productsLink = screen.getByText('Products').closest('a')
542
- const currentItem = screen.getByText('Current Product').closest('li')
543
-
555
+ const currentItem = screen.getByText('Current Product')
556
+
544
557
  expect(homeLink).toHaveAttribute('href', '/')
545
558
  expect(productsLink).toHaveAttribute('href', '/products')
559
+ // #309: <li>'de değil, geçerli sayfa elemanında
546
560
  expect(currentItem).toHaveAttribute('aria-current', 'page')
561
+ expect(currentItem.closest('li')).not.toHaveAttribute('aria-current')
547
562
  })
548
563
 
549
564
  it('renders with custom separators', () => {
@@ -635,8 +650,8 @@ describe('Breadcrumb Components', () => {
635
650
  )
636
651
 
637
652
  expect(screen.getByText('Only Item')).toBeInTheDocument()
638
- const item = screen.getByText('Only Item').closest('li')
639
- expect(item).toHaveAttribute('aria-current', 'page')
653
+ // #309: <li>'de değil, geçerli sayfa elemanında
654
+ expect(screen.getByText('Only Item')).toHaveAttribute('aria-current', 'page')
640
655
  })
641
656
 
642
657
  it('handles breadcrumb with only separators', () => {
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from "react";
4
+ import { Slot } from "@radix-ui/react-slot";
4
5
  import { ChevronRight, MoreHorizontal } from "lucide-react";
5
6
  import { cva, type VariantProps } from "class-variance-authority";
6
7
 
@@ -31,10 +32,24 @@ const breadcrumbVariants = cva(
31
32
  interface BreadcrumbProps
32
33
  extends React.HTMLAttributes<HTMLElement>,
33
34
  VariantProps<typeof breadcrumbVariants> {
35
+ /**
36
+ * Alt `BreadcrumbSeparator`'ların varsayılan ayracı. #309: bu prop tipte tanımlıydı ama
37
+ * component gövdesinde HİÇ okunmuyordu → `{...props}` ile `<nav>`'a sızıyor, React
38
+ * bilinmeyen-öznitelik uyarısı basıyordu. Artık context ile alt ayraçlara iniyor;
39
+ * ayracın kendi `children`/`icon` prop'u bunu ezer.
40
+ */
34
41
  separator?: React.ReactNode;
42
+ /**
43
+ * @deprecated Hiçbir zaman uygulanmadı — kaynakta okunmuyordu ve `<nav>`'a sızıyordu
44
+ * (#309'da sızıntı kesildi, davranış eklenmedi). Ev ikonu için ilk `BreadcrumbItem`
45
+ * içine ikonu doğrudan koyun; işaretleme kararı tüketiciye ait olmalı.
46
+ */
35
47
  showHomeIcon?: boolean;
36
48
  }
37
49
 
50
+ /** #309: kök `separator`'ı alt `BreadcrumbSeparator`'lara taşıyan tek kaynak. */
51
+ const BreadcrumbSeparatorContext = React.createContext<React.ReactNode>(undefined);
52
+
38
53
  interface BreadcrumbListProps extends React.HTMLAttributes<HTMLOListElement> {
39
54
  collapsed?: boolean;
40
55
  collapsedWidth?: number;
@@ -57,13 +72,17 @@ interface BreadcrumbEllipsisProps extends React.HTMLAttributes<HTMLSpanElement>
57
72
  }
58
73
 
59
74
  const Breadcrumb = React.forwardRef<HTMLElement, BreadcrumbProps>(
60
- ({ className, variant, size, ...props }, ref) => (
61
- <nav
62
- ref={ref}
63
- className={cn("moonui-theme", breadcrumbVariants({ variant, size }), className)}
64
- aria-label="breadcrumb"
65
- {...props}
66
- />
75
+ // #309: `separator` ve `showHomeIcon` ESKİDEN destructure EDİLMİYORDU → `{...props}` ile
76
+ // `<nav>`'a öznitelik olarak basılıyor, React uyarı veriyordu. İkisi de artık ayrılıyor.
77
+ ({ className, variant, size, separator, showHomeIcon: _showHomeIcon, ...props }, ref) => (
78
+ <BreadcrumbSeparatorContext.Provider value={separator}>
79
+ <nav
80
+ ref={ref}
81
+ className={cn("moonui-theme", breadcrumbVariants({ variant, size }), className)}
82
+ aria-label="breadcrumb"
83
+ {...props}
84
+ />
85
+ </BreadcrumbSeparatorContext.Provider>
67
86
  )
68
87
  );
69
88
  Breadcrumb.displayName = "Breadcrumb";
@@ -109,27 +128,34 @@ const BreadcrumbList = React.forwardRef<HTMLOListElement, BreadcrumbListProps>(
109
128
  BreadcrumbList.displayName = "BreadcrumbList";
110
129
 
111
130
  const BreadcrumbItem = React.forwardRef<HTMLLIElement, BreadcrumbItemProps>(
112
- ({ className, isCurrent, href, asChild = false, ...props }, ref) => {
113
- const Comp = asChild ? React.Fragment : href ? "a" : "span";
114
- const itemProps = asChild ? {} : href ? { href } : {};
131
+ ({ className, isCurrent, href, asChild = false, children, ...props }, ref) => {
132
+ // #309: `asChild` eskiden `React.Fragment`'e düşüyordu. Fragment yalnız `key` ve
133
+ // `children` kabul eder aşağıdaki `className` sessizce KAYBOLUYOR ve React
134
+ // geliştirme modunda geçersiz-prop uyarısı basıyordu. Radix `Slot` prop'ları tek
135
+ // çocuğa birleştirir (className/style dahil) → hem stil hem a11y korunur.
136
+ const Comp = asChild ? Slot : href ? "a" : "span";
137
+ const itemProps = !asChild && href ? { href } : {};
115
138
 
116
139
  return (
117
140
  <li
118
141
  ref={ref}
119
142
  className={cn("moonui-theme", "inline-flex items-center gap-1.5", className)}
120
- aria-current={isCurrent ? "page" : undefined}
121
143
  {...props}
122
144
  >
123
- <Comp
145
+ <Comp
146
+ // #309: `aria-current` eskiden yalnız sarmalayıcı <li>'deydi; APG breadcrumb
147
+ // deseni onu geçerli sayfayı temsil eden ELEMANDA ister. İkisinde birden
148
+ // bulunması çift duyuruya yol açacağı için <li>'den taşındı (kaldırılmadı).
149
+ aria-current={isCurrent ? "page" : undefined}
124
150
  className={cn(
125
151
  "transition-colors duration-200 hover:text-foreground",
126
- isCurrent
127
- ? "font-medium text-foreground"
152
+ isCurrent
153
+ ? "font-medium text-foreground"
128
154
  : "text-muted-foreground hover:text-foreground hover:underline hover:underline-offset-4 hover:decoration-muted-foreground/30"
129
155
  )}
130
156
  {...itemProps}
131
157
  >
132
- {props.children}
158
+ {children}
133
159
  </Comp>
134
160
  </li>
135
161
  );
@@ -140,30 +166,51 @@ BreadcrumbItem.displayName = "BreadcrumbItem";
140
166
  const BreadcrumbSeparator = ({
141
167
  children,
142
168
  className,
169
+ icon,
143
170
  ...props
144
- }: BreadcrumbSeparatorProps) => (
145
- <span
146
- role="presentation"
147
- aria-hidden="true"
148
- className={cn("moonui-theme", "text-muted-foreground opacity-70", className)}
149
- {...props}
150
- >
151
- {children || <ChevronRight className="h-3.5 w-3.5" />}
152
- </span>
153
- );
171
+ }: BreadcrumbSeparatorProps) => {
172
+ // #309: kök `<Breadcrumb separator={…}>` değeri; yerel `children`/`icon` bunu ezer.
173
+ const rootSeparator = React.useContext(BreadcrumbSeparatorContext);
174
+
175
+ return (
176
+ // Ayraç tümüyle dekoratiftir → `aria-hidden` burada DOĞRU (ellipsis'in aksine içinde
177
+ // ekran-okuyucuya ait metin yok).
178
+ <span
179
+ role="presentation"
180
+ aria-hidden="true"
181
+ className={cn("moonui-theme", "text-muted-foreground opacity-70", className)}
182
+ {...props}
183
+ >
184
+ {/* #309: `icon` prop'u tipte tanımlıydı ama HİÇ OKUNMUYORDU → `{...props}` ile
185
+ DOM'a sızıyor, React bilinmeyen-öznitelik uyarısı basıyordu. Artık `children`
186
+ verilmediğinde ayraç ikonu olarak kullanılıyor. */}
187
+ {children || icon || rootSeparator || <ChevronRight className="h-3.5 w-3.5" />}
188
+ </span>
189
+ );
190
+ };
154
191
  BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
155
192
 
156
193
  const BreadcrumbEllipsis = ({
157
194
  className,
195
+ icon,
158
196
  ...props
159
197
  }: BreadcrumbEllipsisProps) => (
198
+ // #309: `aria-hidden="true"` ESKİDEN BU ELEMANDAYDI → alt ağacın tamamı, içindeki
199
+ // `sr-only` "More pages" metni DAHİL, erişilebilirlik ağacından düşüyordu; yani metin
200
+ // hiçbir zaman duyurulmuyordu (eleman kendi amacını geçersiz kılıyordu). Gizleme artık
201
+ // yalnız dekoratif ikonda.
202
+ //
203
+ // `role="presentation"` KASITLI olarak korundu: span'in zaten örtük rolü yok, dolayısıyla
204
+ // zararsız ve alt ağacı GİZLEMEZ (aria-hidden'ın aksine) — kusur yalnız aria-hidden'dı.
160
205
  <span
161
206
  role="presentation"
162
- aria-hidden="true"
163
207
  className={cn("moonui-theme", "flex items-center text-muted-foreground hover:text-foreground/80 transition-colors duration-200", className)}
164
208
  {...props}
165
209
  >
166
- <MoreHorizontal className="h-4 w-4" />
210
+ <span aria-hidden="true" className="flex items-center">
211
+ {/* #309: `icon` prop'u — ayraçtaki ile aynı ölü-prop kusuru. */}
212
+ {icon || <MoreHorizontal className="h-4 w-4" />}
213
+ </span>
167
214
  <span className="sr-only">More pages</span>
168
215
  </span>
169
216
  );
@@ -206,4 +253,15 @@ export {
206
253
  BreadcrumbPage,
207
254
  BreadcrumbSeparator,
208
255
  BreadcrumbEllipsis,
256
+ breadcrumbVariants,
257
+ };
258
+
259
+ // #309: Props interface'lerinin hiçbiri export edilmiyordu → tüketici sarmalayıcı
260
+ // yazarken tipi yeniden türetmek zorunda kalıyordu.
261
+ export type {
262
+ BreadcrumbProps,
263
+ BreadcrumbListProps,
264
+ BreadcrumbItemProps,
265
+ BreadcrumbSeparatorProps,
266
+ BreadcrumbEllipsisProps,
209
267
  };