Enhance RsTab Component with New Props and Improved Slot Handling
- Added support for tabs to be passed as props, allowing for a more flexible tab configuration. - Implemented a watch on modelValue to synchronize selected tab changes. - Enhanced slot handling to accommodate both named slots and default slots for better usability. - Refactored tab selection logic to utilize helper functions for improved readability and maintainability.
This commit is contained in:
parent
9ea4e18672
commit
373d4fbeda
@ -1,4 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref, provide, useSlots, onMounted, watch } from 'vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
variant: {
|
variant: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -20,20 +22,76 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: "left",
|
default: "left",
|
||||||
},
|
},
|
||||||
|
tabs: {
|
||||||
|
type: Array,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
// Slots
|
// Slots
|
||||||
const slots = useSlots();
|
const slots = useSlots();
|
||||||
|
|
||||||
const tabs = ref(slots.default().map((tab) => tab.props));
|
// Handle cases where slots.default might not exist or not be a function
|
||||||
const selectedTitle = ref(tabs.value[0]["title"]);
|
const tabs = ref([]);
|
||||||
|
const selectedTitle = ref('');
|
||||||
|
|
||||||
tabs.value.forEach((tab) => {
|
// Initialize tabs from slots or props
|
||||||
if (typeof tab.active !== "undefined") {
|
onMounted(() => {
|
||||||
selectedTitle.value = tab.title;
|
if (props.tabs) {
|
||||||
|
// If tabs are provided via props (new pattern)
|
||||||
|
tabs.value = props.tabs;
|
||||||
|
selectedTitle.value = props.modelValue || props.tabs[0]?.key || props.tabs[0]?.label || '';
|
||||||
|
} else if (slots.default && typeof slots.default === 'function') {
|
||||||
|
// If tabs are provided via slots (original pattern)
|
||||||
|
try {
|
||||||
|
const slotContent = slots.default();
|
||||||
|
if (slotContent && Array.isArray(slotContent)) {
|
||||||
|
tabs.value = slotContent.map((tab) => tab.props).filter(Boolean);
|
||||||
|
selectedTitle.value = tabs.value[0]?.title || '';
|
||||||
|
|
||||||
|
// Check for active tab
|
||||||
|
tabs.value.forEach((tab) => {
|
||||||
|
if (typeof tab.active !== "undefined") {
|
||||||
|
selectedTitle.value = tab.title;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Error processing tab slots:', error);
|
||||||
|
tabs.value = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Watch for changes in modelValue
|
||||||
|
watch(() => props.modelValue, (newValue) => {
|
||||||
|
if (newValue && newValue !== selectedTitle.value) {
|
||||||
|
selectedTitle.value = newValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Watch for changes in selectedTitle and emit update
|
||||||
|
watch(selectedTitle, (newValue) => {
|
||||||
|
if (props.modelValue !== undefined) {
|
||||||
|
emit('update:modelValue', newValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper function to get the correct key/title from tab object
|
||||||
|
const getTabKey = (tab) => {
|
||||||
|
return tab.key || tab.title || tab.label || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTabLabel = (tab) => {
|
||||||
|
return tab.label || tab.title || tab.key || '';
|
||||||
|
};
|
||||||
|
|
||||||
provide("selectedTitle", selectedTitle);
|
provide("selectedTitle", selectedTitle);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -69,10 +127,10 @@ provide("selectedTitle", selectedTitle);
|
|||||||
border: type === 'border',
|
border: type === 'border',
|
||||||
'border-horizontal': type === 'border' && !vertical,
|
'border-horizontal': type === 'border' && !vertical,
|
||||||
'border-horizontal-active':
|
'border-horizontal-active':
|
||||||
selectedTitle === val.title && type === 'border' && !vertical,
|
selectedTitle === getTabKey(val) && type === 'border' && !vertical,
|
||||||
'border-vertical': type === 'border' && vertical,
|
'border-vertical': type === 'border' && vertical,
|
||||||
'border-vertical-active':
|
'border-vertical-active':
|
||||||
selectedTitle === val.title && type === 'border' && vertical,
|
selectedTitle === getTabKey(val) && type === 'border' && vertical,
|
||||||
|
|
||||||
// Variant Color for Border Type
|
// Variant Color for Border Type
|
||||||
'border-hover-primary': type === 'border' && variant == 'primary',
|
'border-hover-primary': type === 'border' && variant == 'primary',
|
||||||
@ -85,34 +143,34 @@ provide("selectedTitle", selectedTitle);
|
|||||||
|
|
||||||
// Variant Color for Border Type Active
|
// Variant Color for Border Type Active
|
||||||
'border-active-primary':
|
'border-active-primary':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'border' &&
|
type === 'border' &&
|
||||||
variant == 'primary',
|
variant == 'primary',
|
||||||
'border-active-secondary':
|
'border-active-secondary':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'border' &&
|
type === 'border' &&
|
||||||
variant == 'secondary',
|
variant == 'secondary',
|
||||||
'border-active-info':
|
'border-active-info':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'border' &&
|
type === 'border' &&
|
||||||
variant == 'info',
|
variant == 'info',
|
||||||
'border-active-success':
|
'border-active-success':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'border' &&
|
type === 'border' &&
|
||||||
variant == 'success',
|
variant == 'success',
|
||||||
'border-active-warning':
|
'border-active-warning':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'border' &&
|
type === 'border' &&
|
||||||
variant == 'warning',
|
variant == 'warning',
|
||||||
'border-active-danger':
|
'border-active-danger':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'border' &&
|
type === 'border' &&
|
||||||
variant == 'danger',
|
variant == 'danger',
|
||||||
}"
|
}"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
v-for="(val, index) in tabs"
|
v-for="(val, index) in tabs"
|
||||||
:key="index"
|
:key="index"
|
||||||
@click="selectedTitle = val.title"
|
@click="selectedTitle = getTabKey(val)"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="tab-item-link"
|
class="tab-item-link"
|
||||||
@ -120,9 +178,9 @@ provide("selectedTitle", selectedTitle);
|
|||||||
default: type === 'default' && !vertical,
|
default: type === 'default' && !vertical,
|
||||||
'default-vertical': type === 'default' && vertical,
|
'default-vertical': type === 'default' && vertical,
|
||||||
'default-active':
|
'default-active':
|
||||||
selectedTitle === val.title && type === 'default' && !vertical,
|
selectedTitle === getTabKey(val) && type === 'default' && !vertical,
|
||||||
'default-vertical-active':
|
'default-vertical-active':
|
||||||
selectedTitle === val.title && type === 'default' && vertical,
|
selectedTitle === getTabKey(val) && type === 'default' && vertical,
|
||||||
|
|
||||||
// Variant hover for default type
|
// Variant hover for default type
|
||||||
'default-hover-primary':
|
'default-hover-primary':
|
||||||
@ -138,27 +196,27 @@ provide("selectedTitle", selectedTitle);
|
|||||||
|
|
||||||
// Variant Color for default type Active
|
// Variant Color for default type Active
|
||||||
'default-primary':
|
'default-primary':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'default' &&
|
type === 'default' &&
|
||||||
variant == 'primary',
|
variant == 'primary',
|
||||||
'default-secondary':
|
'default-secondary':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'default' &&
|
type === 'default' &&
|
||||||
variant == 'secondary',
|
variant == 'secondary',
|
||||||
'default-info':
|
'default-info':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'default' &&
|
type === 'default' &&
|
||||||
variant == 'info',
|
variant == 'info',
|
||||||
'default-success':
|
'default-success':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'default' &&
|
type === 'default' &&
|
||||||
variant == 'success',
|
variant == 'success',
|
||||||
'default-warning':
|
'default-warning':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'default' &&
|
type === 'default' &&
|
||||||
variant == 'warning',
|
variant == 'warning',
|
||||||
'default-danger':
|
'default-danger':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'default' &&
|
type === 'default' &&
|
||||||
variant == 'danger',
|
variant == 'danger',
|
||||||
|
|
||||||
@ -175,27 +233,27 @@ provide("selectedTitle", selectedTitle);
|
|||||||
|
|
||||||
// Variant Color for card type Active
|
// Variant Color for card type Active
|
||||||
'link-card-primary-active':
|
'link-card-primary-active':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'card' &&
|
type === 'card' &&
|
||||||
variant == 'primary',
|
variant == 'primary',
|
||||||
'link-card-secondary-active':
|
'link-card-secondary-active':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'card' &&
|
type === 'card' &&
|
||||||
variant == 'secondary',
|
variant == 'secondary',
|
||||||
'link-card-info-active':
|
'link-card-info-active':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'card' &&
|
type === 'card' &&
|
||||||
variant == 'info',
|
variant == 'info',
|
||||||
'link-card-success-active':
|
'link-card-success-active':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'card' &&
|
type === 'card' &&
|
||||||
variant == 'success',
|
variant == 'success',
|
||||||
'link-card-warning-active':
|
'link-card-warning-active':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'card' &&
|
type === 'card' &&
|
||||||
variant == 'warning',
|
variant == 'warning',
|
||||||
'link-card-danger-active':
|
'link-card-danger-active':
|
||||||
selectedTitle === val.title &&
|
selectedTitle === getTabKey(val) &&
|
||||||
type === 'card' &&
|
type === 'card' &&
|
||||||
variant == 'danger',
|
variant == 'danger',
|
||||||
|
|
||||||
@ -203,7 +261,7 @@ provide("selectedTitle", selectedTitle);
|
|||||||
'link-justify-center': justify == 'center',
|
'link-justify-center': justify == 'center',
|
||||||
'link-justify-right': justify == 'right',
|
'link-justify-right': justify == 'right',
|
||||||
}"
|
}"
|
||||||
>{{ val.title }}</a
|
>{{ getTabLabel(val) }}</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -223,7 +281,17 @@ provide("selectedTitle", selectedTitle);
|
|||||||
'content-border-danger': type === 'border' && variant === 'danger',
|
'content-border-danger': type === 'border' && variant === 'danger',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<!-- New pattern: Named slots for each tab -->
|
||||||
|
<template v-if="props.tabs">
|
||||||
|
<div v-for="tab in props.tabs" :key="tab.key" v-show="selectedTitle === tab.key">
|
||||||
|
<slot :name="tab.key"></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Old pattern: Default slot with RsTabItem components -->
|
||||||
|
<template v-else>
|
||||||
|
<slot></slot>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</client-only>
|
</client-only>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user