cynx-lib 1.3.56 → 1.3.58
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 +1 -1
- package/vue/components/Button.vue +39 -8
- package/vue/components/Table.vue +12 -39
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed } from "vue";
|
|
2
|
+
import { computed, useSlots, type VNode } from "vue";
|
|
3
3
|
import Spinner from "./Spinner.vue";
|
|
4
4
|
|
|
5
5
|
const VARIANTS: Record<string, any> = {
|
|
@@ -60,12 +60,22 @@ const VARIANTS: Record<string, any> = {
|
|
|
60
60
|
hoverShadow: "0 2px 8px rgba(249,158,44,.25)",
|
|
61
61
|
},
|
|
62
62
|
ghost: {
|
|
63
|
-
background: "
|
|
63
|
+
background: "transparent",
|
|
64
64
|
color: "var(--t2, #797b8d)",
|
|
65
|
-
border: "1px solid
|
|
65
|
+
border: "1px solid transparent",
|
|
66
66
|
hoverColor: "var(--t1, #1a1a2e)",
|
|
67
67
|
hoverBg: "var(--grey-1, #f5f5f8)",
|
|
68
|
-
hoverBorder: "
|
|
68
|
+
hoverBorder: "transparent",
|
|
69
|
+
boxShadow: "none",
|
|
70
|
+
},
|
|
71
|
+
icon: {
|
|
72
|
+
background: "transparent",
|
|
73
|
+
color: "var(--t2, #797b8d)",
|
|
74
|
+
border: "1px solid transparent",
|
|
75
|
+
hoverColor: "var(--t1, #1a1a2e)",
|
|
76
|
+
hoverBg: "var(--grey-1, #f5f5f8)",
|
|
77
|
+
hoverBorder: "transparent",
|
|
78
|
+
boxShadow: "none",
|
|
69
79
|
},
|
|
70
80
|
outline: {
|
|
71
81
|
background: "transparent",
|
|
@@ -106,6 +116,7 @@ const props = withDefaults(
|
|
|
106
116
|
disabled?: boolean;
|
|
107
117
|
loading?: boolean;
|
|
108
118
|
type?: "button" | "submit" | "reset";
|
|
119
|
+
iconOnly?: boolean;
|
|
109
120
|
}>(),
|
|
110
121
|
{
|
|
111
122
|
variant: "default",
|
|
@@ -113,6 +124,7 @@ const props = withDefaults(
|
|
|
113
124
|
disabled: false,
|
|
114
125
|
loading: false,
|
|
115
126
|
type: "button",
|
|
127
|
+
iconOnly: false,
|
|
116
128
|
}
|
|
117
129
|
);
|
|
118
130
|
|
|
@@ -120,16 +132,34 @@ const emit = defineEmits<{
|
|
|
120
132
|
(e: "click", event: MouseEvent): void;
|
|
121
133
|
}>();
|
|
122
134
|
|
|
135
|
+
const slots = useSlots();
|
|
123
136
|
const v = computed(() => VARIANTS[props.variant] || VARIANTS.default);
|
|
124
137
|
const s = computed(() => SIZES[props.size] || SIZES.md);
|
|
125
|
-
|
|
138
|
+
|
|
139
|
+
const hasDefaultText = computed(() => {
|
|
140
|
+
if (!slots.default) return false;
|
|
141
|
+
const nodes = slots.default();
|
|
142
|
+
if (!nodes || nodes.length === 0) return false;
|
|
143
|
+
return nodes.some((node: VNode) => {
|
|
144
|
+
if (node.type === Symbol.for("v-cmt")) return false;
|
|
145
|
+
if (typeof node.children === "string") return node.children.trim().length > 0;
|
|
146
|
+
if (Array.isArray(node.children)) return node.children.length > 0;
|
|
147
|
+
return Boolean(node.type);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const isIconOnly = computed(() => {
|
|
152
|
+
if (props.iconOnly || props.variant === "icon") return true;
|
|
153
|
+
const hasIconSlot = Boolean(slots.icon) || Boolean(slots.iconRight);
|
|
154
|
+
return hasIconSlot && !hasDefaultText.value;
|
|
155
|
+
});
|
|
126
156
|
</script>
|
|
127
157
|
|
|
128
158
|
<template>
|
|
129
159
|
<button
|
|
130
160
|
:type="type"
|
|
131
161
|
:disabled="disabled || loading"
|
|
132
|
-
:class="[`btn btn-${variant}`, 'inline-flex items-center justify-center gap-1.5 font-medium transition select-none']"
|
|
162
|
+
:class="[`btn btn-${variant}`, 'inline-flex items-center justify-center gap-1.5 font-medium transition select-none flex-shrink-0']"
|
|
133
163
|
:style="{
|
|
134
164
|
borderRadius: 'var(--rads, 8px)',
|
|
135
165
|
fontFamily: 'var(--f, var(--sans, inherit))',
|
|
@@ -137,7 +167,7 @@ const isIconOnly = computed(() => props.variant === "icon");
|
|
|
137
167
|
whiteSpace: 'nowrap',
|
|
138
168
|
letterSpacing: '-.01em',
|
|
139
169
|
outline: 'none',
|
|
140
|
-
boxShadow: 'var(--shadow, 0 1px 3px rgba(20,22,41,.1))',
|
|
170
|
+
boxShadow: v.boxShadow !== undefined ? v.boxShadow : 'var(--shadow, 0 1px 3px rgba(20,22,41,.1))',
|
|
141
171
|
opacity: disabled ? 0.45 : 1,
|
|
142
172
|
pointerEvents: disabled ? 'none' : undefined,
|
|
143
173
|
background: v.background,
|
|
@@ -147,9 +177,10 @@ const isIconOnly = computed(() => props.variant === "icon");
|
|
|
147
177
|
fontSize: `${s.fontSize}px`,
|
|
148
178
|
height: `${s.height}px`,
|
|
149
179
|
width: isIconOnly ? `${s.height}px` : undefined,
|
|
180
|
+
minWidth: isIconOnly ? `${s.height}px` : undefined,
|
|
181
|
+
maxWidth: isIconOnly ? `${s.height}px` : undefined,
|
|
150
182
|
...(v.paddingLeft !== undefined && { paddingLeft: v.paddingLeft }),
|
|
151
183
|
...(v.paddingRight !== undefined && { paddingRight: v.paddingRight }),
|
|
152
|
-
...(v.boxShadow !== undefined && { boxShadow: v.boxShadow }),
|
|
153
184
|
}"
|
|
154
185
|
@click="emit('click', $event)"
|
|
155
186
|
>
|
package/vue/components/Table.vue
CHANGED
|
@@ -52,7 +52,7 @@ const props = withDefaults(
|
|
|
52
52
|
bordered: true,
|
|
53
53
|
skeletonRows: undefined,
|
|
54
54
|
autoSkeleton: true,
|
|
55
|
-
rowHeight:
|
|
55
|
+
rowHeight: 40,
|
|
56
56
|
}
|
|
57
57
|
);
|
|
58
58
|
|
|
@@ -62,18 +62,11 @@ const emit = defineEmits<{
|
|
|
62
62
|
}>();
|
|
63
63
|
|
|
64
64
|
const rootRef = ref<HTMLElement | null>(null);
|
|
65
|
-
const headerScrollRef = ref<HTMLElement | null>(null);
|
|
66
65
|
const bodyScrollRef = ref<HTMLElement | null>(null);
|
|
67
66
|
|
|
68
|
-
const handleBodyScroll = () => {
|
|
69
|
-
if (headerScrollRef.value && bodyScrollRef.value) {
|
|
70
|
-
headerScrollRef.value.scrollLeft = bodyScrollRef.value.scrollLeft;
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
|
|
74
67
|
const { rows: autoRows } = useAutoSkeletonRows(rootRef, {
|
|
75
68
|
rowHeight: props.rowHeight,
|
|
76
|
-
bottomBuffer:
|
|
69
|
+
bottomBuffer: 50,
|
|
77
70
|
});
|
|
78
71
|
|
|
79
72
|
const skeletonCount = computed(() => {
|
|
@@ -133,23 +126,23 @@ const getSkeletonWidth = (rIdx: number, cIdx: number, key: string) => {
|
|
|
133
126
|
]"
|
|
134
127
|
:style="height ? { height: formatWidth(height) } : undefined"
|
|
135
128
|
>
|
|
136
|
-
<!--
|
|
129
|
+
<!-- Scrollable Single Unified Table Container (100% Column Alignment) -->
|
|
137
130
|
<div
|
|
138
|
-
ref="
|
|
139
|
-
class="w-full overflow-
|
|
131
|
+
ref="bodyScrollRef"
|
|
132
|
+
class="w-full overflow-x-auto overflow-y-auto custom-scrollbar flex-1 relative"
|
|
140
133
|
>
|
|
141
134
|
<table class="w-full border-collapse text-left text-xs table-fixed">
|
|
142
135
|
<colgroup>
|
|
143
136
|
<col
|
|
144
137
|
v-for="col in columns"
|
|
145
|
-
:key="`
|
|
138
|
+
:key="`col-${col.key}`"
|
|
146
139
|
:style="{
|
|
147
140
|
width: formatWidth(col.width),
|
|
148
141
|
minWidth: formatWidth(col.minWidth || col.width || 100),
|
|
149
142
|
}"
|
|
150
143
|
/>
|
|
151
144
|
</colgroup>
|
|
152
|
-
<thead>
|
|
145
|
+
<thead class="sticky top-0 z-10 bg-[#f8fafc] dark:bg-[#16192e] border-b border-slate-200 dark:border-slate-800 shadow-[0_1px_0_0_rgba(226,232,240,1)] dark:shadow-[0_1px_0_0_rgba(30,41,59,1)]">
|
|
153
146
|
<tr>
|
|
154
147
|
<th
|
|
155
148
|
v-for="col in columns"
|
|
@@ -160,7 +153,7 @@ const getSkeletonWidth = (rIdx: number, cIdx: number, key: string) => {
|
|
|
160
153
|
textAlign: col.align || 'left',
|
|
161
154
|
}"
|
|
162
155
|
:class="[
|
|
163
|
-
'px-
|
|
156
|
+
'px-3.5 py-2.5 font-semibold text-[11px] uppercase tracking-wider text-slate-500 dark:text-slate-400 select-none transition-colors bg-[#f8fafc] dark:bg-[#16192e]',
|
|
164
157
|
col.sortable ? 'cursor-pointer hover:text-slate-900 dark:hover:text-white' : '',
|
|
165
158
|
col.headerClass || ''
|
|
166
159
|
]"
|
|
@@ -200,28 +193,8 @@ const getSkeletonWidth = (rIdx: number, cIdx: number, key: string) => {
|
|
|
200
193
|
</th>
|
|
201
194
|
</tr>
|
|
202
195
|
</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
196
|
<tbody class="divide-y divide-slate-100 dark:divide-slate-800/80">
|
|
224
|
-
<!-- Loading State (
|
|
197
|
+
<!-- Loading State (Real matching heights and paddings) -->
|
|
225
198
|
<template v-if="loading">
|
|
226
199
|
<tr
|
|
227
200
|
v-for="i in skeletonCount"
|
|
@@ -235,7 +208,7 @@ const getSkeletonWidth = (rIdx: number, cIdx: number, key: string) => {
|
|
|
235
208
|
textAlign: col.align || 'left',
|
|
236
209
|
height: `${rowHeight}px`,
|
|
237
210
|
}"
|
|
238
|
-
class="px-
|
|
211
|
+
class="px-3.5 py-2 align-middle"
|
|
239
212
|
>
|
|
240
213
|
<!-- Action button skeleton -->
|
|
241
214
|
<div v-if="col.key === 'actions'" class="flex items-center justify-center">
|
|
@@ -251,7 +224,7 @@ const getSkeletonWidth = (rIdx: number, cIdx: number, key: string) => {
|
|
|
251
224
|
<Skeleton
|
|
252
225
|
v-else
|
|
253
226
|
:width="getSkeletonWidth(i, cIdx, col.key)"
|
|
254
|
-
:height="
|
|
227
|
+
:height="14"
|
|
255
228
|
:radius="4"
|
|
256
229
|
/>
|
|
257
230
|
</td>
|
|
@@ -291,7 +264,7 @@ const getSkeletonWidth = (rIdx: number, cIdx: number, key: string) => {
|
|
|
291
264
|
height: `${rowHeight}px`,
|
|
292
265
|
}"
|
|
293
266
|
:class="[
|
|
294
|
-
'px-
|
|
267
|
+
'px-3.5 py-2 text-slate-700 dark:text-slate-200 text-xs font-normal align-middle',
|
|
295
268
|
col.cellClass || ''
|
|
296
269
|
]"
|
|
297
270
|
>
|