koddiv-dyn-table 0.1.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/README.md ADDED
@@ -0,0 +1,491 @@
1
+ # DynTable
2
+
3
+ React tabanlı dinamik, tam fonksiyonlu tablo komponenti. AG Grid tarzı kolon özellikleri: sıralama, filtre, sayfalama, sanal listeleme, kolon görünürlük/sıra (sürükle-bırak), sabit kolonlar, yükleme skeleton’ı. İleride npm paketi olarak yayınlanacak şekilde yapılandırıldı.
4
+
5
+ ## Kurulum
6
+
7
+ **Paketi kullanmak (npm / yarn):**
8
+
9
+ ```bash
10
+ npm install dyn-table
11
+ # veya
12
+ yarn add dyn-table
13
+ ```
14
+
15
+ **Geliştirme / demo için (repo klonlandığında):**
16
+
17
+ ```bash
18
+ npm install
19
+ ```
20
+
21
+ ## Geliştirme
22
+
23
+ Demo uygulamasını çalıştırır (tabloyu test etmek için):
24
+
25
+ ```bash
26
+ npm run dev
27
+ ```
28
+
29
+ ## Paket build (npm yayını için)
30
+
31
+ ```bash
32
+ npm run build
33
+ ```
34
+
35
+ Çıktı: `dist/` — `dyn-table.js` (ESM), `dyn-table.umd.cjs`, `*.d.ts`, `dyn-table.css`.
36
+
37
+ **npm / yarn ile yayınlama:** Adımlar için [PUBLISH.md](./PUBLISH.md) dosyasına bakın.
38
+
39
+ ---
40
+
41
+ ## Kullanım
42
+
43
+ ```tsx
44
+ import { Table } from 'dyn-table';
45
+ import type { ColumnDef } from 'dyn-table';
46
+ import 'dyn-table/dist/style.css';
47
+
48
+ <Table<MyRow>
49
+ data={veri}
50
+ columns={columns}
51
+ keyColumnId="id"
52
+ emptyMessage="Kayıt yok"
53
+ theme="light"
54
+ pagination
55
+ pageSize={25}
56
+ loading={isLoading}
57
+ />
58
+ ```
59
+
60
+ Yaygın prop’lar: `theme`, `pagination`, `pageSize`, `filterable`, `rowSelection`, `virtualization`, `loading`, `columnVisibility` / `columnOrder`. Satır anahtarı: `keyColumnId="id"` (DB kolonu) veya `keyExtractor` (özel fonksiyon).
61
+
62
+ ---
63
+
64
+ ## Table props
65
+
66
+ | Prop | Tip | Açıklama |
67
+ |------|-----|----------|
68
+ | `data` | `T[]` | Satır verisi |
69
+ | `columns` | `ColumnDef<T>[]` | Kolon tanımları |
70
+ | `keyExtractor` | `(row: T) => string \| number` | Satır benzersiz anahtarı (fonksiyon); `keyColumnId` verilmezse zorunlu |
71
+ | `keyColumnId` | `string` | Satır anahtarının alınacağı kolon id’si (örn. DB’deki `id`); `keyExtractor` verilmezse kullanılır |
72
+ | `sortable` | `boolean` | Genel sıralama açık/kapalı (varsayılan: `true`) |
73
+ | `defaultSort` | `{ id: string; direction: 'asc' \| 'desc' \| null }` | Varsayılan sıralama |
74
+ | `onSort` | `(id, direction) => void` | Sıralama değişince callback |
75
+ | `emptyMessage` | `ReactNode` | Veri yokken gösterilecek içerik |
76
+ | `className` | `string` | Wrapper class |
77
+ | `headerClassName` | `string` | thead class |
78
+ | `bodyClassName` | `string` | tbody class |
79
+ | `rowClassName` | `string \| (row, index) => string` | Satır class |
80
+ | `rowSelection` | `boolean` | Satır seçimi (checkbox kolonu) |
81
+ | `selectedRowKeys` | `(string \| number)[]` | Seçili satır anahtarları (kontrollü) |
82
+ | `defaultSelectedRowKeys` | `(string \| number)[]` | Başlangıç seçili anahtarlar (kontrollü değilse) |
83
+ | `onSelectionChange` | `(selectedKeys, selectedRows: T[]) => void` | Seçim değişince callback |
84
+ | `selectionToolbarLabel` | `(row: T) => string` | Toolbar dropdown’da satır etiketi; verilmezse key gösterilir |
85
+ | `theme` | `'light' \| 'dark'` | Açık/koyu tema |
86
+ | `actions` | `(row: T) => ReactNode` | Satır bazlı aksiyon butonları (son kolon) |
87
+ | `actionsHeader` | `ReactNode` | Aksiyon kolonu başlığı |
88
+ | `actionsAlign` | `'left' \| 'center' \| 'right'` | Aksiyon kolonu hizalama |
89
+ | `actionsWidth` | `string \| number` | Aksiyon kolonu genişliği |
90
+ | `pagination` | `boolean` | Sayfalama açık |
91
+ | `pageSize` | `number` | Sayfa başına satır (varsayılan: 10) |
92
+ | `pageSizeOptions` | `number[]` | Sayfa boyutu seçenekleri |
93
+ | `page` | `number` | Kontrollü mevcut sayfa (1 tabanlı) |
94
+ | `defaultPage` | `number` | Başlangıç sayfası |
95
+ | `onPageChange` | `(page, pageSize) => void` | Sayfa / sayfa boyutu değişince |
96
+ | `totalCount` | `number` | Toplam kayıt (server-side) |
97
+ | `onBlockNeeded` | `(page: number) => void` | Blok tabanlı sayfalama: kullanıcı bu sayfaya geçti ama data bu sayfayı kapsamıyor; blok yüklemesi yap (page 1 tabanlı) |
98
+ | `blockSize` | `number` | Blok boyutu (örn. 100); bilgi/doğrulama için (opsiyonel) |
99
+ | `minHeight` | `string \| number` | Tablo alanı min yükseklik |
100
+ | `height` | `string \| number` | Tablo alanı sabit yükseklik |
101
+ | `mobileBreakpoint` | `number` | Bu genişliğin altında kart görünümü (varsayılan: 768) |
102
+ | `filterable` | `boolean` | Filtre paneli (toolbar’da Filtre butonu, sağdan açılan panel) |
103
+ | `filterModel` | `Record<string, string \| number \| null>` | Filtre değerleri (kontrollü) |
104
+ | `defaultFilterModel` | `Record<string, string \| number \| null>` | Başlangıç filtre değerleri |
105
+ | `onFilterChange` | `(filterModel) => void` | Filtre değişince |
106
+ | `filterPanelWidth` | `number` | Filtre paneli genişliği (varsayılan: 280) |
107
+ | `virtualization` | `boolean` | Sanal listeleme: sadece görünen satırları render (büyük veri için) |
108
+ | `rowHeight` | `number` | Sanal listelemede satır yüksekliği px (varsayılan: 44) |
109
+ | `virtualizationOverscan` | `number` | Görünüm dışı ekstra render satır sayısı (varsayılan: 5) |
110
+ | `columnVisibility` | `Record<string, boolean>` | Kolon görünürlüğü (kontrollü); id → visible |
111
+ | `onColumnVisibilityChange` | `(visibility) => void` | Kolon görünürlüğü değişince |
112
+ | `columnOrder` | `string[]` | Kolon sırası (kontrollü); id listesi |
113
+ | `onColumnOrderChange` | `(order) => void` | Kolon sırası değişince |
114
+ | `columnReorder` | `boolean` | Kolon sürükle-bırak (header’da tutamaç ile sıra değiştirme); true iken etkin (varsayılan: true) |
115
+ | `loading` | `boolean` | Yükleme durumu; true iken skeleton satırlar (mobilde skeleton kartlar) gösterilir |
116
+ | `stickyHeader` | `boolean` | Yapışkan header; true iken dikey scroll’da thead sabit kalır; height veya minHeight verilmeli |
117
+
118
+ ---
119
+
120
+ ## Yapılanlar
121
+
122
+ - **Kolon:** Sıralama, `valueGetter` / `valueFormatter` / `valueFormat`, `cellFormat` (status, yesNo, badge vb.), `cell` / `cellClass` / `cellStyle`, tooltip, `comparator`, `wrapText`, `hide`, `align` / `headerAlign`
123
+ - **Kolon genişlik:** `width` / `minWidth` / `maxWidth`, **resize** (`resizable: true` ile sürükleyerek genişlik),
124
+ - **flex** (kalan alanı orantılı paylaşma), **pinned** (`pinned: 'left' | 'right'` ile yatay scroll’da sol/sağ sabit), `table-layout: fixed` + colgroup
125
+ - **Tema:** `theme: 'light' | 'dark'`, tüm bileşenlerde CSS değişkenleri ile uyumlu
126
+ - **Aksiyon kolonu:** `actions`, `actionsHeader`, `actionsAlign`, `actionsWidth`, sağda sticky
127
+ - **Sayfalama:** `pagination`, `pageSize`, `pageSizeOptions`, client/server-side, sayfa bilgisi + önceki/sonraki
128
+ - **Yükseklik:** `minHeight`, `height`, scroll alanı
129
+ - **Mobil:** `mobileBreakpoint` altında kart görünümü (etiket–değer), seçim checkbox’ı kartta
130
+ - **Satır seçimi:** Checkbox kolonu, header’da “tümünü seç” (sayfa), `selectedRowKeys` / `onSelectionChange`, kontrollü veya `defaultSelectedRowKeys`
131
+ - **Toolbar:** `rowSelection`, `filterable` veya birden fazla kolon olduğunda: Tümünü seç, Kaldır, seçilenler dropdown,
132
+ - **Filtre** butonu,
133
+ - **Kolonlar** dropdown (görünürlük); mobilde kompakt
134
+ - **Kolon görünürlük:** Toolbar’da «Kolonlar» dropdown ile kolon aç/kapa; `columnVisibility` / `onColumnVisibilityChange` ile kontrollü
135
+ - **Kolon sıralama (drag):** Başlıktaki tutamaç ile sürükleyerek kolon sırası değiştirme; `columnOrder` / `onColumnOrderChange` ile kontrollü; mobilde devre dışı
136
+ - **Filtre paneli:** `filterable`, sağdan açılan panel (AG Grid tarzı), overlay ile kapatma; kolonlarda `filter: 'text' | 'number' | 'date' | 'select'`, `filterSelectOptions`; client-side filtreleme, «Filtreleri temizle»
137
+ - **Sanal listeleme (virtualization):** `virtualization`, `rowHeight`, `virtualizationOverscan`; çok büyük veride sadece görünen satırlar render edilir, mobilde devre dışı
138
+ - **Yükleme durumu (loading):** `loading={true}` iken masaüstünde skeleton satırlar, mobilde skeleton kartlar; shimmer animasyonu
139
+ - **Yapışkan header (stickyHeader):** `stickyHeader={true}` iken dikey scroll’da thead sabit kalır; `height` veya `minHeight` verilmeli; mobilde devre dışı
140
+ - **Blok tabanlı sayfalama:** `onBlockNeeded(page)` — kullanıcı sayfaya geçtiğinde `currentPage * pageSize > data.length` ise bir kez tetiklenir; siz API’den blok yükleyip `data` güncellersiniz. İsteğe bağlı `blockSize`.
141
+
142
+ ---
143
+
144
+ ## Satır seçimi
145
+
146
+ `rowSelection={true}` ile tabloya sol tarafta **checkbox kolonu** eklenir. Header’daki checkbox **sayfadaki tüm satırları** seçer/kaldırır (indeterminate: kısmi seçim). Kontrollü kullanım için `selectedRowKeys` + `onSelectionChange`, kontrollü olmayan için `defaultSelectedRowKeys` kullanılır.
147
+
148
+ **Toolbar** `rowSelection` veya `filterable` verildiğinde görünür: «Tümünü seç», «Kaldır», seçilenler **dropdown** («N seçili»), **Filtre** butonu. Dropdown etiketleri için `selectionToolbarLabel={(row) => ...}` kullanılır.
149
+
150
+ ```tsx
151
+ const [selectedKeys, setSelectedKeys] = useState<(string | number)[]>([]);
152
+
153
+ <Table<MyRow>
154
+ rowSelection
155
+ selectedRowKeys={selectedKeys}
156
+ onSelectionChange={(keys, rows) => setSelectedKeys(keys)}
157
+ data={data}
158
+ columns={columns}
159
+ keyExtractor={(r) => r.id}
160
+ />
161
+ ```
162
+
163
+ Mobil kart görünümünde her kartın üstünde de checkbox gösterilir.
164
+
165
+ ---
166
+
167
+ ## Yükleme durumu (loading)
168
+
169
+ `loading={true}` iken tablo gövdesi yerine **skeleton** satırlar (masaüstü) veya **skeleton kartlar** (mobil) gösterilir. Shimmer animasyonu ile veri yüklenirken boş alan yerine placeholder görünür.
170
+
171
+ ```tsx
172
+ const [loading, setLoading] = useState(true);
173
+ // fetch sonrası setLoading(false)
174
+
175
+ <Table<MyRow>
176
+ loading={loading}
177
+ data={data}
178
+ columns={columns}
179
+ keyExtractor={(r) => r.id}
180
+ />
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Kolon görünürlük ve sıra
186
+
187
+ Birden fazla kolon olduğunda toolbar’da **Kolonlar** dropdown’ı çıkar; kolonları checkbox ile göster/gizle. `columnReorder={true}` (varsayılan) iken başlıktaki **sürükleme tutamacı** (grip ikonu) ile kolon sırası değiştirilir; `columnReorder={false}` ile kapatılır (mobilde zaten devre dışı). Kontrollü kullanım için `columnVisibility` + `onColumnVisibilityChange` ve `columnOrder` + `onColumnOrderChange`.
188
+
189
+ ---
190
+
191
+ ## Kolon tanımı (ColumnDef) — AG Grid tarzı
192
+
193
+ ### Kimlik & veri
194
+
195
+ | Özellik | Tip | Açıklama |
196
+ |---------|-----|----------|
197
+ | `id` | `string` | Benzersiz kolon id (AG Grid: colId) |
198
+ | `header` | `string \| ReactNode` | Başlık (AG Grid: headerName) |
199
+ | `accessorKey` | `keyof T \| string` | Veri alanı (AG Grid: field) |
200
+ | `valueGetter` | `(row: T) => unknown` | Ham değeri custom getter ile al |
201
+ | `valueFormat` | `'date' \| 'datetime' \| 'time' \| 'number' \| 'currency' \| 'percent'` | Yerleşik format — valueFormatter yazmaya gerek yok |
202
+ | `valueFormatOptions` | `ValueFormatOptions` | valueFormat seçenekleri (locale, dateStyle, currency vb.) |
203
+ | `valueFormatter` | `(value, row: T) => ReactNode` | Görüntülenen değeri formatla (valueFormat’tan öncelikli) |
204
+ | `cell` | `(row: T) => ReactNode` | Hücre içeriği tam kontrol (valueFormatter’dan öncelikli) |
205
+
206
+ ### Sıralama
207
+
208
+ | Özellik | Tip | Açıklama |
209
+ |---------|-----|----------|
210
+ | `sortable` | `boolean` | Sıralanabilir |
211
+ | `comparator` | `(valueA, valueB, rowA, rowB) => number` | Özel karşılaştırıcı |
212
+ | `suppressSort` | `boolean` | Bu kolonda sıralama kapalı |
213
+
214
+ ### Boyut & hizalama
215
+
216
+ | Özellik | Tip | Açıklama |
217
+ |---------|-----|----------|
218
+ | `width` | `string \| number` | Genişlik (px veya string) |
219
+ | `minWidth` | `string \| number` | Min genişlik |
220
+ | `maxWidth` | `string \| number` | Max genişlik |
221
+ | `flex` | `number` | Kalan alanı orantılı paylaş (oran) |
222
+ | `align` | `'left' \| 'center' \| 'right'` | Hücre hizalama |
223
+ | `headerAlign` | `'left' \| 'center' \| 'right'` | Başlık hizalama |
224
+
225
+ ### Görünürlük & sabitleme
226
+
227
+ | Özellik | Tip | Açıklama |
228
+ |---------|-----|----------|
229
+ | `hide` | `boolean` | Başlangıçta gizli |
230
+ | `pinned` | `'left' \| 'right'` | Yatay scroll’da sol/sağ sabit (sticky) |
231
+ | `resizable` | `boolean` | Sürükleyerek kolon genişliği değiştirilebilir |
232
+ | `filter` | `'text' \| 'number' \| 'date' \| 'select'` | Filtre panelinde bu kolon için filtre alanı |
233
+ | `filterSelectOptions` | `{ value; label }[]` | select filtresi seçenekleri |
234
+
235
+ ### Stil
236
+
237
+ | Özellik | Tip | Açıklama |
238
+ |---------|-----|----------|
239
+ | `cellClass` | `string \| (row, value) => string` | Hücre CSS class |
240
+ | `headerClass` | `string` | Başlık CSS class |
241
+ | `cellStyle` | `CSSProperties \| (row, value) => CSSProperties` | Hücre inline style |
242
+ | `headerStyle` | `CSSProperties` | Başlık inline style |
243
+
244
+ ### Tooltip
245
+
246
+ | Özellik | Tip | Açıklama |
247
+ |---------|-----|----------|
248
+ | `headerTooltip` | `string` | Başlık tooltip |
249
+ | `tooltip` | `(row, value) => string` | Hücre tooltip |
250
+
251
+ ### Hücre formatörü (cellFormat)
252
+
253
+ Boolean değerler için hazır badge formatörleri — `cell` yazmaya gerek yok.
254
+
255
+ | cellFormat | Varsayılan true | Varsayılan false |
256
+ |------------|-----------------|------------------|
257
+ | `status` | Aktif | Pasif |
258
+ | `yesNo` | Evet | Hayır |
259
+ | `onOff` | Açık | Kapalı |
260
+ | `published` | Yayında | Taslak |
261
+ | `approved` | Onaylı | Beklemede |
262
+ | `badge` | (cellFormatOptions ile özel etiket) | |
263
+
264
+ `cellFormatOptions`: `activeLabel`, `inactiveLabel`, `activeClass`, `inactiveClass` ile override.
265
+
266
+ ### Diğer
267
+
268
+ | Özellik | Tip | Açıklama |
269
+ |---------|-----|----------|
270
+ | `editable` | `boolean` | Düzenlenebilir — tip tanımlı, UI sonra |
271
+ | `wrapText` | `boolean` | Metin satır içinde kırılsın |
272
+
273
+ ---
274
+
275
+ ## Örnek kolon tanımları
276
+
277
+ ```tsx
278
+ const columns: ColumnDef<DemoRow>[] = [
279
+ { id: 'ad', header: 'Ad', accessorKey: 'ad', sortable: true },
280
+ {
281
+ id: 'yas',
282
+ header: 'Yaş',
283
+ accessorKey: 'yas',
284
+ valueFormat: 'number',
285
+ align: 'center',
286
+ },
287
+ {
288
+ id: 'tarih',
289
+ header: 'Tarih',
290
+ accessorKey: 'createdAt',
291
+ valueFormat: 'date',
292
+ valueFormatOptions: { locale: 'tr-TR', dateStyle: 'medium' },
293
+ },
294
+ {
295
+ id: 'maas',
296
+ header: 'Maaş',
297
+ accessorKey: 'maas',
298
+ valueFormat: 'currency',
299
+ valueFormatOptions: { currency: 'TRY', maximumFractionDigits: 0 },
300
+ align: 'right',
301
+ },
302
+ {
303
+ id: 'departman',
304
+ header: 'Departman',
305
+ accessorKey: 'departman',
306
+ headerTooltip: 'Çalışan departmanı',
307
+ tooltip: (row, value) => `${row.ad} — ${value}`,
308
+ },
309
+ {
310
+ id: 'durum',
311
+ header: 'Durum',
312
+ cell: (row) => (row.aktif ? <Badge>Aktif</Badge> : <Badge>Pasif</Badge>),
313
+ cellClass: (row) => (row.aktif ? 'text-green-600' : 'text-gray-500'),
314
+ },
315
+ ];
316
+ ```
317
+
318
+ ---
319
+
320
+ ## Filtre paneli
321
+
322
+ `filterable={true}` ile toolbar’da **Filtre** butonu çıkar. Tıklanınca tablonun **sağından** panel açılır (AG Grid tarzı). Kolonlarda `filter: 'text' | 'number' | 'date' | 'select'` ve gerekirse `filterSelectOptions` tanımlanır. Filtreler client-side uygulanır; «Filtreleri temizle» ile sıfırlanır. Kontrollü kullanım için `filterModel` + `onFilterChange`.
323
+
324
+ ```tsx
325
+ <Table<MyRow>
326
+ filterable
327
+ filterModel={filterModel}
328
+ onFilterChange={setFilterModel}
329
+ columns={[
330
+ { id: 'ad', header: 'Ad', accessorKey: 'ad', filter: 'text' },
331
+ { id: 'departman', header: 'Departman', accessorKey: 'departman', filter: 'select', filterSelectOptions: [{ value: 'Yazılım', label: 'Yazılım' }, ...] },
332
+ ]}
333
+ ...
334
+ />
335
+ ```
336
+
337
+ ---
338
+
339
+ ## Mobil görünüm (kart)
340
+
341
+ `mobileBreakpoint` (varsayılan **768px**) altında tablo yerine **satır başına kart** gösterilir: her satır bir kart, her kolon kartta «Etiket: Değer» satırı. Aksiyon butonları kartın altında.
342
+
343
+ | Prop | Tip | Açıklama |
344
+ |------|-----|----------|
345
+ | `mobileBreakpoint` | `number` | Bu genişliğin (px) altında kart görünümü (varsayılan: 768) |
346
+
347
+ Kullanım: `<Table mobileBreakpoint={768} ... />`. Pencereyi daraltınca veya gerçek cihazda kartlar görünür. `loading` iken mobilde skeleton kartlar gösterilir.
348
+
349
+ ---
350
+
351
+ ## Sanal listeleme (virtualization)
352
+
353
+ Çok satırlı veride performans için `virtualization={true}` kullanın. Sadece görünen satırlar render edilir. `minHeight` veya `height` verin; isteğe göre `rowHeight` (varsayılan 44) ve `virtualizationOverscan` (varsayılan 5) ayarlanabilir. Mobilde otomatik devre dışıdır.
354
+
355
+ ```tsx
356
+ <Table<MyRow>
357
+ virtualization
358
+ minHeight={400}
359
+ rowHeight={44}
360
+ data={cokBuyukListe}
361
+ columns={columns}
362
+ keyExtractor={(r) => r.id}
363
+ />
364
+ ```
365
+
366
+ ---
367
+
368
+ ## Yapışkan header (stickyHeader)
369
+
370
+ Dikey scroll sırasında tablo başlığının sabit kalması için `stickyHeader={true}` kullanın. Tablo gövdesi scroll edilirken thead üstte kalır. `height` veya `minHeight` verilmeli (verilmezse varsayılan 400px kullanılır). Mobilde devre dışıdır.
371
+
372
+ ```tsx
373
+ <Table<MyRow>
374
+ stickyHeader
375
+ minHeight={400}
376
+ data={data}
377
+ columns={columns}
378
+ keyExtractor={(r) => r.id}
379
+ />
380
+ ```
381
+
382
+ ---
383
+
384
+ ## Sabit kolonlar (pinned)
385
+
386
+ Kolonlarda `pinned: 'left'` veya `pinned: 'right'` verildiğinde yatay scroll sırasında bu kolonlar sabit kalır (sticky). Sol sabit kolonlar en başta, sağ sabit kolonlar en sonda (aksiyon kolonundan önce) render edilir.
387
+
388
+ ```tsx
389
+ { id: 'ad', header: 'Ad', accessorKey: 'ad', pinned: 'left' },
390
+ { id: 'maas', header: 'Maaş', accessorKey: 'maas', pinned: 'right' },
391
+ ```
392
+
393
+ ---
394
+
395
+ ## Blok tabanlı sayfalama
396
+
397
+ API’den veri **bloklar halinde** (örn. 100’lük) yüklenir; kullanıcı sayfa değiştirdiğinde gerekli blok henüz yoksa tablo `onBlockNeeded(page)` ile haber verir, siz isteği atıp veriyi ekler/güncellersiniz.
398
+
399
+ ### Akış
400
+
401
+ 1. **blockSize** (örn. 100): API’den her istekte kaç kayıt geleceği.
402
+ 2. **pageSize** (örn. 25): Tabloda sayfa başına satır; 100 kayıt = 4 sayfa.
403
+ 3. İlk yükleme: `data = ilk 100 kayıt`, `totalCount = 500` (veya bilinmiyorsa verilmez).
404
+ 4. Kullanıcı sayfa 1–4 arası gezer → mevcut `data` yeterli.
405
+ 5. Kullanıcı **sayfa 5**’e geçer (kayıt 101–125) → `data.length` (100) &lt; `currentPage * pageSize` (125) → tablo **callback’i bir kez tetikler**.
406
+ 6. Callback’te siz: “Sayfa 5 istendi, henüz 101–200 yok” deyip API’den 2. blok (101–200) çekersiniz; `data`’yı birleştirir veya güncellersiniz (örn. 200 kayıt). `totalCount` varsa güncellersiniz.
407
+ 7. Sayfa 9’a geçince aynı mantıkla 3. blok (201–300) için callback tekrar tetiklenir; siz isteği atıp veriyi ekler/güncellersiniz.
408
+
409
+ ### Prop’lar
410
+
411
+ | Prop | Tip | Açıklama |
412
+ |------|-----|----------|
413
+ | `onBlockNeeded` | `(page: number) => void` | Kullanıcı `page` numaralı sayfaya geçti ama `data` o sayfayı kapsamıyor; bu sayfa için blok yüklemesi yapılmalı. `page` 1 tabanlı. |
414
+ | `blockSize` (opsiyonel) | `number` | Blok boyutu (örn. 100). Sadece bilgi/doğrulama için; tetikleme `data.length` ve `currentPage * pageSize` ile yapılır. |
415
+
416
+ ### Ne zaman tetiklenir?
417
+
418
+ - Sayfa değiştiğinde (kullanıcı pagination’da ileri/geri veya sayfa numarasına tıkladığında).
419
+ - Koşul: `currentPage * pageSize > data.length` → “Bu sayfayı göstermek için daha fazla kayıt lazım” → `onBlockNeeded(currentPage)` çağrılır.
420
+ - Aynı sayfa için tekrar tekrar tetiklemeyi önlemek için: sadece **sayfa değişiminde** ve yukarıdaki koşul sağlanıyorsa çağrı yapılır (isteğe bağlı: son tetiklenen sayfa tutulup aynı sayfa için tekrar çağrı yapılmaz).
421
+
422
+ ### Kullanıcı (geliştirici) tarafı örnek
423
+
424
+ ```tsx
425
+ const [data, setData] = useState<Row[]>([]);
426
+ const [totalCount, setTotalCount] = useState<number | undefined>(undefined);
427
+ const BLOCK_SIZE = 100;
428
+ const PAGE_SIZE = 25;
429
+
430
+ // İlk blok
431
+ useEffect(() => {
432
+ fetchPage(1).then(({ items, total }) => {
433
+ setData(items);
434
+ setTotalCount(total);
435
+ });
436
+ }, []);
437
+
438
+ const handleBlockNeeded = useCallback((page: number) => {
439
+ // page 5 → 101-125 arası lazım → 2. blok (101-200)
440
+ const blockIndex = Math.ceil((page * PAGE_SIZE) / BLOCK_SIZE);
441
+ const alreadyLoaded = data.length;
442
+ if (blockIndex * BLOCK_SIZE <= alreadyLoaded) return; // zaten var
443
+ fetchPage(blockIndex).then(({ items }) => {
444
+ setData(prev => [...prev, ...items]); // veya replace, API’ye göre
445
+ });
446
+ }, [data.length]);
447
+
448
+ <Table<Row>
449
+ data={data}
450
+ totalCount={totalCount}
451
+ pageSize={PAGE_SIZE}
452
+ onBlockNeeded={handleBlockNeeded}
453
+ ...
454
+ />
455
+ ```
456
+
457
+ ### Özet
458
+
459
+ - **void dönen callback:** `onBlockNeeded(page: number) => void` — “Bu sayfa için blok yükle” sinyali.
460
+ - **blockSize:** İsterseniz prop olarak verilir (API’nin blok boyutu); tetikleme mantığı `data.length` ve `currentPage * pageSize` ile yapılır.
461
+ - Geliştirici callback’te `page` ve `pageSize` ile hangi blok gerektiğini hesaplar, API’yi çağırır, `data` (ve gerekiyorsa `totalCount`) günceller; tablo birikmiş veriyi sayfaya göre dilimleyip gösterir. `onBlockNeeded` verildiğinde displayData her zaman `data`’dan sayfa dilimi olarak hesaplanır.
462
+
463
+ ---
464
+
465
+ ## Eksikler / eklenebilecekler
466
+
467
+ **Tiplerde var, UI yok**
468
+
469
+ - **Hücre düzenleme (editable)** — `editable: true` ile inline edit
470
+
471
+ **Yeni eklenebilecekler**
472
+
473
+ - **Genişletilebilir satır** — Satır tıklanınca altında detay/child satır
474
+ - **Çoklu sıralama** — Birden fazla kolona göre sıralama
475
+ - **Dışa aktarma** — CSV / Excel export butonu
476
+ - **Yoğunluk** — `density: 'compact' | 'comfortable'` ile satır yüksekliği
477
+ - **Özet satırı** — Tablo altında toplam / ortalama vb. (footer row)
478
+ - **Klavye navigasyonu** — Ok tuşları, Enter ile satır odaklama
479
+ - **Erişilebilirlik** — ARIA, focus yönetimi, ekran okuyucu uyumu
480
+
481
+ **Öncelik önerisi**
482
+
483
+ 1. Genişletilebilir satır / Çoklu sıralama
484
+
485
+ ---
486
+
487
+ ## Proje yapısı
488
+
489
+ - `src/index.ts` — Paket giriş noktası (sadece Table export)
490
+ - `src/components/Table/` — Tablo komponenti, tipler, stiller
491
+ - `src/main.tsx`, `src/App.tsx` — Sadece geliştirme demo’su (paket build’e dahil değil)
package/dist/App.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function App(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,3 @@
1
+ import { TableProps } from './Table.types';
2
+
3
+ export declare function Table<T>({ data, columns, keyExtractor: keyExtractorProp, keyColumnId, sortable, defaultSort, onSort, emptyMessage, theme, className, headerClassName, bodyClassName, rowClassName, actions, actionsHeader, actionsAlign, actionsWidth, pagination, pageSize: pageSizeProp, pageSizeOptions, page: pageProp, defaultPage, onPageChange, totalCount: totalCountProp, onBlockNeeded, blockSize: _blockSize, minHeight, height, mobileBreakpoint, rowSelection, selectedRowKeys: selectedRowKeysProp, defaultSelectedRowKeys, onSelectionChange, selectionToolbarLabel, filterable, filterModel: filterModelProp, defaultFilterModel, onFilterChange, filterPanelWidth, virtualization, rowHeight, virtualizationOverscan, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, columnOrder: columnOrderProp, onColumnOrderChange, columnReorder, loading, stickyHeader, }: TableProps<T>): import("react/jsx-runtime").JSX.Element;