@kikiloaw/simple-table 1.0.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/README.md ADDED
@@ -0,0 +1,731 @@
1
+ # 📊 SimpleTable
2
+
3
+ > A lightweight, dependency-light DataTable component for Vue 3 with Tailwind CSS. Built for simplicity, performance, and maximum compatibility.
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+ [![Vue 3](https://img.shields.io/badge/Vue-3.x-brightgreen.svg)](https://vuejs.org/)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
8
+
9
+ ---
10
+
11
+ ## âœĻ Why SimpleTable?
12
+
13
+ - **ðŸŠķ Lightweight**: Uses native HTML elements (`<select>`, `<input>`, `<button>`)
14
+ - **ðŸŽĻ Beautiful**: Premium Tailwind CSS styling out of the box
15
+ - **⚡ Fast**: Client-side response caching to minimize API calls
16
+ - **🔄 Flexible**: Works with Laravel, DataTables, or any REST API
17
+ - **ðŸ“ą Responsive**: Mobile-first design with smart pagination
18
+ - **ðŸŽŊ Type-Safe**: Full TypeScript support with autocomplete
19
+ - **🔌 Zero Dependencies**: No Radix, no Headless UI, just Vue + Tailwind
20
+
21
+ ---
22
+
23
+ ## ðŸ“Ķ Installation
24
+
25
+ ### Option 1: NPM Package
26
+
27
+ ```bash
28
+ npm install @kikiloaw/simple-table
29
+ ```
30
+
31
+ ### Option 2: Local Copy
32
+
33
+ ```bash
34
+ cp -r path/to/SimpleTable /your-project/src/components/
35
+ ```
36
+
37
+ ---
38
+
39
+ ## 🚀 Quick Start
40
+
41
+ ### 1. Import the Component
42
+
43
+ ```vue
44
+ <script setup>
45
+ import { ref } from 'vue'
46
+ import SimpleTable from '@kikiloaw/simple-table'
47
+
48
+ const columns = [
49
+ { key: 'id', label: 'ID', sortable: true, width: '80px' },
50
+ { key: 'name', label: 'Name', sortable: true },
51
+ { key: 'email', label: 'Email' },
52
+ { key: 'status', label: 'Status', width: '120px' }
53
+ ]
54
+ </script>
55
+
56
+ <template>
57
+ <SimpleTable
58
+ fetch-url="/api/users"
59
+ :columns="columns"
60
+ searchable
61
+ />
62
+ </template>
63
+ ```
64
+
65
+ ### 2. Backend Setup (Laravel)
66
+
67
+ ```php
68
+ public function getData(Request $request)
69
+ {
70
+ $query = User::query();
71
+
72
+ // Handle search
73
+ if ($search = $request->input('search')) {
74
+ $query->where('name', 'like', "%{$search}%");
75
+ }
76
+
77
+ // Handle sorting
78
+ if ($sort = $request->input('sort')) {
79
+ $query->orderBy($sort, $request->input('order', 'asc'));
80
+ }
81
+
82
+ return response()->json($query->paginate($request->input('per_page', 10)));
83
+ }
84
+ ```
85
+
86
+ **That's it!** You now have a fully functional data table! 🎉
87
+
88
+ ---
89
+
90
+ ## 📖 Table of Contents
91
+
92
+ - [Core Concepts](#-core-concepts)
93
+ - [Props Reference](#-props-reference)
94
+ - [Column Configuration](#-column-configuration)
95
+ - [Features](#-features)
96
+ - [Custom Sort Keys](#custom-sort-keys)
97
+ - [Advanced Filtering](#advanced-filtering-query-parameters)
98
+ - [Response Caching](#response-caching)
99
+ - [Custom Actions](#custom-actions-and-slots)
100
+ - [Cell Customization](#custom-cell-rendering)
101
+ - [DataTables Compatibility](#-datatables-compatibility)
102
+ - [Backend Integration](#-backend-integration)
103
+ - [Styling](#-styling-customization)
104
+ - [Troubleshooting](#-troubleshooting)
105
+
106
+ ---
107
+
108
+ ## ðŸŽŊ Core Concepts
109
+
110
+ ### Data Modes
111
+
112
+ SimpleTable supports three data modes:
113
+
114
+ | Mode | When to Use | Example |
115
+ |------|-------------|---------|
116
+ | **`auto`** (default) | Auto-detect based on data structure | Recommended for most cases |
117
+ | **`server`** | Force server-side pagination | Large datasets (10,000+ rows) |
118
+ | **`client`** | Force client-side pagination | Small static datasets (<1,000 rows) |
119
+
120
+ ### Protocol Formats
121
+
122
+ | Protocol | When to Use | Backend Library |
123
+ |----------|-------------|-----------------|
124
+ | **`laravel`** (default) | New projects, standard Laravel apps | Native Laravel pagination |
125
+ | **`datatables`** | Legacy projects, existing DataTables | Yajra DataTables |
126
+
127
+ ---
128
+
129
+ ## 📋 Props Reference
130
+
131
+ ### Essential Props
132
+
133
+ | Prop | Type | Default | Description |
134
+ |------|------|---------|-------------|
135
+ | `columns` | Array | **Required** | Column definitions ([see below](#-column-configuration)) |
136
+ | `fetchUrl` | String | `null` | API endpoint for server-side data |
137
+ | `data` | Array/Object | `[]` | Static data or Laravel Paginator object |
138
+
139
+ ### Behavior Props
140
+
141
+ | Prop | Type | Default | Description |
142
+ |------|------|---------|-------------|
143
+ | `mode` | String | `'auto'` | Data mode: `'auto'`, `'server'`, or `'client'` |
144
+ | `protocol` | String | `'laravel'` | API format: `'laravel'` or `'datatables'` |
145
+ | `searchable` | Boolean | `true` | Enable search input |
146
+ | `enableCache` | Boolean | `false` | Cache API responses |
147
+
148
+ ### Pagination Props
149
+
150
+ | Prop | Type | Default | Description |
151
+ |------|------|---------|-------------|
152
+ | `perPage` | Number | `10` | Default rows per page |
153
+ | `pageSizes` | Array | `[10,20,30,50,100]` | Page size dropdown options |
154
+
155
+
156
+ ### Advanced Props
157
+
158
+ | Prop | Type | Default | Description |
159
+ |------|------|---------|-------------|
160
+ | `queryParams` | Object | `{}` | Additional parameters for every request |
161
+ | `oddRowColor` | String | `'bg-background'` | Tailwind class for odd rows |
162
+ | `evenRowColor` | String | `'bg-background'` | Tailwind class for even rows |
163
+ | `hoverColor` | String | `'hover:bg-muted/50'` | Tailwind class for row hover |
164
+
165
+ ---
166
+
167
+ ## 🏗ïļ Column Configuration
168
+
169
+ ### Basic Column
170
+
171
+ ```javascript
172
+ {
173
+ key: 'name', // Required: Property key from data
174
+ label: 'Name', // Required: Column header text
175
+ sortable: true, // Optional: Enable sorting
176
+ width: '200px', // Optional: Fixed column width
177
+ fixed: true, // Optional: Sticky column (useful for actions)
178
+ class: 'text-center' // Optional: Additional CSS classes
179
+ }
180
+ ```
181
+
182
+ ### Sortable Options
183
+
184
+ | Value | Behavior | Example |
185
+ |-------|----------|---------|
186
+ | `false` | Not sortable | `sortable: false` (default) |
187
+ | `true` | Sortable using `key` | `{ key: 'name', sortable: true }` |
188
+ | `'column_name'` | Sort by custom column | `{ key: 'user.name', sortable: 'user_id' }` |
189
+
190
+ ### Complete Example
191
+
192
+ ```javascript
193
+ const columns = [
194
+ // Simple sortable column
195
+ {
196
+ key: 'id',
197
+ label: 'ID',
198
+ sortable: true,
199
+ width: '80px'
200
+ },
201
+
202
+ // Custom sort key (for relationships)
203
+ {
204
+ key: 'department.name', // Display: department name
205
+ label: 'Department',
206
+ sortable: 'department_id' // Sort by: department_id
207
+ },
208
+
209
+ // Non-sortable column
210
+ {
211
+ key: 'email',
212
+ label: 'Email'
213
+ },
214
+
215
+ // Sticky actions column (always visible)
216
+ {
217
+ key: 'actions',
218
+ label: 'Actions',
219
+ fixed: true,
220
+ width: '120px'
221
+ }
222
+ ]
223
+ ```
224
+
225
+ #### ðŸ’Ą **When to Use Custom Sort Keys**
226
+
227
+ Use custom sort keys when:
228
+ - Displaying relationship data (e.g., `user.department.name`)
229
+ - Sorting by foreign keys instead of displayed values
230
+ - Your database column name differs from the display key
231
+
232
+ ---
233
+
234
+ ## ðŸŽĻ Features
235
+
236
+ ### Custom Sort Keys
237
+
238
+ **Problem:** You want to display `department.name` but sort by `department_id`.
239
+
240
+ **Solution:**
241
+
242
+ ```vue
243
+ <script setup>
244
+ const columns = [
245
+ {
246
+ key: 'department.name', // What users see
247
+ label: 'Department',
248
+ sortable: 'department_id' // What backend sorts by
249
+ }
250
+ ]
251
+ </script>
252
+
253
+ <template>
254
+ <SimpleTable :columns="columns" fetch-url="/api/users" />
255
+ </template>
256
+ ```
257
+
258
+ **Backend receives:** `?sort=department_id&order=asc`
259
+
260
+ ---
261
+
262
+ ### Advanced Filtering (Query Parameters)
263
+
264
+ **Use Case:** Add filters like status, department, date range, etc.
265
+
266
+ ```vue
267
+ <script setup>
268
+ import { ref } from 'vue'
269
+
270
+ const filters = ref({
271
+ status: 'active',
272
+ department_id: 5,
273
+ year: 2025
274
+ })
275
+
276
+ function updateStatus(newStatus) {
277
+ filters.value.status = newStatus
278
+ // Table automatically refetches!
279
+ }
280
+ </script>
281
+
282
+ <template>
283
+ <!-- Your filter UI -->
284
+ <div class="mb-4 flex gap-4">
285
+ <select v-model="filters.status">
286
+ <option value="active">Active</option>
287
+ <option value="inactive">Inactive</option>
288
+ </select>
289
+
290
+ <select v-model="filters.department_id">
291
+ <option :value="1">IT</option>
292
+ <option :value="5">HR</option>
293
+ </select>
294
+ </div>
295
+
296
+ <!-- Table with filters -->
297
+ <SimpleTable
298
+ fetch-url="/api/users"
299
+ :columns="columns"
300
+ :query-params="filters"
301
+ />
302
+ </template>
303
+ ```
304
+
305
+ **API Request:**
306
+ ```
307
+ GET /api/users?page=1&per_page=10&status=active&department_id=5&year=2025
308
+ ```
309
+
310
+ **Backend:**
311
+ ```php
312
+ public function getData(Request $request)
313
+ {
314
+ $query = User::query();
315
+
316
+ // Your custom filters
317
+ if ($status = $request->input('status')) {
318
+ $query->where('status', $status);
319
+ }
320
+
321
+ if ($deptId = $request->input('department_id')) {
322
+ $query->where('department_id', $deptId);
323
+ }
324
+
325
+ return response()->json($query->paginate($request->per_page));
326
+ }
327
+ ```
328
+
329
+ ---
330
+
331
+ ### Response Caching
332
+
333
+ **Benefit:** Reduce API calls when users navigate back to previously viewed pages.
334
+
335
+ ```vue
336
+ <SimpleTable
337
+ fetch-url="/api/users"
338
+ :columns="columns"
339
+ enable-cache <!-- 👈 Add this -->
340
+ />
341
+ ```
342
+
343
+ **How it works:**
344
+ 1. User goes to Page 1 → API call made, response cached
345
+ 2. User goes to Page 2 → API call made, response cached
346
+ 3. User goes back to Page 1 → **No API call** (uses cache)
347
+
348
+ **Clear cache after data changes:**
349
+
350
+ ```vue
351
+ <script setup>
352
+ const tableRef = ref()
353
+
354
+ function handleCreate() {
355
+ // After creating/updating data
356
+ tableRef.value?.clearCache()
357
+ tableRef.value?.refresh()
358
+ }
359
+ </script>
360
+
361
+ <template>
362
+ <SimpleTable ref="tableRef" enable-cache />
363
+ </template>
364
+ ```
365
+
366
+ #### ✅ **When to Enable Caching**
367
+
368
+ - ✅ Reference data (countries, departments, etc.)
369
+ - ✅ Historical data that doesn't change
370
+ - ✅ User wants to revisit previous pages
371
+
372
+ #### ❌ **When NOT to Enable Caching**
373
+
374
+ - ❌ Real-time dashboards
375
+ - ❌ Frequently updated data
376
+ - ❌ Collaborative editing interfaces
377
+
378
+ ---
379
+
380
+ ### Custom Actions and Slots
381
+
382
+ **Add custom buttons to the toolbar:**
383
+
384
+ ```vue
385
+ <SimpleTable :columns="columns" fetch-url="/api/users">
386
+ <template #actions="{ rows }">
387
+ <button @click="exportCustom(rows)" class="btn">
388
+ Custom Export
389
+ </button>
390
+ <button @click="bulkDelete(rows)" class="btn btn-danger">
391
+ Bulk Delete
392
+ </button>
393
+ </template>
394
+ </SimpleTable>
395
+ ```
396
+
397
+ **Access to:**
398
+ - `rows`: Currently visible data
399
+ - `columns`: Column definitions
400
+
401
+ ---
402
+
403
+ ### Custom Cell Rendering
404
+
405
+ **Customize how data is displayed in specific columns:**
406
+
407
+ ```vue
408
+ <SimpleTable :columns="columns" fetch-url="/api/users">
409
+ <!-- Custom status badge -->
410
+ <template #cell-status="{ row }">
411
+ <span
412
+ :class="row.status === 'active' ? 'badge-success' : 'badge-danger'"
413
+ >
414
+ {{ row.status }}
415
+ </span>
416
+ </template>
417
+
418
+ <!-- Custom actions column -->
419
+ <template #cell-actions="{ row }">
420
+ <button @click="edit(row)" class="btn-sm">Edit</button>
421
+ <button @click="delete(row)" class="btn-sm btn-danger">Delete</button>
422
+ </template>
423
+ </SimpleTable>
424
+ ```
425
+
426
+ **Slot Naming:** `#cell-{columnKey}`
427
+
428
+ ---
429
+
430
+ ## 🔄 DataTables Compatibility
431
+
432
+ **Migrating from jQuery DataTables?** SimpleTable has full backward compatibility!
433
+
434
+ ### Quick Migration
435
+
436
+ **Before (jQuery DataTables):**
437
+ ```javascript
438
+ $('#myTable').DataTable({
439
+ serverSide: true,
440
+ ajax: '/api/users'
441
+ });
442
+ ```
443
+
444
+ **After (SimpleTable):**
445
+ ```vue
446
+ <SimpleTable
447
+ fetch-url="/api/users"
448
+ :columns="columns"
449
+ protocol="datatables" <!-- 👈 This is the magic! -->
450
+ />
451
+ ```
452
+
453
+ **No backend changes required!** ✅
454
+
455
+ ### Request Format
456
+
457
+ **SimpleTable sends:**
458
+ ```
459
+ GET /api/users?start=0&length=10&draw=1&search[value]=john&order[0][column]=1&order[0][dir]=asc
460
+ ```
461
+
462
+ | Parameter | Description | Example |
463
+ |-----------|-------------|---------|
464
+ | `start` | Record offset | `0`, `10`, `20` |
465
+ | `length` | Records per page | `10`, `25`, `50` |
466
+ | `draw` | Request counter | `1`, `2`, `3` |
467
+ | `search[value]` | Search query | `john`, `admin` |
468
+ | `order[0][column]` | Column index to sort | `0`, `1`, `2` |
469
+ | `order[0][dir]` | Sort direction | `asc`, `desc` |
470
+
471
+ ### Response Format
472
+
473
+ **Your backend should return:**
474
+
475
+ ```json
476
+ {
477
+ "draw": 1,
478
+ "recordsTotal": 100,
479
+ "recordsFiltered": 50,
480
+ "data": [
481
+ { "id": 1, "name": "John", "email": "john@example.com" }
482
+ ]
483
+ }
484
+ ```
485
+
486
+ | Field | Description |
487
+ |-------|-------------|
488
+ | `draw` | Echo back the request's draw parameter |
489
+ | `recordsTotal` | Total records before filtering |
490
+ | `recordsFiltered` | Total records after filtering/search |
491
+ | `data` | Array of data objects |
492
+
493
+ ### Backend Implementation
494
+
495
+ #### With Yajra DataTables (Recommended)
496
+
497
+ ```php
498
+ use Yajra\DataTables\Facades\DataTables;
499
+
500
+ public function getData(Request $request)
501
+ {
502
+ return DataTables::of(User::query())->make(true);
503
+ }
504
+ ```
505
+
506
+ #### Manual Implementation
507
+
508
+ ```php
509
+ public function getData(Request $request)
510
+ {
511
+ $query = User::query();
512
+ $recordsTotal = $query->count();
513
+
514
+ // Apply search
515
+ if ($search = $request->input('search.value')) {
516
+ $query->where('name', 'like', "%{$search}%");
517
+ }
518
+
519
+ $recordsFiltered = $query->count();
520
+
521
+ // Apply sorting
522
+ if ($columnIndex = $request->input('order.0.column')) {
523
+ $columns = ['id', 'name', 'email', 'created_at'];
524
+ $column = $columns[$columnIndex] ?? 'id';
525
+ $dir = $request->input('order.0.dir', 'asc');
526
+ $query->orderBy($column, $dir);
527
+ }
528
+
529
+ // Paginate
530
+ $start = $request->input('start', 0);
531
+ $length = $request->input('length', 10);
532
+ $data = $query->skip($start)->take($length)->get();
533
+
534
+ return response()->json([
535
+ 'draw' => (int) $request->input('draw'),
536
+ 'recordsTotal' => $recordsTotal,
537
+ 'recordsFiltered' => $recordsFiltered,
538
+ 'data' => $data
539
+ ]);
540
+ }
541
+ ```
542
+
543
+ ---
544
+
545
+ ## 🔌 Backend Integration
546
+
547
+ ### Laravel (Standard Pagination)
548
+
549
+ ```php
550
+ public function getData(Request $request)
551
+ {
552
+ $query = User::query();
553
+
554
+ // 1. Search
555
+ if ($search = $request->input('search')) {
556
+ $query->where('name', 'like', "%{$search}%")
557
+ ->orWhere('email', 'like', "%{$search}%");
558
+ }
559
+
560
+ // 2. Sort
561
+ if ($sort = $request->input('sort')) {
562
+ $query->orderBy($sort, $request->input('order', 'asc'));
563
+ }
564
+
565
+ // 3. Paginate
566
+ return response()->json($query->paginate($request->input('per_page', 10)));
567
+ }
568
+ ```
569
+
570
+ ### Expected Response
571
+
572
+ ```json
573
+ {
574
+ "current_page": 1,
575
+ "data": [...],
576
+ "last_page": 10,
577
+ "per_page": 10,
578
+ "total": 100,
579
+ "from": 1,
580
+ "to": 10
581
+ }
582
+ ```
583
+
584
+ ---
585
+
586
+ ## ðŸŽĻ Styling Customization
587
+
588
+ ### Row Colors
589
+
590
+ ```vue
591
+ <SimpleTable
592
+ odd-row-color="bg-white"
593
+ even-row-color="bg-gray-50"
594
+ hover-color="hover:bg-blue-50"
595
+ />
596
+ ```
597
+
598
+ ### Tailwind Configuration
599
+
600
+ Ensure your `tailwind.config.js` includes these colors:
601
+
602
+ ```javascript
603
+ module.exports = {
604
+ theme: {
605
+ extend: {
606
+ colors: {
607
+ border: "hsl(var(--border))",
608
+ input: "hsl(var(--input))",
609
+ ring: "hsl(var(--ring))",
610
+ background: "hsl(var(--background))",
611
+ foreground: "hsl(var(--foreground))",
612
+ primary: {
613
+ DEFAULT: "hsl(var(--primary))",
614
+ foreground: "hsl(var(--primary-foreground))",
615
+ },
616
+ muted: {
617
+ DEFAULT: "hsl(var(--muted))",
618
+ foreground: "hsl(var(--muted-foreground))",
619
+ },
620
+ accent: {
621
+ DEFAULT: "hsl(var(--accent))",
622
+ foreground: "hsl(var(--accent-foreground))",
623
+ }
624
+ }
625
+ }
626
+ }
627
+ }
628
+ ```
629
+
630
+ Or define CSS variables:
631
+
632
+ ```css
633
+ :root {
634
+ --background: 0 0% 100%;
635
+ --foreground: 222.2 84% 4.9%;
636
+ --primary: 221.2 83.2% 53.3%;
637
+ --primary-foreground: 210 40% 98%;
638
+ --muted: 210 40% 96.1%;
639
+ --muted-foreground: 215.4 16.3% 46.9%;
640
+ --accent: 210 40% 96.1%;
641
+ --accent-foreground: 222.2 47.4% 11.2%;
642
+ --border: 214.3 31.8% 91.4%;
643
+ --input: 214.3 31.8% 91.4%;
644
+ --ring: 222.2 84% 4.9%;
645
+ }
646
+ ```
647
+
648
+ ---
649
+
650
+ ## 🐛 Troubleshooting
651
+
652
+ ### Data Not Loading
653
+
654
+ **Check:**
655
+ 1. ✅ Is `fetch-url` correct?
656
+ 2. ✅ Does backend return the right format?
657
+ 3. ✅ Open Network tab - any errors?
658
+ 4. ✅ CORS enabled on backend?
659
+
660
+ ### Sorting Not Working
661
+
662
+ **For Laravel:**
663
+ ```vue
664
+ <SimpleTable :columns="columns" />
665
+ <!-- Make sure sortable is set correctly -->
666
+ ```
667
+
668
+ **For DataTables:**
669
+ ```vue
670
+ <SimpleTable protocol="datatables" :columns="columns" />
671
+ <!-- Column index must match backend expectations -->
672
+ ```
673
+
674
+ ### Pagination Numbers Not Showing
675
+
676
+ Check your browser console for errors. The pagination feature requires the updated package (v1.0.3+).
677
+
678
+ ### Cache Not Clearing
679
+
680
+ ```vue
681
+ <script setup>
682
+ const table = ref()
683
+
684
+ // Manually clear cache
685
+ table.value?.clearCache()
686
+ table.value?.refresh()
687
+ </script>
688
+
689
+ <template>
690
+ <SimpleTable ref="table" enable-cache />
691
+ </template>
692
+ ```
693
+
694
+ ---
695
+
696
+ ## 📝 Events
697
+
698
+ | Event | Payload | Description |
699
+ |-------|---------|-------------|
700
+ | `@update:search` | `string` | Emitted when search query changes |
701
+ | `@update:sort` | `{ column, direction }` | Emitted when sort changes |
702
+ | `@page-change` | `number` | Emitted when page changes |
703
+ | `@export` | `{ format, data }` | Emitted when export is triggered |
704
+
705
+ ---
706
+
707
+ ## ðŸĪ Contributing
708
+
709
+ Contributions are welcome! Please feel free to submit a Pull Request.
710
+
711
+ ---
712
+
713
+ ## 📄 License
714
+
715
+ MIT License - see [LICENSE](LICENSE) file for details.
716
+
717
+ ---
718
+
719
+ ## ðŸ‘Ī Author
720
+
721
+ **Ghandi Galila**
722
+
723
+ ---
724
+
725
+ ## 🌟 Support
726
+
727
+ If you find this package helpful, please give it a ⭐ on GitHub!
728
+
729
+ ---
730
+
731
+ **Made with âĪïļ for the Vue community**