@imaginario27/air-ui-ds 1.2.3 → 1.2.5
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/components/badges/Badge.vue +19 -5
- package/components/breadcrumbs/Breadcrumbs.vue +13 -2
- package/components/buttons/ActionButton.vue +40 -9
- package/components/drawers/Drawer.vue +244 -0
- package/components/forms/fields/FileUploadField.vue +32 -17
- package/components/tables/TableHeaderCell.vue +47 -9
- package/models/enums/directions.ts +6 -0
- package/models/enums/tables.ts +4 -0
- package/package.json +1 -1
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
:class="[
|
|
4
|
-
'inline-flex
|
|
3
|
+
:class="[
|
|
4
|
+
'inline-flex',
|
|
5
|
+
'items-center',
|
|
6
|
+
'gap-2',
|
|
7
|
+
'font-semibold',
|
|
8
|
+
'text-xs',
|
|
9
|
+
'px-2',
|
|
10
|
+
'h-[24px]',
|
|
11
|
+
'w-fit',
|
|
5
12
|
styleClass,
|
|
6
13
|
shapeClass,
|
|
7
14
|
colorClass,
|
|
8
15
|
borderColorClass,
|
|
9
16
|
isTransparent ? 'bg-transparent' : undefined,
|
|
10
|
-
showDot ? 'pl-3' : undefined
|
|
17
|
+
showDot ? 'pl-3' : undefined,
|
|
11
18
|
]"
|
|
12
19
|
>
|
|
13
20
|
<!-- Dot -->
|
|
@@ -25,7 +32,14 @@
|
|
|
25
32
|
/>
|
|
26
33
|
|
|
27
34
|
<!-- Text -->
|
|
28
|
-
<span
|
|
35
|
+
<span
|
|
36
|
+
:class="[
|
|
37
|
+
'pt-0.25',
|
|
38
|
+
textClass,
|
|
39
|
+
]"
|
|
40
|
+
>
|
|
41
|
+
{{ text }}
|
|
42
|
+
</span>
|
|
29
43
|
|
|
30
44
|
<!-- Close button -->
|
|
31
45
|
<div
|
|
@@ -195,7 +209,7 @@ const iconColorClass = computed(() => {
|
|
|
195
209
|
})
|
|
196
210
|
|
|
197
211
|
const dotColorClass = computed(() => {
|
|
198
|
-
if (props.styleType === BadgeStyle.FILLED) return "bg-text-on-filled"
|
|
212
|
+
if (props.styleType === BadgeStyle.FILLED) return "bg-text-neutral-on-filled"
|
|
199
213
|
|
|
200
214
|
const dotVariant: Record<ColorAccent, string> = {
|
|
201
215
|
[ColorAccent.NEUTRAL]: "bg-icon-neutral-subtle",
|
|
@@ -75,6 +75,10 @@ const props = defineProps({
|
|
|
75
75
|
type: Boolean as PropType<boolean>,
|
|
76
76
|
default: false,
|
|
77
77
|
},
|
|
78
|
+
customRoute: {
|
|
79
|
+
type: String as PropType<string | undefined | null>,
|
|
80
|
+
default: null,
|
|
81
|
+
},
|
|
78
82
|
homeIconClass: {
|
|
79
83
|
type: String as PropType<string>,
|
|
80
84
|
default: '',
|
|
@@ -96,9 +100,16 @@ const props = defineProps({
|
|
|
96
100
|
// Route
|
|
97
101
|
const route = useRoute()
|
|
98
102
|
|
|
99
|
-
//
|
|
103
|
+
// Decide which path to use
|
|
104
|
+
const basePath = computed(() => {
|
|
105
|
+
return props.customRoute ?? route.path
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// Generate all breadcrumbs from selected path
|
|
100
109
|
const allCrumbs = computed(() => {
|
|
101
|
-
const pathSegments =
|
|
110
|
+
const pathSegments = basePath.value
|
|
111
|
+
.split('/')
|
|
112
|
+
.filter(Boolean)
|
|
102
113
|
|
|
103
114
|
return pathSegments.map((segment, index) => {
|
|
104
115
|
return {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
/>
|
|
33
33
|
</div>
|
|
34
34
|
|
|
35
|
-
<span :class="['font-semibold', textSizeClass, textClass]">
|
|
35
|
+
<span :class="['font-semibold', textSizeClass, textClass, textTopSpacingClass]">
|
|
36
36
|
{{ loadingText }}
|
|
37
37
|
</span>
|
|
38
38
|
</template>
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
/>
|
|
53
53
|
</template>
|
|
54
54
|
|
|
55
|
-
<span :class="['font-semibold', textSizeClass, textClass]">
|
|
55
|
+
<span :class="['font-semibold', textSizeClass, textClass, textTopSpacingClass]">
|
|
56
56
|
{{ text }}
|
|
57
57
|
</span>
|
|
58
58
|
|
|
@@ -330,14 +330,37 @@ const iconColorClass = computed(() => {
|
|
|
330
330
|
})
|
|
331
331
|
|
|
332
332
|
const horizontalPaddingClass = computed(() => {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
333
|
+
let variant
|
|
334
|
+
|
|
335
|
+
if (props.iconPosition === IconPosition.LEFT) {
|
|
336
|
+
variant = {
|
|
337
|
+
[ButtonSize.XS]: 'pl-3 pr-4',
|
|
338
|
+
[ButtonSize.SM]: 'pl-3 pr-4',
|
|
339
|
+
[ButtonSize.MD]: 'pl-4 pr-5',
|
|
340
|
+
[ButtonSize.LG]: 'pl-4 pr-5',
|
|
341
|
+
[ButtonSize.XL]: 'pl-4 pr-5',
|
|
342
|
+
[ButtonSize.XXL]: 'pl-4 pr-5',
|
|
343
|
+
}
|
|
344
|
+
} else if (props.iconPosition === IconPosition.RIGHT) {
|
|
345
|
+
variant = {
|
|
346
|
+
[ButtonSize.XS]: 'pl-4 pr-3',
|
|
347
|
+
[ButtonSize.SM]: 'pl-4 pr-3',
|
|
348
|
+
[ButtonSize.MD]: 'pl-5 pr-4',
|
|
349
|
+
[ButtonSize.LG]: 'pl-5 pr-4',
|
|
350
|
+
[ButtonSize.XL]: 'pl-5 pr-4',
|
|
351
|
+
[ButtonSize.XXL]: 'pl-5 pr-4',
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
variant = {
|
|
355
|
+
[ButtonSize.XS]: 'px-3',
|
|
356
|
+
[ButtonSize.SM]: 'px-3',
|
|
357
|
+
[ButtonSize.MD]: 'px-4',
|
|
358
|
+
[ButtonSize.LG]: 'px-4',
|
|
359
|
+
[ButtonSize.XL]: 'px-4',
|
|
360
|
+
[ButtonSize.XXL]: 'px-4',
|
|
361
|
+
}
|
|
340
362
|
}
|
|
363
|
+
|
|
341
364
|
return variant[props.size as ButtonSize] || 'px-3'
|
|
342
365
|
})
|
|
343
366
|
|
|
@@ -350,9 +373,17 @@ const gapClass = computed(() => {
|
|
|
350
373
|
[ButtonSize.XL]: 'gap-2',
|
|
351
374
|
[ButtonSize.XXL]: 'gap-2',
|
|
352
375
|
}
|
|
376
|
+
|
|
353
377
|
return variant[props.size as ButtonSize] || 'gap-2'
|
|
354
378
|
})
|
|
355
379
|
|
|
380
|
+
|
|
381
|
+
const textTopSpacingClass = computed(() => {
|
|
382
|
+
return props.size === ButtonSize.XXL
|
|
383
|
+
? undefined
|
|
384
|
+
: 'pt-0.25'
|
|
385
|
+
})
|
|
386
|
+
|
|
356
387
|
// Props for the dynamic component
|
|
357
388
|
const componentProps = computed(() => {
|
|
358
389
|
if (props.actionType === ButtonActionType.LINK) {
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- Overlay -->
|
|
3
|
+
<Transition
|
|
4
|
+
enter-active-class="transition-opacity duration-200 ease-out"
|
|
5
|
+
enter-from-class="opacity-0"
|
|
6
|
+
enter-to-class="opacity-100"
|
|
7
|
+
leave-active-class="transition-opacity duration-200 ease-in"
|
|
8
|
+
leave-from-class="opacity-100"
|
|
9
|
+
leave-to-class="opacity-0"
|
|
10
|
+
>
|
|
11
|
+
<div
|
|
12
|
+
v-if="isOpen && hasOverlay"
|
|
13
|
+
:class="[
|
|
14
|
+
'fixed',
|
|
15
|
+
'inset-0',
|
|
16
|
+
'bg-background-overlay',
|
|
17
|
+
'backdrop-blur-sm',
|
|
18
|
+
'z-[10000]',
|
|
19
|
+
overlayClass,
|
|
20
|
+
]"
|
|
21
|
+
@click="closeOnOverlayClick ? close() : null"
|
|
22
|
+
/>
|
|
23
|
+
</Transition>
|
|
24
|
+
|
|
25
|
+
<!-- Drawer -->
|
|
26
|
+
<Transition
|
|
27
|
+
:enter-active-class="transitionClasses.enterActive"
|
|
28
|
+
:enter-from-class="transitionClasses.enterFrom"
|
|
29
|
+
:enter-to-class="transitionClasses.enterTo"
|
|
30
|
+
:leave-active-class="transitionClasses.leaveActive"
|
|
31
|
+
:leave-from-class="transitionClasses.leaveFrom"
|
|
32
|
+
:leave-to-class="transitionClasses.leaveTo"
|
|
33
|
+
>
|
|
34
|
+
<aside
|
|
35
|
+
v-if="isOpen"
|
|
36
|
+
:class="[
|
|
37
|
+
'fixed',
|
|
38
|
+
'bg-background-container-surface',
|
|
39
|
+
'shadow-xl',
|
|
40
|
+
'z-[10000]',
|
|
41
|
+
'p-4',
|
|
42
|
+
'flex',
|
|
43
|
+
'flex-col',
|
|
44
|
+
'gap-4',
|
|
45
|
+
positionClasses,
|
|
46
|
+
sizeClasses,
|
|
47
|
+
drawerClass,
|
|
48
|
+
borderClass,
|
|
49
|
+
]"
|
|
50
|
+
:style="drawerInlineStyle"
|
|
51
|
+
>
|
|
52
|
+
<!-- Header -->
|
|
53
|
+
<div
|
|
54
|
+
v-if="hasHeader"
|
|
55
|
+
:class="[
|
|
56
|
+
'flex',
|
|
57
|
+
'items-center',
|
|
58
|
+
'justify-between',
|
|
59
|
+
]"
|
|
60
|
+
>
|
|
61
|
+
<component
|
|
62
|
+
:is="titleHeadingTag"
|
|
63
|
+
class="text-lg font-semibold"
|
|
64
|
+
>
|
|
65
|
+
{{ title }}
|
|
66
|
+
</component>
|
|
67
|
+
|
|
68
|
+
<ActionIconButton
|
|
69
|
+
v-if="hasCloseButton"
|
|
70
|
+
:icon="buttonCloseIcon"
|
|
71
|
+
:styleType="ButtonStyleType.NEUTRAL_TRANSPARENT"
|
|
72
|
+
@click="close"
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<!-- Content -->
|
|
77
|
+
<div class="flex-1 overflow-y-auto">
|
|
78
|
+
<slot />
|
|
79
|
+
</div>
|
|
80
|
+
</aside>
|
|
81
|
+
</Transition>
|
|
82
|
+
</template>
|
|
83
|
+
|
|
84
|
+
<script setup lang="ts">
|
|
85
|
+
// Props
|
|
86
|
+
const props = defineProps({
|
|
87
|
+
modelValue: {
|
|
88
|
+
type: Boolean as PropType<boolean>,
|
|
89
|
+
required: true,
|
|
90
|
+
},
|
|
91
|
+
direction: {
|
|
92
|
+
type: String as PropType<Direction>,
|
|
93
|
+
default: Direction.RIGHT,
|
|
94
|
+
validator: (value: Direction) => Object.values(Direction).includes(value),
|
|
95
|
+
},
|
|
96
|
+
maxSize: {
|
|
97
|
+
type: Number as PropType<number>,
|
|
98
|
+
default: 320,
|
|
99
|
+
},
|
|
100
|
+
hasHeader: {
|
|
101
|
+
type: Boolean as PropType<boolean>,
|
|
102
|
+
default: true,
|
|
103
|
+
},
|
|
104
|
+
hasCloseButton: {
|
|
105
|
+
type: Boolean as PropType<boolean>,
|
|
106
|
+
default: true,
|
|
107
|
+
},
|
|
108
|
+
hasOverlay: {
|
|
109
|
+
type: Boolean as PropType<boolean>,
|
|
110
|
+
default: true,
|
|
111
|
+
},
|
|
112
|
+
closeOnOverlayClick: {
|
|
113
|
+
type: Boolean as PropType<boolean>,
|
|
114
|
+
default: true,
|
|
115
|
+
},
|
|
116
|
+
title: {
|
|
117
|
+
type: String as PropType<string>,
|
|
118
|
+
default: 'Drawer',
|
|
119
|
+
},
|
|
120
|
+
titleHeadingTag: {
|
|
121
|
+
type: String as PropType<'h2' | 'h3' | 'h4' | 'h5' | 'h6'>,
|
|
122
|
+
default: 'h2',
|
|
123
|
+
},
|
|
124
|
+
buttonCloseIcon: {
|
|
125
|
+
type: String as PropType<string>,
|
|
126
|
+
default: 'mdi:close',
|
|
127
|
+
},
|
|
128
|
+
hasBorder: {
|
|
129
|
+
type: Boolean as PropType<boolean>,
|
|
130
|
+
default: true,
|
|
131
|
+
},
|
|
132
|
+
drawerClass: String as PropType<string>,
|
|
133
|
+
overlayClass: String as PropType<string>,
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
// Emits
|
|
137
|
+
const emit = defineEmits(['update:modelValue'])
|
|
138
|
+
|
|
139
|
+
const isOpen = computed(() => props.modelValue)
|
|
140
|
+
|
|
141
|
+
// Computed classes
|
|
142
|
+
const positionClasses = computed(() => {
|
|
143
|
+
const map: Record<Direction, string[]> = {
|
|
144
|
+
[Direction.RIGHT]: ['top-0', 'right-0', 'h-full'],
|
|
145
|
+
[Direction.LEFT]: ['top-0', 'left-0', 'h-full'],
|
|
146
|
+
[Direction.TOP]: ['top-0', 'left-0', 'w-full'],
|
|
147
|
+
[Direction.BOTTOM]: ['bottom-0', 'left-0', 'w-full'],
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return map[props.direction]
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const sizeClasses = computed(() => {
|
|
154
|
+
if (props.direction === Direction.LEFT || props.direction === Direction.RIGHT) {
|
|
155
|
+
return ['w-full']
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return []
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
const drawerInlineStyle = computed(() => {
|
|
162
|
+
if (props.direction === Direction.LEFT || props.direction === Direction.RIGHT) {
|
|
163
|
+
return { maxWidth: props.maxSize + 'px' }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { maxHeight: props.maxSize + 'px' }
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
const transitionClasses = computed(() => {
|
|
170
|
+
const base = 'transform transition-transform duration-300 ease-out'
|
|
171
|
+
const leaveBase = 'transform transition-transform duration-300 ease-in'
|
|
172
|
+
|
|
173
|
+
const map: Record<
|
|
174
|
+
Direction,
|
|
175
|
+
{
|
|
176
|
+
enterActive: string
|
|
177
|
+
enterFrom: string
|
|
178
|
+
enterTo: string
|
|
179
|
+
leaveActive: string
|
|
180
|
+
leaveFrom: string
|
|
181
|
+
leaveTo: string
|
|
182
|
+
}
|
|
183
|
+
> = {
|
|
184
|
+
[Direction.RIGHT]: {
|
|
185
|
+
enterActive: base,
|
|
186
|
+
enterFrom: 'translate-x-full',
|
|
187
|
+
enterTo: 'translate-x-0',
|
|
188
|
+
leaveActive: leaveBase,
|
|
189
|
+
leaveFrom: 'translate-x-0',
|
|
190
|
+
leaveTo: 'translate-x-full',
|
|
191
|
+
},
|
|
192
|
+
[Direction.LEFT]: {
|
|
193
|
+
enterActive: base,
|
|
194
|
+
enterFrom: '-translate-x-full',
|
|
195
|
+
enterTo: 'translate-x-0',
|
|
196
|
+
leaveActive: leaveBase,
|
|
197
|
+
leaveFrom: 'translate-x-0',
|
|
198
|
+
leaveTo: '-translate-x-full',
|
|
199
|
+
},
|
|
200
|
+
[Direction.TOP]: {
|
|
201
|
+
enterActive: base,
|
|
202
|
+
enterFrom: '-translate-y-full',
|
|
203
|
+
enterTo: 'translate-y-0',
|
|
204
|
+
leaveActive: leaveBase,
|
|
205
|
+
leaveFrom: 'translate-y-0',
|
|
206
|
+
leaveTo: '-translate-y-full',
|
|
207
|
+
},
|
|
208
|
+
[Direction.BOTTOM]: {
|
|
209
|
+
enterActive: base,
|
|
210
|
+
enterFrom: 'translate-y-full',
|
|
211
|
+
enterTo: 'translate-y-0',
|
|
212
|
+
leaveActive: leaveBase,
|
|
213
|
+
leaveFrom: 'translate-y-0',
|
|
214
|
+
leaveTo: 'translate-y-full',
|
|
215
|
+
},
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return map[props.direction]
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
const borderClass = computed(() => {
|
|
222
|
+
if (!props.hasBorder) return ''
|
|
223
|
+
|
|
224
|
+
const colorClass = 'border-border-default'
|
|
225
|
+
|
|
226
|
+
switch (props.direction) {
|
|
227
|
+
case Direction.RIGHT:
|
|
228
|
+
return `border-l ${colorClass}`
|
|
229
|
+
case Direction.LEFT:
|
|
230
|
+
return `border-r ${colorClass}`
|
|
231
|
+
case Direction.TOP:
|
|
232
|
+
return `border-b ${colorClass}`
|
|
233
|
+
case Direction.BOTTOM:
|
|
234
|
+
return `border-t ${colorClass}`
|
|
235
|
+
default:
|
|
236
|
+
return ''
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
// Handlers
|
|
241
|
+
const close = () => {
|
|
242
|
+
emit('update:modelValue', false)
|
|
243
|
+
}
|
|
244
|
+
</script>
|
|
@@ -60,7 +60,10 @@
|
|
|
60
60
|
</p>
|
|
61
61
|
</template>
|
|
62
62
|
<template #button="{ fileInput }">
|
|
63
|
-
<div
|
|
63
|
+
<div
|
|
64
|
+
class="w-full flex justify-center my-4"
|
|
65
|
+
@click.stop
|
|
66
|
+
>
|
|
64
67
|
<ActionButton
|
|
65
68
|
:text="computedButtonText"
|
|
66
69
|
:styleType="ButtonStyleType.PRIMARY_BRAND_SOFT"
|
|
@@ -201,25 +204,41 @@ const acceptedFileTypes = computed(() => {
|
|
|
201
204
|
})
|
|
202
205
|
|
|
203
206
|
const computedTitleText = computed(() => {
|
|
204
|
-
|
|
205
|
-
|
|
207
|
+
const isReplacingExisting =
|
|
208
|
+
props.showPreview &&
|
|
209
|
+
props.previewImageUrl &&
|
|
210
|
+
selectedFiles.value.length === 0
|
|
211
|
+
|
|
212
|
+
if (isReplacingExisting) {
|
|
213
|
+
return props.title ?? 'Upload a new file to replace current one'
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (props.title) {
|
|
217
|
+
return props.title
|
|
206
218
|
}
|
|
207
219
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
} else return props.title
|
|
220
|
+
return props.multiple
|
|
221
|
+
? 'Drag & drop files here or click to upload'
|
|
222
|
+
: 'Drag & drop a file here or click to upload'
|
|
212
223
|
})
|
|
213
224
|
|
|
214
225
|
const computedButtonText = computed(() => {
|
|
215
|
-
|
|
216
|
-
|
|
226
|
+
const isReplacingExisting =
|
|
227
|
+
props.showPreview &&
|
|
228
|
+
props.previewImageUrl &&
|
|
229
|
+
selectedFiles.value.length === 0
|
|
230
|
+
|
|
231
|
+
if (isReplacingExisting) {
|
|
232
|
+
return props.buttonText ?? 'Replace file'
|
|
217
233
|
}
|
|
218
234
|
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
235
|
+
if (props.buttonText) {
|
|
236
|
+
return props.buttonText
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return props.multiple
|
|
240
|
+
? 'Upload files'
|
|
241
|
+
: 'Upload a file'
|
|
223
242
|
})
|
|
224
243
|
|
|
225
244
|
const selectedFiles = computed({
|
|
@@ -230,10 +249,6 @@ const selectedFiles = computed({
|
|
|
230
249
|
if (props.required) {
|
|
231
250
|
runValidation()
|
|
232
251
|
}
|
|
233
|
-
|
|
234
|
-
if (newFiles.length === 0) {
|
|
235
|
-
dropzoneKey.value++ // This forces Vue3Dropzone to re-render
|
|
236
|
-
}
|
|
237
252
|
},
|
|
238
253
|
})
|
|
239
254
|
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<th
|
|
3
|
+
:scope
|
|
3
4
|
:class="[
|
|
4
|
-
|
|
5
|
-
'py-3.5',
|
|
6
|
-
'border-b',
|
|
7
|
-
'border-border-neutral-subtle',
|
|
8
|
-
'font-semibold',
|
|
9
|
-
'text-sm',
|
|
10
|
-
'whitespace-nowrap',
|
|
5
|
+
...headerClass
|
|
11
6
|
]"
|
|
7
|
+
@click="scope === TableHeaderCellScope.ROW && handleNavigation()"
|
|
12
8
|
>
|
|
13
9
|
<div class="w-full flex items-center gap-2">
|
|
14
10
|
<slot />
|
|
15
11
|
|
|
16
12
|
<ActionIconButton
|
|
17
|
-
v-if="sorteable"
|
|
13
|
+
v-if="sorteable && scope === TableHeaderCellScope.COL"
|
|
18
14
|
:size="ButtonSize.XS"
|
|
19
15
|
:icon="sortAsc ? 'mdi:arrow-up' : 'mdi:arrow-down'"
|
|
20
16
|
:styleType="sortKey === columnKey ? ButtonStyleType.NEUTRAL_FILLED : ButtonStyleType.NEUTRAL_OUTLINED"
|
|
@@ -25,7 +21,12 @@
|
|
|
25
21
|
</template>
|
|
26
22
|
<script setup lang="ts">
|
|
27
23
|
// Props
|
|
28
|
-
defineProps({
|
|
24
|
+
const props = defineProps({
|
|
25
|
+
scope : {
|
|
26
|
+
type: String as PropType<TableHeaderCellScope>,
|
|
27
|
+
default: TableHeaderCellScope.COL,
|
|
28
|
+
validator: (value: TableHeaderCellScope) => Object.values(TableHeaderCellScope).includes(value),
|
|
29
|
+
},
|
|
29
30
|
sorteable: {
|
|
30
31
|
type: Boolean as PropType<boolean>,
|
|
31
32
|
default: false,
|
|
@@ -46,5 +47,42 @@ defineProps({
|
|
|
46
47
|
type: Function as PropType<() => void>,
|
|
47
48
|
default: undefined,
|
|
48
49
|
},
|
|
50
|
+
fitToContent: {
|
|
51
|
+
type: Boolean as PropType<boolean>,
|
|
52
|
+
default: false,
|
|
53
|
+
},
|
|
54
|
+
to: String as PropType<string>,
|
|
49
55
|
})
|
|
56
|
+
|
|
57
|
+
// Computed classes
|
|
58
|
+
const headerClass = computed(() => {
|
|
59
|
+
const variants = {
|
|
60
|
+
[TableHeaderCellScope.COL]: [
|
|
61
|
+
'px-3',
|
|
62
|
+
'py-3.5',
|
|
63
|
+
'border-b',
|
|
64
|
+
'border-border-neutral-subtle',
|
|
65
|
+
'font-semibold',
|
|
66
|
+
'text-sm',
|
|
67
|
+
'whitespace-nowrap',
|
|
68
|
+
],
|
|
69
|
+
[TableHeaderCellScope.ROW]: [
|
|
70
|
+
'px-3',
|
|
71
|
+
'py-3.5',
|
|
72
|
+
'border-t',
|
|
73
|
+
'border-border-neutral-subtle',
|
|
74
|
+
'text-sm',
|
|
75
|
+
props.fitToContent ? 'w-[1%]' : 'w-auto',
|
|
76
|
+
props.to ? 'hover:cursor-pointer' : undefined
|
|
77
|
+
],
|
|
78
|
+
}
|
|
79
|
+
return variants[props.scope as TableHeaderCellScope] || 'rounded'
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Navigation handler
|
|
83
|
+
const handleNavigation = () => {
|
|
84
|
+
if (props.to) {
|
|
85
|
+
navigateTo(props.to)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
50
88
|
</script>
|