@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.
- package/dist/cdn/moonui.css +2 -2
- package/dist/cdn/moonui.esm.js +12 -12
- package/dist/cdn/moonui.global.js +12 -12
- package/dist/index.d.mts +24 -6
- package/dist/index.d.ts +24 -6
- package/dist/index.js +436 -93
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +437 -95
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/issue-544-kontrast.test.tsx +153 -145
- package/src/__tests__/issue-645-overlay-tab-focus.test.tsx +262 -0
- package/src/__tests__/issue-646-dark-scope-override.test.tsx +297 -0
- package/src/__tests__/issue-661-a11y.test.tsx +113 -0
- package/src/__tests__/issue-661-token-kontrast.test.tsx +82 -0
- package/src/components/ui/__tests__/accent-foreground-contrast.test.ts +197 -0
- package/src/components/ui/__tests__/accordion.test.tsx +6 -4
- package/src/components/ui/__tests__/alert.test.tsx +4 -1
- package/src/components/ui/__tests__/button.test.tsx +6 -1
- package/src/components/ui/__tests__/dark-token-contrast.test.ts +18 -18
- package/src/components/ui/__tests__/heading-level-policy.test.tsx +4 -4
- package/src/components/ui/__tests__/issue-643-card-token-parite.test.tsx +113 -0
- package/src/components/ui/__tests__/issue-649-button-aschild.test.tsx +199 -0
- package/src/components/ui/__tests__/issue-664-separator-dekoratif-rol.test.tsx +41 -0
- package/src/components/ui/__tests__/preset-color-shadowing.test.ts +10 -10
- package/src/components/ui/__tests__/primary-subtle-token.test.ts +7 -7
- package/src/components/ui/__tests__/progress-secondary-contrast.test.ts +38 -38
- package/src/components/ui/__tests__/progress-warning-error-contrast.test.ts +64 -64
- package/src/components/ui/__tests__/quality-group-de.test.tsx +2 -2
- package/src/components/ui/__tests__/responsive-color-invariance.test.ts +20 -20
- package/src/components/ui/__tests__/select.test.tsx +6 -1
- package/src/components/ui/__tests__/semantic-token-usage.test.ts +23 -23
- package/src/components/ui/__tests__/size-scale-convention.test.ts +10 -10
- package/src/components/ui/__tests__/slider-accent-contrast.test.ts +142 -136
- package/src/components/ui/__tests__/slider-secondary-contrast.test.ts +60 -60
- package/src/components/ui/__tests__/slider-warning-error-contrast.test.ts +100 -100
- package/src/components/ui/__tests__/switch-variant-contrast.test.tsx +103 -103
- package/src/components/ui/__tests__/tooltip.test.tsx +122 -0
- package/src/components/ui/accordion.tsx +16 -3
- package/src/components/ui/alert.tsx +1 -1
- package/src/components/ui/badge.tsx +12 -1
- package/src/components/ui/button.tsx +95 -24
- package/src/components/ui/card.tsx +59 -5
- package/src/components/ui/dropdown-menu.tsx +99 -5
- package/src/components/ui/file-upload.tsx +4 -0
- package/src/components/ui/index.ts +4 -1
- package/src/components/ui/popover-pro.tsx +41 -0
- package/src/components/ui/popover.tsx +162 -7
- package/src/components/ui/rating.tsx +24 -2
- package/src/components/ui/select.tsx +4 -4
- package/src/components/ui/simple-editor.tsx +3 -18
- package/src/components/ui/tabs.tsx +16 -1
- package/src/components/ui/textarea.tsx +6 -1
- package/src/components/ui/toast.tsx +17 -2
- package/src/components/ui/tooltip.tsx +154 -2
- package/src/styles/tokens.css +34 -2
package/package.json
CHANGED
|
@@ -49,21 +49,21 @@ import * as path from 'path'
|
|
|
49
49
|
import { Badge } from '../components/ui/badge'
|
|
50
50
|
import { Button } from '../components/ui/button'
|
|
51
51
|
|
|
52
|
-
const
|
|
52
|
+
const PKG = path.resolve(__dirname, '..')
|
|
53
53
|
const DEPO = path.resolve(__dirname, '../../../..')
|
|
54
|
-
const tokens = fs.readFileSync(path.join(
|
|
55
|
-
const badgeSrc = fs.readFileSync(path.join(
|
|
56
|
-
const buttonSrc = fs.readFileSync(path.join(
|
|
54
|
+
const tokens = fs.readFileSync(path.join(PKG, 'styles/tokens.css'), 'utf8')
|
|
55
|
+
const badgeSrc = fs.readFileSync(path.join(PKG, 'components/ui/badge.tsx'), 'utf8')
|
|
56
|
+
const buttonSrc = fs.readFileSync(path.join(PKG, 'components/ui/button.tsx'), 'utf8')
|
|
57
57
|
|
|
58
58
|
type RGB = [number, number, number]
|
|
59
59
|
type Blok = 'light' | 'dark'
|
|
60
60
|
|
|
61
61
|
// ─── Renk aritmetiği (switch-variant-contrast.test.tsx ile birebir aynı yöntem) ───
|
|
62
62
|
|
|
63
|
-
function tokenOku(
|
|
64
|
-
const
|
|
63
|
+
function tokenOku(name: string, blok: Blok): string | null {
|
|
64
|
+
const source =
|
|
65
65
|
blok === 'dark' ? tokens.slice(tokens.indexOf('.dark')) : tokens.slice(0, tokens.indexOf('.dark'))
|
|
66
|
-
return
|
|
66
|
+
return source.match(new RegExp(`--${name}:\\s*([^;]+);`))?.[1].trim() ?? null
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
function hslToRgb(h: number, s: number, l: number): RGB {
|
|
@@ -88,12 +88,12 @@ const rgbTokenRgb = (v: string): RGB => {
|
|
|
88
88
|
return [p[0], p[1], p[2]]
|
|
89
89
|
}
|
|
90
90
|
/** Semantik token'ın ilgili temadaki değeri — `.dark` ezmesi YOKSA light'a düşer */
|
|
91
|
-
const
|
|
92
|
-
hslTokenRgb(tokenOku(
|
|
91
|
+
const semanticRgb = (name: string, blok: Blok): RGB =>
|
|
92
|
+
hslTokenRgb(tokenOku(name, blok) ?? tokenOku(name, 'light')!)
|
|
93
93
|
/** Skala kademeleri tema-BAĞIMSIZ: `.dark` bloğu onları ezmiyor (preset yorumu) */
|
|
94
|
-
const
|
|
94
|
+
const scaleRgb = (name: string): RGB => rgbTokenRgb(tokenOku(name, 'light')!)
|
|
95
95
|
|
|
96
|
-
function
|
|
96
|
+
function relativeLuminance([r, g, b]: RGB): number {
|
|
97
97
|
const c = (v: number) => {
|
|
98
98
|
const x = v / 255
|
|
99
99
|
return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4)
|
|
@@ -101,8 +101,8 @@ function bagilParlaklik([r, g, b]: RGB): number {
|
|
|
101
101
|
return 0.2126 * c(r) + 0.7152 * c(g) + 0.0722 * c(b)
|
|
102
102
|
}
|
|
103
103
|
function kontrast(a: RGB, b: RGB): number {
|
|
104
|
-
const la =
|
|
105
|
-
const lb =
|
|
104
|
+
const la = relativeLuminance(a)
|
|
105
|
+
const lb = relativeLuminance(b)
|
|
106
106
|
const [hi, lo] = la > lb ? [la, lb] : [lb, la]
|
|
107
107
|
return (hi + 0.05) / (lo + 0.05)
|
|
108
108
|
}
|
|
@@ -114,94 +114,94 @@ const hex = ([r, g, b]: RGB): string =>
|
|
|
114
114
|
'#' + [r, g, b].map((x) => Math.round(x).toString(16).padStart(2, '0')).join('')
|
|
115
115
|
|
|
116
116
|
// ─── Zeminler — HER ÖLÇÜMDE ADIYLA ANILIR ───────────────────────────────────────
|
|
117
|
-
const
|
|
117
|
+
const WHITE: RGB = [255, 255, 255]
|
|
118
118
|
/** Docs kabuğunda ölçülen GERÇEK koyu kart (#284/#368/#448/#454 konvansiyonu) */
|
|
119
|
-
const
|
|
119
|
+
const CARD_ACTUAL_DARK: RGB = [17, 24, 39]
|
|
120
120
|
|
|
121
|
-
const
|
|
122
|
-
const
|
|
121
|
+
const AA_TEXT = 4.5
|
|
122
|
+
const AA_NON_TEXT = 3.0
|
|
123
123
|
|
|
124
124
|
// ─── Varyant sınıf dizgesini KAYNAKTAN çöz (renkler VARSAYILMAZ) ────────────────
|
|
125
125
|
|
|
126
126
|
/** cva `variant` bloğundaki dizi biçimli varyant girdisini birleşik sınıf dizgesi olarak ver */
|
|
127
|
-
function
|
|
128
|
-
const m =
|
|
129
|
-
if (!m) throw new Error(`${
|
|
127
|
+
function variantClass(source: string, variantVal: string): string {
|
|
128
|
+
const m = source.match(new RegExp(`\\n\\s*${variantVal}:\\s*\\[([\\s\\S]*?)\\n\\s*\\]`))
|
|
129
|
+
if (!m) throw new Error(`${variantVal} varyantı bulunamadı`)
|
|
130
130
|
return (m[1].match(/"([^"]*)"/g) ?? []).map((s) => s.slice(1, -1)).join(' ')
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
/** `bg-error-600` / `bg-error` / `bg-error/90` / `text-white` → RGB (alfa varsa zemine bindirir) */
|
|
134
|
-
function
|
|
134
|
+
function classColor(cls: string, blok: Blok, background: RGB): RGB {
|
|
135
135
|
// ⚠️ `+` ŞART: `dark:hover:bg-error-700` gibi ÇOK önekli sınıflarda tek tur
|
|
136
136
|
// soyma yetmez ve sınıf "çözülemez" diye PATLAR — testi kontrast ölçmeden
|
|
137
137
|
// kırmızı gösterir (yanlış sebeple kırmızı = geçersiz ölçüm).
|
|
138
|
-
const g =
|
|
138
|
+
const g = cls.replace(/^(?:(?:dark|hover|active|focus-visible|group-hover):)+/, '')
|
|
139
139
|
const m = g.match(/^(?:bg|text|border|ring)-([a-z-]+?)(?:-(\d{2,3}))?(?:\/(\d{1,3}))?$/)
|
|
140
|
-
if (!m) throw new Error(`çözülemeyen sınıf: ${
|
|
141
|
-
const [,
|
|
142
|
-
if (
|
|
143
|
-
const
|
|
144
|
-
return alfa ? komposit(
|
|
140
|
+
if (!m) throw new Error(`çözülemeyen sınıf: ${cls}`)
|
|
141
|
+
const [, name, kademe, alfa] = m
|
|
142
|
+
if (name === 'white') return alfa ? komposit(WHITE, background, Number(alfa) / 100) : WHITE
|
|
143
|
+
const base = kademe ? scaleRgb(`${name}-${kademe}`) : semanticRgb(name, blok)
|
|
144
|
+
return alfa ? komposit(base, background, Number(alfa) / 100) : base
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
/** Bir varyantın ilgili temadaki ETKİN dolgusu — `dark:` önekli sınıf dark'ta kazanır */
|
|
148
|
-
function
|
|
149
|
-
const adaylar =
|
|
148
|
+
function fill(source: string, variantVal: string, blok: Blok, background: RGB, onek = ''): RGB {
|
|
149
|
+
const adaylar = variantClass(source, variantVal)
|
|
150
150
|
.split(/\s+/)
|
|
151
151
|
.filter((s) => new RegExp(`^(?:dark:)?${onek}bg-`).test(s))
|
|
152
152
|
const dark = adaylar.find((s) => s.startsWith('dark:'))
|
|
153
153
|
const light = adaylar.find((s) => !s.startsWith('dark:'))
|
|
154
154
|
const secilen = blok === 'dark' ? (dark ?? light) : light
|
|
155
|
-
if (!secilen) throw new Error(`${
|
|
156
|
-
return
|
|
155
|
+
if (!secilen) throw new Error(`${variantVal}/${blok}${onek ? ` (${onek})` : ''}: dolgu sınıfı yok`)
|
|
156
|
+
return classColor(secilen, blok, background)
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
/** Varyantın metin rengi (`text-*`), `bg-*` HARİÇ */
|
|
160
|
-
function
|
|
161
|
-
const adaylar =
|
|
160
|
+
function text(source: string, variantVal: string, blok: Blok, background: RGB): RGB {
|
|
161
|
+
const adaylar = variantClass(source, variantVal)
|
|
162
162
|
.split(/\s+/)
|
|
163
163
|
.filter((s) => /^(?:dark:)?text-/.test(s))
|
|
164
164
|
const dark = adaylar.find((s) => s.startsWith('dark:'))
|
|
165
165
|
const light = adaylar.find((s) => !s.startsWith('dark:'))
|
|
166
166
|
const secilen = blok === 'dark' ? (dark ?? light) : light
|
|
167
|
-
if (!secilen) throw new Error(`${
|
|
168
|
-
return
|
|
167
|
+
if (!secilen) throw new Error(`${variantVal}/${blok}: metin sınıfı yok`)
|
|
168
|
+
return classColor(secilen, blok, background)
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
/** Dolgu opaksa zeminden bağımsızdır; yarı saydamsa zemine göre değişir → ikisini de dene */
|
|
172
|
-
function
|
|
173
|
-
const d =
|
|
174
|
-
return { oran: kontrast(d,
|
|
172
|
+
function fillTextRatio(source: string, variantVal: string, blok: Blok, background: RGB) {
|
|
173
|
+
const d = fill(source, variantVal, blok, background)
|
|
174
|
+
return { oran: kontrast(d, text(source, variantVal, blok, d)), fillHex: hex(d) }
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
// ════════════════════════════════════════════════════════════════════════════════
|
|
178
178
|
describe('#544/#545 — hesaplayıcı çapaları (sessiz-yeşil koruması)', () => {
|
|
179
179
|
it('ÇAPA: tokens.css `--info-subtle` çifti belgelenen ~9.5 oranını veriyor', () => {
|
|
180
|
-
const oran = kontrast(
|
|
180
|
+
const oran = kontrast(semanticRgb('info-subtle', 'light'), semanticRgb('info-subtle-foreground', 'light'))
|
|
181
181
|
expect(oran).toBeGreaterThan(9.3)
|
|
182
182
|
expect(oran).toBeLessThan(9.7)
|
|
183
183
|
})
|
|
184
184
|
|
|
185
185
|
it('ÇAPA: siyah↔beyaz tam 21.00', () => {
|
|
186
|
-
expect(kontrast([0, 0, 0],
|
|
186
|
+
expect(kontrast([0, 0, 0], WHITE)).toBeCloseTo(21, 2)
|
|
187
187
|
})
|
|
188
188
|
|
|
189
189
|
it('ÇAPA: hesaplayıcı, issue #544 gövdesindeki GERÇEK TARAYICI ölçümlerini üretiyor', () => {
|
|
190
190
|
// Bu tutmuyorsa aşağıdaki tüm oranlar anlamsızdır.
|
|
191
191
|
// Badge light: zemin YOK (opak dolgu) — çift doğrudan #ffffff / #ef4343
|
|
192
|
-
expect(kontrast(
|
|
192
|
+
expect(kontrast(semanticRgb('error', 'light'), WHITE)).toBeCloseTo(3.78, 2)
|
|
193
193
|
// Button dark: #f8fafc / #ef4343
|
|
194
194
|
expect(
|
|
195
|
-
kontrast(
|
|
195
|
+
kontrast(semanticRgb('destructive', 'dark'), semanticRgb('destructive-foreground', 'dark'))
|
|
196
196
|
).toBeCloseTo(3.61, 2)
|
|
197
197
|
})
|
|
198
198
|
|
|
199
199
|
it('NEGATİF KONTROL: eşik mantığı ayrım yapıyor — eski değerler GERÇEKTEN kalıyor', () => {
|
|
200
200
|
// "Her şey yeşil" tuzağına karşı: ölçüm başarısızlığı tespit edebiliyor mu?
|
|
201
|
-
expect(kontrast(
|
|
202
|
-
expect(kontrast(
|
|
201
|
+
expect(kontrast(semanticRgb('error', 'light'), WHITE)).toBeLessThan(AA_TEXT)
|
|
202
|
+
expect(kontrast(scaleRgb('error-500'), WHITE)).toBeLessThan(AA_TEXT)
|
|
203
203
|
// ve gerçekten geçen bir değeri geçmiş sayıyor
|
|
204
|
-
expect(kontrast(
|
|
204
|
+
expect(kontrast(scaleRgb('error-700'), WHITE)).toBeGreaterThan(AA_TEXT)
|
|
205
205
|
})
|
|
206
206
|
|
|
207
207
|
it('ÇAPA: skala token’ları tema-BAĞIMSIZ — `.dark` bloğu onları EZMİYOR', () => {
|
|
@@ -219,8 +219,8 @@ describe('#544 — KAPSAM KİLİDİ: token değerleri DEĞİŞMEDİ', () => {
|
|
|
219
219
|
['error', 'light', '0 84% 60%'],
|
|
220
220
|
['destructive', 'light', '0 84.2% 48%'],
|
|
221
221
|
['destructive', 'dark', '0 84% 60%'],
|
|
222
|
-
])('--%s (%s) sabit: %s', (
|
|
223
|
-
expect(tokenOku(
|
|
222
|
+
])('--%s (%s) sabit: %s', (name, blok, expected) => {
|
|
223
|
+
expect(tokenOku(name, blok as Blok)).toBe(expected)
|
|
224
224
|
})
|
|
225
225
|
|
|
226
226
|
it('`--error` hâlâ `.dark` bloğunda EZİLMİYOR (#424/#442’nin kök-neden dayanağı)', () => {
|
|
@@ -232,27 +232,27 @@ describe('#544 — KABUL KRİTERİ: Badge destructive dolgusu ≥ 4.5:1', () =>
|
|
|
232
232
|
// Badge `text-white` taşır. Dolgu bugün YARI SAYDAM (`dark:bg-error/90`) olduğu
|
|
233
233
|
// için dark ölçümü ZEMİNE bağlı — iki gerçek zeminde birden ölçülür.
|
|
234
234
|
it('LIGHT — zemin YOK (opak dolgu), çift: dolgu ↔ text-white', () => {
|
|
235
|
-
const { oran,
|
|
236
|
-
expect(`${
|
|
235
|
+
const { oran, fillHex } = fillTextRatio(badgeSrc, 'destructive', 'light', WHITE)
|
|
236
|
+
expect(`${fillHex}: ${oran >= AA_TEXT ? 'geçer' : oran.toFixed(2)}`).toBe(`${fillHex}: geçer`)
|
|
237
237
|
})
|
|
238
238
|
|
|
239
239
|
it.each([
|
|
240
240
|
['koyu sayfa --background', 'dark'],
|
|
241
241
|
['gerçek koyu kart #111827', 'kart'],
|
|
242
|
-
])('DARK — zemin: %s', (
|
|
243
|
-
const
|
|
244
|
-
const { oran,
|
|
245
|
-
expect(`[${
|
|
246
|
-
`[${
|
|
242
|
+
])('DARK — zemin: %s', (backgroundName, tur) => {
|
|
243
|
+
const background = tur === 'kart' ? CARD_ACTUAL_DARK : semanticRgb('background', 'dark')
|
|
244
|
+
const { oran, fillHex } = fillTextRatio(badgeSrc, 'destructive', 'dark', background)
|
|
245
|
+
expect(`[${backgroundName}] ${fillHex}: ${oran >= AA_TEXT ? 'geçer' : oran.toFixed(2)}`).toBe(
|
|
246
|
+
`[${backgroundName}] ${fillHex}: geçer`
|
|
247
247
|
)
|
|
248
248
|
})
|
|
249
249
|
|
|
250
250
|
it('HOVER durumu da AA taşıyor (düzeltme tek duruma sıkışmasın)', () => {
|
|
251
251
|
for (const blok of ['light', 'dark'] as const) {
|
|
252
|
-
const
|
|
253
|
-
const d =
|
|
254
|
-
const oran = kontrast(d,
|
|
255
|
-
expect(`${blok} hover ${hex(d)}: ${oran >=
|
|
252
|
+
const background = blok === 'dark' ? CARD_ACTUAL_DARK : WHITE
|
|
253
|
+
const d = fill(badgeSrc, 'destructive', blok, background, 'hover:')
|
|
254
|
+
const oran = kontrast(d, text(badgeSrc, 'destructive', blok, d))
|
|
255
|
+
expect(`${blok} hover ${hex(d)}: ${oran >= AA_TEXT ? 'geçer' : oran.toFixed(2)}`).toBe(
|
|
256
256
|
`${blok} hover ${hex(d)}: geçer`
|
|
257
257
|
)
|
|
258
258
|
}
|
|
@@ -264,22 +264,22 @@ describe('#544 — KABUL KRİTERİ: Button destructive dolgusu ≥ 4.5:1', () =>
|
|
|
264
264
|
// Kırmızı fazda BİLEREK yeşil kalan test. İki işi var:
|
|
265
265
|
// (1) düzeneğin topluca kırmızı olmadığını gösterir,
|
|
266
266
|
// (2) #442'nin "geçen temayı oynatma, regresyon yüzeyini sıfır tut" kuralını kilitler.
|
|
267
|
-
const { oran } =
|
|
268
|
-
expect(oran).toBeGreaterThanOrEqual(
|
|
267
|
+
const { oran } = fillTextRatio(buttonSrc, 'destructive', 'light', WHITE)
|
|
268
|
+
expect(oran).toBeGreaterThanOrEqual(AA_TEXT)
|
|
269
269
|
expect(oran).toBeCloseTo(4.66, 1)
|
|
270
|
-
expect(
|
|
270
|
+
expect(variantClass(buttonSrc, 'destructive')).toContain('bg-destructive text-destructive-foreground')
|
|
271
271
|
})
|
|
272
272
|
|
|
273
273
|
it('DARK — zemin YOK (opak dolgu), çift: dolgu ↔ text-destructive-foreground', () => {
|
|
274
|
-
const { oran,
|
|
275
|
-
expect(`${
|
|
274
|
+
const { oran, fillHex } = fillTextRatio(buttonSrc, 'destructive', 'dark', CARD_ACTUAL_DARK)
|
|
275
|
+
expect(`${fillHex}: ${oran >= AA_TEXT ? 'geçer' : oran.toFixed(2)}`).toBe(`${fillHex}: geçer`)
|
|
276
276
|
})
|
|
277
277
|
|
|
278
278
|
it('HOVER + ACTIVE durumları da AA taşıyor (dark)', () => {
|
|
279
279
|
for (const onek of ['hover:', 'active:']) {
|
|
280
|
-
const d =
|
|
281
|
-
const oran = kontrast(d,
|
|
282
|
-
expect(`${onek}${hex(d)}: ${oran >=
|
|
280
|
+
const d = fill(buttonSrc, 'destructive', 'dark', CARD_ACTUAL_DARK, onek)
|
|
281
|
+
const oran = kontrast(d, text(buttonSrc, 'destructive', 'dark', d))
|
|
282
|
+
expect(`${onek}${hex(d)}: ${oran >= AA_TEXT ? 'geçer' : oran.toFixed(2)}`).toBe(
|
|
283
283
|
`${onek}${hex(d)}: geçer`
|
|
284
284
|
)
|
|
285
285
|
}
|
|
@@ -296,8 +296,8 @@ describe('#544 — kimlik korunumu: “uyarı kırmızısı” algısı kayboldu
|
|
|
296
296
|
const h = mx === r ? ((g - b) / d) % 6 : mx === g ? (b - r) / d + 2 : (r - g) / d + 4
|
|
297
297
|
return Math.round((h * 60 + 360) % 360)
|
|
298
298
|
}
|
|
299
|
-
expect(hue(
|
|
300
|
-
expect(hue(
|
|
299
|
+
expect(hue(fill(badgeSrc, 'destructive', 'light', WHITE))).toBe(0)
|
|
300
|
+
expect(hue(fill(buttonSrc, 'destructive', 'dark', CARD_ACTUAL_DARK))).toBe(0)
|
|
301
301
|
})
|
|
302
302
|
})
|
|
303
303
|
|
|
@@ -306,43 +306,43 @@ describe('#544 — REGRESYON BEKÇİSİ: dokunulmayan varyantlar bit-bit aynı',
|
|
|
306
306
|
// düzeltme kapsam dışına taşmış demektir.
|
|
307
307
|
it('Badge: destructive DIŞINDAKİ varyant dizgeleri değişmedi', () => {
|
|
308
308
|
// NOT: Badge'in VARSAYILAN varyantının adı `default` değil `primary`.
|
|
309
|
-
expect(
|
|
310
|
-
expect(
|
|
311
|
-
expect(
|
|
312
|
-
expect(
|
|
313
|
-
expect(
|
|
309
|
+
expect(variantClass(badgeSrc, 'primary')).toContain('bg-primary text-primary-foreground')
|
|
310
|
+
expect(variantClass(badgeSrc, 'secondary')).toContain('bg-secondary text-secondary-foreground')
|
|
311
|
+
expect(variantClass(badgeSrc, 'outline')).toContain('bg-transparent text-foreground')
|
|
312
|
+
expect(variantClass(badgeSrc, 'success')).toContain('bg-success text-success-foreground')
|
|
313
|
+
expect(variantClass(badgeSrc, 'warning')).toContain('bg-warning text-warning-foreground')
|
|
314
314
|
})
|
|
315
315
|
|
|
316
316
|
it('Button: destructive DIŞINDAKİ varyant dizgeleri değişmedi', () => {
|
|
317
|
-
expect(
|
|
318
|
-
expect(
|
|
319
|
-
expect(
|
|
317
|
+
expect(variantClass(buttonSrc, 'ghost')).toContain('text-gray-700')
|
|
318
|
+
expect(variantClass(buttonSrc, 'success')).toContain('bg-success text-success-foreground')
|
|
319
|
+
expect(variantClass(buttonSrc, 'outline')).toContain('border border-input')
|
|
320
320
|
})
|
|
321
321
|
|
|
322
322
|
it('KAPSAM KİLİDİ: `--error`/`--destructive` DEFAULT token’ının diğer tüketicileri etkilenmedi', () => {
|
|
323
323
|
// Token değişmediği için `bg-error` yazan 35 başka yüzey ve `text-destructive`
|
|
324
324
|
// yazan 94 yüzey bit-bit aynı kalır. Switch bunun canlı kanıtı.
|
|
325
|
-
const switchSrc = fs.readFileSync(path.join(
|
|
325
|
+
const switchSrc = fs.readFileSync(path.join(PKG, 'components/ui/switch.tsx'), 'utf8')
|
|
326
326
|
expect(switchSrc).toContain('data-[state=checked]:bg-error')
|
|
327
327
|
expect(switchSrc).not.toContain('bg-error-600')
|
|
328
328
|
})
|
|
329
329
|
})
|
|
330
330
|
|
|
331
331
|
describe('#544 — render sözleşmesi (<Badge /> / <Button />)', () => {
|
|
332
|
-
const
|
|
332
|
+
const cls = (jsx: React.ReactElement, sec: string) => {
|
|
333
333
|
const { container } = render(jsx)
|
|
334
334
|
return (container.querySelector(sec) as HTMLElement).className
|
|
335
335
|
}
|
|
336
336
|
|
|
337
337
|
it('Badge destructive ARTIK yarı saydam `dark:bg-error/90` taşımıyor', () => {
|
|
338
338
|
// Yarı saydam dolgu, kontrastı ZEMİNE bağımlı kılıyordu (gerçek kartta 4.43).
|
|
339
|
-
const c =
|
|
339
|
+
const c = cls(<Badge variant="destructive">x</Badge>, 'div')
|
|
340
340
|
expect(c).not.toContain('dark:bg-error/90')
|
|
341
341
|
expect(c).toContain('text-white')
|
|
342
342
|
})
|
|
343
343
|
|
|
344
344
|
it('Button destructive dark dolgusu opak bir skala kademesi', () => {
|
|
345
|
-
const c =
|
|
345
|
+
const c = cls(<Button variant="destructive">x</Button>, 'button')
|
|
346
346
|
expect(c).not.toContain('dark:bg-destructive ')
|
|
347
347
|
expect(c).toContain('text-destructive-foreground')
|
|
348
348
|
})
|
|
@@ -350,11 +350,11 @@ describe('#544 — render sözleşmesi (<Badge /> / <Button />)', () => {
|
|
|
350
350
|
it('TEK dolgu utility’si per durum — Tailwind sıra belirsizliği YOK', () => {
|
|
351
351
|
// `dark:bg-destructive dark:bg-error-600` ikisi birden yazılırsa hangisinin
|
|
352
352
|
// kazanacağı Tailwind'in İÇ sıralamasına kalır (sınıf dizgesi sırasına DEĞİL).
|
|
353
|
-
for (const [
|
|
354
|
-
const
|
|
353
|
+
for (const [source, name] of [[badgeSrc, 'badge'], [buttonSrc, 'button']] as const) {
|
|
354
|
+
const classNames = variantClass(source, 'destructive').split(/\s+/)
|
|
355
355
|
for (const onek of ['', 'dark:', 'hover:', 'dark:hover:', 'active:', 'dark:active:']) {
|
|
356
|
-
const n =
|
|
357
|
-
expect(`${
|
|
356
|
+
const n = classNames.filter((s) => new RegExp(`^${onek}bg-(?!.*shadow)`).test(s)).length
|
|
357
|
+
expect(`${name} "${onek}bg-*" adedi: ${n}`).toBe(`${name} "${onek}bg-*" adedi: ${n > 1 ? 1 : n}`)
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
360
|
})
|
|
@@ -376,11 +376,11 @@ const CONFIGLER = {
|
|
|
376
376
|
'error-boundary': 'src/docs-config/error-boundary.config.tsx',
|
|
377
377
|
'pinch-zoom': 'src/docs-config/pinch-zoom.config.tsx',
|
|
378
378
|
} as const
|
|
379
|
-
const
|
|
379
|
+
const read = (rel: string) => fs.readFileSync(path.join(DEPO, rel), 'utf8')
|
|
380
380
|
|
|
381
381
|
/** Bir sınıfın kaç kez geçtiği — "kaç düğüm" iddiasını SAYIYLA kilitler */
|
|
382
|
-
const
|
|
383
|
-
(src.match(new RegExp(`(?<![\\w-])${
|
|
382
|
+
const counter = (src: string, cls: string) =>
|
|
383
|
+
(src.match(new RegExp(`(?<![\\w-])${cls.replace(/[/]/g, '\\/')}(?![\\w-])`, 'g')) ?? []).length
|
|
384
384
|
|
|
385
385
|
describe('#545 — hesaplayıcı çapası: issue’nun tarayıcı ölçümleri birebir üretiliyor', () => {
|
|
386
386
|
it('Tailwind paleti beklenen sürümden ve beklenen hex’lerde', () => {
|
|
@@ -392,33 +392,33 @@ describe('#545 — hesaplayıcı çapası: issue’nun tarayıcı ölçümleri b
|
|
|
392
392
|
})
|
|
393
393
|
|
|
394
394
|
it('altı ripple butonu: issue’daki 1.91-3.95 aralığı yeniden üretiliyor (zemin: opak dolgu ↔ #ffffff metin)', () => {
|
|
395
|
-
const
|
|
395
|
+
const expected: Record<string, number> = {
|
|
396
396
|
yellow: 1.92, green: 2.28, cyan: 2.43, blue: 3.68, red: 3.76, purple: 3.96,
|
|
397
397
|
}
|
|
398
|
-
for (const [aile, oran] of Object.entries(
|
|
399
|
-
expect(kontrast(hx(twColors[aile][500]),
|
|
398
|
+
for (const [aile, oran] of Object.entries(expected)) {
|
|
399
|
+
expect(kontrast(hx(twColors[aile][500]), WHITE)).toBeCloseTo(oran, 1)
|
|
400
400
|
}
|
|
401
401
|
})
|
|
402
402
|
|
|
403
403
|
it('ZEMİN DERSİ: `text-red-600` BEYAZA karşı geçer, GERÇEK zemininde (bg-red-50) KALIR', () => {
|
|
404
404
|
// Issue'nun kaydettiği ölçüm hatası tam buydu; burası onu kalıcı kılar.
|
|
405
|
-
expect(kontrast(hx(twColors.red[600]),
|
|
405
|
+
expect(kontrast(hx(twColors.red[600]), WHITE)).toBeCloseTo(4.83, 2)
|
|
406
406
|
expect(kontrast(hx(twColors.red[600]), hx(twColors.red[50]))).toBeCloseTo(4.41, 2)
|
|
407
|
-
expect(kontrast(hx(twColors.red[600]), hx(twColors.red[50]))).toBeLessThan(
|
|
407
|
+
expect(kontrast(hx(twColors.red[600]), hx(twColors.red[50]))).toBeLessThan(AA_TEXT)
|
|
408
408
|
})
|
|
409
409
|
|
|
410
410
|
it('`text-green-600` beyaz zeminde 3.29 (issue’nun hiç ölçmediği kalem)', () => {
|
|
411
|
-
expect(kontrast(hx(twColors.green[600]),
|
|
411
|
+
expect(kontrast(hx(twColors.green[600]), WHITE)).toBeCloseTo(3.30, 1)
|
|
412
412
|
})
|
|
413
413
|
|
|
414
414
|
it('semantik skala sınıfları ÖLÜ DEĞİL — preset onları açıyor ve hex’ler ham palete eşit', () => {
|
|
415
415
|
// #307/#529 ölü-sınıf tuzağı: `text-success-700` CSS üretmiyorsa düzeltme
|
|
416
416
|
// sessizce hiçbir şey yapmaz. Token tanımı + ham palet eşitliği ikisi de kanıt.
|
|
417
|
-
const preset = fs.readFileSync(path.resolve(
|
|
417
|
+
const preset = fs.readFileSync(path.resolve(PKG, '../tailwind-preset.js'), 'utf8')
|
|
418
418
|
for (const [aile, ham] of [['error', 'red'], ['success', 'green'], ['warning', 'yellow']] as const) {
|
|
419
419
|
for (const k of [400, 700]) {
|
|
420
420
|
expect(preset).toMatch(new RegExp(`\\b${aile}:\\s*\\{[^}]*\\b${k}:\\s*"rgb\\(var\\(--${aile}-${k}\\)`, 's'))
|
|
421
|
-
expect(hex(
|
|
421
|
+
expect(hex(scaleRgb(`${aile}-${k}`))).toBe(twColors[ham][k])
|
|
422
422
|
}
|
|
423
423
|
}
|
|
424
424
|
})
|
|
@@ -426,11 +426,11 @@ describe('#545 — hesaplayıcı çapası: issue’nun tarayıcı ölçümleri b
|
|
|
426
426
|
|
|
427
427
|
describe('#545 — KABUL KRİTERİ: kalan ham literal SAYISI sıfır', () => {
|
|
428
428
|
it('click-animations: altı `bg-{aile}-500` ripple butonu KALMADI (bugün 6)', () => {
|
|
429
|
-
const src =
|
|
430
|
-
const
|
|
431
|
-
.map((a) => [a,
|
|
429
|
+
const src = read(CONFIGLER['click-animations'])
|
|
430
|
+
const remaining = ['blue', 'purple', 'green', 'red', 'yellow', 'cyan']
|
|
431
|
+
.map((a) => [a, counter(src, `bg-${a}-500`)] as const)
|
|
432
432
|
.filter(([, n]) => n > 0)
|
|
433
|
-
expect(
|
|
433
|
+
expect(remaining).toEqual([])
|
|
434
434
|
})
|
|
435
435
|
|
|
436
436
|
it('error-boundary: METİN rolündeki `-600` tonları KALMADI (ikonlar bilerek DURUYOR)', () => {
|
|
@@ -443,23 +443,23 @@ describe('#545 — KABUL KRİTERİ: kalan ham literal SAYISI sıfır', () => {
|
|
|
443
443
|
* Bunları -700'e çekmek dark'ı 2.76'ya düşürüp EŞİĞİN ALTINA indirirdi — yani
|
|
444
444
|
* bir ihlali başkasıyla takas ederdi. Metin rolündekiler (4.5 eşiği) dönüştü.
|
|
445
445
|
*/
|
|
446
|
-
const src =
|
|
447
|
-
expect(
|
|
446
|
+
const src = read(CONFIGLER['error-boundary'])
|
|
447
|
+
expect(counter(src, 'text-yellow-600')).toBe(0) // hem ikon hem metin ihlaldi (2.84)
|
|
448
448
|
// Kalan `-600`lar YALNIZ ikon üzerinde olmalı
|
|
449
|
-
const
|
|
449
|
+
const remainingRed = [...src.matchAll(/text-red-600/g)].map((m) =>
|
|
450
450
|
src.slice(Math.max(0, m.index! - 90), m.index!)
|
|
451
451
|
)
|
|
452
|
-
expect(
|
|
453
|
-
for (const
|
|
454
|
-
const
|
|
452
|
+
expect(remainingRed).toHaveLength(2) // :109 canlı + :475 code ikizi
|
|
453
|
+
for (const entry of remainingRed) expect(entry).toContain('<AlertTriangle')
|
|
454
|
+
const remainingGreen = [...src.matchAll(/text-green-600/g)].map((m) =>
|
|
455
455
|
src.slice(Math.max(0, m.index! - 90), m.index!)
|
|
456
456
|
)
|
|
457
|
-
expect(
|
|
458
|
-
expect(
|
|
457
|
+
expect(remainingGreen).toHaveLength(1) // :158 ikon
|
|
458
|
+
expect(remainingGreen[0]).toContain('<CheckCircle')
|
|
459
459
|
})
|
|
460
460
|
|
|
461
461
|
it('pinch-zoom: `text-green-600` KALMADI (bugün 2 — biri canlı JSX, biri code bloğu)', () => {
|
|
462
|
-
expect(
|
|
462
|
+
expect(counter(read(CONFIGLER['pinch-zoom']), 'text-green-600')).toBe(0)
|
|
463
463
|
})
|
|
464
464
|
|
|
465
465
|
it('moonui-pro error-boundary: dark ikizi OLMAYAN `text-red-600` KALMADI (latent kalem)', () => {
|
|
@@ -473,12 +473,12 @@ describe('#545 — KABUL KRİTERİ: kalan ham literal SAYISI sıfır', () => {
|
|
|
473
473
|
|
|
474
474
|
describe('#545 — KABUL KRİTERİ: yeni tonlar İKİ TEMADA da eşiği geçiyor', () => {
|
|
475
475
|
it('altı ripple butonu ≥4.5 (zemin: opak dolgu ↔ #ffffff metin, tema-bağımsız)', () => {
|
|
476
|
-
const src =
|
|
476
|
+
const src = read(CONFIGLER['click-animations'])
|
|
477
477
|
for (const aile of ['blue', 'purple', 'green', 'red', 'yellow', 'cyan']) {
|
|
478
478
|
const m = src.match(new RegExp(`bg-${aile}-(\\d{3})`))
|
|
479
479
|
expect(`${aile}: sınıf var`).toBe(m ? `${aile}: sınıf var` : `${aile}: sınıf YOK`)
|
|
480
|
-
const oran = kontrast(hx(twColors[aile][Number(m![1])]),
|
|
481
|
-
expect(`${aile}-${m![1]}: ${oran >=
|
|
480
|
+
const oran = kontrast(hx(twColors[aile][Number(m![1])]), WHITE)
|
|
481
|
+
expect(`${aile}-${m![1]}: ${oran >= AA_TEXT ? 'geçer' : oran.toFixed(2)}`).toBe(
|
|
482
482
|
`${aile}-${m![1]}: geçer`
|
|
483
483
|
)
|
|
484
484
|
}
|
|
@@ -492,29 +492,29 @@ describe('#545 — KABUL KRİTERİ: yeni tonlar İKİ TEMADA da eşiği geçiyor
|
|
|
492
492
|
[CONFIGLER['error-boundary'], 'text-warning-700'],
|
|
493
493
|
[CONFIGLER['pinch-zoom'], 'text-success-700'],
|
|
494
494
|
]
|
|
495
|
-
for (const [rel,
|
|
496
|
-
const src =
|
|
497
|
-
const n =
|
|
498
|
-
expect(`${rel} ${
|
|
495
|
+
for (const [rel, cls] of hedefler) {
|
|
496
|
+
const src = read(rel)
|
|
497
|
+
const n = counter(src, cls)
|
|
498
|
+
expect(`${rel} ${cls} adedi > 0`).toBe(`${rel} ${cls} adedi > ${n > 0 ? 0 : 'YOK'}`)
|
|
499
499
|
// her geçtiği yerde hemen ardından dark ikizi gelmeli
|
|
500
|
-
const ikizsiz = [...src.matchAll(new RegExp(`${
|
|
500
|
+
const ikizsiz = [...src.matchAll(new RegExp(`${cls}(?![\\w-])(\\s+dark:text-\\S+)?`, 'g'))]
|
|
501
501
|
.filter((m) => !m[1])
|
|
502
|
-
expect(`${rel} ${
|
|
502
|
+
expect(`${rel} ${cls} ikizsiz: ${ikizsiz.length}`).toBe(`${rel} ${cls} ikizsiz: 0`)
|
|
503
503
|
}
|
|
504
504
|
})
|
|
505
505
|
|
|
506
506
|
it('yeni tonların ölçülen oranları — zemin ADIYLA', () => {
|
|
507
|
-
const
|
|
508
|
-
['error-700 · zemin bg-red-50 #fef2f2', hx(twColors.red[700]), hx(twColors.red[50]),
|
|
509
|
-
['success-700 · zemin kart #ffffff', hx(twColors.green[700]),
|
|
510
|
-
['success-400 · zemin koyu kart #111827', hx(twColors.green[400]),
|
|
511
|
-
['warning-700 · zemin bg-yellow-50 #fefce8', hx(twColors.yellow[700]), hx(twColors.yellow[50]),
|
|
512
|
-
['warning-400 · zemin koyu kart #111827', hx(twColors.yellow[400]),
|
|
513
|
-
['error-400 · zemin koyu kart #111827', hx(twColors.red[400]),
|
|
507
|
+
const measurements: Array<[string, RGB, RGB, number]> = [
|
|
508
|
+
['error-700 · zemin bg-red-50 #fef2f2', hx(twColors.red[700]), hx(twColors.red[50]), AA_TEXT],
|
|
509
|
+
['success-700 · zemin kart #ffffff', hx(twColors.green[700]), WHITE, AA_TEXT],
|
|
510
|
+
['success-400 · zemin koyu kart #111827', hx(twColors.green[400]), CARD_ACTUAL_DARK, AA_TEXT],
|
|
511
|
+
['warning-700 · zemin bg-yellow-50 #fefce8', hx(twColors.yellow[700]), hx(twColors.yellow[50]), AA_TEXT],
|
|
512
|
+
['warning-400 · zemin koyu kart #111827', hx(twColors.yellow[400]), CARD_ACTUAL_DARK, AA_TEXT],
|
|
513
|
+
['error-400 · zemin koyu kart #111827', hx(twColors.red[400]), CARD_ACTUAL_DARK, AA_TEXT],
|
|
514
514
|
]
|
|
515
|
-
for (const [
|
|
516
|
-
const o = kontrast(
|
|
517
|
-
expect(`${
|
|
515
|
+
for (const [name, color, background, threshold] of measurements) {
|
|
516
|
+
const o = kontrast(color, background)
|
|
517
|
+
expect(`${name}: ${o >= threshold ? 'geçer' : o.toFixed(2)}`).toBe(`${name}: geçer`)
|
|
518
518
|
}
|
|
519
519
|
})
|
|
520
520
|
})
|
|
@@ -525,17 +525,17 @@ describe('#545 — `code:` ↔ canlı `component:` TUTARLILIĞI', () => {
|
|
|
525
525
|
((src.slice(0, idx).match(/`/g) ?? []).length) % 2 === 1
|
|
526
526
|
|
|
527
527
|
it('error-boundary: düzeltilen sınıf HEM canlı JSX’te HEM code bloğunda var', () => {
|
|
528
|
-
const src =
|
|
529
|
-
for (const
|
|
530
|
-
const yerler = [...src.matchAll(new RegExp(`${
|
|
528
|
+
const src = read(CONFIGLER['error-boundary'])
|
|
529
|
+
for (const cls of ['text-error-700', 'text-warning-700']) {
|
|
530
|
+
const yerler = [...src.matchAll(new RegExp(`${cls}(?![\\w-])`, 'g'))].map((m) => m.index!)
|
|
531
531
|
const canli = yerler.filter((i) => !codeBlogundaMi(src, i)).length
|
|
532
532
|
const kod = yerler.filter((i) => codeBlogundaMi(src, i)).length
|
|
533
|
-
expect(`${
|
|
533
|
+
expect(`${cls} canlı:${canli > 0} kod:${kod > 0}`).toBe(`${cls} canlı:true kod:true`)
|
|
534
534
|
}
|
|
535
535
|
})
|
|
536
536
|
|
|
537
537
|
it('pinch-zoom: iki `In Stock` (canlı + code) AYNI sınıfı taşıyor', () => {
|
|
538
|
-
const src =
|
|
538
|
+
const src = read(CONFIGLER['pinch-zoom'])
|
|
539
539
|
const yerler = [...src.matchAll(/<span className="([^"]*)">In Stock<\/span>/g)].map((m) => m[1])
|
|
540
540
|
expect(yerler).toHaveLength(2)
|
|
541
541
|
expect(new Set(yerler).size).toBe(1)
|
|
@@ -545,20 +545,20 @@ describe('#545 — `code:` ↔ canlı `component:` TUTARLILIĞI', () => {
|
|
|
545
545
|
|
|
546
546
|
describe('#545 — REGRESYON BEKÇİSİ: kapsam dışına taşma yok', () => {
|
|
547
547
|
it('geçen tonlara DOKUNULMADI (ölçülen: eşiğin üstündeler)', () => {
|
|
548
|
-
const src =
|
|
548
|
+
const src = read(CONFIGLER['error-boundary'])
|
|
549
549
|
// -800 başlıklar (#991b1b, bg-red-50 üstünde 7.6) korunur
|
|
550
|
-
expect(
|
|
550
|
+
expect(counter(src, 'text-red-800')).toBeGreaterThan(0)
|
|
551
551
|
// ikon rolündeki `text-red-600` (bg-red-50 üstünde 4.41 ≥ 3.0) metin eşiğine
|
|
552
552
|
// tabi DEĞİL; dönüştürmemek bilinçli bir karardır
|
|
553
|
-
expect(kontrast(hx(twColors.red[600]), hx(twColors.red[50]))).toBeGreaterThanOrEqual(
|
|
553
|
+
expect(kontrast(hx(twColors.red[600]), hx(twColors.red[50]))).toBeGreaterThanOrEqual(AA_NON_TEXT)
|
|
554
554
|
})
|
|
555
555
|
|
|
556
556
|
it('vokabüler dönüşümü PİKSEL-BEDAVA: `error-300` = ham `red-300`', () => {
|
|
557
557
|
// `dark:text-red-300` → `dark:text-error-300` görünümü DEĞİŞTİRMEZ; yalnız
|
|
558
558
|
// ham paletten semantik skalaya geçer. Eşit olmasalardı sessiz bir renk
|
|
559
559
|
// kayması olurdu — bu yüzden ölçülür, varsayılmaz.
|
|
560
|
-
expect(hex(
|
|
561
|
-
expect(
|
|
560
|
+
expect(hex(scaleRgb('error-300'))).toBe(twColors.red[300])
|
|
561
|
+
expect(counter(read(CONFIGLER['error-boundary']), 'dark:text-error-300')).toBeGreaterThan(0)
|
|
562
562
|
})
|
|
563
563
|
|
|
564
564
|
it('moonui-pro: dark ikizi OLAN ikon (`:256`) bilerek DOKUNULMADI', () => {
|
|
@@ -578,24 +578,32 @@ describe('#545 — REGRESYON BEKÇİSİ: kapsam dışına taşma yok', () => {
|
|
|
578
578
|
* bırakıldı; burası onları DONDURUR (yeni ihlal eklenirse kırmızı olur)
|
|
579
579
|
* ve click-animations'ın sıfırlandığını ayrıca kilitler.
|
|
580
580
|
*/
|
|
581
|
-
const
|
|
581
|
+
const BASELINE: Record<string, number> = {
|
|
582
582
|
'bounce-effect.config.tsx': 16,
|
|
583
583
|
'button.config.tsx': 1,
|
|
584
584
|
'confetti.config.tsx': 1,
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
585
|
+
// focus-transitions.config.tsx: 6 → 0 (#663 — "Focus Colors" örneğindeki altı
|
|
586
|
+
// düğme beyaz metni `-500` tonuna basıyordu: blue 3.68 · purple 3.96 ·
|
|
587
|
+
// green 2.28 · red 3.76 · yellow 1.92 · cyan 2.43. Hue korunarak ton
|
|
588
|
+
// koyulaştırıldı (blue/purple/red -600, green/cyan -700); sarı hiçbir tonda
|
|
589
|
+
// beyazla 4.5'i geçmediği için tek istisna olarak polarite ters çevrildi
|
|
590
|
+
// (`bg-yellow-400 text-gray-900`, 11.90:1). Hepsi tema-değişmez.)
|
|
591
|
+
// glow-card.config.tsx: 1 → 0 (#594 — `bg-red-500 text-white` 3.76:1 idi,
|
|
592
|
+
// `bg-destructive` + `text-destructive-foreground` ile 4.66:1'e çekildi)
|
|
593
|
+
// slider.config.tsx: 4 → 0 (#661-15 — değer rozetleri: `bg-red-500`+beyaz
|
|
594
|
+
// 3.78:1 → `bg-red-600` 4.83:1; `bg-orange-500`+beyaz 2.80:1 →
|
|
595
|
+
// `bg-orange-700` 5.18:1. İkisi de tema-bağımsız: rozet kendi zeminini taşır.)
|
|
588
596
|
'sparkles.config.tsx': 2,
|
|
589
597
|
// click-animations.config.tsx: 6 → 0 (bu issue'nun kapsamı)
|
|
590
598
|
}
|
|
591
|
-
const
|
|
599
|
+
const dir = path.join(DEPO, 'src/docs-config')
|
|
592
600
|
const bugun: Record<string, number> = {}
|
|
593
|
-
for (const f of fs.readdirSync(
|
|
594
|
-
const src = fs.readFileSync(path.join(
|
|
601
|
+
for (const f of fs.readdirSync(dir).filter((x) => x.endsWith('.config.tsx'))) {
|
|
602
|
+
const src = fs.readFileSync(path.join(dir, f), 'utf8')
|
|
595
603
|
const n = (src.match(/bg-(?:blue|purple|green|red|yellow|cyan|orange|pink|indigo)-500 text-white/g) ?? [])
|
|
596
604
|
.length
|
|
597
605
|
if (n > 0) bugun[f] = n
|
|
598
606
|
}
|
|
599
|
-
expect(bugun).toEqual(
|
|
607
|
+
expect(bugun).toEqual(BASELINE)
|
|
600
608
|
})
|
|
601
609
|
})
|