@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/dist/hooks/index.global.js.map +1 -1
- package/dist/index.d.mts +24 -24
- package/dist/index.d.ts +24 -24
- package/dist/index.global.js +365 -52
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +145 -111
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +143 -112
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/ui/__tests__/a11y-group-c.test.tsx +266 -0
- package/src/components/ui/__tests__/breadcrumb.test.tsx +24 -9
- package/src/components/ui/breadcrumb.tsx +85 -27
- package/src/components/ui/pagination.tsx +64 -44
- package/src/components/ui/tabs.tsx +66 -16
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
|
344
|
-
expect(
|
|
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
|
-
|
|
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')
|
|
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
|
-
|
|
639
|
-
expect(
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
<
|
|
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
|
};
|