@moontra/moonui 6.19.0 → 6.19.2
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/index.js +185 -38
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +185 -38
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/ui/__tests__/progress-warning-error-contrast.test.ts +331 -0
- package/src/components/ui/__tests__/progress.test.tsx +9 -2
- package/src/components/ui/__tests__/slider-accent-contrast.test.ts +553 -0
- package/src/components/ui/__tests__/slider-warning-error-contrast.test.ts +478 -0
- package/src/components/ui/__tests__/slider.test.tsx +12 -4
- package/src/components/ui/__tests__/switch-variant-contrast.test.tsx +460 -0
- package/src/components/ui/__tests__/switch.test.tsx +11 -2
- package/src/components/ui/__tests__/table.test.tsx +222 -8
- package/src/components/ui/progress.tsx +25 -2
- package/src/components/ui/slider.tsx +92 -6
- package/src/components/ui/switch.tsx +44 -5
- package/src/components/ui/table.tsx +88 -43
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
4
|
import '@testing-library/jest-dom'
|
|
4
5
|
import {
|
|
5
6
|
Table,
|
|
@@ -101,19 +102,21 @@ describe('Table Components', () => {
|
|
|
101
102
|
render(
|
|
102
103
|
<Table variant="striped" data-testid="table">
|
|
103
104
|
<TableBody data-testid="tbody">
|
|
104
|
-
<TableRow>
|
|
105
|
+
<TableRow data-testid="row">
|
|
105
106
|
<TableCell>Test</TableCell>
|
|
106
107
|
</TableRow>
|
|
107
108
|
</TableBody>
|
|
108
109
|
</Table>
|
|
109
110
|
)
|
|
110
|
-
|
|
111
|
+
|
|
111
112
|
const table = screen.getByTestId('table')
|
|
112
113
|
expect(table).toHaveClass('dark:text-gray-200')
|
|
113
|
-
|
|
114
|
-
// TableBody should have the striped class applied
|
|
114
|
+
|
|
115
115
|
const tbody = screen.getByTestId('tbody')
|
|
116
116
|
expect(tbody).toHaveClass('[&_tr:last-child]:border-0')
|
|
117
|
+
|
|
118
|
+
// #444: zebra SATIR düzeyinde uygulanır — tbody'de değil.
|
|
119
|
+
expect(screen.getByTestId('row')).toHaveClass('odd:bg-muted/50')
|
|
117
120
|
})
|
|
118
121
|
|
|
119
122
|
it('renders card variant correctly', () => {
|
|
@@ -761,21 +764,232 @@ describe('Table Components', () => {
|
|
|
761
764
|
render(
|
|
762
765
|
<Table variant="striped" data-testid="table">
|
|
763
766
|
<TableBody data-testid="tbody">
|
|
764
|
-
<TableRow>
|
|
767
|
+
<TableRow data-testid="row-1">
|
|
765
768
|
<TableCell>Row 1</TableCell>
|
|
766
769
|
</TableRow>
|
|
767
|
-
<TableRow>
|
|
770
|
+
<TableRow data-testid="row-2">
|
|
768
771
|
<TableCell>Row 2</TableCell>
|
|
769
772
|
</TableRow>
|
|
770
773
|
</TableBody>
|
|
771
774
|
</Table>
|
|
772
775
|
)
|
|
773
|
-
|
|
776
|
+
|
|
774
777
|
const table = screen.getByTestId('table')
|
|
775
778
|
expect(table).toHaveClass('dark:text-gray-200')
|
|
776
|
-
|
|
779
|
+
|
|
777
780
|
const tbody = screen.getByTestId('tbody')
|
|
778
781
|
expect(tbody).toHaveClass('[&_tr:last-child]:border-0')
|
|
782
|
+
|
|
783
|
+
// #444: her gövde satırı zebra sınıfını taşır; boyamayı `:nth-child(odd)` seçer.
|
|
784
|
+
expect(screen.getByTestId('row-1')).toHaveClass('odd:bg-muted/50')
|
|
785
|
+
expect(screen.getByTestId('row-2')).toHaveClass('odd:bg-muted/50')
|
|
786
|
+
})
|
|
787
|
+
})
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* #444 — `variant="striped"` HİÇ uygulanmıyordu.
|
|
791
|
+
*
|
|
792
|
+
* Kök neden: `Table`, çocuklarını gezip `child.type === "tbody"` diye STRING
|
|
793
|
+
* karşılaştırıyordu; `TableBody` bir `forwardRef` nesnesi olduğu için koşul daima
|
|
794
|
+
* false'tu. İkinci kusur: hedeflenen `even:[&>tr]:bg-muted/50` sınıfı tbody'nin
|
|
795
|
+
* kendi kardeş sırasına bakıyordu, satırlara değil.
|
|
796
|
+
*
|
|
797
|
+
* Bu blok "sessiz eşitliği" kilitler: striped çıktısı default'tan GERÇEKTEN
|
|
798
|
+
* farklı olmalı. Aşağıdaki testler düzeltme geri alındığında kırmızıya döner.
|
|
799
|
+
*/
|
|
800
|
+
describe('Striped Variant (#444)', () => {
|
|
801
|
+
const rows = (
|
|
802
|
+
<>
|
|
803
|
+
<TableRow>
|
|
804
|
+
<TableCell>Row 1</TableCell>
|
|
805
|
+
</TableRow>
|
|
806
|
+
<TableRow>
|
|
807
|
+
<TableCell>Row 2</TableCell>
|
|
808
|
+
</TableRow>
|
|
809
|
+
<TableRow>
|
|
810
|
+
<TableCell>Row 3</TableCell>
|
|
811
|
+
</TableRow>
|
|
812
|
+
</>
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
const markupFor = (variant: 'default' | 'striped') =>
|
|
816
|
+
renderToStaticMarkup(
|
|
817
|
+
<Table variant={variant}>
|
|
818
|
+
<TableBody>{rows}</TableBody>
|
|
819
|
+
</Table>
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
it('produces markup that actually differs from the default variant', () => {
|
|
823
|
+
const striped = markupFor('striped')
|
|
824
|
+
const plain = markupFor('default')
|
|
825
|
+
|
|
826
|
+
// #444'ün ta kendisi: bu iki çıktı eskiden BİREBİR aynıydı.
|
|
827
|
+
expect(striped).not.toBe(plain)
|
|
828
|
+
expect(striped).toContain('odd:bg-muted/50')
|
|
829
|
+
expect(plain).not.toContain('odd:bg-muted/50')
|
|
830
|
+
})
|
|
831
|
+
|
|
832
|
+
it('never emits the old tbody-level selector', () => {
|
|
833
|
+
// Eski (çalışmayan) desen: tbody'nin kardeş sırasına bakan `even:` seçicisi.
|
|
834
|
+
expect(markupFor('striped')).not.toContain('even:[&>tr]:bg-muted/50')
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
it('applies the zebra class to rows, not to the tbody', () => {
|
|
838
|
+
render(
|
|
839
|
+
<Table variant="striped">
|
|
840
|
+
<TableBody data-testid="tbody">
|
|
841
|
+
<TableRow data-testid="row-1">
|
|
842
|
+
<TableCell>Row 1</TableCell>
|
|
843
|
+
</TableRow>
|
|
844
|
+
<TableRow data-testid="row-2">
|
|
845
|
+
<TableCell>Row 2</TableCell>
|
|
846
|
+
</TableRow>
|
|
847
|
+
</TableBody>
|
|
848
|
+
</Table>
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
expect(screen.getByTestId('tbody').className).not.toMatch(/bg-muted/)
|
|
852
|
+
expect(screen.getByTestId('row-1')).toHaveClass('odd:bg-muted/50')
|
|
853
|
+
expect(screen.getByTestId('row-2')).toHaveClass('odd:bg-muted/50')
|
|
854
|
+
})
|
|
855
|
+
|
|
856
|
+
it('does not stripe rows in non-striped variants', () => {
|
|
857
|
+
const { rerender } = render(
|
|
858
|
+
<Table variant="default">
|
|
859
|
+
<TableBody>
|
|
860
|
+
<TableRow data-testid="row">
|
|
861
|
+
<TableCell>Row</TableCell>
|
|
862
|
+
</TableRow>
|
|
863
|
+
</TableBody>
|
|
864
|
+
</Table>
|
|
865
|
+
)
|
|
866
|
+
expect(screen.getByTestId('row')).not.toHaveClass('odd:bg-muted/50')
|
|
867
|
+
|
|
868
|
+
rerender(
|
|
869
|
+
<Table variant="bordered">
|
|
870
|
+
<TableBody>
|
|
871
|
+
<TableRow data-testid="row">
|
|
872
|
+
<TableCell>Row</TableCell>
|
|
873
|
+
</TableRow>
|
|
874
|
+
</TableBody>
|
|
875
|
+
</Table>
|
|
876
|
+
)
|
|
877
|
+
expect(screen.getByTestId('row')).not.toHaveClass('odd:bg-muted/50')
|
|
878
|
+
})
|
|
879
|
+
|
|
880
|
+
it('leaves header and footer rows unstriped', () => {
|
|
881
|
+
render(
|
|
882
|
+
<Table variant="striped">
|
|
883
|
+
<TableHeader>
|
|
884
|
+
<TableRow data-testid="header-row">
|
|
885
|
+
<TableHead>Name</TableHead>
|
|
886
|
+
</TableRow>
|
|
887
|
+
</TableHeader>
|
|
888
|
+
<TableBody>
|
|
889
|
+
<TableRow data-testid="body-row">
|
|
890
|
+
<TableCell>John</TableCell>
|
|
891
|
+
</TableRow>
|
|
892
|
+
</TableBody>
|
|
893
|
+
<TableFooter>
|
|
894
|
+
<TableRow data-testid="footer-row">
|
|
895
|
+
<TableCell>Total</TableCell>
|
|
896
|
+
</TableRow>
|
|
897
|
+
</TableFooter>
|
|
898
|
+
</Table>
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
expect(screen.getByTestId('body-row')).toHaveClass('odd:bg-muted/50')
|
|
902
|
+
// Başlık satırı ve `bg-primary` zeminli altbilgi satırı zebraya girmez.
|
|
903
|
+
expect(screen.getByTestId('header-row')).not.toHaveClass('odd:bg-muted/50')
|
|
904
|
+
expect(screen.getByTestId('footer-row')).not.toHaveClass('odd:bg-muted/50')
|
|
905
|
+
})
|
|
906
|
+
|
|
907
|
+
it('reaches rows through fragments, .map() and wrapper components', () => {
|
|
908
|
+
// Eski `React.Children.map` + tip karşılaştırması bu yapıların hiçbirinde
|
|
909
|
+
// çalışamazdı; bağlam tabanlı çözüm derinlikten bağımsızdır.
|
|
910
|
+
const RowGroup = ({ items }: { items: string[] }) => (
|
|
911
|
+
<>
|
|
912
|
+
{items.map((item) => (
|
|
913
|
+
<TableRow key={item} data-testid={`row-${item}`}>
|
|
914
|
+
<TableCell>{item}</TableCell>
|
|
915
|
+
</TableRow>
|
|
916
|
+
))}
|
|
917
|
+
</>
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
render(
|
|
921
|
+
<Table variant="striped">
|
|
922
|
+
<TableBody>
|
|
923
|
+
<RowGroup items={['a', 'b']} />
|
|
924
|
+
</TableBody>
|
|
925
|
+
</Table>
|
|
926
|
+
)
|
|
927
|
+
|
|
928
|
+
expect(screen.getByTestId('row-a')).toHaveClass('odd:bg-muted/50')
|
|
929
|
+
expect(screen.getByTestId('row-b')).toHaveClass('odd:bg-muted/50')
|
|
930
|
+
})
|
|
931
|
+
|
|
932
|
+
it('also stripes rows placed inside a raw <tbody>', () => {
|
|
933
|
+
render(
|
|
934
|
+
<Table variant="striped">
|
|
935
|
+
<tbody>
|
|
936
|
+
<TableRow data-testid="row">
|
|
937
|
+
<TableCell>Row</TableCell>
|
|
938
|
+
</TableRow>
|
|
939
|
+
</tbody>
|
|
940
|
+
</Table>
|
|
941
|
+
)
|
|
942
|
+
|
|
943
|
+
expect(screen.getByTestId('row')).toHaveClass('odd:bg-muted/50')
|
|
944
|
+
})
|
|
945
|
+
|
|
946
|
+
it('keeps hover feedback distinguishable from the stripe', () => {
|
|
947
|
+
render(
|
|
948
|
+
<Table variant="striped">
|
|
949
|
+
<TableBody>
|
|
950
|
+
<TableRow data-testid="row">
|
|
951
|
+
<TableCell>Row</TableCell>
|
|
952
|
+
</TableRow>
|
|
953
|
+
</TableBody>
|
|
954
|
+
</Table>
|
|
955
|
+
)
|
|
956
|
+
|
|
957
|
+
const row = screen.getByTestId('row')
|
|
958
|
+
// Varsayılan hover (`bg-muted/50`) şeritle AYNI renk olurdu → görünmez hover.
|
|
959
|
+
expect(row).toHaveClass('hover:bg-accent')
|
|
960
|
+
expect(row).not.toHaveClass('hover:bg-muted/50')
|
|
961
|
+
})
|
|
962
|
+
|
|
963
|
+
it('keeps the selected state distinct from the stripe colour', () => {
|
|
964
|
+
render(
|
|
965
|
+
<Table variant="striped">
|
|
966
|
+
<TableBody>
|
|
967
|
+
<TableRow data-testid="row" data-state="selected">
|
|
968
|
+
<TableCell>Row</TableCell>
|
|
969
|
+
</TableRow>
|
|
970
|
+
</TableBody>
|
|
971
|
+
</Table>
|
|
972
|
+
)
|
|
973
|
+
|
|
974
|
+
const row = screen.getByTestId('row')
|
|
975
|
+
// Şerit `bg-muted/50`, seçili `bg-muted` — tam opaklık BİLEREK şeritte
|
|
976
|
+
// kullanılmadı, yoksa seçili satır seçilmemişten ayırt edilemezdi.
|
|
977
|
+
expect(row).toHaveClass('data-[state=selected]:bg-muted')
|
|
978
|
+
expect(row).toHaveClass('odd:bg-muted/50')
|
|
979
|
+
})
|
|
980
|
+
|
|
981
|
+
it('still lets consumer classes win over the stripe', () => {
|
|
982
|
+
render(
|
|
983
|
+
<Table variant="striped">
|
|
984
|
+
<TableBody>
|
|
985
|
+
<TableRow className="bg-red-500" data-testid="row">
|
|
986
|
+
<TableCell>Row</TableCell>
|
|
987
|
+
</TableRow>
|
|
988
|
+
</TableBody>
|
|
989
|
+
</Table>
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
expect(screen.getByTestId('row')).toHaveClass('bg-red-500', 'odd:bg-muted/50')
|
|
779
993
|
})
|
|
780
994
|
})
|
|
781
995
|
|
|
@@ -84,8 +84,31 @@ const progressIndicatorVariants = cva(
|
|
|
84
84
|
// Regresyon: __tests__/progress-secondary-contrast.test.ts
|
|
85
85
|
secondary: "bg-[rgb(var(--secondary-500))]",
|
|
86
86
|
success: "bg-success",
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
// #424 (#372'nin kardeşi): dolgu `bg-warning`/`bg-error` idi — track
|
|
88
|
+
// (`bg-warning/20` · `bg-error/20`) AYNI token'ın alfalı hâli olduğundan
|
|
89
|
+
// LIGHT temada ayrım kayboluyordu: warning 1.83 · error 2.89 (eşik 3:1,
|
|
90
|
+
// WCAG 1.4.11). DARK tema zaten temizdi (warning 6.97 · error 4.46).
|
|
91
|
+
//
|
|
92
|
+
// KÖK NEDEN: `--warning` ve `--error` tokens.css'te `.dark` bloğunda
|
|
93
|
+
// EZİLMİYOR — iki temada aynı renk. Kardeşleri `--success` (light 29% →
|
|
94
|
+
// dark 36%) ve `--primary` tema-uyarlanabilir olduğu için eşiği geçiyor;
|
|
95
|
+
// bu iki aile o uyarlamadan yoksun kaldığı için light'ta kalıyor.
|
|
96
|
+
//
|
|
97
|
+
// ÇÖZÜM: eksik tema-uyarlamasını component düzeyinde ver — light'ta
|
|
98
|
+
// skalanın -700 kademesine in, dark'ta semantik token'a DOKUNMA.
|
|
99
|
+
// Tema-KÖR bir -700 ataması dark'ı kırardı (docs kartında warning 2.50 ✗
|
|
100
|
+
// · error 2.23 ✗); `dark:` ayrımı tam olarak bunu önler ve dark render'ı
|
|
101
|
+
// bit-bit aynı bırakır. Aynı desen badge.tsx:51-63'te zaten kullanılıyor.
|
|
102
|
+
// Track alfaları BİLEREK `/20` kaldı: altı varyantın tamamı o konvansiyonu
|
|
103
|
+
// paylaşıyor ve alfayı düşürmek track↔zemin ayrımını zayıflatırdı
|
|
104
|
+
// (warning 1.162 → 1.081). Ölçülen sonuç — warning: light 4.24 ✓ /
|
|
105
|
+
// dark 6.97 ✓ (docs kartı #111827: 5.77) · error: light 4.95 ✓ /
|
|
106
|
+
// dark 4.46 ✓ (docs kartı 3.82). Skala token'ları RGB üçlüsüdür
|
|
107
|
+
// (semantiklerin HSL'inden farklı) → `rgb(var(--x-700))` kompozisyonu;
|
|
108
|
+
// `bg-warning-700` YAZILMADI, preset o sınıfı üretmez (#307 ölü-sınıf).
|
|
109
|
+
// Regresyon: __tests__/progress-warning-error-contrast.test.ts
|
|
110
|
+
warning: "bg-[rgb(var(--warning-700))] dark:bg-warning",
|
|
111
|
+
error: "bg-[rgb(var(--error-700))] dark:bg-error",
|
|
89
112
|
},
|
|
90
113
|
animation: {
|
|
91
114
|
default: "transition-all duration-500",
|
|
@@ -72,10 +72,70 @@ const sliderRangeVariants = cva(
|
|
|
72
72
|
// tüketici --secondary-500'ü ezerek özelleştirebilir.
|
|
73
73
|
// Regresyon: __tests__/slider-secondary-contrast.test.ts
|
|
74
74
|
secondary: "bg-[rgb(var(--secondary-500))]",
|
|
75
|
-
accent:
|
|
75
|
+
// #448 (#389/#407'nin `accent` kardeşi — ama ÇÖZÜM SINIFI farklı): range
|
|
76
|
+
// `bg-accent`, track `bg-accent/20` idi — aynı yüzey token'ının iki alfası.
|
|
77
|
+
// `--accent` HER İKİ temada da kusurluydu (warning/error'dan ağır: onlar
|
|
78
|
+
// yalnız light'ta kusurluydu): A ekseni light 1.123 · dark 1.303 · gerçek
|
|
79
|
+
// docs kartı (#111827) 1.162. Eşik 3:1 (WCAG 1.4.11 non-text).
|
|
80
|
+
//
|
|
81
|
+
// KÖK NEDEN: `--accent` bir YÜZEY token'ı — `hover:bg-accent` tinti olarak
|
|
82
|
+
// tasarlanmış ve iki temada da kendi zeminine BİTİŞİK duruyor (light
|
|
83
|
+
// rgb(235,239,245) beyaz üstünde, dark rgb(29,40,58) rgb(3,7,17) üstünde).
|
|
84
|
+
// Dolgu/kenarlık rolüne yapısal olarak uygun değil; bu yüzden track'i
|
|
85
|
+
// değiştirerek onarmak da MÜMKÜN DEĞİL (light'ta accent'ten 3:1 uzakta
|
|
86
|
+
// duran daha açık bir yüzey yok).
|
|
87
|
+
//
|
|
88
|
+
// #389'un tek-orta-ton kademe araması AYNEN uygulanamadı: tokens.css'te
|
|
89
|
+
// `--accent-50 … --accent-950` skalası YOK (skalası olan aileler:
|
|
90
|
+
// primary/secondary/success/warning/error/brand/brand-accent).
|
|
91
|
+
// `bg-accent-foreground` de elendi — `--accent-foreground` iki temada da
|
|
92
|
+
// `--primary` ile BİREBİR aynı değer (varyant primary klonuna dönerdi) ve
|
|
93
|
+
// 7 tema preset'inin 2'sinde beyaz (Creative/Nature), yani tek başına
|
|
94
|
+
// dolgu olamaz.
|
|
95
|
+
//
|
|
96
|
+
// ÇÖZÜM: sistemin TEK nötr skalası olan slate (`--secondary-*`) — `--accent`
|
|
97
|
+
// ile aynı hue ailesi (216° ↔ 215°), üstelik light temada `--accent` ile
|
|
98
|
+
// `--secondary` BİREBİR aynı token değerini taşıyor. Tema-kör tek kademe
|
|
99
|
+
// SEÇİLEMEDİ: bu ailede iki temayı birden geçen tek kademe 500'dür, o da
|
|
100
|
+
// `secondary` varyantının kendi rengi (ΔE*ab = 0.0 — iki varyant klon olurdu).
|
|
101
|
+
// Bu yüzden #442'nin `dark:` ayrımı deseni kullanıldı, tema başına ayrı kademe.
|
|
102
|
+
// Tam tarama (11 kademe × 2 tema × 3 zemin × 2 eksen): DARK'ta eşiği geçen VE
|
|
103
|
+
// kardeş varyantlardan ayırt edilebilen (ΔE*ab ≥ 10) TEK kademe 400
|
|
104
|
+
// (500 = secondary klonu · ≤300 `default`/`primary` ile çakışıyor · ≥600 eşiğin
|
|
105
|
+
// altında). LIGHT'ta {600,700,800} uygun; 700 kardeş ayrımını maksimize eder
|
|
106
|
+
// (ΔE 21.2 · 600→12.7 · 800→14.9).
|
|
107
|
+
// Ölçülen — A: light 1.123 → 10.081 ✓ · dark 1.303 → 7.533 ✓ · docs kartı
|
|
108
|
+
// 1.162 → 6.717 ✓. Track `bg-accent/20` DEĞİŞMEDİ (yedi varyantın ortak deseni).
|
|
109
|
+
// Regresyon: __tests__/slider-accent-contrast.test.ts
|
|
110
|
+
accent: "bg-[rgb(var(--secondary-700))] dark:bg-[rgb(var(--secondary-400))]",
|
|
76
111
|
success: "bg-success",
|
|
77
|
-
|
|
78
|
-
|
|
112
|
+
// #442 (#424/PR #440'ın slider ikizi): range `bg-warning`/`bg-error` idi —
|
|
113
|
+
// track (`bg-warning/20` · `bg-error/20`) AYNI token'ın alfalı hâli olduğundan
|
|
114
|
+
// LIGHT temada ayrım kayboluyordu: warning 1.835 · error 2.893 (eşik 3:1,
|
|
115
|
+
// WCAG 1.4.11 non-text). DARK tema zaten temizdi (warning 6.97 · error 4.46).
|
|
116
|
+
//
|
|
117
|
+
// KÖK NEDEN: `--warning` ve `--error` tokens.css'in `.dark` bloğunda
|
|
118
|
+
// EZİLMİYOR — iki temada aynı renk. Kardeşleri tema-uyarlanabilir olduğu
|
|
119
|
+
// için geçiyor (`--success` light %29 → dark %36; ölçüm: success A ekseni
|
|
120
|
+
// light 3.73 / dark 4.83). Bu iki aile o uyarlamadan yoksun.
|
|
121
|
+
//
|
|
122
|
+
// ÇÖZÜM: eksik tema-uyarlamasını component düzeyinde ver — light'ta skalanın
|
|
123
|
+
// -700 kademesine in, dark'ta semantik token'a DOKUNMA (dark render bit-bit
|
|
124
|
+
// aynı kalır, regresyon yüzeyi sıfır). Tema-KÖR bir -700 ataması dark'ı
|
|
125
|
+
// kırardı: gerçek docs kartı (#111827) üstünde warning 2.499 ✗ · error 2.232 ✗.
|
|
126
|
+
// Tam skala taraması (11 kademe × 2 aile × 3 zemin × 2 tema) track `/20` iken
|
|
127
|
+
// iki temayı birden geçen kademe kümesini BOŞ buluyor → `dark:` ayrımı ZORUNLU;
|
|
128
|
+
// #389'un tek-orta-ton (`--secondary-500`) çözümü burada uygulanamaz, çünkü
|
|
129
|
+
// orada `--secondary` temaya göre değişiyordu. Aynı `dark:` deseni badge.tsx:51-63
|
|
130
|
+
// ve progress.tsx:110-111'de zaten kullanılıyor.
|
|
131
|
+
// Ölçülen sonuç — warning: light 1.835 → 4.235 ✓ · error: light 2.893 → 4.950 ✓
|
|
132
|
+
// (dark değişmedi: 6.97 / 4.46; docs kartı 5.77 / 3.82).
|
|
133
|
+
// Skala token'ları RGB üçlüsüdür (semantiklerin HSL'inden farklı) →
|
|
134
|
+
// `rgb(var(--x-700))` kompozisyonu; `bg-warning-700` YAZILMADI, preset bu
|
|
135
|
+
// ailelere sayısal skala açmıyor (#307 ölü-sınıf tuzağı).
|
|
136
|
+
// Regresyon: __tests__/slider-warning-error-contrast.test.ts
|
|
137
|
+
warning: "bg-[rgb(var(--warning-700))] dark:bg-warning",
|
|
138
|
+
error: "bg-[rgb(var(--error-700))] dark:bg-error",
|
|
79
139
|
},
|
|
80
140
|
},
|
|
81
141
|
defaultVariants: {
|
|
@@ -103,10 +163,36 @@ const sliderThumbVariants = cva(
|
|
|
103
163
|
// aynı kademe: tutamaç ve dolgu tek renkte okunur.
|
|
104
164
|
// Regresyon: __tests__/slider-secondary-contrast.test.ts
|
|
105
165
|
secondary: "border-[rgb(var(--secondary-500))] bg-background",
|
|
106
|
-
|
|
166
|
+
// #448 EKSEN B (range'den FARKLI eksen: bileşen↔sayfa-zemini) — tutamacın
|
|
167
|
+
// dolgusu `bg-background`, yani zeminin kendisi; ayırt edilebilirliğini
|
|
168
|
+
// YALNIZ kenarlık taşır → non-text UI bileşeni, WCAG 1.4.11 ≥3:1.
|
|
169
|
+
// `border-accent` iki temada da kayboluyordu: light 1.154 · dark 1.359 ·
|
|
170
|
+
// gerçek docs kartı 1.197. Range ile AYNI kademeler seçildi — #407'nin
|
|
171
|
+
// gerekçesi burada da geçerli: "tutamaç ve dolgu tek renkte okunur".
|
|
172
|
+
// Ölçülen — light 1.154 → 10.355 ✓ · dark 1.359 → 7.857 ✓ · docs kartı
|
|
173
|
+
// 1.197 → 6.919 ✓.
|
|
174
|
+
// Regresyon: __tests__/slider-accent-contrast.test.ts
|
|
175
|
+
accent: "border-[rgb(var(--secondary-700))] dark:border-[rgb(var(--secondary-400))] bg-background",
|
|
107
176
|
success: "border-success bg-background",
|
|
108
|
-
|
|
109
|
-
|
|
177
|
+
// #442 EKSEN B (range'den FARKLI eksen: bileşen↔sayfa-zemini) — #407'de
|
|
178
|
+
// `secondary` için açılan eksenin warning/error karşılığı. Tutamacın dolgusu
|
|
179
|
+
// `bg-background`, yani zeminin kendisi; ayırt edilebilirliği YALNIZ kenarlık
|
|
180
|
+
// taşır → non-text UI bileşeni, WCAG 1.4.11 ≥3:1.
|
|
181
|
+
// ÖLÇÜLEN (eski değerler): warning light **2.133 ✗** · error light 3.781 ✓;
|
|
182
|
+
// dark ikisi de rahat geçiyordu (9.45 / 5.33).
|
|
183
|
+
//
|
|
184
|
+
// `warning` KUSURLUYDU, düzeltildi. `error` bu eksende zaten geçiyordu ama
|
|
185
|
+
// yine de aynı ifadeye çekildi: range light'ta -700'e indiği için kenarlık
|
|
186
|
+
// `border-error`da bırakılsaydı LIGHT temada dolgu rgb(185,28,28), tutamaç
|
|
187
|
+
// rgb(239,67,67) olurdu — düzeltmenin KENDİSİ iki-tonlu bir görünüm üretirdi.
|
|
188
|
+
// #407'nin gerekçesi birebir geçerli: "tutamaç ve dolgu tek renkte okunur".
|
|
189
|
+
// Değişim kontrastı düşürmüyor, artırıyor: error light 3.781 → 6.470.
|
|
190
|
+
// Dark'ta `dark:border-*` semantik token'ı koruduğu için render bit-bit aynı.
|
|
191
|
+
// Ölçülen sonuç — warning: light 2.133 → 4.923 ✓ · error: light 3.781 → 6.470 ✓
|
|
192
|
+
// (dark değişmedi: 9.45 / 5.33; docs kartı 8.32 / 4.69).
|
|
193
|
+
// Regresyon: __tests__/slider-warning-error-contrast.test.ts
|
|
194
|
+
warning: "border-[rgb(var(--warning-700))] dark:border-warning bg-background",
|
|
195
|
+
error: "border-[rgb(var(--error-700))] dark:border-error bg-background",
|
|
110
196
|
},
|
|
111
197
|
size: {
|
|
112
198
|
sm: "h-3 w-3",
|
|
@@ -34,12 +34,51 @@ const switchVariants = cva(
|
|
|
34
34
|
md: "h-6 w-11",
|
|
35
35
|
lg: "h-7 w-14",
|
|
36
36
|
},
|
|
37
|
+
// #454 — checked track'in İKİ ekseni de ölçüldü (eşik 3:1, WCAG 1.4.11
|
|
38
|
+
// non-text UI). Switch'te bu iki eksen AYNI sayıyı verir, çünkü thumb
|
|
39
|
+
// `bg-background` taşır (switchThumbVariants tabanı):
|
|
40
|
+
// A) checked track ↔ sayfa/kart zemini → kontrol "açık" okunuyor mu?
|
|
41
|
+
// B) thumb ↔ checked track → tutamacın KONUMU görülüyor mu?
|
|
42
|
+
// Ölçüm zeminleri: `--background` · `--card` · docs kabuğunun gerçek koyu
|
|
43
|
+
// kartı (#111827). Regresyon: __tests__/switch-variant-contrast.test.tsx
|
|
37
44
|
variant: {
|
|
38
|
-
primary: "data-[state=checked]:bg-primary",
|
|
39
|
-
success: "data-[state=checked]:bg-success",
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
primary: "data-[state=checked]:bg-primary", // light 4.920 · dark 19.256 ✓
|
|
46
|
+
success: "data-[state=checked]:bg-success", // light 4.904 · dark 6.043 ✓
|
|
47
|
+
// #454 kardeş kusuru (#424/PR #440 progress · #442/PR #446 slider ile AYNI
|
|
48
|
+
// sınıf, üçüncü component): `--warning` tokens.css'in `.dark` bloğunda
|
|
49
|
+
// EZİLMİYOR, yani iki temada da aynı turuncu. Dark'ta zemin koyu olduğu
|
|
50
|
+
// için geçiyor (9.446), LIGHT'ta beyaz üstünde 2.133 ✗ — açık temada
|
|
51
|
+
// checked warning switch'in tutamacı da kayboluyordu (B ekseni 2.133).
|
|
52
|
+
// ÇÖZÜM (emsalle birebir aynı şekil): eksik tema-uyarlamasını component
|
|
53
|
+
// düzeyinde ver — light'ta skalanın -700 kademesine in, dark'ta semantik
|
|
54
|
+
// token'a DOKUNMA (dark render bit-bit aynı kalır, regresyon yüzeyi sıfır).
|
|
55
|
+
// Light'ta eşiği geçen kademe kümesi {700,800,900,950}; en açık olanı
|
|
56
|
+
// seçildi (turuncu kimliğini en çok koruyan). Ölçülen: 2.133 → 4.923 ✓.
|
|
57
|
+
warning:
|
|
58
|
+
"data-[state=checked]:bg-[rgb(var(--warning-700))] dark:data-[state=checked]:bg-warning",
|
|
59
|
+
destructive: "data-[state=checked]:bg-error", // light 3.781 · dark 5.328 ✓ (dokunulmadı)
|
|
60
|
+
// #454 asıl kusuru: `secondary` varyantı `bg-accent` kullanıyordu — ADI
|
|
61
|
+
// `secondary`, TOKEN'ı `accent`. `--accent` bir YÜZEY token'ı (`hover:bg-accent`
|
|
62
|
+
// tinti) ve iki temada da kendi zeminine bitişik: light rgb(235,239,245)
|
|
63
|
+
// beyaz üstünde 1.154 · dark rgb(29,40,58) 1.359 · docs kartı 1.197.
|
|
64
|
+
// Yani checked switch hem sayfadan hem de kendi tutamacından ayrışmıyordu:
|
|
65
|
+
// "açık" durum GÖRÜNMÜYORDU (switch'in tek işi durumu göstermek).
|
|
66
|
+
//
|
|
67
|
+
// AD ↔ TOKEN KARARI: varyant kendi ailesine, `--secondary-*` slate skalasına
|
|
68
|
+
// bağlandı. `bg-secondary` (semantik) BİLEREK seçilmedi — light'ta `--accent`
|
|
69
|
+
// ile BİREBİR aynı değer (1.154), dark'ta daha da kötü (1.007). Skala
|
|
70
|
+
// taraması (11 kademe × 2 tema × 3 zemin × 2 eksen + kardeş ΔE*ab ≥ 10 klon
|
|
71
|
+
// filtresi): light {500,600,700,800,900,950} · dark {300,400,500} → iki
|
|
72
|
+
// temayı birden geçen TEK kademe **500**, yani `dark:` ayrımına gerek yok.
|
|
73
|
+
// #448'in slider'daki 700/400 ayrımı burada GEREKMEZ ve İSTENMEZ: orada 500
|
|
74
|
+
// elenmişti çünkü slider'ın `secondary` varyantı zaten o rengi kullanıyordu
|
|
75
|
+
// (ΔE = 0.0 → klon). Switch'in `accent` adlı bir varyantı YOK, dolayısıyla
|
|
76
|
+
// 500 serbest ve kardeşlerden ayrık (ΔE: primary 69.5/51.6 · success
|
|
77
|
+
// 63.6/74.8 · warning 96.0 · destructive 85.0 — hepsi ≥ 10).
|
|
78
|
+
// Yan fayda: slider'ın `secondary` varyantı da #389/PR #406'da aynı kademeye
|
|
79
|
+
// sabitlenmişti → aynı varyant adı artık iki component'te aynı rengi veriyor.
|
|
80
|
+
// Ölçülen: A/B light 1.154 → 4.759 ✓ · dark 1.359 → 4.234 ✓ · kart 1.197 → 3.728 ✓.
|
|
81
|
+
secondary: "data-[state=checked]:bg-[rgb(var(--secondary-500))]",
|
|
43
82
|
},
|
|
44
83
|
},
|
|
45
84
|
defaultVariants: {
|
|
@@ -26,6 +26,51 @@ interface TableSortContextValue {
|
|
|
26
26
|
|
|
27
27
|
const TableSortContext = React.createContext<TableSortContextValue | null>(null);
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Zebra (striped) bağlamı (#444).
|
|
31
|
+
*
|
|
32
|
+
* ESKİ HÂLİ İKİ AYRI NEDENLE ÇALIŞMIYORDU:
|
|
33
|
+
* 1. `Table`, çocuklarını gezip `child.type === "tbody"` diye STRING
|
|
34
|
+
* karşılaştırıyordu. `TableBody` bir `React.forwardRef` nesnesidir; bu koşul
|
|
35
|
+
* hiçbir zaman doğru olmaz → `striped` sınıfı HİÇ eklenmiyordu (ölçüldü:
|
|
36
|
+
* `variant="striped"` çıktısı `variant="default"` ile birebir aynıydı).
|
|
37
|
+
* 2. Koşul düzelse bile hedeflenen sınıf yanlıştı: `even:[&>tr]:bg-muted/50`
|
|
38
|
+
* içindeki `even:` tbody'nin KENDİ kardeş sırasına bakar (tek tbody varsa hiç
|
|
39
|
+
* eşleşmez), satırlara değil.
|
|
40
|
+
*
|
|
41
|
+
* Artık `Table` yalnızca bir bayrak YAYINLAR; zebra sınıfını `TableRow` kendi
|
|
42
|
+
* üstüne koyar. Böylece eleman-tipi karşılaştırması tamamen kalkar: satırlar
|
|
43
|
+
* fragment, `.map()`, ara sarmalayıcı bir component ya da düz `<tbody>` içinden
|
|
44
|
+
* gelse de çalışır. `TableHeader`/`TableFooter` bayrağı `false`'a çevirir —
|
|
45
|
+
* başlık satırı ve `bg-primary` zeminli altbilgi satırı çizgilenmemelidir.
|
|
46
|
+
*/
|
|
47
|
+
const TableStripedContext = React.createContext(false);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Zebra + etkileşim yüzeyleri (#444) — ölçülmüş değerler, tokens.css'e dokunulmadan.
|
|
51
|
+
*
|
|
52
|
+
* `odd:bg-muted/50` → satır düzeyi zebra. `:nth-child(odd)` satırın kendi
|
|
53
|
+
* kardeş sırasına bakar, yani gerçekten 1./3./5. GÖVDE SATIRI boyanır.
|
|
54
|
+
*
|
|
55
|
+
* `hover:bg-accent` → zebra açıkken ZORUNLU: `TableRow`'un varsayılan hover'ı
|
|
56
|
+
* zaten `bg-muted/50`, yani şeritle AYNI renk; override olmasa çizgili satırlarda
|
|
57
|
+
* hover geri bildirimi görünmez olurdu. `--accent` bu tasarım sisteminin kanonik
|
|
58
|
+
* etkileşim yüzeyidir (button ghost/outline, dropdown item aynı token'ı kullanır).
|
|
59
|
+
* `cn()` = twMerge olduğu için `hover:bg-muted/50` ile çakışma CSS sırasına değil,
|
|
60
|
+
* deterministik olarak sona yazılan sınıfa göre çözülür.
|
|
61
|
+
*
|
|
62
|
+
* `bg-muted` (tam opaklık) BİLEREK seçilmedi: `data-[state=selected]:bg-muted` ile
|
|
63
|
+
* birebir aynı renk olurdu ve çizgili tabloda seçili satır seçilmemişten
|
|
64
|
+
* ayırt edilemezdi. Ölçülen CIE L* değerleri (zemin → şerit → seçili → hover):
|
|
65
|
+
* açık tema : 100 → 98.06 → 96.12 → 94.09 (şerit ΔL* 1.94, kontrast 1.05:1)
|
|
66
|
+
* koyu tema : 1.89 → 9.61 → 18.96 → 16.06 (şerit ΔL* 7.72, kontrast 1.17:1)
|
|
67
|
+
* Dört yüzey de her iki temada ayrı; hover her satır paritesinde görünür değişir.
|
|
68
|
+
* WCAG 1.4.11'in 3:1 eşiği zebraya UYGULANMAZ — şerit dekoratif bir tarama
|
|
69
|
+
* yardımıdır, içeriği anlamak için gerekli bir grafik ya da UI bileşeni değildir
|
|
70
|
+
* (veri hücre metninde; metin kontrastı her iki temada AA üstünde kalır).
|
|
71
|
+
*/
|
|
72
|
+
const STRIPED_ROW_CLASSES = "odd:bg-muted/50 hover:bg-accent";
|
|
73
|
+
|
|
29
74
|
/** asc → desc → null → asc döngüsü */
|
|
30
75
|
function nextSortDirection(current: SortDirection): SortDirection {
|
|
31
76
|
if (current === "asc") return "desc";
|
|
@@ -114,9 +159,10 @@ const Table = React.forwardRef<
|
|
|
114
159
|
onRowSelectionChange: _onRowSelectionChange,
|
|
115
160
|
disableRowSelection: _disableRowSelection,
|
|
116
161
|
getRowId: _getRowId,
|
|
117
|
-
|
|
162
|
+
children,
|
|
163
|
+
...props
|
|
118
164
|
}, ref) => {
|
|
119
|
-
//
|
|
165
|
+
// #444: zebra bir bayrak olarak yayınlanır; sınıfı TableRow kendi üstüne koyar.
|
|
120
166
|
const striped = variant === "striped";
|
|
121
167
|
|
|
122
168
|
// #321: sıralama prop'ları alt başlıklara bağlam üzerinden ulaşır
|
|
@@ -124,21 +170,7 @@ const Table = React.forwardRef<
|
|
|
124
170
|
() => ({ sortColumn, sortDirection, onSortChange }),
|
|
125
171
|
[sortColumn, sortDirection, onSortChange]
|
|
126
172
|
);
|
|
127
|
-
|
|
128
|
-
// Çocukları güvenli bir şekilde işle ve tip kontrollerini doğru yap
|
|
129
|
-
const childrenWithProps = React.Children.map(props.children, (child) => {
|
|
130
|
-
if (React.isValidElement(child)) {
|
|
131
|
-
if (child.type === "tbody") {
|
|
132
|
-
// Tip güvenliği için props'ları düzgün şekilde ele alıyoruz
|
|
133
|
-
const tbodyProps = child.props as Record<string, unknown>;
|
|
134
|
-
return React.cloneElement(child as React.ReactElement<React.HTMLAttributes<HTMLTableSectionElement>>, {
|
|
135
|
-
className: cn(tbodyProps.className as string | undefined, striped && "even:[&>tr]:bg-muted/50")
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return child;
|
|
140
|
-
});
|
|
141
|
-
|
|
173
|
+
|
|
142
174
|
return (
|
|
143
175
|
<div className="relative w-full overflow-auto">
|
|
144
176
|
{/* Yükleniyor durumu için overlay */}
|
|
@@ -149,19 +181,20 @@ const Table = React.forwardRef<
|
|
|
149
181
|
)}
|
|
150
182
|
|
|
151
183
|
<TableSortContext.Provider value={sortContextValue}>
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
184
|
+
<TableStripedContext.Provider value={striped}>
|
|
185
|
+
<table
|
|
186
|
+
ref={ref}
|
|
187
|
+
className={cn(
|
|
188
|
+
tableVariants({ variant, size }),
|
|
189
|
+
loading && "opacity-70",
|
|
190
|
+
className
|
|
191
|
+
)}
|
|
192
|
+
{...props}
|
|
193
|
+
>
|
|
194
|
+
{children}
|
|
195
|
+
</table>
|
|
196
|
+
</TableStripedContext.Provider>
|
|
163
197
|
</TableSortContext.Provider>
|
|
164
|
-
|
|
165
198
|
</div>
|
|
166
199
|
);
|
|
167
200
|
});
|
|
@@ -170,8 +203,11 @@ Table.displayName = "Table";
|
|
|
170
203
|
const TableHeader = React.forwardRef<
|
|
171
204
|
HTMLTableSectionElement,
|
|
172
205
|
React.HTMLAttributes<HTMLTableSectionElement>
|
|
173
|
-
>(({ className, ...props }, ref) => (
|
|
174
|
-
<thead ref={ref} className={cn("moonui-theme", "[&_tr]:border-b", className)} {...props}
|
|
206
|
+
>(({ className, children, ...props }, ref) => (
|
|
207
|
+
<thead ref={ref} className={cn("moonui-theme", "[&_tr]:border-b", className)} {...props}>
|
|
208
|
+
{/* #444: başlık satırı zebraya dahil değildir */}
|
|
209
|
+
<TableStripedContext.Provider value={false}>{children}</TableStripedContext.Provider>
|
|
210
|
+
</thead>
|
|
175
211
|
));
|
|
176
212
|
TableHeader.displayName = "TableHeader";
|
|
177
213
|
|
|
@@ -218,28 +254,37 @@ TableBody.displayName = "TableBody";
|
|
|
218
254
|
const TableFooter = React.forwardRef<
|
|
219
255
|
HTMLTableSectionElement,
|
|
220
256
|
React.HTMLAttributes<HTMLTableSectionElement>
|
|
221
|
-
>(({ className, ...props }, ref) => (
|
|
257
|
+
>(({ className, children, ...props }, ref) => (
|
|
222
258
|
<tfoot
|
|
223
259
|
ref={ref}
|
|
224
260
|
className={cn("moonui-theme", "bg-primary text-primary-foreground font-medium", className)}
|
|
225
261
|
{...props}
|
|
226
|
-
|
|
262
|
+
>
|
|
263
|
+
{/* #444: altbilginin kendi `bg-primary` zemini var; şerit onu bozmamalı */}
|
|
264
|
+
<TableStripedContext.Provider value={false}>{children}</TableStripedContext.Provider>
|
|
265
|
+
</tfoot>
|
|
227
266
|
));
|
|
228
267
|
TableFooter.displayName = "TableFooter";
|
|
229
268
|
|
|
230
269
|
const TableRow = React.forwardRef<
|
|
231
270
|
HTMLTableRowElement,
|
|
232
271
|
React.HTMLAttributes<HTMLTableRowElement>
|
|
233
|
-
>(({ className, ...props }, ref) =>
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
272
|
+
>(({ className, ...props }, ref) => {
|
|
273
|
+
// #444: zebra bayrağı Table'dan gelir; TableHeader/TableFooter içinde false'tur.
|
|
274
|
+
const striped = React.useContext(TableStripedContext);
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<tr
|
|
278
|
+
ref={ref}
|
|
279
|
+
className={cn(
|
|
280
|
+
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
|
281
|
+
striped && STRIPED_ROW_CLASSES,
|
|
282
|
+
className
|
|
283
|
+
)}
|
|
284
|
+
{...props}
|
|
285
|
+
/>
|
|
286
|
+
);
|
|
287
|
+
});
|
|
243
288
|
TableRow.displayName = "TableRow";
|
|
244
289
|
|
|
245
290
|
interface TableHeadProps extends React.ThHTMLAttributes<HTMLTableCellElement> {
|