cynx-lib 1.3.50 → 1.3.52
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/package.json +6 -1
- package/ui/components/Layout.jsx +138 -831
- package/ui/components/Sidebar.jsx +820 -0
- package/ui/index.cjs +3 -0
- package/ui/index.js +2 -0
- package/utils/utils/db.cjs +40 -2
- package/utils/utils/db.js +43 -2
- package/vue/components/Badge.vue +64 -0
- package/vue/components/Button.vue +161 -0
- package/vue/components/Checkbox.vue +97 -0
- package/vue/components/Input.vue +341 -0
- package/vue/components/Pagination.vue +127 -0
- package/vue/components/Select.vue +755 -0
- package/vue/components/Sidebar.vue +954 -0
- package/vue/components/Skeleton.vue +91 -0
- package/vue/components/Spinner.vue +146 -0
- package/vue/components/TabBar.vue +176 -0
- package/vue/components/Table.vue +323 -0
- package/vue/components/XTable.vue +119 -0
- package/vue/composables/useAutoSkeletonRows.ts +71 -0
- package/vue/index.d.ts +58 -0
- package/vue/index.js +56 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
ChevronUp,
|
|
5
|
+
ChevronDown,
|
|
6
|
+
ArrowUpDown,
|
|
7
|
+
FileText,
|
|
8
|
+
} from "lucide-vue-next";
|
|
9
|
+
import Skeleton from "./Skeleton.vue";
|
|
10
|
+
import { useAutoSkeletonRows } from "../composables/useAutoSkeletonRows";
|
|
11
|
+
|
|
12
|
+
export interface TableColumn {
|
|
13
|
+
key: string;
|
|
14
|
+
title?: string;
|
|
15
|
+
label?: string;
|
|
16
|
+
width?: string | number;
|
|
17
|
+
minWidth?: string | number;
|
|
18
|
+
align?: "left" | "center" | "right";
|
|
19
|
+
sortable?: boolean;
|
|
20
|
+
fixed?: "left" | "right" | boolean;
|
|
21
|
+
headerClass?: string;
|
|
22
|
+
cellClass?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const props = withDefaults(
|
|
26
|
+
defineProps<{
|
|
27
|
+
columns?: TableColumn[];
|
|
28
|
+
data?: any[];
|
|
29
|
+
loading?: boolean;
|
|
30
|
+
rowKey?: string | ((row: any) => string | number);
|
|
31
|
+
sortBy?: string;
|
|
32
|
+
sortOrder?: "asc" | "desc" | string;
|
|
33
|
+
emptyText?: string;
|
|
34
|
+
height?: string | number;
|
|
35
|
+
hoverable?: boolean;
|
|
36
|
+
striped?: boolean;
|
|
37
|
+
bordered?: boolean;
|
|
38
|
+
skeletonRows?: number;
|
|
39
|
+
autoSkeleton?: boolean;
|
|
40
|
+
rowHeight?: number;
|
|
41
|
+
}>(),
|
|
42
|
+
{
|
|
43
|
+
columns: () => [],
|
|
44
|
+
data: () => [],
|
|
45
|
+
loading: false,
|
|
46
|
+
rowKey: "id",
|
|
47
|
+
sortBy: "",
|
|
48
|
+
sortOrder: "",
|
|
49
|
+
emptyText: "No data available",
|
|
50
|
+
hoverable: true,
|
|
51
|
+
striped: false,
|
|
52
|
+
bordered: true,
|
|
53
|
+
skeletonRows: undefined,
|
|
54
|
+
autoSkeleton: true,
|
|
55
|
+
rowHeight: 51,
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const emit = defineEmits<{
|
|
60
|
+
(e: "row-click", row: any, event: MouseEvent): void;
|
|
61
|
+
(e: "sort-change", payload: { key: string; order: "asc" | "desc" | null }): void;
|
|
62
|
+
}>();
|
|
63
|
+
|
|
64
|
+
const rootRef = ref<HTMLElement | null>(null);
|
|
65
|
+
const headerScrollRef = ref<HTMLElement | null>(null);
|
|
66
|
+
const bodyScrollRef = ref<HTMLElement | null>(null);
|
|
67
|
+
|
|
68
|
+
const handleBodyScroll = () => {
|
|
69
|
+
if (headerScrollRef.value && bodyScrollRef.value) {
|
|
70
|
+
headerScrollRef.value.scrollLeft = bodyScrollRef.value.scrollLeft;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const { rows: autoRows } = useAutoSkeletonRows(rootRef, {
|
|
75
|
+
rowHeight: props.rowHeight,
|
|
76
|
+
bottomBuffer: 72,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const skeletonCount = computed(() => {
|
|
80
|
+
if (props.skeletonRows && props.skeletonRows > 0) return props.skeletonRows;
|
|
81
|
+
if (props.autoSkeleton) return autoRows.value;
|
|
82
|
+
return 10;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const getRowKey = (row: any, index: number) => {
|
|
86
|
+
if (typeof props.rowKey === "function") return props.rowKey(row);
|
|
87
|
+
if (typeof props.rowKey === "string" && row[props.rowKey] !== undefined) return row[props.rowKey];
|
|
88
|
+
return index;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const handleSort = (col: TableColumn) => {
|
|
92
|
+
if (!col.sortable) return;
|
|
93
|
+
const isCurrent = props.sortBy === col.key;
|
|
94
|
+
let nextOrder: "asc" | "desc" | null = "asc";
|
|
95
|
+
|
|
96
|
+
if (isCurrent) {
|
|
97
|
+
if (props.sortOrder === "asc") nextOrder = "desc";
|
|
98
|
+
else if (props.sortOrder === "desc") nextOrder = null;
|
|
99
|
+
else nextOrder = "asc";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
emit("sort-change", { key: col.key, order: nextOrder });
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const formatWidth = (val?: string | number) => {
|
|
106
|
+
if (val === undefined || val === null) return undefined;
|
|
107
|
+
if (typeof val === "number") return `${val}px`;
|
|
108
|
+
return val;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const titleWidths = ["68%", "82%", "55%", "75%", "62%", "88%", "70%", "64%"];
|
|
112
|
+
const textWidths = ["80%", "65%", "88%", "72%", "58%", "84%", "62%", "76%"];
|
|
113
|
+
const codeWidths = ["50%", "62%", "45%", "58%", "52%", "60%"];
|
|
114
|
+
|
|
115
|
+
const getSkeletonWidth = (rIdx: number, cIdx: number, key: string) => {
|
|
116
|
+
if (key === "title" || cIdx === 0) {
|
|
117
|
+
return titleWidths[(rIdx + cIdx) % titleWidths.length];
|
|
118
|
+
}
|
|
119
|
+
if (key === "usdot" || key === "phone" || key === "id") {
|
|
120
|
+
return codeWidths[(rIdx + cIdx) % codeWidths.length];
|
|
121
|
+
}
|
|
122
|
+
return textWidths[(rIdx + cIdx) % textWidths.length];
|
|
123
|
+
};
|
|
124
|
+
</script>
|
|
125
|
+
|
|
126
|
+
<template>
|
|
127
|
+
<div
|
|
128
|
+
ref="rootRef"
|
|
129
|
+
class="w-full flex flex-col overflow-hidden transition-colors"
|
|
130
|
+
:class="[
|
|
131
|
+
bordered ? 'border border-slate-200 dark:border-slate-800' : '',
|
|
132
|
+
'bg-white dark:bg-[#141629]'
|
|
133
|
+
]"
|
|
134
|
+
:style="height ? { height: formatWidth(height) } : undefined"
|
|
135
|
+
>
|
|
136
|
+
<!-- Separate Dedicated Fixed Header Container -->
|
|
137
|
+
<div
|
|
138
|
+
ref="headerScrollRef"
|
|
139
|
+
class="w-full overflow-hidden flex-shrink-0 border-b border-slate-200 dark:border-slate-800 bg-[#f8fafc] dark:bg-[#16192e]"
|
|
140
|
+
>
|
|
141
|
+
<table class="w-full border-collapse text-left text-xs table-fixed">
|
|
142
|
+
<colgroup>
|
|
143
|
+
<col
|
|
144
|
+
v-for="col in columns"
|
|
145
|
+
:key="`h-col-${col.key}`"
|
|
146
|
+
:style="{
|
|
147
|
+
width: formatWidth(col.width),
|
|
148
|
+
minWidth: formatWidth(col.minWidth || col.width || 100),
|
|
149
|
+
}"
|
|
150
|
+
/>
|
|
151
|
+
</colgroup>
|
|
152
|
+
<thead>
|
|
153
|
+
<tr>
|
|
154
|
+
<th
|
|
155
|
+
v-for="col in columns"
|
|
156
|
+
:key="col.key"
|
|
157
|
+
:style="{
|
|
158
|
+
width: formatWidth(col.width),
|
|
159
|
+
minWidth: formatWidth(col.minWidth || col.width || 100),
|
|
160
|
+
textAlign: col.align || 'left',
|
|
161
|
+
}"
|
|
162
|
+
:class="[
|
|
163
|
+
'px-4 py-3 font-semibold text-[11px] uppercase tracking-wider text-slate-500 dark:text-slate-400 select-none transition-colors',
|
|
164
|
+
col.sortable ? 'cursor-pointer hover:text-slate-900 dark:hover:text-white' : '',
|
|
165
|
+
col.headerClass || ''
|
|
166
|
+
]"
|
|
167
|
+
@click="handleSort(col)"
|
|
168
|
+
>
|
|
169
|
+
<div
|
|
170
|
+
class="flex items-center gap-1.5"
|
|
171
|
+
:class="{
|
|
172
|
+
'justify-start': !col.align || col.align === 'left',
|
|
173
|
+
'justify-center': col.align === 'center',
|
|
174
|
+
'justify-end': col.align === 'right',
|
|
175
|
+
}"
|
|
176
|
+
>
|
|
177
|
+
<slot :name="`header-${col.key}`" :column="col">
|
|
178
|
+
<span>{{ col.title || col.label || col.key }}</span>
|
|
179
|
+
</slot>
|
|
180
|
+
|
|
181
|
+
<!-- Sort indicator icon -->
|
|
182
|
+
<span v-if="col.sortable" class="inline-flex items-center text-slate-400">
|
|
183
|
+
<ChevronUp
|
|
184
|
+
v-if="sortBy === col.key && sortOrder === 'asc'"
|
|
185
|
+
:size="13"
|
|
186
|
+
class="text-[#12675D] dark:text-teal-400"
|
|
187
|
+
/>
|
|
188
|
+
<ChevronDown
|
|
189
|
+
v-else-if="sortBy === col.key && sortOrder === 'desc'"
|
|
190
|
+
:size="13"
|
|
191
|
+
class="text-[#12675D] dark:text-teal-400"
|
|
192
|
+
/>
|
|
193
|
+
<ArrowUpDown
|
|
194
|
+
v-else
|
|
195
|
+
:size="11"
|
|
196
|
+
class="opacity-40 hover:opacity-100 transition-opacity"
|
|
197
|
+
/>
|
|
198
|
+
</span>
|
|
199
|
+
</div>
|
|
200
|
+
</th>
|
|
201
|
+
</tr>
|
|
202
|
+
</thead>
|
|
203
|
+
</table>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<!-- Scrollable Body Container -->
|
|
207
|
+
<div
|
|
208
|
+
ref="bodyScrollRef"
|
|
209
|
+
class="w-full overflow-x-auto overflow-y-auto custom-scrollbar flex-1 relative"
|
|
210
|
+
@scroll="handleBodyScroll"
|
|
211
|
+
>
|
|
212
|
+
<table class="w-full border-collapse text-left text-xs table-fixed">
|
|
213
|
+
<colgroup>
|
|
214
|
+
<col
|
|
215
|
+
v-for="col in columns"
|
|
216
|
+
:key="`b-col-${col.key}`"
|
|
217
|
+
:style="{
|
|
218
|
+
width: formatWidth(col.width),
|
|
219
|
+
minWidth: formatWidth(col.minWidth || col.width || 100),
|
|
220
|
+
}"
|
|
221
|
+
/>
|
|
222
|
+
</colgroup>
|
|
223
|
+
<tbody class="divide-y divide-slate-100 dark:divide-slate-800/80">
|
|
224
|
+
<!-- Loading State (Content-Aware Professional Shimmer) -->
|
|
225
|
+
<template v-if="loading">
|
|
226
|
+
<tr
|
|
227
|
+
v-for="i in skeletonCount"
|
|
228
|
+
:key="`sk-${i}`"
|
|
229
|
+
:style="{ height: `${rowHeight}px` }"
|
|
230
|
+
>
|
|
231
|
+
<td
|
|
232
|
+
v-for="(col, cIdx) in columns"
|
|
233
|
+
:key="`sk-c-${col.key}`"
|
|
234
|
+
:style="{
|
|
235
|
+
textAlign: col.align || 'left',
|
|
236
|
+
height: `${rowHeight}px`,
|
|
237
|
+
}"
|
|
238
|
+
class="px-4 py-3 align-middle"
|
|
239
|
+
>
|
|
240
|
+
<!-- Action button skeleton -->
|
|
241
|
+
<div v-if="col.key === 'actions'" class="flex items-center justify-center">
|
|
242
|
+
<Skeleton :width="28" :height="28" :radius="6" />
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<!-- Centered badge skeleton -->
|
|
246
|
+
<div v-else-if="col.align === 'center'" class="flex items-center justify-center">
|
|
247
|
+
<Skeleton :width="44" :height="20" :radius="10" />
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<!-- Text / Column skeleton -->
|
|
251
|
+
<Skeleton
|
|
252
|
+
v-else
|
|
253
|
+
:width="getSkeletonWidth(i, cIdx, col.key)"
|
|
254
|
+
:height="col.key === 'title' || cIdx === 0 ? 16 : 14"
|
|
255
|
+
:radius="4"
|
|
256
|
+
/>
|
|
257
|
+
</td>
|
|
258
|
+
</tr>
|
|
259
|
+
</template>
|
|
260
|
+
|
|
261
|
+
<!-- Empty State -->
|
|
262
|
+
<tr v-else-if="!data || data.length === 0">
|
|
263
|
+
<td :colspan="columns.length || 1" class="py-16 text-center text-slate-400 dark:text-slate-500">
|
|
264
|
+
<slot name="empty">
|
|
265
|
+
<div class="flex flex-col items-center justify-center gap-2">
|
|
266
|
+
<FileText :size="32" class="opacity-30" />
|
|
267
|
+
<span class="text-xs">{{ emptyText }}</span>
|
|
268
|
+
</div>
|
|
269
|
+
</slot>
|
|
270
|
+
</td>
|
|
271
|
+
</tr>
|
|
272
|
+
|
|
273
|
+
<!-- Data Rows -->
|
|
274
|
+
<template v-else>
|
|
275
|
+
<tr
|
|
276
|
+
v-for="(row, rIdx) in data"
|
|
277
|
+
:key="getRowKey(row, rIdx)"
|
|
278
|
+
:style="{ height: `${rowHeight}px` }"
|
|
279
|
+
:class="[
|
|
280
|
+
'transition-colors duration-100',
|
|
281
|
+
hoverable ? 'hover:bg-slate-50/80 dark:hover:bg-slate-800/50 cursor-pointer' : '',
|
|
282
|
+
striped && rIdx % 2 === 1 ? 'bg-slate-50/40 dark:bg-slate-900/30' : '',
|
|
283
|
+
]"
|
|
284
|
+
@click="emit('row-click', row, $event)"
|
|
285
|
+
>
|
|
286
|
+
<td
|
|
287
|
+
v-for="col in columns"
|
|
288
|
+
:key="col.key"
|
|
289
|
+
:style="{
|
|
290
|
+
textAlign: col.align || 'left',
|
|
291
|
+
height: `${rowHeight}px`,
|
|
292
|
+
}"
|
|
293
|
+
:class="[
|
|
294
|
+
'px-4 py-3 text-slate-700 dark:text-slate-200 text-xs font-normal align-middle',
|
|
295
|
+
col.cellClass || ''
|
|
296
|
+
]"
|
|
297
|
+
>
|
|
298
|
+
<!-- Dedicated Column Cell Slot (e.g. #cell-title, #cell-actions) or default slot -->
|
|
299
|
+
<slot
|
|
300
|
+
:name="`cell-${col.key}`"
|
|
301
|
+
:row="row"
|
|
302
|
+
:column="col"
|
|
303
|
+
:index="rIdx"
|
|
304
|
+
:value="row[col.key]"
|
|
305
|
+
>
|
|
306
|
+
<slot
|
|
307
|
+
:name="col.key"
|
|
308
|
+
:row="row"
|
|
309
|
+
:column="col"
|
|
310
|
+
:index="rIdx"
|
|
311
|
+
:value="row[col.key]"
|
|
312
|
+
>
|
|
313
|
+
<span>{{ row[col.key] != null && row[col.key] !== '' ? row[col.key] : '-' }}</span>
|
|
314
|
+
</slot>
|
|
315
|
+
</slot>
|
|
316
|
+
</td>
|
|
317
|
+
</tr>
|
|
318
|
+
</template>
|
|
319
|
+
</tbody>
|
|
320
|
+
</table>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
</template>
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { defineComponent, h } from "vue";
|
|
3
|
+
|
|
4
|
+
export const XTable = defineComponent({
|
|
5
|
+
name: "XTable",
|
|
6
|
+
props: {
|
|
7
|
+
style: { type: Object, default: () => ({}) },
|
|
8
|
+
},
|
|
9
|
+
setup(props, { slots }) {
|
|
10
|
+
return () =>
|
|
11
|
+
h(
|
|
12
|
+
"table",
|
|
13
|
+
{
|
|
14
|
+
style: {
|
|
15
|
+
width: "100%",
|
|
16
|
+
borderCollapse: "collapse",
|
|
17
|
+
tableLayout: "fixed",
|
|
18
|
+
fontSize: ".8rem",
|
|
19
|
+
...props.style,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
slots.default?.()
|
|
23
|
+
);
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const XThead = defineComponent({
|
|
28
|
+
name: "XThead",
|
|
29
|
+
setup(_, { slots }) {
|
|
30
|
+
return () => h("thead", null, slots.default?.());
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const XTbody = defineComponent({
|
|
35
|
+
name: "XTbody",
|
|
36
|
+
setup(_, { slots }) {
|
|
37
|
+
return () => h("tbody", null, slots.default?.());
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const XTr = defineComponent({
|
|
42
|
+
name: "XTr",
|
|
43
|
+
props: {
|
|
44
|
+
style: { type: Object, default: () => ({}) },
|
|
45
|
+
onClick: { type: Function, default: undefined },
|
|
46
|
+
},
|
|
47
|
+
setup(props, { slots }) {
|
|
48
|
+
return () =>
|
|
49
|
+
h(
|
|
50
|
+
"tr",
|
|
51
|
+
{
|
|
52
|
+
onClick: props.onClick,
|
|
53
|
+
style: {
|
|
54
|
+
borderBottom: "1px solid var(--border-light, #ebebf0)",
|
|
55
|
+
transition: "background .12s",
|
|
56
|
+
cursor: props.onClick ? "pointer" : undefined,
|
|
57
|
+
...props.style,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
slots.default?.()
|
|
61
|
+
);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export const XTh = defineComponent({
|
|
66
|
+
name: "XTh",
|
|
67
|
+
props: {
|
|
68
|
+
style: { type: Object, default: () => ({}) },
|
|
69
|
+
},
|
|
70
|
+
setup(props, { slots }) {
|
|
71
|
+
return () =>
|
|
72
|
+
h(
|
|
73
|
+
"th",
|
|
74
|
+
{
|
|
75
|
+
style: {
|
|
76
|
+
textAlign: "left",
|
|
77
|
+
padding: "8px 12px",
|
|
78
|
+
fontSize: ".68rem",
|
|
79
|
+
fontWeight: 600,
|
|
80
|
+
textTransform: "uppercase",
|
|
81
|
+
letterSpacing: ".06em",
|
|
82
|
+
color: "var(--muted, #9b9daa)",
|
|
83
|
+
borderBottom: "1px solid var(--border-light, #ebebf0)",
|
|
84
|
+
...props.style,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
slots.default?.()
|
|
88
|
+
);
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export const XTd = defineComponent({
|
|
93
|
+
name: "XTd",
|
|
94
|
+
props: {
|
|
95
|
+
style: { type: Object, default: () => ({}) },
|
|
96
|
+
colSpan: { type: Number, default: undefined },
|
|
97
|
+
className: { type: String, default: "" },
|
|
98
|
+
},
|
|
99
|
+
setup(props, { slots }) {
|
|
100
|
+
return () =>
|
|
101
|
+
h(
|
|
102
|
+
"td",
|
|
103
|
+
{
|
|
104
|
+
colSpan: props.colSpan,
|
|
105
|
+
class: props.className,
|
|
106
|
+
style: {
|
|
107
|
+
padding: "9px 12px",
|
|
108
|
+
verticalAlign: "middle",
|
|
109
|
+
color: "var(--primary-text, #0f111c)",
|
|
110
|
+
...props.style,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
slots.default?.()
|
|
114
|
+
);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
export default XTable;
|
|
119
|
+
</script>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ref, onMounted, onUnmounted, nextTick, type Ref } from "vue";
|
|
2
|
+
|
|
3
|
+
export interface UseAutoSkeletonRowsOptions {
|
|
4
|
+
rowHeight?: number;
|
|
5
|
+
initialOffset?: number;
|
|
6
|
+
bottomBuffer?: number;
|
|
7
|
+
minRows?: number;
|
|
8
|
+
maxRows?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useAutoSkeletonRows(
|
|
12
|
+
containerRef: Ref<HTMLElement | null | undefined>,
|
|
13
|
+
options: UseAutoSkeletonRowsOptions = {}
|
|
14
|
+
) {
|
|
15
|
+
const {
|
|
16
|
+
rowHeight = 51,
|
|
17
|
+
initialOffset = 240,
|
|
18
|
+
bottomBuffer = 72,
|
|
19
|
+
minRows = 4,
|
|
20
|
+
maxRows = 30,
|
|
21
|
+
} = options;
|
|
22
|
+
|
|
23
|
+
const rows = ref<number>(
|
|
24
|
+
typeof window !== "undefined"
|
|
25
|
+
? Math.max(minRows, Math.floor((window.innerHeight - initialOffset) / rowHeight))
|
|
26
|
+
: 10
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const calculate = () => {
|
|
30
|
+
if (typeof window === "undefined") return;
|
|
31
|
+
const el = containerRef.value;
|
|
32
|
+
if (!el) {
|
|
33
|
+
const calculated = Math.floor((window.innerHeight - initialOffset) / rowHeight);
|
|
34
|
+
rows.value = Math.max(minRows, Math.min(maxRows, calculated));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const rect = el.getBoundingClientRect();
|
|
39
|
+
const thead = el.querySelector("thead");
|
|
40
|
+
const theadHeight = thead ? thead.offsetHeight : 40;
|
|
41
|
+
|
|
42
|
+
const containerHeight = el.clientHeight;
|
|
43
|
+
let availableHeight = 0;
|
|
44
|
+
|
|
45
|
+
if (containerHeight && containerHeight > theadHeight + 100) {
|
|
46
|
+
availableHeight = containerHeight - theadHeight - 12;
|
|
47
|
+
} else {
|
|
48
|
+
availableHeight = window.innerHeight - rect.top - theadHeight - bottomBuffer;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (availableHeight > 0) {
|
|
52
|
+
const calculated = Math.floor(availableHeight / rowHeight);
|
|
53
|
+
rows.value = Math.max(minRows, Math.min(maxRows, calculated));
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
onMounted(() => {
|
|
58
|
+
calculate();
|
|
59
|
+
nextTick(calculate);
|
|
60
|
+
window.addEventListener("resize", calculate);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
onUnmounted(() => {
|
|
64
|
+
window.removeEventListener("resize", calculate);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
rows,
|
|
69
|
+
calculate,
|
|
70
|
+
};
|
|
71
|
+
}
|
package/vue/index.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { DefineComponent, Ref } from "vue";
|
|
2
|
+
|
|
3
|
+
export interface TabItem {
|
|
4
|
+
id: string | number;
|
|
5
|
+
label?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
icon?: any;
|
|
8
|
+
badge?: string;
|
|
9
|
+
count?: number;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface TableColumn {
|
|
14
|
+
key: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
label?: string;
|
|
17
|
+
width?: string | number;
|
|
18
|
+
minWidth?: string | number;
|
|
19
|
+
align?: "left" | "center" | "right";
|
|
20
|
+
sortable?: boolean;
|
|
21
|
+
fixed?: "left" | "right" | boolean;
|
|
22
|
+
headerClass?: string;
|
|
23
|
+
cellClass?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface UseAutoSkeletonRowsOptions {
|
|
27
|
+
rowHeight?: number;
|
|
28
|
+
initialOffset?: number;
|
|
29
|
+
bottomBuffer?: number;
|
|
30
|
+
minRows?: number;
|
|
31
|
+
maxRows?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export declare function useAutoSkeletonRows(
|
|
35
|
+
containerRef: Ref<HTMLElement | null | undefined>,
|
|
36
|
+
options?: UseAutoSkeletonRowsOptions
|
|
37
|
+
): {
|
|
38
|
+
rows: Ref<number>;
|
|
39
|
+
calculate: () => void;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export declare const Button: DefineComponent<any, any, any>;
|
|
43
|
+
export declare const Checkbox: DefineComponent<any, any, any>;
|
|
44
|
+
export declare const TabBar: DefineComponent<any, any, any>;
|
|
45
|
+
export declare const Input: DefineComponent<any, any, any>;
|
|
46
|
+
export declare const Select: DefineComponent<any, any, any>;
|
|
47
|
+
export declare const Badge: DefineComponent<any, any, any>;
|
|
48
|
+
export declare const Spinner: DefineComponent<any, any, any>;
|
|
49
|
+
export declare const Sidebar: DefineComponent<any, any, any>;
|
|
50
|
+
export declare const Table: DefineComponent<any, any, any>;
|
|
51
|
+
export declare const Pagination: DefineComponent<any, any, any>;
|
|
52
|
+
export declare const Skeleton: DefineComponent<any, any, any>;
|
|
53
|
+
export declare const XTable: DefineComponent<any, any, any>;
|
|
54
|
+
export declare const XThead: DefineComponent<any, any, any>;
|
|
55
|
+
export declare const XTbody: DefineComponent<any, any, any>;
|
|
56
|
+
export declare const XTr: DefineComponent<any, any, any>;
|
|
57
|
+
export declare const XTh: DefineComponent<any, any, any>;
|
|
58
|
+
export declare const XTd: DefineComponent<any, any, any>;
|
package/vue/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// cynx-lib/vue — Native Vue 3 component exports
|
|
2
|
+
import Button from "./components/Button.vue";
|
|
3
|
+
import Checkbox from "./components/Checkbox.vue";
|
|
4
|
+
import TabBar from "./components/TabBar.vue";
|
|
5
|
+
import Input from "./components/Input.vue";
|
|
6
|
+
import Select from "./components/Select.vue";
|
|
7
|
+
import Badge from "./components/Badge.vue";
|
|
8
|
+
import Spinner from "./components/Spinner.vue";
|
|
9
|
+
import Sidebar from "./components/Sidebar.vue";
|
|
10
|
+
import Table from "./components/Table.vue";
|
|
11
|
+
import Pagination from "./components/Pagination.vue";
|
|
12
|
+
import Skeleton from "./components/Skeleton.vue";
|
|
13
|
+
import { XTable, XThead, XTbody, XTr, XTh, XTd } from "./components/XTable.vue";
|
|
14
|
+
import { useAutoSkeletonRows } from "./composables/useAutoSkeletonRows";
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
Button,
|
|
18
|
+
Checkbox,
|
|
19
|
+
TabBar,
|
|
20
|
+
Input,
|
|
21
|
+
Select,
|
|
22
|
+
Badge,
|
|
23
|
+
Spinner,
|
|
24
|
+
Sidebar,
|
|
25
|
+
Table,
|
|
26
|
+
Pagination,
|
|
27
|
+
Skeleton,
|
|
28
|
+
XTable,
|
|
29
|
+
XThead,
|
|
30
|
+
XTbody,
|
|
31
|
+
XTr,
|
|
32
|
+
XTh,
|
|
33
|
+
XTd,
|
|
34
|
+
useAutoSkeletonRows,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default {
|
|
38
|
+
Button,
|
|
39
|
+
Checkbox,
|
|
40
|
+
TabBar,
|
|
41
|
+
Input,
|
|
42
|
+
Select,
|
|
43
|
+
Badge,
|
|
44
|
+
Spinner,
|
|
45
|
+
Sidebar,
|
|
46
|
+
Table,
|
|
47
|
+
Pagination,
|
|
48
|
+
Skeleton,
|
|
49
|
+
XTable,
|
|
50
|
+
XThead,
|
|
51
|
+
XTbody,
|
|
52
|
+
XTr,
|
|
53
|
+
XTh,
|
|
54
|
+
XTd,
|
|
55
|
+
useAutoSkeletonRows,
|
|
56
|
+
};
|