pgo-ui 1.0.76 → 1.0.78
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/dist/Radio-CuaoA4ip.js +4 -0
- package/dist/{index-BgeiUi3s.js → index-DXUeDVX5.js} +8203 -7972
- package/dist/index.es.js +33 -28
- package/dist/index.umd.js +46 -46
- package/dist/pgo-ui.css +1 -1
- package/package.json +1 -1
- package/src/App.vue +1 -2
- package/src/assets/fonts/MVFaseyha.woff2 +0 -0
- package/src/components/examples/FormExample.vue +13 -0
- package/src/components/pgo/AppBar.vue +44 -4
- package/src/components/pgo/CopyTextBox copy.vue +187 -0
- package/src/components/pgo/CopyTextBox.vue +10 -30
- package/src/components/pgo/{DataTable copy.vue → DataTable copy 2.vue } +13 -0
- package/src/components/pgo/DataTable.vue +128 -31
- package/src/components/pgo/HeroIcon.vue +82 -50
- package/src/components/pgo/forms/DynamicForm.vue +1 -0
- package/src/components/pgo/inputs/Select.vue +2 -3
- package/src/index.js +15 -0
- package/src/pgo-components/assets/fonts/MVFaseyha.woff2 +0 -0
- package/src/pgo-components/lib/core/fontSize/fontSize.ts +3 -0
- package/src/pgo-components/lib/core/fontSize/setFontScale.ts +30 -0
- package/src/pgo-components/pages/ComponentRenderer.vue +21 -14
- package/src/pgo-components/services/cform.json +1 -117
- package/src/pgo-components/styles/global.css +2 -1
- package/src/pgo-components/styles/reset.css +3 -2
- package/dist/Radio-BMsJLVIa.js +0 -4
- /package/src/pgo-components/assets/fonts/{Faruma.ttf → _original_Faruma.ttf} +0 -0
package/package.json
CHANGED
package/src/App.vue
CHANGED
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
fixed
|
|
19
19
|
:dense="isDense"
|
|
20
20
|
elevation="4"
|
|
21
|
-
II
|
|
22
21
|
border="border-b"
|
|
23
22
|
p="px-2"
|
|
24
23
|
:rtl="rtl || isRtl"
|
|
@@ -59,7 +58,7 @@
|
|
|
59
58
|
<!-- ACTIONS -->
|
|
60
59
|
<template #actions>
|
|
61
60
|
<button class="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-slate-700">
|
|
62
|
-
<HeroIcon name="bell" size="22" />
|
|
61
|
+
<HeroIcon name="bell" size="22" class="text-black" />
|
|
63
62
|
</button>
|
|
64
63
|
|
|
65
64
|
<button class="hidden md:block p-2 rounded-full hover:bg-gray-100 dark:hover:bg-slate-700">
|
|
Binary file
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
<div>Form Valid: {{ isValid }}</div>
|
|
10
10
|
<div>Form Data: {{ data }}</div>
|
|
11
11
|
</div>
|
|
12
|
+
|
|
12
13
|
|
|
13
14
|
<!-- New Vuetify-style Form -->
|
|
14
15
|
<Form
|
|
@@ -17,6 +18,18 @@
|
|
|
17
18
|
validate-on="blur"
|
|
18
19
|
padding="p-4 space-y-4"
|
|
19
20
|
>
|
|
21
|
+
<div class="flex gap-1 items-center mb-4">
|
|
22
|
+
<Select
|
|
23
|
+
v-model="data.country"
|
|
24
|
+
label="Country"
|
|
25
|
+
:items="countries"
|
|
26
|
+
item-title="text"
|
|
27
|
+
item-value="value"
|
|
28
|
+
:rules="[rules.required]"
|
|
29
|
+
:searchable="true"
|
|
30
|
+
:multiple="false"
|
|
31
|
+
/>
|
|
32
|
+
</div>
|
|
20
33
|
<div class="space-y-4">
|
|
21
34
|
<InputSearch
|
|
22
35
|
v-model="data.name"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
class="md:hidden inline-flex items-center justify-center .vts-pa-2 rounded-md"
|
|
38
38
|
>
|
|
39
39
|
<slot name="drawer-icon">
|
|
40
|
-
<HeroIcon name="bars-3" size="24" color="text-
|
|
40
|
+
<HeroIcon name="bars-3" size="24" color="text-current" />
|
|
41
41
|
</slot>
|
|
42
42
|
</button>
|
|
43
43
|
|
|
@@ -65,10 +65,10 @@
|
|
|
65
65
|
<!-- Overflow actions -->
|
|
66
66
|
<div v-if="overflowActions.length" class="relative">
|
|
67
67
|
<button @click="toggleOverflow" class="p-2 rounded-md hover:bg-black/5 dark:hover:bg-white/10">
|
|
68
|
-
<svg class="h-5 w-5"
|
|
68
|
+
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
69
|
+
<circle cx="5" cy="12" r="2" />
|
|
69
70
|
<circle cx="12" cy="12" r="2" />
|
|
70
71
|
<circle cx="19" cy="12" r="2" />
|
|
71
|
-
<circle cx="5" cy="12" r="2" />
|
|
72
72
|
</svg>
|
|
73
73
|
</button>
|
|
74
74
|
|
|
@@ -86,10 +86,37 @@
|
|
|
86
86
|
</div>
|
|
87
87
|
</div>
|
|
88
88
|
|
|
89
|
+
<!-- Font Size Accessibility -->
|
|
90
|
+
<div class="flex items-center">
|
|
91
|
+
<button
|
|
92
|
+
@click="onDecreaseFontScale"
|
|
93
|
+
:disabled="fontScaleAtMin"
|
|
94
|
+
class="px-1.5 py-1 rounded-md hover:bg-black/5 dark:hover:bg-white/10 cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed"
|
|
95
|
+
title="Decrease font size"
|
|
96
|
+
>
|
|
97
|
+
<span class="font-bold" style="font-size: 0.7rem;">A-</span>
|
|
98
|
+
</button>
|
|
99
|
+
<button
|
|
100
|
+
@click="onResetFontScale"
|
|
101
|
+
class="px-1 py-1 rounded-md hover:bg-black/5 dark:hover:bg-white/10 cursor-pointer"
|
|
102
|
+
title="Reset font size"
|
|
103
|
+
>
|
|
104
|
+
<span style="font-size: 0.65rem;">{{ currentFontScale }}%</span>
|
|
105
|
+
</button>
|
|
106
|
+
<button
|
|
107
|
+
@click="onIncreaseFontScale"
|
|
108
|
+
:disabled="fontScaleAtMax"
|
|
109
|
+
class="px-1.5 py-1 rounded-md hover:bg-black/5 dark:hover:bg-white/10 cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed"
|
|
110
|
+
title="Increase font size"
|
|
111
|
+
>
|
|
112
|
+
<span class="font-bold" style="font-size: 0.85rem;">A+</span>
|
|
113
|
+
</button>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
89
116
|
<!-- Language Selector -->
|
|
90
117
|
<div class="relative" ref="langMenuRef">
|
|
91
118
|
<button @click="toggleLangMenu" class="px-2 py-1 rounded-md hover:bg-black/5 dark:hover:bg-white/10 cursor-pointer">
|
|
92
|
-
<HeroIcon name="language" size="22" />
|
|
119
|
+
<HeroIcon name="language" size="22" color="text-current" />
|
|
93
120
|
</button>
|
|
94
121
|
<div
|
|
95
122
|
v-if="isLangOpen"
|
|
@@ -185,6 +212,19 @@
|
|
|
185
212
|
|
|
186
213
|
const globalRtl = inject('globalRtl', ref(false))
|
|
187
214
|
const selectedRtl = computed(() => props.rtl !== undefined ? props.rtl : globalRtl.value)
|
|
215
|
+
|
|
216
|
+
/* ---------------------------------------------
|
|
217
|
+
* font size accessibility
|
|
218
|
+
----------------------------------------------*/
|
|
219
|
+
const globalFontScale = inject('globalFontScale', ref(100))
|
|
220
|
+
const onIncreaseFontScale = inject('increaseFontScale', () => {}) as () => void
|
|
221
|
+
const onDecreaseFontScale = inject('decreaseFontScale', () => {}) as () => void
|
|
222
|
+
const onResetFontScale = inject('resetFontScale', () => {}) as () => void
|
|
223
|
+
|
|
224
|
+
const FONT_SCALE_STEPS = [85, 100, 115, 130, 145]
|
|
225
|
+
const currentFontScale = computed(() => globalFontScale.value)
|
|
226
|
+
const fontScaleAtMin = computed(() => globalFontScale.value <= FONT_SCALE_STEPS[0])
|
|
227
|
+
const fontScaleAtMax = computed(() => globalFontScale.value >= FONT_SCALE_STEPS[FONT_SCALE_STEPS.length - 1])
|
|
188
228
|
/* ---------------------------------------------
|
|
189
229
|
* layout integration
|
|
190
230
|
----------------------------------------------*/
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Chip
|
|
3
|
+
v-tooltip="{en: 'Copy', dv: 'ކޮޕީ'}"
|
|
4
|
+
v-bind="chipProps"
|
|
5
|
+
clickable
|
|
6
|
+
:class="[
|
|
7
|
+
'transition-all duration-200 cursor-pointer select-none',
|
|
8
|
+
'hover:opacity-80 hover:scale-101',
|
|
9
|
+
copied ? 'animate-pulse' : '',
|
|
10
|
+
$attrs.class
|
|
11
|
+
]"
|
|
12
|
+
@click="handleChipClick"
|
|
13
|
+
>
|
|
14
|
+
<template #append v-if="showIcon && appendIcon === undefined">
|
|
15
|
+
<HeroIcon v-if="copied" :name="copied ? 'check' : 'clipboard'" type="outline" :size="iconSizes['sm']" color="text-current" />
|
|
16
|
+
<HeroIcon v-else name="clipboard" type="outline" :size="iconSizes['sm']" color="text-current" />
|
|
17
|
+
</template>
|
|
18
|
+
</Chip>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup>
|
|
22
|
+
import { ref, computed, inject } from 'vue'
|
|
23
|
+
import Chip from './buttons/Chip.vue'
|
|
24
|
+
import { iconSizes} from '../../pgo-components/lib/componentConfig'
|
|
25
|
+
|
|
26
|
+
const props = defineProps({
|
|
27
|
+
// Text to copy (optional - will use label if not provided)
|
|
28
|
+
text: {
|
|
29
|
+
type: String,
|
|
30
|
+
default: ''
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
// All Chip props
|
|
34
|
+
label: String,
|
|
35
|
+
size: {
|
|
36
|
+
type: String,
|
|
37
|
+
default: 'sm'
|
|
38
|
+
},
|
|
39
|
+
color: {
|
|
40
|
+
type: String,
|
|
41
|
+
default: 'primary'
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// CopyChip specific props
|
|
45
|
+
showIcon: {
|
|
46
|
+
type: Boolean,
|
|
47
|
+
default: false
|
|
48
|
+
},
|
|
49
|
+
showSuccessMessage: {
|
|
50
|
+
type: Boolean,
|
|
51
|
+
default: true
|
|
52
|
+
},
|
|
53
|
+
successMessage: {
|
|
54
|
+
type: String,
|
|
55
|
+
default: ''
|
|
56
|
+
},
|
|
57
|
+
successDuration: {
|
|
58
|
+
type: Number,
|
|
59
|
+
default: 1500
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// Pass through all other Chip props
|
|
63
|
+
variant: String,
|
|
64
|
+
prependIcon: String,
|
|
65
|
+
appendIcon: String,
|
|
66
|
+
disabled: Boolean,
|
|
67
|
+
rounded: { type: String, default: 'sm' },
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const emit = defineEmits(['copy', 'copy-success', 'copy-error'])
|
|
71
|
+
|
|
72
|
+
const copied = ref(false)
|
|
73
|
+
const snackbar = inject('snackbar', null)
|
|
74
|
+
|
|
75
|
+
// Computed for append icon
|
|
76
|
+
// const getAppendIcon = computed(() => {
|
|
77
|
+
// if (props.showIcon) {
|
|
78
|
+
// return copied.value ? 'check' : 'clipboard'
|
|
79
|
+
// }
|
|
80
|
+
// return props.appendIcon || undefined
|
|
81
|
+
// })
|
|
82
|
+
|
|
83
|
+
// Computed props to pass to Chip (excluding CopyChip specific props AND appendIcon)
|
|
84
|
+
const chipProps = computed(() => {
|
|
85
|
+
const {
|
|
86
|
+
text,
|
|
87
|
+
showIcon,
|
|
88
|
+
showSuccessMessage,
|
|
89
|
+
successMessage,
|
|
90
|
+
successDuration,
|
|
91
|
+
...rest
|
|
92
|
+
} = props
|
|
93
|
+
return rest
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// Use text prop if provided, otherwise use label
|
|
97
|
+
const textToCopy = computed(() => {
|
|
98
|
+
return props.text || props.label || ''
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const handleChipClick = (event) => {
|
|
102
|
+
// console.log('Chip clicked!')
|
|
103
|
+
if (event && event.stopPropagation) {
|
|
104
|
+
event.stopPropagation()
|
|
105
|
+
}
|
|
106
|
+
handleCopy()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const handleCopy = async () => {
|
|
110
|
+
// console.log('handleCopy called')
|
|
111
|
+
// console.log('textToCopy:', textToCopy.value)
|
|
112
|
+
// console.log('disabled:', props.disabled)
|
|
113
|
+
|
|
114
|
+
if (props.disabled || !textToCopy.value) {
|
|
115
|
+
// console.log('Copy cancelled - disabled or no text')
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Check if clipboard API is available
|
|
120
|
+
// console.log('navigator.clipboard available:', !!navigator.clipboard)
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
await navigator.clipboard.writeText(textToCopy.value)
|
|
124
|
+
// console.log('Copy successful via clipboard API')
|
|
125
|
+
|
|
126
|
+
// Show copied state
|
|
127
|
+
copied.value = true
|
|
128
|
+
setTimeout(() => {
|
|
129
|
+
copied.value = false
|
|
130
|
+
}, props.successDuration)
|
|
131
|
+
|
|
132
|
+
// Show success message
|
|
133
|
+
if (props.showSuccessMessage && snackbar) {
|
|
134
|
+
const message = props.successMessage || `Copied: ${textToCopy.value}`
|
|
135
|
+
snackbar.show({
|
|
136
|
+
message,
|
|
137
|
+
variant: 'success',
|
|
138
|
+
duration: props.successDuration
|
|
139
|
+
})
|
|
140
|
+
} else {
|
|
141
|
+
console.log('No snackbar available or showSuccessMessage is false')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
emit('copy', textToCopy.value)
|
|
145
|
+
emit('copy-success', textToCopy.value)
|
|
146
|
+
|
|
147
|
+
} catch (err) {
|
|
148
|
+
console.error('Clipboard API failed:', err)
|
|
149
|
+
|
|
150
|
+
// Fallback for older browsers or HTTPS issues
|
|
151
|
+
try {
|
|
152
|
+
const textArea = document.createElement('textarea')
|
|
153
|
+
textArea.value = textToCopy.value
|
|
154
|
+
textArea.style.position = 'fixed'
|
|
155
|
+
textArea.style.left = '-999999px'
|
|
156
|
+
textArea.style.top = '-999999px'
|
|
157
|
+
document.body.appendChild(textArea)
|
|
158
|
+
textArea.focus()
|
|
159
|
+
textArea.select()
|
|
160
|
+
const successful = document.execCommand('copy')
|
|
161
|
+
document.body.removeChild(textArea)
|
|
162
|
+
|
|
163
|
+
if (successful) {
|
|
164
|
+
// console.log('Copy successful via fallback method')
|
|
165
|
+
copied.value = true
|
|
166
|
+
setTimeout(() => {
|
|
167
|
+
copied.value = false
|
|
168
|
+
}, props.successDuration)
|
|
169
|
+
emit('copy-success', textToCopy.value)
|
|
170
|
+
} else {
|
|
171
|
+
throw new Error('execCommand failed')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
} catch (fallbackErr) {
|
|
175
|
+
console.error('Fallback copy also failed:', fallbackErr)
|
|
176
|
+
emit('copy-error', fallbackErr)
|
|
177
|
+
|
|
178
|
+
if (snackbar) {
|
|
179
|
+
snackbar.show({
|
|
180
|
+
message: 'Failed to copy text',
|
|
181
|
+
variant: 'error'
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<Chip
|
|
2
|
+
<!-- <Chip
|
|
3
3
|
v-tooltip="{en: 'Copy', dv: 'ކޮޕީ'}"
|
|
4
4
|
v-bind="chipProps"
|
|
5
5
|
clickable
|
|
@@ -15,13 +15,16 @@
|
|
|
15
15
|
<HeroIcon v-if="copied" :name="copied ? 'check' : 'clipboard'" type="outline" :size="iconSizes['sm']" color="text-current" />
|
|
16
16
|
<HeroIcon v-else name="clipboard" type="outline" :size="iconSizes['sm']" color="text-current" />
|
|
17
17
|
</template>
|
|
18
|
-
</Chip>
|
|
18
|
+
</Chip> -->
|
|
19
|
+
<div v-tooltip="{en: 'Copy', dv: 'ކޮޕީ'}" class="cursor-pointer hover:text-gray-800" @click="handleChipClick">
|
|
20
|
+
{{ text }}
|
|
21
|
+
</div>
|
|
19
22
|
</template>
|
|
20
23
|
|
|
21
24
|
<script setup>
|
|
22
25
|
import { ref, computed, inject } from 'vue'
|
|
23
|
-
import Chip from './buttons/Chip.vue'
|
|
24
|
-
import { iconSizes} from '../../pgo-components/lib/componentConfig'
|
|
26
|
+
// import Chip from './buttons/Chip.vue'
|
|
27
|
+
// import { iconSizes} from '../../pgo-components/lib/componentConfig'
|
|
25
28
|
|
|
26
29
|
const props = defineProps({
|
|
27
30
|
// Text to copy (optional - will use label if not provided)
|
|
@@ -29,23 +32,7 @@ const props = defineProps({
|
|
|
29
32
|
type: String,
|
|
30
33
|
default: ''
|
|
31
34
|
},
|
|
32
|
-
|
|
33
|
-
// All Chip props
|
|
34
|
-
label: String,
|
|
35
|
-
size: {
|
|
36
|
-
type: String,
|
|
37
|
-
default: 'sm'
|
|
38
|
-
},
|
|
39
|
-
color: {
|
|
40
|
-
type: String,
|
|
41
|
-
default: 'primary'
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
// CopyChip specific props
|
|
45
|
-
showIcon: {
|
|
46
|
-
type: Boolean,
|
|
47
|
-
default: false
|
|
48
|
-
},
|
|
35
|
+
|
|
49
36
|
showSuccessMessage: {
|
|
50
37
|
type: Boolean,
|
|
51
38
|
default: true
|
|
@@ -57,14 +44,7 @@ const props = defineProps({
|
|
|
57
44
|
successDuration: {
|
|
58
45
|
type: Number,
|
|
59
46
|
default: 1500
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Pass through all other Chip props
|
|
63
|
-
variant: String,
|
|
64
|
-
prependIcon: String,
|
|
65
|
-
appendIcon: String,
|
|
66
|
-
disabled: Boolean,
|
|
67
|
-
rounded: { type: String, default: 'sm' },
|
|
47
|
+
}
|
|
68
48
|
})
|
|
69
49
|
|
|
70
50
|
const emit = defineEmits(['copy', 'copy-success', 'copy-error'])
|
|
@@ -84,7 +64,7 @@ const snackbar = inject('snackbar', null)
|
|
|
84
64
|
const chipProps = computed(() => {
|
|
85
65
|
const {
|
|
86
66
|
text,
|
|
87
|
-
showIcon,
|
|
67
|
+
// showIcon,
|
|
88
68
|
showSuccessMessage,
|
|
89
69
|
successMessage,
|
|
90
70
|
successDuration,
|
|
@@ -225,6 +225,19 @@
|
|
|
225
225
|
</div>
|
|
226
226
|
<!-- Chip Display -->
|
|
227
227
|
|
|
228
|
+
<div
|
|
229
|
+
v-else-if="header.displayType == 'copyButton' "
|
|
230
|
+
:class="['align-middle leading-0', getColumnFont(lang)]"
|
|
231
|
+
>
|
|
232
|
+
<!-- <pre style="font-size:10px">{{ JSON.stringify(getNestedValue(item, header.value)) }}</pre> -->
|
|
233
|
+
<CopyTextBox
|
|
234
|
+
:text="getNestedValue(item, header.value)"
|
|
235
|
+
class="mr-1 "
|
|
236
|
+
/>
|
|
237
|
+
<!-- <span v-else class="text-xs text-gray-400">
|
|
238
|
+
{{ getNestedValue(item, header.value) || '-' }}
|
|
239
|
+
</span> -->
|
|
240
|
+
</div>
|
|
228
241
|
<div
|
|
229
242
|
v-else-if="header.displayType == 'chip' "
|
|
230
243
|
:class="['align-middle leading-0', getColumnFont(lang)]"
|
|
@@ -43,7 +43,18 @@
|
|
|
43
43
|
</div>
|
|
44
44
|
</div>
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
<!-- Desktop Table View -->
|
|
47
|
+
<table :dir="dir" class="w-full caption-bottom text-sm hidden md:table" style="table-layout: fixed;">
|
|
48
|
+
<!-- Colgroup for consistent column widths -->
|
|
49
|
+
<colgroup>
|
|
50
|
+
<col v-if="selectable" style="width: 48px;" />
|
|
51
|
+
<col
|
|
52
|
+
v-for="header in headers"
|
|
53
|
+
:key="'col-' + header.value"
|
|
54
|
+
:style="{ width: header.width || 'auto' }"
|
|
55
|
+
/>
|
|
56
|
+
<col v-if="$slots['item-actions'] || showActions" style="width: 120px;" />
|
|
57
|
+
</colgroup>
|
|
47
58
|
<!-- Table Header -->
|
|
48
59
|
<thead :class="[headerBg]">
|
|
49
60
|
<tr :class="[headerBorder, 'transition-colors']">
|
|
@@ -63,7 +74,6 @@
|
|
|
63
74
|
<th
|
|
64
75
|
v-for="header in headers"
|
|
65
76
|
:key="header.value"
|
|
66
|
-
:style="{ width: header.width || '' }"
|
|
67
77
|
:class="[
|
|
68
78
|
'min-h-8 px-3 align-middle font-medium cursor-pointer select-none transition-colors',
|
|
69
79
|
headerText,
|
|
@@ -75,13 +85,10 @@
|
|
|
75
85
|
@click="header.sortable == true ? toggleSort(header.value) : null"
|
|
76
86
|
>
|
|
77
87
|
<div :class="['flex items-center space-x-2', textAlign[header.headerAlign ?? 'defaults']]">
|
|
78
|
-
<span
|
|
79
|
-
:class="['w-full', header.width ? 'truncate' : 'whitespace-nowrap',]"
|
|
80
|
-
:style="{ maxWidth: header.width || 'auto', }"
|
|
81
|
-
>
|
|
88
|
+
<span class="w-full truncate whitespace-nowrap">
|
|
82
89
|
{{ header.title }}
|
|
83
90
|
</span>
|
|
84
|
-
<div v-if="header.sortable == true" class="flex h-4 w-4 items-center justify-center">
|
|
91
|
+
<div v-if="header.sortable == true" class="shrink-0 flex h-4 w-4 items-center justify-center">
|
|
85
92
|
<!-- Unsorted state -->
|
|
86
93
|
<svg
|
|
87
94
|
v-if="!isSorted(header.value)"
|
|
@@ -118,7 +125,6 @@
|
|
|
118
125
|
<!-- Actions column -->
|
|
119
126
|
<th
|
|
120
127
|
v-if="$slots['item-actions'] || showActions" :class="['h-10 px-3 text-end align-middle font-medium', headerText]"
|
|
121
|
-
:style="{ width: '25px' || 'auto' }"
|
|
122
128
|
>
|
|
123
129
|
<Button
|
|
124
130
|
v-if="inlineEdit"
|
|
@@ -173,15 +179,12 @@
|
|
|
173
179
|
<!-- Data columns -->
|
|
174
180
|
<template v-for="header in headers" :key="header.value">
|
|
175
181
|
<td
|
|
176
|
-
:style="{ width: header.width || 'flex', maxWidth: header.width || 'auto' }"
|
|
177
182
|
:class="[
|
|
178
|
-
'px-2 py-2 ',
|
|
183
|
+
'px-2 py-2 overflow-hidden',
|
|
179
184
|
]"
|
|
180
185
|
@click.stop
|
|
181
186
|
>
|
|
182
|
-
<div
|
|
183
|
-
class=" "
|
|
184
|
-
>
|
|
187
|
+
<div class="min-w-0">
|
|
185
188
|
<slot
|
|
186
189
|
:name="`item.${header.value}`"
|
|
187
190
|
:item="item"
|
|
@@ -193,7 +196,7 @@
|
|
|
193
196
|
<!-- html -->
|
|
194
197
|
<div
|
|
195
198
|
v-if="header.displayType == 'html'" :class="[
|
|
196
|
-
'text-sm truncate
|
|
199
|
+
'text-sm truncate',
|
|
197
200
|
cellText,
|
|
198
201
|
textAlign[header?.align ?? 'defaults'],
|
|
199
202
|
]" v-html="getCustomColumnData(header, item)"
|
|
@@ -202,22 +205,22 @@
|
|
|
202
205
|
<!-- Custom column -->
|
|
203
206
|
<div
|
|
204
207
|
v-else-if="header.displayType == 'custom'" :class="[
|
|
205
|
-
'text-sm
|
|
208
|
+
'text-sm',
|
|
206
209
|
cellText,
|
|
207
210
|
textAlign[header?.align ?? 'defaults'],
|
|
208
211
|
]"
|
|
209
212
|
>
|
|
210
213
|
<template v-if="getCustomColumnData(header, item)?.type === 'CopyTextBox'">
|
|
211
|
-
<div class="flex flex-wrap items-center gap-1">
|
|
214
|
+
<div class="flex flex-wrap items-center gap-1">fff
|
|
212
215
|
<template
|
|
213
216
|
v-for="(chip, idx) in getCustomColumnData(header, item).CopyTextBox"
|
|
214
217
|
:key="idx"
|
|
215
|
-
>
|
|
218
|
+
>dda
|
|
216
219
|
<CopyTextBox
|
|
217
|
-
|
|
220
|
+
:text="chip.text"
|
|
218
221
|
class="mr-1 "
|
|
219
222
|
/>
|
|
220
|
-
|
|
223
|
+
b
|
|
221
224
|
</template>
|
|
222
225
|
</div>
|
|
223
226
|
</template>
|
|
@@ -225,11 +228,20 @@
|
|
|
225
228
|
</div>
|
|
226
229
|
<!-- Chip Display -->
|
|
227
230
|
|
|
231
|
+
<div
|
|
232
|
+
v-else-if="header.displayType == 'copyButton'"
|
|
233
|
+
:class="['align-middle leading-0', getColumnFont(lang)]"
|
|
234
|
+
>
|
|
235
|
+
|
|
236
|
+
<CopyTextBox
|
|
237
|
+
:text="getNestedValue(item, header.value)"
|
|
238
|
+
class="mr-1 "
|
|
239
|
+
/>
|
|
240
|
+
</div>
|
|
228
241
|
<div
|
|
229
242
|
v-else-if="header.displayType == 'chip' "
|
|
230
243
|
:class="['align-middle leading-0', getColumnFont(lang)]"
|
|
231
244
|
>
|
|
232
|
-
<!-- <pre style="font-size:10px">{{ JSON.stringify(getNestedValue(item, header.value)) }}</pre> -->
|
|
233
245
|
<Chip
|
|
234
246
|
size="small"
|
|
235
247
|
v-if="header?.chip && header?.chip[getNestedValue(item, header.value)]"
|
|
@@ -295,18 +307,9 @@
|
|
|
295
307
|
v-bind="header.docButton"
|
|
296
308
|
@view-file="emit('viewpdf', $event)"
|
|
297
309
|
/>
|
|
298
|
-
<!-- <Button
|
|
299
|
-
icon="document-text"
|
|
300
|
-
icon-type="solid"
|
|
301
|
-
variant="text"
|
|
302
|
-
size="xs"
|
|
303
|
-
@click="emit('viewpdf', formatCellValue(getNestedValue(item, header.value), header))"
|
|
304
|
-
/> -->
|
|
305
|
-
<!-- @click="handlePdfView(formatCellValue(getNestedValue(item, header.value), header))" -->
|
|
306
310
|
</div>
|
|
307
311
|
|
|
308
312
|
<!-- Default Text Display -->
|
|
309
|
-
<!-- <bdi class="flex items-center"></bdi> -->
|
|
310
313
|
<bdi
|
|
311
314
|
v-else :class="[
|
|
312
315
|
'text-sm',
|
|
@@ -316,8 +319,7 @@
|
|
|
316
319
|
]"
|
|
317
320
|
>
|
|
318
321
|
<span
|
|
319
|
-
|
|
320
|
-
:style="{ maxWidth: header.width || 'auto', }"
|
|
322
|
+
class="block truncate"
|
|
321
323
|
>
|
|
322
324
|
{{ formatCellValue(getNestedValue(item, header.value), header) }}
|
|
323
325
|
</span>
|
|
@@ -340,6 +342,101 @@
|
|
|
340
342
|
</tr>
|
|
341
343
|
</tbody>
|
|
342
344
|
</table>
|
|
345
|
+
|
|
346
|
+
<!-- Mobile Card View -->
|
|
347
|
+
<div class="md:hidden" :dir="dir">
|
|
348
|
+
<!-- No Data State -->
|
|
349
|
+
<div v-if="!items || items.length === 0" class="px-6 py-12 text-center">
|
|
350
|
+
<div class="flex flex-col items-center">
|
|
351
|
+
<svg class="w-12 h-12 text-gray-300 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
352
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
353
|
+
</svg>
|
|
354
|
+
<span class="text-sm text-gray-500">{{ noDataText }}</span>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
|
|
358
|
+
<!-- Card Items -->
|
|
359
|
+
<div
|
|
360
|
+
v-else
|
|
361
|
+
v-for="(item, index) in items"
|
|
362
|
+
:key="'card-' + getItemKey(item, index)"
|
|
363
|
+
:class="[
|
|
364
|
+
'border-b border-input-border p-4 transition-colors cursor-pointer',
|
|
365
|
+
rowHover,
|
|
366
|
+
selectedItems.includes(getItemKey(item, index)) ? rowSelected : ''
|
|
367
|
+
]"
|
|
368
|
+
@click="handleRowClick(item, index)"
|
|
369
|
+
>
|
|
370
|
+
<!-- Selection checkbox for mobile -->
|
|
371
|
+
<div v-if="selectable" class="flex items-center mb-3">
|
|
372
|
+
<input
|
|
373
|
+
type="checkbox"
|
|
374
|
+
:checked="selectedItems.includes(getItemKey(item, index))"
|
|
375
|
+
class="h-4 w-4 rounded border-2 border-gray-300 text-blue-600 focus:ring-2 focus:ring-blue-500 focus:ring-offset-0"
|
|
376
|
+
@click.stop
|
|
377
|
+
@change="toggleItemSelection(item, index)"
|
|
378
|
+
>
|
|
379
|
+
</div>
|
|
380
|
+
|
|
381
|
+
<!-- Each header-value pair as a row -->
|
|
382
|
+
<div
|
|
383
|
+
v-for="header in headers"
|
|
384
|
+
:key="'card-field-' + header.value"
|
|
385
|
+
class="flex justify-between items-start py-1.5 gap-4"
|
|
386
|
+
@click.stop
|
|
387
|
+
>
|
|
388
|
+
<!-- Label -->
|
|
389
|
+
<span :class="['text-xs font-medium shrink-0 w-2/5', headerText, getColumnFont(lang)]">
|
|
390
|
+
{{ header.title }}
|
|
391
|
+
</span>
|
|
392
|
+
<!-- Value -->
|
|
393
|
+
<div class="flex-1 min-w-0 text-end">
|
|
394
|
+
<slot
|
|
395
|
+
:name="`item.${header.value}`"
|
|
396
|
+
:item="item"
|
|
397
|
+
:value="getNestedValue(item, header.value)"
|
|
398
|
+
:header="header"
|
|
399
|
+
:index="index"
|
|
400
|
+
>
|
|
401
|
+
<div v-if="header.displayType == 'html'" :class="['text-sm', cellText]" v-html="getCustomColumnData(header, item)" />
|
|
402
|
+
<div v-else-if="header.displayType == 'custom'" :class="['text-sm', cellText]">
|
|
403
|
+
<template v-if="getCustomColumnData(header, item)?.type === 'CopyTextBox'">
|
|
404
|
+
<div class="flex flex-wrap items-center gap-1 justify-end">
|
|
405
|
+
<CopyTextBox v-for="(chip, idx) in getCustomColumnData(header, item).CopyTextBox" :key="idx" v-bind="chip" />
|
|
406
|
+
</div>
|
|
407
|
+
</template>
|
|
408
|
+
<div v-else v-html="getCustomColumnData(header, item)" />
|
|
409
|
+
</div>
|
|
410
|
+
<div v-else-if="header.displayType == 'copyButton'" :class="['align-middle', getColumnFont(lang)]">
|
|
411
|
+
<CopyTextBox :text="getNestedValue(item, header.value)" />
|
|
412
|
+
</div>
|
|
413
|
+
<div v-else-if="header.displayType == 'chip'" :class="['align-middle', getColumnFont(lang)]">
|
|
414
|
+
<Chip size="small" v-if="header?.chip && header?.chip[getNestedValue(item, header.value)]" v-bind="header.chip[getNestedValue(item, header.value)]" />
|
|
415
|
+
<span v-else class="text-xs text-gray-400">{{ getNestedValue(item, header.value) || '-' }}</span>
|
|
416
|
+
</div>
|
|
417
|
+
<div v-else-if="header.displayType == 'checkbox'" :class="['align-middle', getColumnFont(header.lang)]">
|
|
418
|
+
<Checkbox :model-value="!!getNestedValue(item, header.value)" v-bind="header.displayProps" :disabled="!editMode || !header.inlineEditable" @update:model-value="(value) => handleCellUpdate(item, header.value, value, index)" />
|
|
419
|
+
</div>
|
|
420
|
+
<div v-else-if="header.displayType == 'docButton'" :class="['align-middle', getColumnFont(header.lang)]">
|
|
421
|
+
<FileDisplay :item="getNestedValue(item, header.value)" v-bind="header.docButton" @view-file="emit('viewpdf', $event)" />
|
|
422
|
+
</div>
|
|
423
|
+
<bdi v-else :class="['text-sm', cellText, header.displayType == 'englishText' || header.displayType == 'date' ? 'eng-font' : getColumnFont(lang)]">
|
|
424
|
+
{{ formatCellValue(getNestedValue(item, header.value), header) }}
|
|
425
|
+
</bdi>
|
|
426
|
+
</slot>
|
|
427
|
+
</div>
|
|
428
|
+
</div>
|
|
429
|
+
|
|
430
|
+
<!-- Actions for mobile -->
|
|
431
|
+
<div v-if="$slots['item-actions'] || showActions" class="flex items-center justify-end space-x-2 mt-3 pt-2 border-t border-input-border">
|
|
432
|
+
<slot name="item-actions" :item="item" :index="index">
|
|
433
|
+
<Button v-if="showView && evaluateCondition(settings?.actions?.viewCondition, item)" v-tooltip="{en: 'View', dv: 'ވިއު'}" @click.stop="emit('view', item)" size="md" color="info" variant="text" icon="eye" icon-type="outline" label="" />
|
|
434
|
+
<Button v-if="showEdit && evaluateCondition(settings?.actions?.editCondition, item)" v-tooltip="{en: 'Edit', dv: 'އެޑިޓް'}" @click.stop="emit('edit', item)" size="md" icon="pencil-square" color="warning" variant="text" label="" />
|
|
435
|
+
<Button v-if="showDelete && evaluateCondition(settings?.actions?.deleteCondition, item)" v-tooltip="{en: 'Delete', dv: 'ޑިލީޓް'}" @click.stop="emit('delete', item)" size="md" color="danger" variant="text" icon="trash" label="" />
|
|
436
|
+
</slot>
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
343
440
|
</div>
|
|
344
441
|
|
|
345
442
|
<!-- Pagination Component -->
|