@moontra/moonui 6.24.0 → 6.26.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 +436 -93
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +437 -95
  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 +95 -24
  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
@@ -40,12 +40,12 @@ const KOK = path.resolve(__dirname, '../../..')
40
40
  const tokens = fs.readFileSync(path.join(KOK, 'styles/tokens.css'), 'utf8')
41
41
 
42
42
  /** "H S% L%" biçimindeki token değerini oku (light = :root, dark = .dark bloğu) */
43
- function tokenOku(ad: string, blok: 'light' | 'dark'): string | null {
44
- const kaynak =
43
+ function tokenOku(name: string, blok: 'light' | 'dark'): string | null {
44
+ const source =
45
45
  blok === 'dark'
46
46
  ? tokens.slice(tokens.indexOf('.dark'))
47
47
  : tokens.slice(0, tokens.indexOf('.dark'))
48
- return kaynak.match(new RegExp(`--${ad}:\\s*([^;]+);`))?.[1].trim() ?? null
48
+ return source.match(new RegExp(`--${name}:\\s*([^;]+);`))?.[1].trim() ?? null
49
49
  }
50
50
 
51
51
  function hslToRgb(h: number, s: number, l: number): [number, number, number] {
@@ -75,7 +75,7 @@ const rgbTokenRgb = (v: string): [number, number, number] => {
75
75
  return [p[0], p[1], p[2]]
76
76
  }
77
77
 
78
- function bagilParlaklik([r, g, b]: [number, number, number]): number {
78
+ function relativeLuminance([r, g, b]: [number, number, number]): number {
79
79
  const c = (v: number) => {
80
80
  const x = v / 255
81
81
  return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4)
@@ -84,8 +84,8 @@ function bagilParlaklik([r, g, b]: [number, number, number]): number {
84
84
  }
85
85
 
86
86
  function rgbKontrast(a: [number, number, number], b: [number, number, number]): number {
87
- const la = bagilParlaklik(a)
88
- const lb = bagilParlaklik(b)
87
+ const la = relativeLuminance(a)
88
+ const lb = relativeLuminance(b)
89
89
  const [hi, lo] = la > lb ? [la, lb] : [lb, la]
90
90
  return (hi + 0.05) / (lo + 0.05)
91
91
  }
@@ -94,12 +94,12 @@ function rgbKontrast(a: [number, number, number], b: [number, number, number]):
94
94
  function alfaBileske(
95
95
  ust: [number, number, number],
96
96
  alfa: number,
97
- zemin: [number, number, number]
97
+ background: [number, number, number]
98
98
  ): [number, number, number] {
99
99
  return [
100
- Math.round(alfa * ust[0] + (1 - alfa) * zemin[0]),
101
- Math.round(alfa * ust[1] + (1 - alfa) * zemin[1]),
102
- Math.round(alfa * ust[2] + (1 - alfa) * zemin[2]),
100
+ Math.round(alfa * ust[0] + (1 - alfa) * background[0]),
101
+ Math.round(alfa * ust[1] + (1 - alfa) * background[1]),
102
+ Math.round(alfa * ust[2] + (1 - alfa) * background[2]),
103
103
  ]
104
104
  }
105
105
 
@@ -108,23 +108,23 @@ function alfaBileske(
108
108
  * #284/#368 konvansiyonu: kontrast, --background'ın yanında bu gerçek yüzeye
109
109
  * göre de ölçülür. Bu zemin bu bulguda DARBOĞAZ: en düşük oranları o veriyor.
110
110
  */
111
- const KART_GERCEK_DARK: [number, number, number] = [17, 24, 39]
111
+ const CARD_ACTUAL_DARK: [number, number, number] = [17, 24, 39]
112
112
 
113
113
  const AILELER = ['warning', 'error'] as const
114
114
  type Aile = (typeof AILELER)[number]
115
115
 
116
116
  /** Semantik token'ın ilgili temadaki değeri — `.dark` ezmesi YOKSA light'a düşer */
117
- function semantikRgb(aile: Aile, blok: 'light' | 'dark'): [number, number, number] {
117
+ function semanticRgb(aile: Aile, blok: 'light' | 'dark'): [number, number, number] {
118
118
  return hslTokenRgb(tokenOku(aile, blok) ?? tokenOku(aile, 'light')!)
119
119
  }
120
120
 
121
121
  /** O temada track'in oluşabileceği tüm zeminler */
122
- function zeminler(blok: 'light' | 'dark'): Array<[string, [number, number, number]]> {
122
+ function backgrounds(blok: 'light' | 'dark'): Array<[string, [number, number, number]]> {
123
123
  const z: Array<[string, [number, number, number]]> = [
124
124
  ['--background', hslTokenRgb(tokenOku('background', blok)!)],
125
125
  ['--card', hslTokenRgb(tokenOku('card', blok)!)],
126
126
  ]
127
- if (blok === 'dark') z.push(['gerçek kart #111827', KART_GERCEK_DARK])
127
+ if (blok === 'dark') z.push(['gerçek kart #111827', CARD_ACTUAL_DARK])
128
128
  return z
129
129
  }
130
130
 
@@ -133,44 +133,44 @@ function zeminler(blok: 'light' | 'dark'): Array<[string, [number, number, numbe
133
133
  // dizgelerine göre yapıyoruz. Böylece aşağıdaki ≥3:1 testleri component'e
134
134
  // GERÇEKTEN bağlanır: biri dolguyu eski hâline döndürürse (ya da track alfasını
135
135
  // oynatırsa) kriter testi kırmızı olur — yalnız sözleşme testleri değil.
136
- const kaynak = fs.readFileSync(path.join(KOK, 'components/ui/progress.tsx'), 'utf8')
137
- const AYRAC = kaynak.indexOf('progressIndicatorVariants')
138
- const TRACK_BLOK = kaynak.slice(0, AYRAC)
139
- const DOLGU_BLOK = kaynak.slice(AYRAC)
136
+ const source = fs.readFileSync(path.join(KOK, 'components/ui/progress.tsx'), 'utf8')
137
+ const AYRAC = source.indexOf('progressIndicatorVariants')
138
+ const TRACK_BLOK = source.slice(0, AYRAC)
139
+ const FILL_BLOCK = source.slice(AYRAC)
140
140
 
141
141
  /** CVA bloğundan `<aile>: "<sınıflar>"` değerini oku */
142
- function varyantSinifi(blok: string, aile: Aile): string {
142
+ function variantClass(blok: string, aile: Aile): string {
143
143
  const m = blok.match(new RegExp(`\\n\\s*${aile}:\\s*"([^"]+)"`))
144
144
  if (!m) throw new Error(`${aile} varyantı bulunamadı`)
145
145
  return m[1]
146
146
  }
147
147
 
148
148
  /** Tek bir Tailwind bg sınıfını renge çevir (arbitrary skala VEYA semantik token) */
149
- function sinifRengi(sinif: string, blok: 'light' | 'dark'): [number, number, number] {
150
- const skala = sinif.match(/^bg-\[rgb\(var\(--([a-z0-9-]+)\)\)\]$/)
151
- if (skala) return rgbTokenRgb(tokenOku(skala[1], 'light')!) // skala yalnız :root'ta
152
- const semantik = sinif.match(/^bg-([a-z-]+)$/)
153
- if (semantik) return hslTokenRgb(tokenOku(semantik[1], blok) ?? tokenOku(semantik[1], 'light')!)
154
- throw new Error(`çözülemeyen sınıf: ${sinif}`)
149
+ function classColor(cls: string, blok: 'light' | 'dark'): [number, number, number] {
150
+ const scale = cls.match(/^bg-\[rgb\(var\(--([a-z0-9-]+)\)\)\]$/)
151
+ if (scale) return rgbTokenRgb(tokenOku(scale[1], 'light')!) // skala yalnız :root'ta
152
+ const semantic = cls.match(/^bg-([a-z-]+)$/)
153
+ if (semantic) return hslTokenRgb(tokenOku(semantic[1], blok) ?? tokenOku(semantic[1], 'light')!)
154
+ throw new Error(`çözülemeyen sınıf: ${cls}`)
155
155
  }
156
156
 
157
157
  /** Dolgunun o temada GERÇEKTEN uygulanan rengi — `dark:` öneki dark'ta kazanır */
158
- function dolguRengi(aile: Aile, blok: 'light' | 'dark'): [number, number, number] {
159
- const siniflar = varyantSinifi(DOLGU_BLOK, aile).split(/\s+/)
160
- const darkSinif = siniflar.find((s) => s.startsWith('dark:'))?.slice('dark:'.length)
161
- const lightSinif = siniflar.find((s) => !s.startsWith('dark:'))
162
- if (!lightSinif) throw new Error(`${aile}: temel (light) sınıf yok`)
163
- return sinifRengi(blok === 'dark' ? darkSinif ?? lightSinif : lightSinif, blok)
158
+ function fillColor(aile: Aile, blok: 'light' | 'dark'): [number, number, number] {
159
+ const classNames = variantClass(FILL_BLOCK, aile).split(/\s+/)
160
+ const darkClass = classNames.find((s) => s.startsWith('dark:'))?.slice('dark:'.length)
161
+ const lightClass = classNames.find((s) => !s.startsWith('dark:'))
162
+ if (!lightClass) throw new Error(`${aile}: temel (light) sınıf yok`)
163
+ return classColor(blok === 'dark' ? darkClass ?? lightClass : lightClass, blok)
164
164
  }
165
165
 
166
166
  /** Track'in o zemin üstündeki bileşke rengi — alfa da kaynaktan okunur */
167
167
  function trackler(aile: Aile, blok: 'light' | 'dark') {
168
- const sinif = varyantSinifi(TRACK_BLOK, aile)
169
- const m = sinif.match(/^bg-([a-z-]+)\/(\d+)$/)
170
- if (!m) throw new Error(`track sınıfı çözülemedi: ${sinif}`)
171
- const taban = hslTokenRgb(tokenOku(m[1], blok) ?? tokenOku(m[1], 'light')!)
168
+ const cls = variantClass(TRACK_BLOK, aile)
169
+ const m = cls.match(/^bg-([a-z-]+)\/(\d+)$/)
170
+ if (!m) throw new Error(`track sınıfı çözülemedi: ${cls}`)
171
+ const base = hslTokenRgb(tokenOku(m[1], blok) ?? tokenOku(m[1], 'light')!)
172
172
  const alfa = Number(m[2]) / 100
173
- return zeminler(blok).map(([ad, z]) => [ad, alfaBileske(taban, alfa, z)] as const)
173
+ return backgrounds(blok).map(([name, z]) => [name, alfaBileske(base, alfa, z)] as const)
174
174
  }
175
175
 
176
176
  const KADEMELER = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]
@@ -192,8 +192,8 @@ describe('#424 progress warning/error dolgu↔track kontrastı', () => {
192
192
  // warning dolgu rgb(245,159,10) / track rgb(253,236,206), error dolgu
193
193
  // rgb(239,67,67) / track rgb(252,217,217). Model bunları tutturamıyorsa
194
194
  // aşağıdaki tüm oranlar anlamsızdır.
195
- expect(semantikRgb('warning', 'light')).toEqual([245, 159, 10])
196
- expect(semantikRgb('error', 'light')).toEqual([239, 67, 67])
195
+ expect(semanticRgb('warning', 'light')).toEqual([245, 159, 10])
196
+ expect(semanticRgb('error', 'light')).toEqual([239, 67, 67])
197
197
  expect(trackler('warning', 'light')[0][1]).toEqual([253, 236, 206])
198
198
  expect(trackler('error', 'light')[0][1]).toEqual([252, 217, 217])
199
199
  })
@@ -204,7 +204,7 @@ describe('#424 progress warning/error dolgu↔track kontrastı', () => {
204
204
  // `--warning`/`--error` eklerse bu test kırmızı olur ve component
205
205
  // düzeyindeki `dark:` telafisinin yeniden ölçülmesi gerektiğini söyler.
206
206
  expect(tokenOku(aile, 'dark')).toBeNull()
207
- expect(semantikRgb(aile, 'light')).toEqual(semantikRgb(aile, 'dark'))
207
+ expect(semanticRgb(aile, 'light')).toEqual(semanticRgb(aile, 'dark'))
208
208
  })
209
209
 
210
210
  it('karşıtlık: --success tema-uyarlanabilir olduğu İÇİN eşiği geçiyor', () => {
@@ -226,11 +226,11 @@ describe('#424 progress warning/error dolgu↔track kontrastı', () => {
226
226
 
227
227
  describe('kaynak sözleşmesi: dolgu ile track FARKLI renklere basıyor', () => {
228
228
  it.each(AILELER)('track %s sınıfı `/20` alfasını KORUYOR (altı varyantın ortak deseni)', (aile) => {
229
- expect(kaynak).toMatch(new RegExp(`${aile}:\\s*"bg-${aile}\\/20"`))
229
+ expect(source).toMatch(new RegExp(`${aile}:\\s*"bg-${aile}\\/20"`))
230
230
  })
231
231
 
232
232
  it.each(AILELER)('dolgu %s sınıfı light\'ta -700, dark\'ta semantik token', (aile) => {
233
- expect(kaynak).toMatch(
233
+ expect(source).toMatch(
234
234
  new RegExp(`${aile}:\\s*"bg-\\[rgb\\(var\\(--${aile}-700\\)\\)\\] dark:bg-${aile}"`)
235
235
  )
236
236
  })
@@ -241,22 +241,22 @@ describe('#424 progress warning/error dolgu↔track kontrastı', () => {
241
241
  render(React.createElement(Progress, { variant: aile, value: 50 }))
242
242
  const track = screen.getByRole('progressbar')
243
243
  expect(track).toHaveClass(`bg-${aile}/20`)
244
- const dolgu = track.firstElementChild as HTMLElement
245
- expect(dolgu).not.toBeNull()
246
- expect(dolgu.className).toContain(`bg-[rgb(var(--${aile}-700))]`)
247
- expect(dolgu.className).toContain(`dark:bg-${aile}`)
244
+ const fill = track.firstElementChild as HTMLElement
245
+ expect(fill).not.toBeNull()
246
+ expect(fill.className).toContain(`bg-[rgb(var(--${aile}-700))]`)
247
+ expect(fill.className).toContain(`dark:bg-${aile}`)
248
248
  // Light'ta semantik token dolguya UYGULANMAMALI (track'in aynısıydı):
249
249
  // yalnız `dark:` önekli hâli kalmalı.
250
- expect(dolgu.className).not.toMatch(new RegExp(`(?:^|\\s)bg-${aile}(?:\\s|$)`))
250
+ expect(fill.className).not.toMatch(new RegExp(`(?:^|\\s)bg-${aile}(?:\\s|$)`))
251
251
  })
252
252
 
253
253
  it.each(AILELER)('#318 düşüş mantığı KORUNUYOR — indicatorVariant hâlâ %s varyantını ezer', (aile) => {
254
254
  render(
255
255
  React.createElement(Progress, { variant: aile, indicatorVariant: 'success', value: 50 })
256
256
  )
257
- const dolgu = screen.getByRole('progressbar').firstElementChild as HTMLElement
258
- expect(dolgu.className).toContain('bg-success')
259
- expect(dolgu.className).not.toContain(`--${aile}-700`)
257
+ const fill = screen.getByRole('progressbar').firstElementChild as HTMLElement
258
+ expect(fill.className).toContain('bg-success')
259
+ expect(fill.className).not.toContain(`--${aile}-700`)
260
260
  })
261
261
  })
262
262
 
@@ -264,10 +264,10 @@ describe('#424 progress warning/error dolgu↔track kontrastı', () => {
264
264
  it.each(['light', 'dark'] as const)('%s temada TÜM zeminlerde eşiği geçiyor', (blok) => {
265
265
  // Ölçülen: warning light 4.24 · dark 6.97 (docs kartı 5.77)
266
266
  // error light 4.95 · dark 4.46 (docs kartı 3.82)
267
- const dolgu = dolguRengi(aile, blok)
268
- for (const [ad, track] of trackler(aile, blok)) {
269
- const oran = rgbKontrast(dolgu, track)
270
- expect({ zemin: ad, gecti: oran >= 3.0 }).toEqual({ zemin: ad, gecti: true })
267
+ const fill = fillColor(aile, blok)
268
+ for (const [name, track] of trackler(aile, blok)) {
269
+ const oran = rgbKontrast(fill, track)
270
+ expect({ background: name, didPass: oran >= 3.0 }).toEqual({ background: name, didPass: true })
271
271
  }
272
272
  })
273
273
 
@@ -275,18 +275,18 @@ describe('#424 progress warning/error dolgu↔track kontrastı', () => {
275
275
  // warning 1.83 · error 2.89. Bu test düzeltmenin GEREKLİLİĞİNİ sabitler:
276
276
  // biri dolguyu `bg-warning`/`bg-error`e geri döndürürse üstteki kriter
277
277
  // testi kırmızı olur, burası da nedenini belgeler.
278
- const eski = semantikRgb(aile, 'light')
278
+ const old = semanticRgb(aile, 'light')
279
279
  for (const [, track] of trackler(aile, 'light')) {
280
- expect(rgbKontrast(eski, track)).toBeLessThan(3.0)
280
+ expect(rgbKontrast(old, track)).toBeLessThan(3.0)
281
281
  }
282
282
  })
283
283
 
284
284
  it('DARK render değişmedi — eski dolgu dark\'ta zaten geçiyordu ve hâlâ o renk', () => {
285
285
  // Bulgu light'a özgüydü; düzeltmenin dark'ta hiçbir şeyi oynatmaması
286
286
  // bilinçli bir kısıt (regresyon yüzeyini sıfıra indirir).
287
- expect(dolguRengi(aile, 'dark')).toEqual(semantikRgb(aile, 'dark'))
287
+ expect(fillColor(aile, 'dark')).toEqual(semanticRgb(aile, 'dark'))
288
288
  for (const [, track] of trackler(aile, 'dark')) {
289
- expect(rgbKontrast(semantikRgb(aile, 'dark'), track)).toBeGreaterThanOrEqual(3.0)
289
+ expect(rgbKontrast(semanticRgb(aile, 'dark'), track)).toBeGreaterThanOrEqual(3.0)
290
290
  }
291
291
  })
292
292
  })
@@ -297,7 +297,7 @@ describe('#424 progress warning/error dolgu↔track kontrastı', () => {
297
297
  // (gerçek docs kartı zemininde) eşiğin ALTINDA: warning 2.50 · error 2.23.
298
298
  // `dark:` ayrımı tam olarak bunu önlüyor.
299
299
  const k700 = rgbTokenRgb(tokenOku(`${aile}-700`, 'light')!)
300
- const docsTrack = trackler(aile, 'dark').find(([ad]) => ad === 'gerçek kart #111827')![1]
300
+ const docsTrack = trackler(aile, 'dark').find(([name]) => name === 'gerçek kart #111827')![1]
301
301
  expect(rgbKontrast(k700, docsTrack)).toBeLessThan(3.0)
302
302
  })
303
303
 
@@ -308,23 +308,23 @@ describe('#424 progress warning/error dolgu↔track kontrastı', () => {
308
308
  // skalanın 11 kademesinin HİÇBİRİ tema-kör olarak iki temayı birden
309
309
  // geçemiyor. Bu, `dark:` ayrımını zorunlu kılan kanıttır.
310
310
  const tumTrackler = [...trackler(aile, 'light'), ...trackler(aile, 'dark')]
311
- const gecen = KADEMELER.filter((k) => {
311
+ const passedCount = KADEMELER.filter((k) => {
312
312
  const ham = tokenOku(`${aile}-${k}`, 'light')
313
313
  if (!ham) return false
314
314
  const d = rgbTokenRgb(ham)
315
315
  return tumTrackler.every(([, t]) => rgbKontrast(d, t) >= 3.0)
316
316
  })
317
- expect(gecen).toEqual([])
317
+ expect(passedCount).toEqual([])
318
318
  })
319
319
 
320
320
  it.each(AILELER)('%s track alfası düşürülmedi — /20 ayrımı /10\'dan güçlü', (aile) => {
321
321
  // Alternatif öneri track'i `/10`'a çekmekti; kabul edilmedi çünkü track
322
322
  // zemine yaklaşır ve barın TOPLAM uzunluğu görünmez olur. Altı varyantın
323
323
  // ortak `/20` deseni de bozulurdu.
324
- const zemin = zeminler('light')[0][1]
325
- const sem = semantikRgb(aile, 'light')
326
- const yirmi = rgbKontrast(alfaBileske(sem, 0.2, zemin), zemin)
327
- const on = rgbKontrast(alfaBileske(sem, 0.1, zemin), zemin)
324
+ const background = backgrounds('light')[0][1]
325
+ const sem = semanticRgb(aile, 'light')
326
+ const yirmi = rgbKontrast(alfaBileske(sem, 0.2, background), background)
327
+ const on = rgbKontrast(alfaBileske(sem, 0.1, background), background)
328
328
  expect(yirmi).toBeGreaterThan(on)
329
329
  })
330
330
  })
@@ -152,8 +152,8 @@ describe('#308-5/6 command', () => {
152
152
  </Command>
153
153
  )
154
154
  const listbox = container.querySelector('[role="listbox"]')!
155
- const gorunurCocuklar = Array.from(listbox.querySelectorAll('[role="separator"]'))
155
+ const visibleChildren = Array.from(listbox.querySelectorAll('[role="separator"]'))
156
156
  .filter((el) => el.getAttribute('aria-hidden') !== 'true')
157
- expect(gorunurCocuklar).toHaveLength(0)
157
+ expect(visibleChildren).toHaveLength(0)
158
158
  })
159
159
  })
@@ -25,31 +25,31 @@ const KOKLER = [
25
25
  ]
26
26
 
27
27
  /** Renk ailesinden yardımcı adları — boyut/hiza/spacing DEĞİL. */
28
- const RENK_ONEKLERI = ['text', 'bg', 'border', 'from', 'via', 'to', 'ring', 'divide',
28
+ const COLOR_PREFIXES = ['text', 'bg', 'border', 'from', 'via', 'to', 'ring', 'divide',
29
29
  'placeholder', 'decoration', 'caret', 'accent', 'outline', 'shadow']
30
30
 
31
31
  /** `text-*` ailesinde renk OLMAYAN değerler (boyut, hiza, taşma). */
32
- const RENK_OLMAYAN_TEXT = new Set([
32
+ const NON_COLOR_TEXT = new Set([
33
33
  'xs','sm','base','lg','xl','2xl','3xl','4xl','5xl','6xl','7xl','8xl','9xl',
34
34
  'left','center','right','justify','start','end',
35
35
  'wrap','nowrap','balance','pretty','clip','ellipsis',
36
36
  ])
37
37
 
38
- function dosyalar(kok: string): string[] {
39
- const cikti: string[] = []
40
- if (!fs.existsSync(kok)) return cikti
38
+ function files(root: string): string[] {
39
+ const output: string[] = []
40
+ if (!fs.existsSync(root)) return output
41
41
  ;(function walk(dir: string) {
42
42
  for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
43
43
  const p = path.join(dir, e.name)
44
44
  if (e.isDirectory()) {
45
45
  if (!/node_modules|__tests__|dist/.test(p)) walk(p)
46
- } else if (/\.(tsx?|css)$/.test(e.name)) cikti.push(p)
46
+ } else if (/\.(tsx?|css)$/.test(e.name)) output.push(p)
47
47
  }
48
- })(kok)
49
- return cikti
48
+ })(root)
49
+ return output
50
50
  }
51
51
 
52
- const tumDosyalar = KOKLER.flatMap(dosyalar)
52
+ const tumDosyalar = KOKLER.flatMap(files)
53
53
  const RESPONSIVE = /\b(sm|md|lg|xl|2xl):([a-z][a-z0-9-]*)(?:-([a-z0-9[\]#/.%-]+))?/g
54
54
 
55
55
  describe('#354 responsive renk değişmezi', () => {
@@ -62,31 +62,31 @@ describe('#354 responsive renk değişmezi', () => {
62
62
  // günkü değer 202'ydi; #365'te ölü `dashboard/demo.tsx` (3 varyant) silinince
63
63
  // kırmızıya döndü — regex bozulmadan. Amaç "tarayıcı ölü mü" sorusunu
64
64
  // yanıtlamak olduğundan büyüklük mertebesi yeterli.
65
- let toplam = 0
65
+ let total = 0
66
66
  for (const f of tumDosyalar) {
67
- toplam += [...fs.readFileSync(f, 'utf8').matchAll(RESPONSIVE)].length
67
+ total += [...fs.readFileSync(f, 'utf8').matchAll(RESPONSIVE)].length
68
68
  }
69
- expect(toplam).toBeGreaterThan(100)
69
+ expect(total).toBeGreaterThan(100)
70
70
  expect(tumDosyalar.length).toBeGreaterThan(100)
71
71
  })
72
72
 
73
73
  it('hiçbir kırılma noktası RENK değiştirmiyor', () => {
74
- const ihlaller: string[] = []
74
+ const violations: string[] = []
75
75
  for (const f of tumDosyalar) {
76
76
  const src = fs.readFileSync(f, 'utf8')
77
77
  for (const m of src.matchAll(RESPONSIVE)) {
78
- const [tam, , yardimci, deger] = m
79
- if (!RENK_ONEKLERI.includes(yardimci)) continue
80
- if (!deger) continue
78
+ const [tam, , helper, value] = m
79
+ if (!COLOR_PREFIXES.includes(helper)) continue
80
+ if (!value) continue
81
81
  // `text-` ailesinde boyut/hiza değerleri renk değildir
82
- if (yardimci === 'text' && RENK_OLMAYAN_TEXT.has(deger)) continue
82
+ if (helper === 'text' && NON_COLOR_TEXT.has(value)) continue
83
83
  // köşeli parantezli keyfi değerler: renk mi boyut mu? `#`/`rgb` varsa renk.
84
- if (deger.startsWith('[') && !/#|rgb|hsl/.test(deger)) continue
85
- ihlaller.push(`${path.basename(f)} → ${tam}`)
84
+ if (value.startsWith('[') && !/#|rgb|hsl/.test(value)) continue
85
+ violations.push(`${path.basename(f)} → ${tam}`)
86
86
  }
87
87
  }
88
88
  // Boş olmalı: aksi halde contrast ölçümü artık viewport'a bağımlıdır ve
89
89
  // #354'ün "tek genişlikte ölçmek yeterli" gerekçesi düşer.
90
- expect(ihlaller).toEqual([])
90
+ expect(violations).toEqual([])
91
91
  })
92
92
  })
@@ -393,9 +393,14 @@ describe('Select Components', () => {
393
393
  item = screen.getByTestId('select-item')
394
394
  expect(item).toHaveClass('text-success')
395
395
 
396
+ // #623: `text-warning` (DEFAULT token) açık temada beyaz zeminde 2.14:1 ölçüldü —
397
+ // AA'nın (4.5) ve hatta büyük-metin eşiğinin (3.0) altında. `--warning` aynı zamanda
398
+ // `bg-warning` yüzeylerinde kullanıldığı için token'ın kendisi DEĞİŞTİRİLMEDİ;
399
+ // metin rengi ölçülmüş bir light/dark çiftine taşındı: 4.92 (light) / 10.51 (dark).
396
400
  rerender(<SelectItem value="item1" variant="warning">Item</SelectItem>)
397
401
  item = screen.getByTestId('select-item')
398
- expect(item).toHaveClass('text-warning')
402
+ expect(item).toHaveClass('text-warning-700')
403
+ expect(item).toHaveClass('dark:text-warning-500')
399
404
  })
400
405
 
401
406
  it('applies size classes correctly', () => {
@@ -20,16 +20,16 @@ import * as path from 'path'
20
20
  const HAM_PALET = /\b(?:bg|text|border|ring|from|to|via)-(red|green|yellow|orange|blue)-\d{2,3}\b/
21
21
 
22
22
  /** Durum varyantı adları — cva içinde bu anahtarların değerleri token kullanmalı. */
23
- const DURUM_ANAHTARLARI = ['destructive', 'success', 'warning', 'info', 'error']
23
+ const STATUS_KEYS = ['destructive', 'success', 'warning', 'info', 'error']
24
24
 
25
25
  interface Ihlal {
26
26
  file: string
27
27
  line: number
28
- anahtar: string
29
- parca: string
28
+ key: string
29
+ part: string
30
30
  }
31
31
 
32
- function tara(root: string): Ihlal[] {
32
+ function scan(root: string): Ihlal[] {
33
33
  const files: string[] = []
34
34
  ;(function walk(dir: string) {
35
35
  if (!fs.existsSync(dir)) return
@@ -41,27 +41,27 @@ function tara(root: string): Ihlal[] {
41
41
  }
42
42
  })(root)
43
43
 
44
- const ihlaller: Ihlal[] = []
44
+ const violations: Ihlal[] = []
45
45
  for (const f of files) {
46
46
  const src = fs.readFileSync(f, 'utf8')
47
- for (const anahtar of DURUM_ANAHTARLARI) {
47
+ for (const key of STATUS_KEYS) {
48
48
  // `destructive: "…"` biçimindeki cva varyant girdisini yakala
49
- const re = new RegExp(`\\n[ \\t]*${anahtar}:\\s*(["'])([\\s\\S]*?)\\1`, 'g')
49
+ const re = new RegExp(`\\n[ \\t]*${key}:\\s*(["'])([\\s\\S]*?)\\1`, 'g')
50
50
  for (const m of src.matchAll(re)) {
51
- const deger = m[2]
52
- const hit = deger.match(HAM_PALET)
51
+ const value = m[2]
52
+ const hit = value.match(HAM_PALET)
53
53
  if (hit) {
54
- ihlaller.push({
54
+ violations.push({
55
55
  file: path.relative(process.cwd(), f),
56
56
  line: src.slice(0, m.index).split('\n').length + 1,
57
- anahtar,
58
- parca: hit[0],
57
+ key,
58
+ part: hit[0],
59
59
  })
60
60
  }
61
61
  }
62
62
  }
63
63
  }
64
- return ihlaller
64
+ return violations
65
65
  }
66
66
 
67
67
  /**
@@ -76,12 +76,12 @@ function tara(root: string): Ihlal[] {
76
76
  * Takip işi bu dosyaları dönüştürüp satırları buradan siler — liste boşalınca kural
77
77
  * tam sıkı hale gelir.
78
78
  */
79
- const BILINEN_KALANLAR: string[] = []
79
+ const KNOWN_REMAINING: string[] = []
80
80
 
81
81
  const SRC_ROOT = path.resolve(__dirname, '../../..')
82
- const tumIhlaller = tara(SRC_ROOT)
83
- const ihlaller = tumIhlaller.filter(
84
- (i) => !BILINEN_KALANLAR.some((b) => i.file.replace(/\\/g, '/').endsWith(b))
82
+ const tumIhlaller = scan(SRC_ROOT)
83
+ const violations = tumIhlaller.filter(
84
+ (i) => !KNOWN_REMAINING.some((b) => i.file.replace(/\\/g, '/').endsWith(b))
85
85
  )
86
86
 
87
87
  describe('#313/#314/#323 semantik durum varyantları token kullanır', () => {
@@ -96,18 +96,18 @@ describe('#313/#314/#323 semantik durum varyantları token kullanır', () => {
96
96
  })
97
97
 
98
98
  it('durum varyantlarında ham Tailwind paleti YOK (bilinen kalanlar hariç)', () => {
99
- const rapor = ihlaller.map((i) => `${i.file}:${i.line} → ${i.anahtar} içinde "${i.parca}"`)
100
- expect(rapor).toEqual([])
99
+ const report = violations.map((i) => `${i.file}:${i.line} → ${i.key} içinde "${i.part}"`)
100
+ expect(report).toEqual([])
101
101
  })
102
102
 
103
103
  it('cırcır listesi ÇALIŞIYOR — listelenen dosyalar gerçekten hâlâ ihlalli', () => {
104
104
  // Bir dosya dönüştürüldüğünde bu test kırmızı olur ve satırı listeden silmeyi hatırlatır.
105
105
  // Aksi hâlde liste sonsuza dek şişer ve kural sessizce gevşer.
106
- const halaIhlalli = new Set(
107
- tumIhlaller.map((i) => BILINEN_KALANLAR.find((b) => i.file.replace(/\\/g, '/').endsWith(b)))
106
+ const stillViolating = new Set(
107
+ tumIhlaller.map((i) => KNOWN_REMAINING.find((b) => i.file.replace(/\\/g, '/').endsWith(b)))
108
108
  )
109
- const gereksizListelenen = BILINEN_KALANLAR.filter((b) => !halaIhlalli.has(b))
110
- expect(gereksizListelenen).toEqual([])
109
+ const redundantlyListed = KNOWN_REMAINING.filter((b) => !stillViolating.has(b))
110
+ expect(redundantlyListed).toEqual([])
111
111
  })
112
112
 
113
113
  it('toast success/warning/info gerçekten `--*-subtle` token\'larını kullanıyor', () => {
@@ -109,7 +109,7 @@ describe('#319 size skalası konvansiyonu (free)', () => {
109
109
  })
110
110
 
111
111
  it('`default` yalnız SAF ALIAS olarak var — başka bir basamakla aynı değeri taşır', () => {
112
- const ihlaller = maps
112
+ const violations = maps
113
113
  .filter((m) => m.entries.some(([k]) => k === DEPRECATED_ALIAS))
114
114
  .filter((m) => {
115
115
  const val = m.entries.find(([k]) => k === DEPRECATED_ALIAS)![1]
@@ -117,14 +117,14 @@ describe('#319 size skalası konvansiyonu (free)', () => {
117
117
  return !m.entries.some(([k, v]) => k !== DEPRECATED_ALIAS && v === val)
118
118
  })
119
119
  .map((m) => `${m.file}:${m.line}`)
120
- expect(ihlaller).toEqual([])
120
+ expect(violations).toEqual([])
121
121
  })
122
122
 
123
123
  it('`defaultVariants.size` kanonik bir ada işaret eder, `default`a DEĞİL', () => {
124
- const ihlaller = maps
124
+ const violations = maps
125
125
  .filter((m) => m.defaultVariant === DEPRECATED_ALIAS)
126
126
  .map((m) => `${m.file}:${m.line}`)
127
- expect(ihlaller).toEqual([])
127
+ expect(violations).toEqual([])
128
128
  })
129
129
 
130
130
  /**
@@ -137,7 +137,7 @@ describe('#319 size skalası konvansiyonu (free)', () => {
137
137
  * yazan tüketici, prop hiç vermemiş gibi bir sonuç alır.
138
138
  */
139
139
  it('`default`, `defaultVariants`ın işaret ettiği basamakla AYNI değeri üretir', () => {
140
- const ihlaller = maps
140
+ const violations = maps
141
141
  .filter((m) => m.entries.some(([k]) => k === DEPRECATED_ALIAS))
142
142
  .filter((m) => {
143
143
  const aliasVal = m.entries.find(([k]) => k === DEPRECATED_ALIAS)![1]
@@ -145,7 +145,7 @@ describe('#319 size skalası konvansiyonu (free)', () => {
145
145
  return dvVal === undefined || aliasVal !== dvVal
146
146
  })
147
147
  .map((m) => `${m.file}:${m.line} (default→"${m.defaultVariant}")`)
148
- expect(ihlaller).toEqual([])
148
+ expect(violations).toEqual([])
149
149
  })
150
150
 
151
151
  /**
@@ -165,7 +165,7 @@ describe('#319 size skalası konvansiyonu (free)', () => {
165
165
  }
166
166
  })(SRC_ROOT)
167
167
 
168
- const ihlaller: string[] = []
168
+ const violations: string[] = []
169
169
  for (const f of files) {
170
170
  const src = fs.readFileSync(f, 'utf8')
171
171
  for (const m of src.matchAll(/\bsize\??\s*:\s*((?:"[a-zA-Z0-9_]+"\s*\|\s*)+"[a-zA-Z0-9_]+")/g)) {
@@ -174,16 +174,16 @@ describe('#319 size skalası konvansiyonu (free)', () => {
174
174
 
175
175
  // (a) sözlük dışı bir değer
176
176
  const disi = values.filter((v) => !ALLOWED.has(v))
177
- if (disi.length) ihlaller.push(`${yer} → sözlük dışı: ${disi.join(', ')}`)
177
+ if (disi.length) violations.push(`${yer} → sözlük dışı: ${disi.join(', ')}`)
178
178
 
179
179
  // (b) `default` ALIAS'tır: kanonik adın YERİNE geçemez, yanında durur.
180
180
  // `"sm" | "default" | "lg"` gibi bir union `md`yi hiç sunmadığı için ihlaldir —
181
181
  // color-picker tam olarak bu şekilde migrasyondan kaçmıştı.
182
182
  if (values.includes(DEPRECATED_ALIAS) && !values.includes('md')) {
183
- ihlaller.push(`${yer} → \`default\` var ama kanonik \`md\` YOK`)
183
+ violations.push(`${yer} → \`default\` var ama kanonik \`md\` YOK`)
184
184
  }
185
185
  }
186
186
  }
187
- expect(ihlaller).toEqual([])
187
+ expect(violations).toEqual([])
188
188
  })
189
189
  })