@kikiloaw/simple-table 1.0.2 → 1.0.3
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 +301 -0
- package/package.json +1 -1
- package/src/SimpleTable.vue +41 -3
- package/src/components/table/TableCell.vue +13 -2
- package/src/components/table/TableHead.vue +6 -2
package/README.md
CHANGED
|
@@ -87,6 +87,307 @@ public function getData(Request $request)
|
|
|
87
87
|
|
|
88
88
|
---
|
|
89
89
|
|
|
90
|
+
## 📦 Using Predefined/Static Data
|
|
91
|
+
|
|
92
|
+
**Want to use static data instead of an API?** SimpleTable handles this perfectly in client-side mode!
|
|
93
|
+
|
|
94
|
+
### Basic Setup
|
|
95
|
+
|
|
96
|
+
```vue
|
|
97
|
+
<script setup lang="ts">
|
|
98
|
+
import SimpleTable from '@kikiloaw/simple-table'
|
|
99
|
+
|
|
100
|
+
// Define your static data
|
|
101
|
+
const data = [
|
|
102
|
+
{ id: 1, name: 'John Doe', email: 'john@example.com', status: 'active' },
|
|
103
|
+
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', status: 'active' },
|
|
104
|
+
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', status: 'inactive' },
|
|
105
|
+
// ... more rows
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
const columns = [
|
|
109
|
+
{ key: 'id', label: '#', sortable: true, width: '80px' },
|
|
110
|
+
{ key: 'name', label: 'Name', sortable: true },
|
|
111
|
+
{ key: 'email', label: 'Email', sortable: true },
|
|
112
|
+
{ key: 'status', label: 'Status', width: '120px' }
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
const pageSizes = [
|
|
116
|
+
{ label: '50 Rows', value: 50 },
|
|
117
|
+
{ label: '100 Rows', value: 100 },
|
|
118
|
+
]
|
|
119
|
+
</script>
|
|
120
|
+
|
|
121
|
+
<template>
|
|
122
|
+
<SimpleTable
|
|
123
|
+
:data="data"
|
|
124
|
+
:columns="columns"
|
|
125
|
+
:page-sizes="pageSizes"
|
|
126
|
+
:per-page="50"
|
|
127
|
+
mode="client"
|
|
128
|
+
searchable
|
|
129
|
+
/>
|
|
130
|
+
</template>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Key Props for Static Data
|
|
134
|
+
|
|
135
|
+
| Prop | Required | Default | Description |
|
|
136
|
+
|------|----------|---------|-------------|
|
|
137
|
+
| `:data` | **Yes** | `[]` | Your static array of objects |
|
|
138
|
+
| `mode` | **Yes** | `'auto'` | Set to `"client"` for static data |
|
|
139
|
+
| `:per-page` | Recommended | `10` | Initial page size (should match first option in pageSizes) |
|
|
140
|
+
| `:page-sizes` | Optional | `[10,20,30,50,100]` | Available page size options |
|
|
141
|
+
|
|
142
|
+
### ⚠️ Common Pitfalls
|
|
143
|
+
|
|
144
|
+
#### 1. **Don't Mix Static Data with `fetch-url`**
|
|
145
|
+
|
|
146
|
+
❌ **Wrong:**
|
|
147
|
+
```vue
|
|
148
|
+
<!-- This will ignore your static data! -->
|
|
149
|
+
<SimpleTable
|
|
150
|
+
:data="myData"
|
|
151
|
+
fetch-url="/api/users" <!-- ❌ Conflicts with :data -->
|
|
152
|
+
/>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
✅ **Correct:**
|
|
156
|
+
```vue
|
|
157
|
+
<!-- Remove fetch-url when using static data -->
|
|
158
|
+
<SimpleTable
|
|
159
|
+
:data="myData"
|
|
160
|
+
mode="client"
|
|
161
|
+
/>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### 2. **Set Initial Page Size to Match Your Options**
|
|
165
|
+
|
|
166
|
+
❌ **Wrong:**
|
|
167
|
+
```vue
|
|
168
|
+
<!-- Component defaults to 10, but you only have 50/100 options -->
|
|
169
|
+
<SimpleTable
|
|
170
|
+
:data="data"
|
|
171
|
+
:page-sizes="[{ label: '50 Rows', value: 50 }, { label: '100 Rows', value: 100 }]"
|
|
172
|
+
<!-- ❌ Will show "10 Rows" which doesn't exist in dropdown -->
|
|
173
|
+
/>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
✅ **Correct:**
|
|
177
|
+
```vue
|
|
178
|
+
<SimpleTable
|
|
179
|
+
:data="data"
|
|
180
|
+
:page-sizes="[50, 100]"
|
|
181
|
+
:per-page="50" <!-- ✅ Matches first option -->
|
|
182
|
+
/>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
#### 3. **Columns Must Match Your Data Structure**
|
|
186
|
+
|
|
187
|
+
❌ **Wrong:**
|
|
188
|
+
```vue
|
|
189
|
+
<script setup>
|
|
190
|
+
const data = [
|
|
191
|
+
{ CourseCode: 'CS101', Description: 'Intro to CS', Units: 3 }
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
const columns = [
|
|
195
|
+
{ key: 'course_code', label: 'Code' }, // ❌ Wrong key!
|
|
196
|
+
{ key: 'description', label: 'Name' }, // ❌ Wrong key!
|
|
197
|
+
]
|
|
198
|
+
</script>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
✅ **Correct:**
|
|
202
|
+
```vue
|
|
203
|
+
<script setup>
|
|
204
|
+
const data = [
|
|
205
|
+
{ CourseCode: 'CS101', Description: 'Intro to CS', Units: 3 }
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
const columns = [
|
|
209
|
+
{ key: 'CourseCode', label: 'Code' }, // ✅ Matches data
|
|
210
|
+
{ key: 'Description', label: 'Name' }, // ✅ Matches data
|
|
211
|
+
{ key: 'Units', label: 'Units' },
|
|
212
|
+
]
|
|
213
|
+
</script>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Features Available in Client-Side Mode
|
|
217
|
+
|
|
218
|
+
✅ **Works:**
|
|
219
|
+
- Client-side searching (filters through your data array)
|
|
220
|
+
- Client-side sorting (by sortable columns)
|
|
221
|
+
- Client-side pagination (chunks your data into pages)
|
|
222
|
+
- Data transformation via `beforeRender`
|
|
223
|
+
- Custom cell rendering
|
|
224
|
+
- Group headers
|
|
225
|
+
- Auto-numbering
|
|
226
|
+
|
|
227
|
+
❌ **Not Available:**
|
|
228
|
+
- Server-side sorting (data is sorted locally)
|
|
229
|
+
- API caching (no API calls)
|
|
230
|
+
- Query parameters (no server to send them to)
|
|
231
|
+
|
|
232
|
+
### Complete Example with Group Headers
|
|
233
|
+
|
|
234
|
+
```vue
|
|
235
|
+
<script setup lang="ts">
|
|
236
|
+
import SimpleTable from '@kikiloaw/simple-table'
|
|
237
|
+
|
|
238
|
+
// Static course data
|
|
239
|
+
const data = [
|
|
240
|
+
{
|
|
241
|
+
CourseCode: 'CS101',
|
|
242
|
+
Description: 'Intro to Computer Science',
|
|
243
|
+
Units: 3,
|
|
244
|
+
Grade: 'A',
|
|
245
|
+
semester: '1st Semester, 2023-2024'
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
CourseCode: 'MATH101',
|
|
249
|
+
Description: 'Calculus I',
|
|
250
|
+
Units: 4,
|
|
251
|
+
Grade: 'B+',
|
|
252
|
+
semester: '1st Semester, 2023-2024'
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
CourseCode: 'CS102',
|
|
256
|
+
Description: 'Data Structures',
|
|
257
|
+
Units: 3,
|
|
258
|
+
Grade: 'A-',
|
|
259
|
+
semester: '2nd Semester, 2023-2024'
|
|
260
|
+
},
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
const columns = [
|
|
264
|
+
{ key: 'CourseCode', label: 'Course Code', sortable: true, width: '150px' },
|
|
265
|
+
{ key: 'Description', label: 'Description', sortable: true, width: '300px' },
|
|
266
|
+
{ key: 'Units', label: 'Units', width: '80px' },
|
|
267
|
+
{ key: 'Grade', label: 'Grade', sortable: true, width: '80px' },
|
|
268
|
+
]
|
|
269
|
+
|
|
270
|
+
// Add group headers by semester
|
|
271
|
+
const addGroupHeaders = (rows) => {
|
|
272
|
+
// DON'T sort here if your data is already in the correct order!
|
|
273
|
+
// Sorting will override your predefined order
|
|
274
|
+
|
|
275
|
+
const result = []
|
|
276
|
+
let currentSemester = null
|
|
277
|
+
|
|
278
|
+
rows.forEach(row => {
|
|
279
|
+
const semester = row.semester || 'No Semester'
|
|
280
|
+
|
|
281
|
+
// When semester changes, add a header row
|
|
282
|
+
if (semester !== currentSemester) {
|
|
283
|
+
result.push({
|
|
284
|
+
_isGroupHeader: true,
|
|
285
|
+
_groupTitle: semester,
|
|
286
|
+
// Empty values for all columns
|
|
287
|
+
CourseCode: '',
|
|
288
|
+
Description: semester,
|
|
289
|
+
Units: '',
|
|
290
|
+
Grade: '',
|
|
291
|
+
})
|
|
292
|
+
currentSemester = semester
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
result.push(row)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
return result
|
|
299
|
+
}
|
|
300
|
+
</script>
|
|
301
|
+
|
|
302
|
+
<template>
|
|
303
|
+
<SimpleTable
|
|
304
|
+
:data="data"
|
|
305
|
+
:columns="columns"
|
|
306
|
+
:page-sizes="[50, 100]"
|
|
307
|
+
:per-page="50"
|
|
308
|
+
:before-render="addGroupHeaders"
|
|
309
|
+
mode="client"
|
|
310
|
+
searchable
|
|
311
|
+
odd-row-color="bg-gray-50"
|
|
312
|
+
even-row-color="bg-white"
|
|
313
|
+
hover-color="hover:bg-green-100"
|
|
314
|
+
/>
|
|
315
|
+
</template>
|
|
316
|
+
|
|
317
|
+
### 📏 Row Height Control
|
|
318
|
+
|
|
319
|
+
Control the exact height of your table rows with the `rowHeight` prop:
|
|
320
|
+
|
|
321
|
+
```vue
|
|
322
|
+
<template>
|
|
323
|
+
<SimpleTable
|
|
324
|
+
:data="data"
|
|
325
|
+
:columns="columns"
|
|
326
|
+
:row-height="38" <!-- Rows will be exactly 38px tall -->
|
|
327
|
+
/>
|
|
328
|
+
</template>
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**How it works:**
|
|
332
|
+
- Sets the `height` style on both header and data rows
|
|
333
|
+
- Automatically adjusts cell padding based on the height
|
|
334
|
+
- Default: `38px` (compact and readable)
|
|
335
|
+
|
|
336
|
+
**Recommended Values:**
|
|
337
|
+
|
|
338
|
+
| Height | Padding | Use Case |
|
|
339
|
+
|--------|---------|----------|
|
|
340
|
+
| `30-36px` | `p-2` (8px) | **Extra compact** - Maximum rows visible, dense data |
|
|
341
|
+
| `38-42px` | `p-2` (8px) | **Standard** - Good balance (default: 38px) |
|
|
342
|
+
| `44-55px` | `p-3` (12px) | **Comfortable** - Easy to read, spacious |
|
|
343
|
+
| `56px+` | `p-4` (16px) | **Very spacious** - Accessibility-friendly, large text |
|
|
344
|
+
|
|
345
|
+
**Examples:**
|
|
346
|
+
|
|
347
|
+
```vue
|
|
348
|
+
<!-- Ultra compact for dashboards -->
|
|
349
|
+
<SimpleTable :row-height="32" />
|
|
350
|
+
|
|
351
|
+
<!-- Default - balanced -->
|
|
352
|
+
<SimpleTable :row-height="38" /> <!-- or omit for default -->
|
|
353
|
+
|
|
354
|
+
<!-- Comfortable reading -->
|
|
355
|
+
<SimpleTable :row-height="48" />
|
|
356
|
+
|
|
357
|
+
<!-- Accessibility-friendly -->
|
|
358
|
+
<SimpleTable :row-height="60" />
|
|
359
|
+
|
|
360
|
+
<!-- No prop = uses default 38px -->
|
|
361
|
+
<SimpleTable :data="data" :columns="columns" />
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**Padding Auto-Adjustment:**
|
|
365
|
+
|
|
366
|
+
The component automatically adjusts internal padding:
|
|
367
|
+
- **< 44px**: Uses `p-2` (8px) - Compact
|
|
368
|
+
- **44-55px**: Uses `p-3` (12px) - Normal
|
|
369
|
+
- **56px+**: Uses `p-4` (16px) - Comfortable
|
|
370
|
+
|
|
371
|
+
### 💡 Best Practices
|
|
372
|
+
|
|
373
|
+
1. **Data Order Preservation**
|
|
374
|
+
- If your data is already sorted correctly, **don't sort it again** in `beforeRender`
|
|
375
|
+
- Let users sort by clicking column headers if needed
|
|
376
|
+
|
|
377
|
+
2. **Performance**
|
|
378
|
+
- Client-side mode works great for **< 1,000 rows**
|
|
379
|
+
- For larger datasets, consider server-side mode with `fetch-url`
|
|
380
|
+
|
|
381
|
+
3. **Reactivity**
|
|
382
|
+
- Use `ref()` or `reactive()` if your data changes
|
|
383
|
+
- The table will automatically update when data changes
|
|
384
|
+
|
|
385
|
+
4. **Page Size Options**
|
|
386
|
+
- Keep options reasonable: `[10, 25, 50, 100]`
|
|
387
|
+
- Set `:per-page` to match your first option
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
90
391
|
## 📖 Table of Contents
|
|
91
392
|
|
|
92
393
|
- [Core Concepts](#-core-concepts)
|
package/package.json
CHANGED
package/src/SimpleTable.vue
CHANGED
|
@@ -45,6 +45,7 @@ interface Props {
|
|
|
45
45
|
queryParams?: Record<string, any> // Additional parameters to send with every request (e.g., filters, user context)
|
|
46
46
|
|
|
47
47
|
// Style Props
|
|
48
|
+
rowHeight?: number // Table row height in pixels (default: 38)
|
|
48
49
|
oddRowColor?: string // Tailwind color class, e.g. 'bg-white'
|
|
49
50
|
evenRowColor?: string // Tailwind color class, e.g. 'bg-gray-50'
|
|
50
51
|
hoverColor?: string // Tailwind color class for hover, e.g. 'hover:bg-gray-100'. If passed, we'll try to apply group-hover for fixed cols.
|
|
@@ -60,6 +61,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
60
61
|
queryParams: () => ({}),
|
|
61
62
|
perPage: 10,
|
|
62
63
|
pageSizes: () => [10, 20, 30, 50, 100],
|
|
64
|
+
rowHeight: 38,
|
|
63
65
|
oddRowColor: 'bg-background',
|
|
64
66
|
evenRowColor: 'bg-background',
|
|
65
67
|
hoverColor: 'hover:bg-muted/50'
|
|
@@ -88,6 +90,37 @@ const normalizedPageSizes = computed(() => {
|
|
|
88
90
|
return []
|
|
89
91
|
})
|
|
90
92
|
|
|
93
|
+
// -- Computed: Row height-based sizing --
|
|
94
|
+
const densityConfig = computed(() => {
|
|
95
|
+
const height = props.rowHeight || 38
|
|
96
|
+
|
|
97
|
+
// Calculate padding based on height
|
|
98
|
+
// For 38px height: use p-2 (8px)
|
|
99
|
+
// For 48px height: use p-3 (12px)
|
|
100
|
+
// For 56px+ height: use p-4 (16px)
|
|
101
|
+
let cellPadding = 'p-2'
|
|
102
|
+
let headerPadding = 'px-2'
|
|
103
|
+
let groupHeaderPadding = 'py-1'
|
|
104
|
+
|
|
105
|
+
if (height >= 56) {
|
|
106
|
+
cellPadding = 'p-4'
|
|
107
|
+
headerPadding = 'px-4'
|
|
108
|
+
groupHeaderPadding = 'py-2'
|
|
109
|
+
} else if (height >= 44) {
|
|
110
|
+
cellPadding = 'p-3'
|
|
111
|
+
headerPadding = 'px-3'
|
|
112
|
+
groupHeaderPadding = 'py-1.5'
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
cellPadding,
|
|
117
|
+
cellHeight: `${height}px`,
|
|
118
|
+
headerHeight: `h-[${height}px]`,
|
|
119
|
+
headerPadding,
|
|
120
|
+
groupHeaderPadding
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
91
124
|
|
|
92
125
|
|
|
93
126
|
// <template>
|
|
@@ -722,12 +755,14 @@ function getCellStyle(col: any) {
|
|
|
722
755
|
<!-- We add min-w-full to Table to ensure it stretches -->
|
|
723
756
|
<Table class="min-w-full table-fixed">
|
|
724
757
|
<TableHeader>
|
|
725
|
-
<TableRow>
|
|
758
|
+
<TableRow :style="{ height: densityConfig.cellHeight }">
|
|
726
759
|
<TableHead
|
|
727
760
|
v-for="(col, idx) in columns"
|
|
728
761
|
:key="col.key"
|
|
729
762
|
:class="getCellClass(col, idx, columns.length)"
|
|
730
763
|
:style="getCellStyle(col)"
|
|
764
|
+
:height="densityConfig.headerHeight"
|
|
765
|
+
:padding="densityConfig.headerPadding"
|
|
731
766
|
>
|
|
732
767
|
<div
|
|
733
768
|
v-if="col.sortable"
|
|
@@ -757,11 +792,12 @@ function getCellStyle(col: any) {
|
|
|
757
792
|
:key="idx"
|
|
758
793
|
class="group"
|
|
759
794
|
:class="getRowClass(row, idx)"
|
|
795
|
+
:style="{ height: densityConfig.cellHeight }"
|
|
760
796
|
>
|
|
761
797
|
<!-- Group Header Row: Single cell spanning all columns -->
|
|
762
798
|
<template v-if="row._isGroupHeader">
|
|
763
799
|
<TableCell :colspan="columns.length" class="border-b border-gray-200">
|
|
764
|
-
<div class="px-2
|
|
800
|
+
<div :class="['px-2', densityConfig.groupHeaderPadding, 'font-semibold text-gray-700 text-sm uppercase tracking-wide']">
|
|
765
801
|
{{ row._groupTitle }}
|
|
766
802
|
</div>
|
|
767
803
|
</TableCell>
|
|
@@ -774,6 +810,8 @@ function getCellStyle(col: any) {
|
|
|
774
810
|
:key="col.key"
|
|
775
811
|
:class="getCellClass(col, cIdx, columns.length, idx)"
|
|
776
812
|
:style="getCellStyle(col)"
|
|
813
|
+
:padding="densityConfig.cellPadding"
|
|
814
|
+
:height="densityConfig.cellHeight"
|
|
777
815
|
>
|
|
778
816
|
<!-- Auto-numbering or custom cell rendering -->
|
|
779
817
|
<div>
|
|
@@ -790,7 +828,7 @@ function getCellStyle(col: any) {
|
|
|
790
828
|
</template>
|
|
791
829
|
</TableRow>
|
|
792
830
|
</template>
|
|
793
|
-
<TableRow v-else>
|
|
831
|
+
<TableRow v-else :style="{ height: densityConfig.cellHeight }">
|
|
794
832
|
<TableCell :colspan="columns.length" class="h-24 text-center">
|
|
795
833
|
No results.
|
|
796
834
|
</TableCell>
|
|
@@ -4,18 +4,29 @@ import { cn } from '@/lib/utils'
|
|
|
4
4
|
|
|
5
5
|
const props = defineProps<{
|
|
6
6
|
class?: HTMLAttributes['class']
|
|
7
|
+
style?: any
|
|
8
|
+
padding?: string
|
|
9
|
+
height?: string
|
|
7
10
|
}>()
|
|
8
11
|
|
|
9
12
|
const delegatedProps = computed(() => {
|
|
10
|
-
const { class: _, ...delegated } = props
|
|
13
|
+
const { class: _, padding: __, height: ___, style: ____, ...delegated } = props
|
|
11
14
|
|
|
12
15
|
return delegated
|
|
13
16
|
})
|
|
17
|
+
|
|
18
|
+
const cellStyle = computed(() => {
|
|
19
|
+
const baseStyle = (props as any).style || {}
|
|
20
|
+
const heightStyle = props.height ? { minHeight: props.height } : {}
|
|
21
|
+
|
|
22
|
+
return { ...baseStyle, ...heightStyle }
|
|
23
|
+
})
|
|
14
24
|
</script>
|
|
15
25
|
|
|
16
26
|
<template>
|
|
17
27
|
<td
|
|
18
|
-
:class="cn('p-
|
|
28
|
+
:class="cn(props.padding || 'p-2', 'align-middle [&:has([role=checkbox])]:pr-0', props.class)"
|
|
29
|
+
:style="cellStyle"
|
|
19
30
|
v-bind="delegatedProps"
|
|
20
31
|
>
|
|
21
32
|
<slot />
|
|
@@ -4,10 +4,12 @@ import { cn } from '@/lib/utils'
|
|
|
4
4
|
|
|
5
5
|
const props = defineProps<{
|
|
6
6
|
class?: HTMLAttributes['class']
|
|
7
|
+
height?: string
|
|
8
|
+
padding?: string
|
|
7
9
|
}>()
|
|
8
10
|
|
|
9
11
|
const delegatedProps = computed(() => {
|
|
10
|
-
const { class: _, ...delegated } = props
|
|
12
|
+
const { class: _, height: __, padding: ___, ...delegated } = props
|
|
11
13
|
|
|
12
14
|
return delegated
|
|
13
15
|
})
|
|
@@ -17,7 +19,9 @@ const delegatedProps = computed(() => {
|
|
|
17
19
|
<th
|
|
18
20
|
:class="
|
|
19
21
|
cn(
|
|
20
|
-
'h-
|
|
22
|
+
props.height || 'h-[38px]',
|
|
23
|
+
props.padding || 'px-2',
|
|
24
|
+
'text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0',
|
|
21
25
|
props.class,
|
|
22
26
|
)
|
|
23
27
|
"
|