feat: Vite + vue 3 💚 (#10047)
Fixes https://github.com/chatwoot/chatwoot/issues/8436 Fixes https://github.com/chatwoot/chatwoot/issues/9767 Fixes https://github.com/chatwoot/chatwoot/issues/10156 Fixes https://github.com/chatwoot/chatwoot/issues/6031 Fixes https://github.com/chatwoot/chatwoot/issues/5696 Fixes https://github.com/chatwoot/chatwoot/issues/9250 Fixes https://github.com/chatwoot/chatwoot/issues/9762 --------- Co-authored-by: Pranav <pranavrajs@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
@@ -1,91 +0,0 @@
|
||||
export default {
|
||||
name: 'WootTabs',
|
||||
props: {
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return { hasScroll: false };
|
||||
},
|
||||
created() {
|
||||
window.addEventListener('resize', this.computeScrollWidth);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.computeScrollWidth);
|
||||
},
|
||||
mounted() {
|
||||
this.computeScrollWidth();
|
||||
},
|
||||
methods: {
|
||||
computeScrollWidth() {
|
||||
const tabElement = this.$el.getElementsByClassName('tabs')[0];
|
||||
this.hasScroll = tabElement.scrollWidth > tabElement.clientWidth;
|
||||
},
|
||||
onScrollClick(direction) {
|
||||
const tabElement = this.$el.getElementsByClassName('tabs')[0];
|
||||
let scrollPosition = tabElement.scrollLeft;
|
||||
if (direction === 'left') {
|
||||
scrollPosition -= 100;
|
||||
} else {
|
||||
scrollPosition += 100;
|
||||
}
|
||||
tabElement.scrollTo({
|
||||
top: 0,
|
||||
left: scrollPosition,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
},
|
||||
createScrollButton(createElement, direction) {
|
||||
if (!this.hasScroll) {
|
||||
return false;
|
||||
}
|
||||
return createElement(
|
||||
'button',
|
||||
{
|
||||
class: 'tabs--scroll-button button clear secondary button--only-icon',
|
||||
on: { click: () => this.onScrollClick(direction) },
|
||||
},
|
||||
[
|
||||
createElement('fluent-icon', {
|
||||
props: { icon: `chevron-${direction}`, size: 16 },
|
||||
}),
|
||||
]
|
||||
);
|
||||
},
|
||||
},
|
||||
render(createElement) {
|
||||
const Tabs = this.$slots.default
|
||||
.filter(
|
||||
node =>
|
||||
node.componentOptions &&
|
||||
node.componentOptions.tag === 'woot-tabs-item'
|
||||
)
|
||||
.map((node, index) => {
|
||||
const data = node.componentOptions.propsData;
|
||||
data.index = index;
|
||||
return node;
|
||||
});
|
||||
const leftButton = this.createScrollButton(createElement, 'left');
|
||||
const rightButton = this.createScrollButton(createElement, 'right');
|
||||
return (
|
||||
<div
|
||||
class={{
|
||||
'tabs--container--with-border': this.border,
|
||||
'tabs--container': true,
|
||||
}}
|
||||
>
|
||||
{leftButton}
|
||||
<ul class={{ tabs: true, 'tabs--with-scroll': this.hasScroll }}>
|
||||
{Tabs}
|
||||
</ul>
|
||||
{rightButton}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
89
app/javascript/dashboard/components/ui/Tabs/Tabs.vue
Normal file
89
app/javascript/dashboard/components/ui/Tabs/Tabs.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<script setup>
|
||||
// [VITE] TODO: Test this component across different screen sizes and usages
|
||||
import { ref, provide, onMounted, computed } from 'vue';
|
||||
import { useEventListener } from '@vueuse/core';
|
||||
|
||||
const props = defineProps({
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
const hasScroll = ref(false);
|
||||
// TODO: We may not this internalActiveIndex, we can use activeIndex directly
|
||||
// But right I'll keep it and fix it when testing the rest of the codebase
|
||||
const internalActiveIndex = ref(props.index);
|
||||
|
||||
// Create a proxy for activeIndex using computed
|
||||
const activeIndex = computed({
|
||||
get: () => internalActiveIndex.value,
|
||||
set: newValue => {
|
||||
internalActiveIndex.value = newValue;
|
||||
emit('change', newValue);
|
||||
},
|
||||
});
|
||||
|
||||
provide('activeIndex', activeIndex);
|
||||
provide('updateActiveIndex', index => {
|
||||
activeIndex.value = index;
|
||||
});
|
||||
|
||||
const computeScrollWidth = () => {
|
||||
// TODO: use useElementSize from vueuse
|
||||
const tabElement = document.querySelector('.tabs');
|
||||
if (tabElement) {
|
||||
hasScroll.value = tabElement.scrollWidth > tabElement.clientWidth;
|
||||
}
|
||||
};
|
||||
|
||||
const onScrollClick = direction => {
|
||||
// TODO: use useElementSize from vueuse
|
||||
const tabElement = document.querySelector('.tabs');
|
||||
if (tabElement) {
|
||||
let scrollPosition = tabElement.scrollLeft;
|
||||
scrollPosition += direction === 'left' ? -100 : 100;
|
||||
tabElement.scrollTo({
|
||||
top: 0,
|
||||
left: scrollPosition,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEventListener(window, 'resize', computeScrollWidth);
|
||||
onMounted(() => {
|
||||
computeScrollWidth();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="{ 'tabs--container--with-border': border }"
|
||||
class="tabs--container"
|
||||
>
|
||||
<button
|
||||
v-if="hasScroll"
|
||||
class="tabs--scroll-button button clear secondary button--only-icon"
|
||||
@click="onScrollClick('left')"
|
||||
>
|
||||
<fluent-icon icon="chevron-left" :size="16" />
|
||||
</button>
|
||||
<ul :class="{ 'tabs--with-scroll': hasScroll }" class="tabs">
|
||||
<slot />
|
||||
</ul>
|
||||
<button
|
||||
v-if="hasScroll"
|
||||
class="tabs--scroll-button button clear secondary button--only-icon"
|
||||
@click="onScrollClick('right')"
|
||||
>
|
||||
<fluent-icon icon="chevron-right" :size="16" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,47 +1,40 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'WootTabsItem',
|
||||
props: {
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
showBadge: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
<script setup>
|
||||
import { computed, inject } from 'vue';
|
||||
|
||||
computed: {
|
||||
active() {
|
||||
return this.index === this.$parent.index;
|
||||
},
|
||||
|
||||
getItemCount() {
|
||||
return this.count;
|
||||
},
|
||||
const props = defineProps({
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
|
||||
methods: {
|
||||
onTabClick(event) {
|
||||
event.preventDefault();
|
||||
if (!this.disabled) {
|
||||
this.$parent.$emit('change', this.index);
|
||||
}
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
showBadge: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const activeIndex = inject('activeIndex');
|
||||
const updateActiveIndex = inject('updateActiveIndex');
|
||||
|
||||
const active = computed(() => props.index === activeIndex.value);
|
||||
const getItemCount = computed(() => props.count);
|
||||
|
||||
const onTabClick = event => {
|
||||
event.preventDefault();
|
||||
if (!props.disabled) {
|
||||
updateActiveIndex(props.index);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user