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 +491 -0
- package/dist/App.d.ts +1 -0
- package/dist/components/Table/Table.d.ts +3 -0
- package/dist/components/Table/Table.types.d.ts +195 -0
- package/dist/components/Table/index.d.ts +3 -0
- package/dist/components/Table/utils.d.ts +5 -0
- package/dist/components/Table/valueFormats.d.ts +3 -0
- package/dist/components/TableFormatters/TableFormatters.d.ts +12 -0
- package/dist/components/TableFormatters/index.d.ts +2 -0
- package/dist/components/TableRowActions/TableRowActions.d.ts +15 -0
- package/dist/components/TableRowActions/index.d.ts +2 -0
- package/dist/dyn-table.js +1387 -0
- package/dist/dyn-table.js.map +1 -0
- package/dist/dyn-table.umd.cjs +2 -0
- package/dist/dyn-table.umd.cjs.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/main.d.ts +1 -0
- package/dist/style.css +1 -0
- package/package.json +57 -0
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) < `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;
|