@moontra/moonui 6.7.0 → 6.9.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 +1 -6
- package/dist/hooks/index.global.js.map +1 -1
- package/dist/index.d.mts +33 -25
- package/dist/index.d.ts +33 -25
- package/dist/index.global.js +18 -18
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +441 -388
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +403 -353
- package/dist/index.mjs.map +1 -1
- package/dist/lib/theme.global.js +1 -14
- package/dist/lib/theme.global.js.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/__tests__/carousel.test.tsx +37 -0
- package/src/components/ui/__tests__/quality-group-de.test.tsx +159 -0
- package/src/components/ui/breadcrumb.tsx +85 -27
- package/src/components/ui/carousel.tsx +24 -0
- package/src/components/ui/command.tsx +25 -0
- package/src/components/ui/pagination.tsx +64 -44
- package/src/components/ui/scroll-area.tsx +23 -3
- package/src/components/ui/skeleton.tsx +16 -1
- package/src/components/ui/tabs.tsx +66 -16
|
@@ -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
|
};
|
|
@@ -89,12 +89,25 @@ const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
|
|
|
89
89
|
);
|
|
90
90
|
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
|
|
91
91
|
const [canScrollNext, setCanScrollNext] = React.useState(false);
|
|
92
|
+
/**
|
|
93
|
+
* #308-2: `aria-roledescription="carousel"/"slide"` doğru set edilmişti ama slayt
|
|
94
|
+
* DEĞİŞİMİNİ duyuran hiçbir şey yoktu — klavye/buton ile ilerleyen ekran-okuyucu
|
|
95
|
+
* kullanıcısı hangi slaytta olduğunu öğrenemiyordu. Embla'nın zaten dinlenen "select"
|
|
96
|
+
* olayına bağlı bir `aria-live="polite"` durum bölgesi eklendi.
|
|
97
|
+
*/
|
|
98
|
+
const [slideState, setSlideState] = React.useState<{ current: number; total: number }>(
|
|
99
|
+
{ current: 0, total: 0 }
|
|
100
|
+
);
|
|
92
101
|
|
|
93
102
|
// Embla "select" olayında ileri/geri butonlarının durumunu güncelle
|
|
94
103
|
const onSelect = React.useCallback((emblaApi: CarouselApi) => {
|
|
95
104
|
if (!emblaApi) return;
|
|
96
105
|
setCanScrollPrev(emblaApi.canScrollPrev());
|
|
97
106
|
setCanScrollNext(emblaApi.canScrollNext());
|
|
107
|
+
setSlideState({
|
|
108
|
+
current: emblaApi.selectedScrollSnap() + 1,
|
|
109
|
+
total: emblaApi.scrollSnapList().length,
|
|
110
|
+
});
|
|
98
111
|
}, []);
|
|
99
112
|
|
|
100
113
|
const scrollPrev = React.useCallback(() => {
|
|
@@ -164,6 +177,17 @@ const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
|
|
|
164
177
|
{...props}
|
|
165
178
|
>
|
|
166
179
|
{children}
|
|
180
|
+
{/*
|
|
181
|
+
#308-2: Görsel olarak gizli durum bölgesi. `aria-live="polite"` sayesinde slayt
|
|
182
|
+
değişimi (buton, klavye ya da sürükleme fark etmeksizin) duyurulur; `aria-atomic`
|
|
183
|
+
metnin tamamının okunmasını sağlar. Slayt sayısı henüz bilinmiyorken (ilk render,
|
|
184
|
+
embla init olmadan) hiçbir şey basılmaz — boş duyuru üretmemek için.
|
|
185
|
+
*/}
|
|
186
|
+
<div aria-live="polite" aria-atomic="true" className="sr-only">
|
|
187
|
+
{slideState.total > 0
|
|
188
|
+
? `Slide ${slideState.current} of ${slideState.total}`
|
|
189
|
+
: ""}
|
|
190
|
+
</div>
|
|
167
191
|
</div>
|
|
168
192
|
</CarouselContext.Provider>
|
|
169
193
|
);
|
|
@@ -167,6 +167,18 @@ const CommandSeparator = React.forwardRef<
|
|
|
167
167
|
>(({ className, ...props }, ref) => (
|
|
168
168
|
<CommandPrimitive.Separator
|
|
169
169
|
ref={ref}
|
|
170
|
+
/**
|
|
171
|
+
* #308-6: cmdk, Separator'a `role="separator"` basıyor ve bu eleman
|
|
172
|
+
* `CommandList`'in (`role="listbox"`) çocuğu oluyor → listbox'ın izinli çocuğu
|
|
173
|
+
* yalnız `option`/`group` olduğu için axe `aria-required-children` (critical) veriyordu.
|
|
174
|
+
*
|
|
175
|
+
* `role` EZİLEMİYOR: cmdk 1.1.1 `{...props, "cmdk-separator":"", role:"separator"}`
|
|
176
|
+
* sırasıyla yazıyor, yani kendi rolü prop'lardan SONRA geliyor (kaynak doğrulandı).
|
|
177
|
+
* `aria-hidden` ise prop yayılımının içinde olduğu için geçiyor — ve ayraç zaten
|
|
178
|
+
* tümüyle dekoratif olduğundan erişilebilirlik ağacından çıkarılması DOĞRU davranış.
|
|
179
|
+
* Böylece listbox'ın sahiplendiği çocuklar arasında görünmez.
|
|
180
|
+
*/
|
|
181
|
+
aria-hidden="true"
|
|
170
182
|
className={cn("moonui-theme", "-mx-1 h-px bg-border", className)}
|
|
171
183
|
{...props}
|
|
172
184
|
/>
|
|
@@ -219,4 +231,17 @@ export {
|
|
|
219
231
|
CommandItem,
|
|
220
232
|
CommandShortcut,
|
|
221
233
|
CommandSeparator,
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// #308-5: Sekiz Props interface'inin HİÇBİRİ export edilmiyordu → tüketici sarmalayıcı
|
|
237
|
+
// yazarken tipi yeniden türetmek zorundaydı.
|
|
238
|
+
export type {
|
|
239
|
+
CommandProps,
|
|
240
|
+
CommandDialogProps,
|
|
241
|
+
CommandInputProps,
|
|
242
|
+
CommandListProps,
|
|
243
|
+
CommandEmptyProps,
|
|
244
|
+
CommandGroupProps,
|
|
245
|
+
CommandSeparatorProps,
|
|
246
|
+
CommandItemProps,
|
|
222
247
|
}
|
|
@@ -5,13 +5,16 @@ import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
|
|
|
5
5
|
import { cn } from "../../lib/utils"
|
|
6
6
|
import { ButtonProps, buttonVariants } from "./button"
|
|
7
7
|
|
|
8
|
-
const Pagination =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
const Pagination = React.forwardRef<HTMLElement, React.ComponentProps<"nav">>(
|
|
9
|
+
({ className, ...props }, ref) => (
|
|
10
|
+
<nav
|
|
11
|
+
ref={ref}
|
|
12
|
+
role="navigation"
|
|
13
|
+
aria-label="pagination"
|
|
14
|
+
className={cn("moonui-theme", "mx-auto flex w-full justify-center", className)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
15
18
|
)
|
|
16
19
|
Pagination.displayName = "Pagination"
|
|
17
20
|
|
|
@@ -40,71 +43,85 @@ type PaginationLinkProps = {
|
|
|
40
43
|
} & Partial<Pick<ButtonProps, "size">> &
|
|
41
44
|
React.ComponentProps<"a">
|
|
42
45
|
|
|
43
|
-
const PaginationLink = (
|
|
44
|
-
className,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
{...props}
|
|
59
|
-
/>
|
|
46
|
+
const PaginationLink = React.forwardRef<HTMLAnchorElement, PaginationLinkProps>(
|
|
47
|
+
({ className, isActive, size = "icon", ...props }, ref) => (
|
|
48
|
+
<a
|
|
49
|
+
ref={ref}
|
|
50
|
+
aria-current={isActive ? "page" : undefined}
|
|
51
|
+
className={cn(
|
|
52
|
+
buttonVariants({
|
|
53
|
+
variant: isActive ? "outline" : "ghost",
|
|
54
|
+
size,
|
|
55
|
+
}),
|
|
56
|
+
className
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
)
|
|
60
61
|
)
|
|
61
62
|
PaginationLink.displayName = "PaginationLink"
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
/**
|
|
65
|
+
* #315: "Previous"/"Next" etiketleri KOŞULSUZ render ediliyordu ve component'te hiçbir
|
|
66
|
+
* responsive affordance yoktu. 375px'te ölçüm: nav clientWidth=229 / scrollWidth=317
|
|
67
|
+
* (88px iç taşma) → ata zincirindeki `overflow-hidden` etiketi kırpıyor, kullanıcı
|
|
68
|
+
* "Previous" yerine "vious" görüyordu. Etiket artık `sm` altında gizleniyor; erişilebilir
|
|
69
|
+
* ad `aria-label` ile korunduğu için ekran-okuyucu deneyimi DEĞİŞMİYOR.
|
|
70
|
+
*/
|
|
71
|
+
const paginationLabelClass = "hidden sm:inline"
|
|
72
|
+
|
|
73
|
+
const PaginationPrevious = React.forwardRef<
|
|
74
|
+
HTMLAnchorElement,
|
|
75
|
+
React.ComponentProps<typeof PaginationLink>
|
|
76
|
+
>(({ className, ...props }, ref) => (
|
|
67
77
|
<PaginationLink
|
|
78
|
+
ref={ref}
|
|
68
79
|
aria-label="Go to previous page"
|
|
69
80
|
size="md"
|
|
70
81
|
className={cn("moonui-theme", "gap-1 pl-2.5", className)}
|
|
71
82
|
{...props}
|
|
72
83
|
>
|
|
73
84
|
<ChevronLeft className="h-4 w-4" />
|
|
74
|
-
<span>Previous</span>
|
|
85
|
+
<span className={paginationLabelClass}>Previous</span>
|
|
75
86
|
</PaginationLink>
|
|
76
|
-
)
|
|
87
|
+
))
|
|
77
88
|
PaginationPrevious.displayName = "PaginationPrevious"
|
|
78
89
|
|
|
79
|
-
const PaginationNext =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
90
|
+
const PaginationNext = React.forwardRef<
|
|
91
|
+
HTMLAnchorElement,
|
|
92
|
+
React.ComponentProps<typeof PaginationLink>
|
|
93
|
+
>(({ className, ...props }, ref) => (
|
|
83
94
|
<PaginationLink
|
|
95
|
+
ref={ref}
|
|
84
96
|
aria-label="Go to next page"
|
|
85
97
|
size="md"
|
|
86
98
|
className={cn("moonui-theme", "gap-1 pr-2.5", className)}
|
|
87
99
|
{...props}
|
|
88
100
|
>
|
|
89
|
-
<span>Next</span>
|
|
101
|
+
<span className={paginationLabelClass}>Next</span>
|
|
90
102
|
<ChevronRight className="h-4 w-4" />
|
|
91
103
|
</PaginationLink>
|
|
92
|
-
)
|
|
104
|
+
))
|
|
93
105
|
PaginationNext.displayName = "PaginationNext"
|
|
94
106
|
|
|
95
|
-
const PaginationEllipsis =
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
107
|
+
const PaginationEllipsis = React.forwardRef<
|
|
108
|
+
HTMLSpanElement,
|
|
109
|
+
React.ComponentProps<"span">
|
|
110
|
+
>(({ className, ...props }, ref) => (
|
|
111
|
+
// #315: `aria-hidden` ESKİDEN BU ELEMANDAYDI → içindeki `sr-only` "More pages" metni
|
|
112
|
+
// erişilebilirlik ağacından düşüyor, hiç duyurulmuyordu. Gizleme artık yalnız dekoratif
|
|
113
|
+
// ikonda. (breadcrumb.tsx'te birebir aynı çakışma vardı — aynı PR'da düzeltildi.)
|
|
99
114
|
<span
|
|
100
|
-
|
|
115
|
+
ref={ref}
|
|
101
116
|
className={cn("moonui-theme", "flex h-9 w-9 items-center justify-center", className)}
|
|
102
117
|
{...props}
|
|
103
118
|
>
|
|
104
|
-
<
|
|
119
|
+
<span aria-hidden="true" className="flex items-center">
|
|
120
|
+
<MoreHorizontal className="h-4 w-4" />
|
|
121
|
+
</span>
|
|
105
122
|
<span className="sr-only">More pages</span>
|
|
106
123
|
</span>
|
|
107
|
-
)
|
|
124
|
+
))
|
|
108
125
|
PaginationEllipsis.displayName = "PaginationEllipsis"
|
|
109
126
|
|
|
110
127
|
export {
|
|
@@ -115,4 +132,7 @@ export {
|
|
|
115
132
|
PaginationLink,
|
|
116
133
|
PaginationNext,
|
|
117
134
|
PaginationPrevious,
|
|
118
|
-
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// #315: Props tiplerinin hiçbiri export edilmiyordu.
|
|
138
|
+
export type { PaginationLinkProps }
|
|
@@ -5,16 +5,36 @@ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
|
|
5
5
|
|
|
6
6
|
import { cn } from "../../lib/utils"
|
|
7
7
|
|
|
8
|
+
export interface ScrollAreaProps
|
|
9
|
+
extends React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> {
|
|
10
|
+
/**
|
|
11
|
+
* #308-1: Kaydırma alanının klavyeyle odaklanabilir olup olmayacağı. Varsayılan `true`
|
|
12
|
+
* — odaklanabilir içeriği olmayan bir kaydırma alanı aksi hâlde klavyeyle erişilemez
|
|
13
|
+
* (WCAG 2.1.1). Fazladan sekme durağı istenmeyen durumlarda `false`.
|
|
14
|
+
*/
|
|
15
|
+
focusable?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
8
18
|
const ScrollArea = React.forwardRef<
|
|
9
19
|
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
|
10
|
-
|
|
11
|
-
>(({ className, children, ...props }, ref) => (
|
|
20
|
+
ScrollAreaProps
|
|
21
|
+
>(({ className, children, focusable = true, ...props }, ref) => (
|
|
12
22
|
<ScrollAreaPrimitive.Root
|
|
13
23
|
ref={ref}
|
|
14
24
|
className={cn("relative overflow-hidden", className)}
|
|
15
25
|
{...props}
|
|
16
26
|
>
|
|
17
|
-
|
|
27
|
+
{/*
|
|
28
|
+
#308-1: Viewport'a `tabIndex` VERİLMİYORDU ve bu davranış Radix'ten kalıtsal DEĞİL
|
|
29
|
+
(`@radix-ui/react-scroll-area` kaynağında tabIndex geçmiyor). Sonuç: içinde
|
|
30
|
+
odaklanabilir eleman bulunmayan bir ScrollArea klavyeyle HİÇ kaydırılamıyordu
|
|
31
|
+
(WCAG 2.1.1). Kaydırılabilir bölgenin odaklanabilir olması kanonik çözümdür;
|
|
32
|
+
ek sekme durağı istemeyen tüketici `focusable={false}` diyebilir.
|
|
33
|
+
*/}
|
|
34
|
+
<ScrollAreaPrimitive.Viewport
|
|
35
|
+
tabIndex={focusable ? 0 : undefined}
|
|
36
|
+
className="h-full w-full rounded-[inherit]"
|
|
37
|
+
>
|
|
18
38
|
{children}
|
|
19
39
|
</ScrollAreaPrimitive.Viewport>
|
|
20
40
|
<ScrollBar />
|
|
@@ -144,10 +144,25 @@ export const Skeleton = React.forwardRef<HTMLDivElement, SkeletonProps>(
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
const SkeletonElement = useMotion ? motion.div : "div";
|
|
147
|
-
|
|
147
|
+
|
|
148
148
|
return (
|
|
149
149
|
<SkeletonElement
|
|
150
150
|
ref={ref}
|
|
151
|
+
/**
|
|
152
|
+
* #320: Skeleton hiçbir a11y işareti taşımıyordu — ne `aria-hidden`, ne `role`,
|
|
153
|
+
* ne `aria-busy`. Radix kullanmadığı için kalıtsal semantik de yok.
|
|
154
|
+
*
|
|
155
|
+
* Konvansiyon olarak `aria-hidden` seçildi (`role="status"` DEĞİL): tek bir yükleme
|
|
156
|
+
* durumu tipik olarak ONLARCA skeleton render eder (`SkeletonText` tek başına N satır
|
|
157
|
+
* basar) — her biri canlı bölge olsaydı ekran okuyucu aynı olayı N kez duyururdu.
|
|
158
|
+
* "Yükleniyor" duyurusunun sahibi `spinner.tsx:79-105` (`role="status"` +
|
|
159
|
+
* `aria-live="polite"` + `sr-only` etiket); skeleton onun görsel yer tutucusudur.
|
|
160
|
+
* Tüketici kapsayıcıya `aria-busy` koyarak durumu bildirir.
|
|
161
|
+
*
|
|
162
|
+
* Yalnız YER TUTUCU dalında: `isLoaded && children` dalı gerçek içerik döndürür ve
|
|
163
|
+
* yukarıda kasıtlı olarak işaretlenmemiştir.
|
|
164
|
+
*/
|
|
165
|
+
aria-hidden="true"
|
|
151
166
|
className={cn(
|
|
152
167
|
"moonui-theme",
|
|
153
168
|
skeletonVariants({ variant, size, shape, animation }),
|
|
@@ -9,22 +9,52 @@ import { cn } from "../../lib/utils";
|
|
|
9
9
|
/* -------------------------------------------------------------------------------------------------
|
|
10
10
|
* Tabs Root
|
|
11
11
|
* -----------------------------------------------------------------------------------------------*/
|
|
12
|
+
type TabsOrientation = "horizontal" | "vertical";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* #322: Yönelim için TEK KAYNAK.
|
|
16
|
+
*
|
|
17
|
+
* Eskiden üç ayrı yol vardı ve birbirini eziyordu:
|
|
18
|
+
* 1. Root `vertical` prop'undan `orientation` hesaplıyor, ARDINDAN `{...props}` yayıyordu
|
|
19
|
+
* → tüketicinin (ya da Radix'in kalıtsal) `orientation` prop'u `vertical`'ı SESSİZCE
|
|
20
|
+
* eziyordu.
|
|
21
|
+
* 2. `TabsList`/`TabsTrigger` kendi `orientation` prop'unu yalnız CVA sınıfına veriyor,
|
|
22
|
+
* Root'tan hiç beslenmiyordu → klavye yönü dikey, görünüm yatay kalabiliyordu.
|
|
23
|
+
*
|
|
24
|
+
* Artık Root çözülmüş yönelimi context ile dağıtıyor; alt component'ler kendi prop'unu
|
|
25
|
+
* verirse o kazanır (yerel ezme kasıtlı olarak korundu).
|
|
26
|
+
*/
|
|
27
|
+
const TabsOrientationContext = React.createContext<TabsOrientation>("horizontal");
|
|
28
|
+
|
|
12
29
|
interface TabsProps extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root> {
|
|
13
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated `orientation="vertical"` kullanın — pro `MoonUITabsPro` ile aynı ad ve
|
|
32
|
+
* tip. `vertical` yalnız `orientation` verilmediğinde dikkate alınır.
|
|
33
|
+
*/
|
|
14
34
|
vertical?: boolean;
|
|
15
35
|
}
|
|
16
36
|
|
|
17
37
|
const Tabs = React.forwardRef<
|
|
18
38
|
React.ElementRef<typeof TabsPrimitive.Root>,
|
|
19
39
|
TabsProps
|
|
20
|
-
>(({ vertical = false, ...props }, ref) =>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
{
|
|
26
|
-
|
|
27
|
-
|
|
40
|
+
>(({ vertical = false, orientation, className, ...props }, ref) => {
|
|
41
|
+
const resolvedOrientation: TabsOrientation =
|
|
42
|
+
orientation ?? (vertical ? "vertical" : "horizontal");
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<TabsOrientationContext.Provider value={resolvedOrientation}>
|
|
46
|
+
<TabsPrimitive.Root
|
|
47
|
+
ref={ref}
|
|
48
|
+
// #322: `className` eskiden destructure EDİLMİYORDU; `cn("moonui-theme", …)`
|
|
49
|
+
// hesaplanıp ardından `{...props}` yayıldığı için tüketici `className` verdiğinde
|
|
50
|
+
// hesaplanan değer tümüyle eziliyor ve `moonui-theme` tema kapsamı düşüyordu.
|
|
51
|
+
className={cn("moonui-theme", className)}
|
|
52
|
+
orientation={resolvedOrientation}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
</TabsOrientationContext.Provider>
|
|
56
|
+
);
|
|
57
|
+
});
|
|
28
58
|
|
|
29
59
|
Tabs.displayName = TabsPrimitive.Root.displayName;
|
|
30
60
|
|
|
@@ -77,10 +107,14 @@ const TabsList = React.forwardRef<
|
|
|
77
107
|
TabsListProps
|
|
78
108
|
>(({ className, variant, orientation, fullWidth, scrollable, ...props }, ref) => {
|
|
79
109
|
// scrollable prop'unu DOM'a geçirmemek için destructure ediyoruz
|
|
110
|
+
// #322: yönelim Root'tan context ile geliyor; yerel prop verilirse o kazanır.
|
|
111
|
+
const contextOrientation = React.useContext(TabsOrientationContext);
|
|
112
|
+
const resolvedOrientation = orientation ?? contextOrientation;
|
|
113
|
+
|
|
80
114
|
return (
|
|
81
115
|
<TabsPrimitive.List
|
|
82
116
|
ref={ref}
|
|
83
|
-
className={cn("moonui-theme", tabsListVariants({ variant, orientation, fullWidth, scrollable, className }))}
|
|
117
|
+
className={cn("moonui-theme", tabsListVariants({ variant, orientation: resolvedOrientation, fullWidth, scrollable, className }))}
|
|
84
118
|
{...props}
|
|
85
119
|
/>
|
|
86
120
|
);
|
|
@@ -152,14 +186,19 @@ const TabsTrigger = React.forwardRef<
|
|
|
152
186
|
fadeTabs = false,
|
|
153
187
|
orientation,
|
|
154
188
|
fullWidth,
|
|
155
|
-
children,
|
|
156
|
-
...props
|
|
157
|
-
}, ref) =>
|
|
189
|
+
children,
|
|
190
|
+
...props
|
|
191
|
+
}, ref) => {
|
|
192
|
+
// #322: yönelim Root'tan context ile geliyor; yerel prop verilirse o kazanır.
|
|
193
|
+
const contextOrientation = React.useContext(TabsOrientationContext);
|
|
194
|
+
const resolvedOrientation = orientation ?? contextOrientation;
|
|
195
|
+
|
|
196
|
+
return (
|
|
158
197
|
<TabsPrimitive.Trigger
|
|
159
198
|
ref={ref}
|
|
160
199
|
className={cn(
|
|
161
200
|
"moonui-theme",
|
|
162
|
-
tabsTriggerVariants({ variant, size, orientation, fullWidth }),
|
|
201
|
+
tabsTriggerVariants({ variant, size, orientation: resolvedOrientation, fullWidth }),
|
|
163
202
|
fadeTabs && "data-[state=inactive]:opacity-60",
|
|
164
203
|
className
|
|
165
204
|
)}
|
|
@@ -176,7 +215,8 @@ const TabsTrigger = React.forwardRef<
|
|
|
176
215
|
<span className="ml-2">{badge}</span>
|
|
177
216
|
)}
|
|
178
217
|
</TabsPrimitive.Trigger>
|
|
179
|
-
)
|
|
218
|
+
);
|
|
219
|
+
});
|
|
180
220
|
|
|
181
221
|
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
182
222
|
|
|
@@ -207,4 +247,14 @@ const TabsContent = React.forwardRef<
|
|
|
207
247
|
|
|
208
248
|
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
209
249
|
|
|
210
|
-
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
250
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants, tabsTriggerVariants };
|
|
251
|
+
|
|
252
|
+
// #322: Props interface'lerinin hiçbiri export edilmiyordu (pro tarafı `MoonUITabsProProps`
|
|
253
|
+
// ve `TabItem`'ı export ediyor — asimetriydi).
|
|
254
|
+
export type {
|
|
255
|
+
TabsProps,
|
|
256
|
+
TabsListProps,
|
|
257
|
+
TabsTriggerProps,
|
|
258
|
+
TabsContentProps,
|
|
259
|
+
TabsOrientation,
|
|
260
|
+
};
|