@moontra/moonui 6.24.0 → 6.25.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.
Files changed (56) hide show
  1. package/dist/cdn/moonui.css +2 -2
  2. package/dist/cdn/moonui.esm.js +12 -12
  3. package/dist/cdn/moonui.global.js +12 -12
  4. package/dist/index.d.mts +24 -6
  5. package/dist/index.d.ts +24 -6
  6. package/dist/index.js +417 -87
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +418 -89
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +1 -1
  11. package/src/__tests__/issue-544-kontrast.test.tsx +153 -145
  12. package/src/__tests__/issue-645-overlay-tab-focus.test.tsx +262 -0
  13. package/src/__tests__/issue-646-dark-scope-override.test.tsx +297 -0
  14. package/src/__tests__/issue-661-a11y.test.tsx +113 -0
  15. package/src/__tests__/issue-661-token-kontrast.test.tsx +82 -0
  16. package/src/components/ui/__tests__/accent-foreground-contrast.test.ts +197 -0
  17. package/src/components/ui/__tests__/accordion.test.tsx +6 -4
  18. package/src/components/ui/__tests__/alert.test.tsx +4 -1
  19. package/src/components/ui/__tests__/button.test.tsx +6 -1
  20. package/src/components/ui/__tests__/dark-token-contrast.test.ts +18 -18
  21. package/src/components/ui/__tests__/heading-level-policy.test.tsx +4 -4
  22. package/src/components/ui/__tests__/issue-643-card-token-parite.test.tsx +113 -0
  23. package/src/components/ui/__tests__/issue-649-button-aschild.test.tsx +199 -0
  24. package/src/components/ui/__tests__/issue-664-separator-dekoratif-rol.test.tsx +41 -0
  25. package/src/components/ui/__tests__/preset-color-shadowing.test.ts +10 -10
  26. package/src/components/ui/__tests__/primary-subtle-token.test.ts +7 -7
  27. package/src/components/ui/__tests__/progress-secondary-contrast.test.ts +38 -38
  28. package/src/components/ui/__tests__/progress-warning-error-contrast.test.ts +64 -64
  29. package/src/components/ui/__tests__/quality-group-de.test.tsx +2 -2
  30. package/src/components/ui/__tests__/responsive-color-invariance.test.ts +20 -20
  31. package/src/components/ui/__tests__/select.test.tsx +6 -1
  32. package/src/components/ui/__tests__/semantic-token-usage.test.ts +23 -23
  33. package/src/components/ui/__tests__/size-scale-convention.test.ts +10 -10
  34. package/src/components/ui/__tests__/slider-accent-contrast.test.ts +142 -136
  35. package/src/components/ui/__tests__/slider-secondary-contrast.test.ts +60 -60
  36. package/src/components/ui/__tests__/slider-warning-error-contrast.test.ts +100 -100
  37. package/src/components/ui/__tests__/switch-variant-contrast.test.tsx +103 -103
  38. package/src/components/ui/__tests__/tooltip.test.tsx +122 -0
  39. package/src/components/ui/accordion.tsx +16 -3
  40. package/src/components/ui/alert.tsx +1 -1
  41. package/src/components/ui/badge.tsx +12 -1
  42. package/src/components/ui/button.tsx +76 -18
  43. package/src/components/ui/card.tsx +59 -5
  44. package/src/components/ui/dropdown-menu.tsx +99 -5
  45. package/src/components/ui/file-upload.tsx +4 -0
  46. package/src/components/ui/index.ts +4 -1
  47. package/src/components/ui/popover-pro.tsx +41 -0
  48. package/src/components/ui/popover.tsx +162 -7
  49. package/src/components/ui/rating.tsx +24 -2
  50. package/src/components/ui/select.tsx +4 -4
  51. package/src/components/ui/simple-editor.tsx +3 -18
  52. package/src/components/ui/tabs.tsx +16 -1
  53. package/src/components/ui/textarea.tsx +6 -1
  54. package/src/components/ui/toast.tsx +17 -2
  55. package/src/components/ui/tooltip.tsx +154 -2
  56. package/src/styles/tokens.css +34 -2
@@ -0,0 +1,199 @@
1
+ /**
2
+ * #649 — `Button.asChild` ölü ternary'den GERÇEK Radix `Slot` polimorfizmine.
3
+ *
4
+ * Kusur: `const Comp = asChild ? "button" : "button"` — ternary'nin iki dalı birebir
5
+ * aynıydı, yani prop tipte ve docs'ta vaat edilmesine rağmen HİÇBİR ŞEY yapmıyordu.
6
+ *
7
+ * ⚠️ Mevcut `button.test.tsx`'teki "renders as child component when asChild is true"
8
+ * testi bu kusuru YAKALAYAMIYORDU: kusurlu kodda `<a>` çocuğu native <button>'ın İÇİNE
9
+ * render ediliyordu, dolayısıyla `getByRole('link')` + `href` iddiaları YANLIŞ NEDENLE
10
+ * geçiyordu. Bu suite'in ayırt edici iddiası "<button> DOM'da BULUNMAMALI"dır — kusurlu
11
+ * kodda kırmızı, düzeltilmiş kodda yeşil.
12
+ *
13
+ * Ayrıca button'a özgü davranışların Slot dalındaki karşılıklarını kilitler:
14
+ * · `loading` spinner'ı ve `leftIcon`/`rightIcon` çocuğun İÇİNE taşınır (`Slottable`).
15
+ * · `disabled`/`loading` native `disabled` yerine `aria-disabled` + `data-disabled`
16
+ * ile ifade edilir — <a>/<span> native `disabled`'ı TANIMAZ.
17
+ * · Etkinleştirme (fare + klavye Enter, ikisi de click event'i üretir) engellenir.
18
+ * · Çocuk geçerli bir element değilse native <button>'a geri düşülür (Slot tek
19
+ * geçerli element ister; aksi hâlde SESSİZCE boş render ederdi).
20
+ */
21
+ import * as React from 'react'
22
+ import { render, screen, fireEvent, createEvent } from '@testing-library/react'
23
+ import { Button } from '../button'
24
+
25
+ describe('#649 Button asChild — Radix Slot polimorfizmi', () => {
26
+ it('asChild ile <a> render eder, native <button> ÜRETMEZ', () => {
27
+ render(
28
+ <Button asChild>
29
+ <a href="/test">Link Button</a>
30
+ </Button>
31
+ )
32
+
33
+ const link = screen.getByRole('link', { name: /link button/i })
34
+ expect(link.tagName).toBe('A')
35
+ expect(link).toHaveAttribute('href', '/test')
36
+ // Ölü ternary'nin imzası: çocuk <a> bir <button>'ın içinde duruyordu.
37
+ expect(screen.queryByRole('button')).not.toBeInTheDocument()
38
+ expect(document.querySelector('button')).toBeNull()
39
+ })
40
+
41
+ it('buttonVariants sınıfları çocuğa geçer', () => {
42
+ render(
43
+ <Button asChild variant="destructive" size="lg" rounded="full">
44
+ <a href="/x">Sil</a>
45
+ </Button>
46
+ )
47
+
48
+ const link = screen.getByRole('link')
49
+ expect(link).toHaveClass('bg-destructive')
50
+ expect(link).toHaveClass('h-12')
51
+ expect(link).toHaveClass('rounded-full')
52
+ expect(link).toHaveClass('moonui-theme')
53
+ })
54
+
55
+ it("çocuğun kendi className'i korunur ve birleştirilir", () => {
56
+ render(
57
+ <Button asChild className="custom-class">
58
+ <a href="/x" className="child-class">
59
+ Git
60
+ </a>
61
+ </Button>
62
+ )
63
+
64
+ const link = screen.getByRole('link')
65
+ expect(link).toHaveClass('custom-class')
66
+ expect(link).toHaveClass('child-class')
67
+ })
68
+
69
+ it('loading spinner ve ikonlar çocuğun İÇİNE taşınır (Slottable)', () => {
70
+ render(
71
+ <Button
72
+ asChild
73
+ loading
74
+ leftIcon={<span data-testid="left-icon" />}
75
+ rightIcon={<span data-testid="right-icon" />}
76
+ >
77
+ <a href="/x">
78
+ <span data-testid="child-label">Yükleniyor</span>
79
+ </a>
80
+ </Button>
81
+ )
82
+
83
+ const link = screen.getByRole('link')
84
+ // Çocuğun kendi içeriği korunur…
85
+ expect(link).toContainElement(screen.getByTestId('child-label'))
86
+ // …spinner de onun içine girer (Slot'un `Children.only` tuzağına düşmeden).
87
+ expect(link.querySelector('svg')).not.toBeNull()
88
+ expect(link).toHaveAttribute('data-loading', '')
89
+ expect(link).toHaveAttribute('aria-busy', 'true')
90
+ // loading, ikonları gizleme sözleşmesini asChild'da da korur.
91
+ expect(screen.queryByTestId('left-icon')).not.toBeInTheDocument()
92
+ expect(screen.queryByTestId('right-icon')).not.toBeInTheDocument()
93
+ })
94
+
95
+ it('loading yokken leftIcon/rightIcon çocuğun içinde render edilir', () => {
96
+ render(
97
+ <Button
98
+ asChild
99
+ leftIcon={<span data-testid="left-icon" />}
100
+ rightIcon={<span data-testid="right-icon" />}
101
+ >
102
+ <a href="/x">Git</a>
103
+ </Button>
104
+ )
105
+
106
+ const link = screen.getByRole('link')
107
+ expect(link).toContainElement(screen.getByTestId('left-icon'))
108
+ expect(link).toContainElement(screen.getByTestId('right-icon'))
109
+ })
110
+
111
+ it('disabled: native `disabled` yerine aria-disabled + inert sınıflar verilir', () => {
112
+ render(
113
+ <Button asChild disabled>
114
+ <a href="/x">Git</a>
115
+ </Button>
116
+ )
117
+
118
+ const link = screen.getByRole('link')
119
+ // <a> native `disabled`ı tanımaz — DOM'a basılmamalı.
120
+ expect(link).not.toHaveAttribute('disabled')
121
+ expect(link).toHaveAttribute('aria-disabled', 'true')
122
+ expect(link).toHaveAttribute('data-disabled', '')
123
+ expect(link).toHaveClass('pointer-events-none')
124
+ expect(link).toHaveClass('opacity-50')
125
+ })
126
+
127
+ it('loading da asChild dalında aria-disabled üretir', () => {
128
+ render(
129
+ <Button asChild loading>
130
+ <a href="/x">Git</a>
131
+ </Button>
132
+ )
133
+
134
+ const link = screen.getByRole('link')
135
+ expect(link).toHaveAttribute('aria-disabled', 'true')
136
+ expect(link).not.toHaveAttribute('disabled')
137
+ })
138
+
139
+ it('devre dışıyken tıklama handler çağrılmaz ve gezinme iptal edilir', () => {
140
+ const handleClick = jest.fn()
141
+ render(
142
+ <Button asChild disabled onClick={handleClick}>
143
+ <a href="/x">Git</a>
144
+ </Button>
145
+ )
146
+
147
+ const link = screen.getByRole('link')
148
+ const clickEvent = createEvent.click(link)
149
+ fireEvent(link, clickEvent)
150
+
151
+ expect(handleClick).not.toHaveBeenCalled()
152
+ // <a> için preventDefault gezinmeyi durdurur (klavye Enter de click üretir).
153
+ expect(clickEvent.defaultPrevented).toBe(true)
154
+ })
155
+
156
+ it('devre dışı değilken onClick normal çalışır', () => {
157
+ const handleClick = jest.fn()
158
+ render(
159
+ <Button asChild onClick={handleClick}>
160
+ <a href="/x">Git</a>
161
+ </Button>
162
+ )
163
+
164
+ fireEvent.click(screen.getByRole('link'))
165
+ expect(handleClick).toHaveBeenCalledTimes(1)
166
+ })
167
+
168
+ it('ref slot edilen elemana çözülür', () => {
169
+ const ref = React.createRef<HTMLElement>()
170
+ render(
171
+ <Button asChild ref={ref as React.Ref<HTMLButtonElement>}>
172
+ <a href="/x">Git</a>
173
+ </Button>
174
+ )
175
+
176
+ expect(ref.current).not.toBeNull()
177
+ expect(ref.current?.tagName).toBe('A')
178
+ })
179
+
180
+ it('çocuk geçerli element değilse native <button>a geri düşer (boş render YOK)', () => {
181
+ // Geriye dönük güvenlik: `asChild` bugüne dek etkisizdi, düz metinle yazan
182
+ // mevcut kullanım Slot'a düşseydi HİÇBİR ŞEY render etmezdi.
183
+ render(<Button asChild>Düz metin</Button>)
184
+
185
+ const button = screen.getByRole('button', { name: /düz metin/i })
186
+ expect(button.tagName).toBe('BUTTON')
187
+ })
188
+
189
+ it('asChild olmadan davranış DEĞİŞMEZ (native button + native disabled)', () => {
190
+ render(<Button disabled>Devre dışı</Button>)
191
+
192
+ const button = screen.getByRole('button')
193
+ expect(button.tagName).toBe('BUTTON')
194
+ expect(button).toBeDisabled()
195
+ // Slot dalına özgü öznitelikler normal dala SIZMAMALI.
196
+ expect(button).not.toHaveAttribute('aria-disabled')
197
+ expect(button).not.toHaveAttribute('data-disabled')
198
+ })
199
+ })
@@ -0,0 +1,41 @@
1
+ /**
2
+ * issue #664 — S4: `separator` dekoratif rolu (free ikiz).
3
+ *
4
+ * OLCULDU, KAYNAK DEGISTIRILMEDI. S4 tablosu `separator` icin
5
+ * "`decorative` → `role=\"none\"`" istiyordu; olcum bunun ZATEN saglandigini
6
+ * gosterdi:
7
+ *
8
+ * - `separator.tsx` `decorative` varsayilanini `true` verip Radix'e geciriyor,
9
+ * - `@radix-ui/react-separator@1.1.7` (`dist/index.js`) tam olarak sunu yapiyor:
10
+ * `decorative ? { role: "none" } : { "aria-orientation": ariaOrientation }`
11
+ *
12
+ * Yani gerekli olan tek sey durumun KILITLENMESI: Radix surum yukseltmesi,
13
+ * `decorative` varsayilaninin degismesi ya da prop'un Radix'e gecirilmesinin
14
+ * unutulmasi bu testi kirmizi yapar.
15
+ *
16
+ * Pro ikizi ayni iddiayla kilitli:
17
+ * packages/moonui-pro/src/__tests__/issue-664-s4-dekoratif-katmanlar.test.tsx
18
+ */
19
+ import React from 'react'
20
+ import { render } from '@testing-library/react'
21
+ import '@testing-library/jest-dom'
22
+ import { Separator } from '../separator'
23
+
24
+ describe('#664 S4 — Separator dekoratif rolu', () => {
25
+ it('varsayilan (dekoratif) → erisilebilirlik agacindan cikaran role="none"', () => {
26
+ const { container } = render(<Separator />)
27
+ expect(container.firstElementChild).toHaveAttribute('role', 'none')
28
+ })
29
+
30
+ it('`decorative={false}` → anlamli `role="separator"`', () => {
31
+ const { container } = render(<Separator decorative={false} />)
32
+ expect(container.firstElementChild).toHaveAttribute('role', 'separator')
33
+ })
34
+
35
+ it('dekoratif rol gorsel varyantlari ETKILEMEZ (regresyon kapisi)', () => {
36
+ const { container } = render(<Separator orientation="vertical" variant="dashed" />)
37
+ const el = container.firstElementChild as HTMLElement
38
+ expect(el).toHaveAttribute('role', 'none')
39
+ expect(el.className).toContain('border-dashed')
40
+ })
41
+ })
@@ -20,7 +20,7 @@ const extend = (preset as any).theme?.extend as Record<string, unknown> | undefi
20
20
  const colors = extend?.colors as Record<string, ColorValue> | undefined
21
21
 
22
22
  /** `border-*`, `text-*`, `bg-*` … ailelerini besleyen, renk gölgeleyebilen extend anahtarları */
23
- const RENK_AILELERI = [
23
+ const COLOR_FAMILIES = [
24
24
  'borderColor',
25
25
  'textColor',
26
26
  'backgroundColor',
@@ -42,22 +42,22 @@ describe('tailwind preset — iç içe renk anahtarları gölgelenmiyor', () =>
42
42
  })
43
43
 
44
44
  it('hiçbir renk ailesi extend\'i, iç içe bir `colors` girdisini DÜZ STRING ile ezmiyor', () => {
45
- const ihlaller: string[] = []
46
- for (const aile of RENK_AILELERI) {
45
+ const violations: string[] = []
46
+ for (const aile of COLOR_FAMILIES) {
47
47
  const blok = extend?.[aile] as Record<string, ColorValue> | undefined
48
48
  if (!blok || typeof blok !== 'object') continue
49
- for (const [isim, deger] of Object.entries(blok)) {
50
- const asil = colors?.[isim]
51
- if (asil && typeof asil === 'object' && typeof deger === 'string') {
52
- const kayipAnahtarlar = Object.keys(asil).filter((k) => k !== 'DEFAULT')
53
- ihlaller.push(
49
+ for (const [isim, value] of Object.entries(blok)) {
50
+ const original = colors?.[isim]
51
+ if (original && typeof original === 'object' && typeof value === 'string') {
52
+ const missingKeys = Object.keys(original).filter((k) => k !== 'DEFAULT')
53
+ violations.push(
54
54
  `theme.extend.${aile}.${isim} düz string → colors.${isim}'in ` +
55
- `[${kayipAnahtarlar.join(', ')}] alt anahtarları ${aile} için ÖLÜ`
55
+ `[${missingKeys.join(', ')}] alt anahtarları ${aile} için ÖLÜ`
56
56
  )
57
57
  }
58
58
  }
59
59
  }
60
- expect(ihlaller).toEqual([])
60
+ expect(violations).toEqual([])
61
61
  })
62
62
 
63
63
  it('semantik renklerin subtle alt anahtarları preset\'te tanımlı', () => {
@@ -21,12 +21,12 @@ const tokens = fs.readFileSync(path.join(KOK, 'styles/tokens.css'), 'utf8')
21
21
  const preset = fs.readFileSync(path.resolve(KOK, '../tailwind-preset.js'), 'utf8')
22
22
 
23
23
  /** "H S% L%" biçimindeki token değerini oku (light = :root, dark = .dark bloğu) */
24
- function tokenOku(ad: string, blok: 'light' | 'dark'): string | null {
25
- const kaynak =
24
+ function tokenOku(name: string, blok: 'light' | 'dark'): string | null {
25
+ const source =
26
26
  blok === 'dark'
27
27
  ? tokens.slice(tokens.indexOf('.dark'))
28
28
  : tokens.slice(0, tokens.indexOf('.dark'))
29
- return kaynak.match(new RegExp(`--${ad}:\\s*([^;]+);`))?.[1].trim() ?? null
29
+ return source.match(new RegExp(`--${name}:\\s*([^;]+);`))?.[1].trim() ?? null
30
30
  }
31
31
 
32
32
  function hslToRgb(h: number, s: number, l: number): [number, number, number] {
@@ -41,7 +41,7 @@ function hslToRgb(h: number, s: number, l: number): [number, number, number] {
41
41
  return [Math.round(255 * f(0)), Math.round(255 * f(8)), Math.round(255 * f(4))]
42
42
  }
43
43
 
44
- function bagilParlaklik([r, g, b]: [number, number, number]): number {
44
+ function relativeLuminance([r, g, b]: [number, number, number]): number {
45
45
  const c = (v: number) => {
46
46
  const x = v / 255
47
47
  return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4)
@@ -50,12 +50,12 @@ function bagilParlaklik([r, g, b]: [number, number, number]): number {
50
50
  }
51
51
 
52
52
  function kontrast(a: string, b: string): number {
53
- const ayrıştır = (v: string): [number, number, number] => {
53
+ const parse = (v: string): [number, number, number] => {
54
54
  const [h, s, l] = v.split(/\s+/).map((x) => parseFloat(x))
55
55
  return hslToRgb(h, s, l)
56
56
  }
57
- const la = bagilParlaklik(ayrıştır(a))
58
- const lb = bagilParlaklik(ayrıştır(b))
57
+ const la = relativeLuminance(parse(a))
58
+ const lb = relativeLuminance(parse(b))
59
59
  const [hi, lo] = la > lb ? [la, lb] : [lb, la]
60
60
  return (hi + 0.05) / (lo + 0.05)
61
61
  }
@@ -35,12 +35,12 @@ const KOK = path.resolve(__dirname, '../../..')
35
35
  const tokens = fs.readFileSync(path.join(KOK, 'styles/tokens.css'), 'utf8')
36
36
 
37
37
  /** "H S% L%" biçimindeki token değerini oku (light = :root, dark = .dark bloğu) */
38
- function tokenOku(ad: string, blok: 'light' | 'dark'): string | null {
39
- const kaynak =
38
+ function tokenOku(name: string, blok: 'light' | 'dark'): string | null {
39
+ const source =
40
40
  blok === 'dark'
41
41
  ? tokens.slice(tokens.indexOf('.dark'))
42
42
  : tokens.slice(0, tokens.indexOf('.dark'))
43
- return kaynak.match(new RegExp(`--${ad}:\\s*([^;]+);`))?.[1].trim() ?? null
43
+ return source.match(new RegExp(`--${name}:\\s*([^;]+);`))?.[1].trim() ?? null
44
44
  }
45
45
 
46
46
  function hslToRgb(h: number, s: number, l: number): [number, number, number] {
@@ -70,7 +70,7 @@ const rgbTokenRgb = (v: string): [number, number, number] => {
70
70
  return [p[0], p[1], p[2]]
71
71
  }
72
72
 
73
- function bagilParlaklik([r, g, b]: [number, number, number]): number {
73
+ function relativeLuminance([r, g, b]: [number, number, number]): number {
74
74
  const c = (v: number) => {
75
75
  const x = v / 255
76
76
  return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4)
@@ -79,8 +79,8 @@ function bagilParlaklik([r, g, b]: [number, number, number]): number {
79
79
  }
80
80
 
81
81
  function rgbKontrast(a: [number, number, number], b: [number, number, number]): number {
82
- const la = bagilParlaklik(a)
83
- const lb = bagilParlaklik(b)
82
+ const la = relativeLuminance(a)
83
+ const lb = relativeLuminance(b)
84
84
  const [hi, lo] = la > lb ? [la, lb] : [lb, la]
85
85
  return (hi + 0.05) / (lo + 0.05)
86
86
  }
@@ -89,12 +89,12 @@ function rgbKontrast(a: [number, number, number], b: [number, number, number]):
89
89
  function alfaBileske(
90
90
  ust: [number, number, number],
91
91
  alfa: number,
92
- zemin: [number, number, number]
92
+ backgroundVal: [number, number, number]
93
93
  ): [number, number, number] {
94
94
  return [
95
- Math.round(alfa * ust[0] + (1 - alfa) * zemin[0]),
96
- Math.round(alfa * ust[1] + (1 - alfa) * zemin[1]),
97
- Math.round(alfa * ust[2] + (1 - alfa) * zemin[2]),
95
+ Math.round(alfa * ust[0] + (1 - alfa) * backgroundVal[0]),
96
+ Math.round(alfa * ust[1] + (1 - alfa) * backgroundVal[1]),
97
+ Math.round(alfa * ust[2] + (1 - alfa) * backgroundVal[2]),
98
98
  ]
99
99
  }
100
100
 
@@ -103,20 +103,20 @@ function alfaBileske(
103
103
  * #284/#368 konvansiyonu: kontrast, --background'ın yanında bu gerçek yüzeye
104
104
  * göre de ölçülür (tokens.css --input notu).
105
105
  */
106
- const KART_GERCEK_DARK: [number, number, number] = [17, 24, 39]
106
+ const CARD_ACTUAL_DARK: [number, number, number] = [17, 24, 39]
107
107
 
108
108
  /** Tema başına dolgu (secondary-500) ve track (secondary@%20 ∘ zemin) renkleri */
109
- function temaRenkleri(blok: 'light' | 'dark') {
110
- const dolgu = rgbTokenRgb(tokenOku('secondary-500', 'light')!) // skala yalnız :root'ta — iki temada aynı
109
+ function themeColors(blok: 'light' | 'dark') {
110
+ const fill = rgbTokenRgb(tokenOku('secondary-500', 'light')!) // skala yalnız :root'ta — iki temada aynı
111
111
  const secondary = hslTokenRgb(tokenOku('secondary', blok)!)
112
112
  const background = hslTokenRgb(tokenOku('background', blok)!)
113
113
  const card = hslTokenRgb(tokenOku('card', blok)!)
114
- const zeminler: Array<[string, [number, number, number]]> = [
114
+ const backgrounds: Array<[string, [number, number, number]]> = [
115
115
  ['--background', background],
116
116
  ['--card', card],
117
117
  ]
118
- if (blok === 'dark') zeminler.push(['gerçek kart #111827', KART_GERCEK_DARK])
119
- return { dolgu, secondary, zeminler }
118
+ if (blok === 'dark') backgrounds.push(['gerçek kart #111827', CARD_ACTUAL_DARK])
119
+ return { fill, secondary, backgrounds }
120
120
  }
121
121
 
122
122
  describe('#372 progress secondary dolgu↔track kontrastı', () => {
@@ -143,14 +143,14 @@ describe('#372 progress secondary dolgu↔track kontrastı', () => {
143
143
  })
144
144
 
145
145
  describe('kaynak sözleşmesi: dolgu ile track FARKLI token’lara basıyor', () => {
146
- const kaynak = fs.readFileSync(path.join(KOK, 'components/ui/progress.tsx'), 'utf8')
146
+ const source = fs.readFileSync(path.join(KOK, 'components/ui/progress.tsx'), 'utf8')
147
147
 
148
148
  it('track secondary sınıfı `bg-secondary/20` (issue seçenek 1 — değişmedi)', () => {
149
- expect(kaynak).toMatch(/secondary:\s*"bg-secondary\/20"/)
149
+ expect(source).toMatch(/secondary:\s*"bg-secondary\/20"/)
150
150
  })
151
151
 
152
152
  it('dolgu secondary sınıfı `bg-[rgb(var(--secondary-500))]` (artık --secondary DEĞİL)', () => {
153
- expect(kaynak).toMatch(/secondary:\s*"bg-\[rgb\(var\(--secondary-500\)\)\]"/)
153
+ expect(source).toMatch(/secondary:\s*"bg-\[rgb\(var\(--secondary-500\)\)\]"/)
154
154
  })
155
155
  })
156
156
 
@@ -159,17 +159,17 @@ describe('#372 progress secondary dolgu↔track kontrastı', () => {
159
159
  render(React.createElement(Progress, { variant: 'secondary', value: 50 }))
160
160
  const track = screen.getByRole('progressbar')
161
161
  expect(track).toHaveClass('bg-secondary/20')
162
- const dolgu = track.firstElementChild as HTMLElement
163
- expect(dolgu).not.toBeNull()
164
- expect(dolgu.className).toContain('bg-[rgb(var(--secondary-500))]')
165
- expect(dolgu.className).not.toMatch(/(?:^|\s)bg-secondary(?:\s|$)/)
162
+ const fill = track.firstElementChild as HTMLElement
163
+ expect(fill).not.toBeNull()
164
+ expect(fill.className).toContain('bg-[rgb(var(--secondary-500))]')
165
+ expect(fill.className).not.toMatch(/(?:^|\s)bg-secondary(?:\s|$)/)
166
166
  })
167
167
 
168
168
  it('#318 düşüş mantığı KORUNUYOR — indicatorVariant hâlâ variant’ı ezer', () => {
169
169
  render(React.createElement(Progress, { variant: 'secondary', indicatorVariant: 'success', value: 50 }))
170
- const dolgu = screen.getByRole('progressbar').firstElementChild as HTMLElement
171
- expect(dolgu.className).toContain('bg-success')
172
- expect(dolgu.className).not.toContain('bg-[rgb(var(--secondary-500))]')
170
+ const fill = screen.getByRole('progressbar').firstElementChild as HTMLElement
171
+ expect(fill.className).toContain('bg-success')
172
+ expect(fill.className).not.toContain('bg-[rgb(var(--secondary-500))]')
173
173
  })
174
174
  })
175
175
 
@@ -177,11 +177,11 @@ describe('#372 progress secondary dolgu↔track kontrastı', () => {
177
177
  it('YENİ dolgu (secondary-500), track’in her olası zemindeki bileşkesine ≥3:1', () => {
178
178
  // Ölçülen değerler: light 4.63 (--background=--card=beyaz) · dark 4.23
179
179
  // (--background=--card) / 3.83 (gerçek kart #111827).
180
- const { dolgu, secondary, zeminler } = temaRenkleri(blok)
181
- for (const [ad, zemin] of zeminler) {
182
- const track = alfaBileske(secondary, 0.2, zemin)
183
- const oran = rgbKontrast(dolgu, track)
184
- expect({ zemin: ad, oran }).toEqual({ zemin: ad, oran: expect.any(Number) })
180
+ const { fill, secondary, backgrounds } = themeColors(blok)
181
+ for (const [name, backgroundVal] of backgrounds) {
182
+ const track = alfaBileske(secondary, 0.2, backgroundVal)
183
+ const oran = rgbKontrast(fill, track)
184
+ expect({ backgroundVal: name, oran }).toEqual({ backgroundVal: name, oran: expect.any(Number) })
185
185
  expect(oran).toBeGreaterThanOrEqual(3.0)
186
186
  }
187
187
  })
@@ -190,9 +190,9 @@ describe('#372 progress secondary dolgu↔track kontrastı', () => {
190
190
  // Biri dolguyu `bg-secondary`ye geri döndürürse bu test ona "eski değer
191
191
  // zaten görünmüyordu" diye değil, üstteki kriter testi kırmızıyla söyler;
192
192
  // burası ise düzeltmenin GEREKLİLİĞİNİ sabitler (light 1.02, dark ~1.0-1.5).
193
- const { secondary, zeminler } = temaRenkleri(blok)
194
- for (const [, zemin] of zeminler) {
195
- const track = alfaBileske(secondary, 0.2, zemin)
193
+ const { secondary, backgrounds } = themeColors(blok)
194
+ for (const [, backgroundVal] of backgrounds) {
195
+ const track = alfaBileske(secondary, 0.2, backgroundVal)
196
196
  expect(rgbKontrast(secondary, track)).toBeLessThan(3.0)
197
197
  }
198
198
  })
@@ -204,15 +204,15 @@ describe('#372 progress secondary dolgu↔track kontrastı', () => {
204
204
  // kademe dark track'e (near-black) yetişemiyor. Komşu kademeler bunu kanıtlar.
205
205
  it('secondary-400 light temada eşiğin ALTINDA kalır (~2.50)', () => {
206
206
  const k400 = rgbTokenRgb(tokenOku('secondary-400', 'light')!)
207
- const { secondary, zeminler } = temaRenkleri('light')
208
- const track = alfaBileske(secondary, 0.2, zeminler[0][1])
207
+ const { secondary, backgrounds } = themeColors('light')
208
+ const track = alfaBileske(secondary, 0.2, backgrounds[0][1])
209
209
  expect(rgbKontrast(k400, track)).toBeLessThan(3.0)
210
210
  })
211
211
 
212
212
  it('secondary-600 dark temada eşiğin ALTINDA kalır (~2.66)', () => {
213
213
  const k600 = rgbTokenRgb(tokenOku('secondary-600', 'light')!)
214
- const { secondary, zeminler } = temaRenkleri('dark')
215
- const track = alfaBileske(secondary, 0.2, zeminler[0][1])
214
+ const { secondary, backgrounds } = themeColors('dark')
215
+ const track = alfaBileske(secondary, 0.2, backgrounds[0][1])
216
216
  expect(rgbKontrast(k600, track)).toBeLessThan(3.0)
217
217
  })
218
218