@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
|
@@ -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(
|
|
44
|
-
const
|
|
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
|
|
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
|
|
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 =
|
|
88
|
-
const lb =
|
|
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
|
-
|
|
97
|
+
background: [number, number, number]
|
|
98
98
|
): [number, number, number] {
|
|
99
99
|
return [
|
|
100
|
-
Math.round(alfa * ust[0] + (1 - alfa) *
|
|
101
|
-
Math.round(alfa * ust[1] + (1 - alfa) *
|
|
102
|
-
Math.round(alfa * ust[2] + (1 - alfa) *
|
|
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
|
|
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
|
|
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
|
|
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',
|
|
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
|
|
137
|
-
const AYRAC =
|
|
138
|
-
const TRACK_BLOK =
|
|
139
|
-
const
|
|
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
|
|
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
|
|
150
|
-
const
|
|
151
|
-
if (
|
|
152
|
-
const
|
|
153
|
-
if (
|
|
154
|
-
throw new Error(`çözülemeyen sınıf: ${
|
|
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
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
const
|
|
162
|
-
if (!
|
|
163
|
-
return
|
|
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
|
|
169
|
-
const m =
|
|
170
|
-
if (!m) throw new Error(`track sınıfı çözülemedi: ${
|
|
171
|
-
const
|
|
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
|
|
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(
|
|
196
|
-
expect(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
245
|
-
expect(
|
|
246
|
-
expect(
|
|
247
|
-
expect(
|
|
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(
|
|
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
|
|
258
|
-
expect(
|
|
259
|
-
expect(
|
|
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
|
|
268
|
-
for (const [
|
|
269
|
-
const oran = rgbKontrast(
|
|
270
|
-
expect({
|
|
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
|
|
278
|
+
const old = semanticRgb(aile, 'light')
|
|
279
279
|
for (const [, track] of trackler(aile, 'light')) {
|
|
280
|
-
expect(rgbKontrast(
|
|
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(
|
|
287
|
+
expect(fillColor(aile, 'dark')).toEqual(semanticRgb(aile, 'dark'))
|
|
288
288
|
for (const [, track] of trackler(aile, 'dark')) {
|
|
289
|
-
expect(rgbKontrast(
|
|
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(([
|
|
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
|
|
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(
|
|
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
|
|
325
|
-
const sem =
|
|
326
|
-
const yirmi = rgbKontrast(alfaBileske(sem, 0.2,
|
|
327
|
-
const on = rgbKontrast(alfaBileske(sem, 0.1,
|
|
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
|
|
155
|
+
const visibleChildren = Array.from(listbox.querySelectorAll('[role="separator"]'))
|
|
156
156
|
.filter((el) => el.getAttribute('aria-hidden') !== 'true')
|
|
157
|
-
expect(
|
|
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
|
|
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
|
|
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
|
|
39
|
-
const
|
|
40
|
-
if (!fs.existsSync(
|
|
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))
|
|
46
|
+
} else if (/\.(tsx?|css)$/.test(e.name)) output.push(p)
|
|
47
47
|
}
|
|
48
|
-
})(
|
|
49
|
-
return
|
|
48
|
+
})(root)
|
|
49
|
+
return output
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
const tumDosyalar = KOKLER.flatMap(
|
|
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
|
|
65
|
+
let total = 0
|
|
66
66
|
for (const f of tumDosyalar) {
|
|
67
|
-
|
|
67
|
+
total += [...fs.readFileSync(f, 'utf8').matchAll(RESPONSIVE)].length
|
|
68
68
|
}
|
|
69
|
-
expect(
|
|
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
|
|
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, ,
|
|
79
|
-
if (!
|
|
80
|
-
if (!
|
|
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 (
|
|
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 (
|
|
85
|
-
|
|
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(
|
|
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
|
|
23
|
+
const STATUS_KEYS = ['destructive', 'success', 'warning', 'info', 'error']
|
|
24
24
|
|
|
25
25
|
interface Ihlal {
|
|
26
26
|
file: string
|
|
27
27
|
line: number
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
key: string
|
|
29
|
+
part: string
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
function
|
|
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
|
|
44
|
+
const violations: Ihlal[] = []
|
|
45
45
|
for (const f of files) {
|
|
46
46
|
const src = fs.readFileSync(f, 'utf8')
|
|
47
|
-
for (const
|
|
47
|
+
for (const key of STATUS_KEYS) {
|
|
48
48
|
// `destructive: "…"` biçimindeki cva varyant girdisini yakala
|
|
49
|
-
const re = new RegExp(`\\n[ \\t]*${
|
|
49
|
+
const re = new RegExp(`\\n[ \\t]*${key}:\\s*(["'])([\\s\\S]*?)\\1`, 'g')
|
|
50
50
|
for (const m of src.matchAll(re)) {
|
|
51
|
-
const
|
|
52
|
-
const hit =
|
|
51
|
+
const value = m[2]
|
|
52
|
+
const hit = value.match(HAM_PALET)
|
|
53
53
|
if (hit) {
|
|
54
|
-
|
|
54
|
+
violations.push({
|
|
55
55
|
file: path.relative(process.cwd(), f),
|
|
56
56
|
line: src.slice(0, m.index).split('\n').length + 1,
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
key,
|
|
58
|
+
part: hit[0],
|
|
59
59
|
})
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
return
|
|
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
|
|
79
|
+
const KNOWN_REMAINING: string[] = []
|
|
80
80
|
|
|
81
81
|
const SRC_ROOT = path.resolve(__dirname, '../../..')
|
|
82
|
-
const tumIhlaller =
|
|
83
|
-
const
|
|
84
|
-
(i) => !
|
|
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
|
|
100
|
-
expect(
|
|
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
|
|
107
|
-
tumIhlaller.map((i) =>
|
|
106
|
+
const stillViolating = new Set(
|
|
107
|
+
tumIhlaller.map((i) => KNOWN_REMAINING.find((b) => i.file.replace(/\\/g, '/').endsWith(b)))
|
|
108
108
|
)
|
|
109
|
-
const
|
|
110
|
-
expect(
|
|
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
|
|
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(
|
|
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
|
|
124
|
+
const violations = maps
|
|
125
125
|
.filter((m) => m.defaultVariant === DEPRECATED_ALIAS)
|
|
126
126
|
.map((m) => `${m.file}:${m.line}`)
|
|
127
|
-
expect(
|
|
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
|
|
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(
|
|
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
|
|
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)
|
|
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
|
-
|
|
183
|
+
violations.push(`${yer} → \`default\` var ama kanonik \`md\` YOK`)
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
|
-
expect(
|
|
187
|
+
expect(violations).toEqual([])
|
|
188
188
|
})
|
|
189
189
|
})
|