kui-vue
Version:
A lightweight desktop UI component library suitable for Vue.js 2.
302 lines (286 loc) • 9.26 kB
JSX
import Icon from "../icon";
import { getChildren } from "../utils/vnode";
import { Close, ChevronBack, ChevronForward } from "kui-icons";
import {
defineComponent,
onMounted,
onBeforeMount,
ref,
nextTick,
watch,
computed,
} from "vue";
import { withInstall, cloneVNode } from "../utils/vue";
const Tabs = defineComponent({
name: "Tabs",
props: {
// activeKey: [String,Number], // for 3
value: [String, Number],
card: Boolean,
sample: Boolean,
centered: Boolean,
animated: { type: Boolean, default: true },
},
setup(ps, { slots, emit }) {
const defaultActiveKey = ref(ps.value);
const currentIndex = ref(-1);
const scrollable = ref(false);
const navOffsetLeft = ref(0);
const prevBtnDisabled = ref(false);
const nextBtnDisabled = ref(false);
const navRef = ref();
const navScrollRef = ref();
const navBoxRef = ref();
const inkBarRef = ref();
// const children = ref(getChildren(slots.default?.()));
const children = computed(() => {
return getChildren(slots.default?.());
});
watch(
// () => ps.activeKey,
() => ps.value,
(nv) => {
defaultActiveKey.value = nv;
updateIndex();
}
);
onMounted(() => {
nextTick(() => {
updateIndex();
});
window.addEventListener("resize", resetNavPosition);
});
onBeforeMount(() => {
window.removeEventListener("resize", resetNavPosition);
});
const closeTab = (key, e) => {
emit("remove", key);
e.stopPropagation();
// e.preventDefault();
};
const resetActivePosition = () => {
const target = navRef.value.children[currentIndex.value];
if (!target) return;
// show active tab in client
const nav = navScrollRef.value;
// let totalWidth = panel.offsetWidth
let clientWidth = navBoxRef.value.clientWidth;
let navLeft = navOffsetLeft.value;
let { offsetLeft, offsetWidth } = target;
// min left
if (navLeft + offsetLeft < 0) {
navLeft = -offsetLeft;
}
//max right
else if (clientWidth - navLeft < offsetLeft + offsetWidth) {
navLeft -= offsetLeft + offsetWidth + navLeft - clientWidth + 2; //marginRight
}
navOffsetLeft.value = navLeft;
nav.style.transform = `translate3d(${navLeft}px,0,0)`;
};
const resetNavPosition = () => {
// when one tab removed or append
nextTick(() => {
const nav = navScrollRef.value;
if (!nav) return;
let totalWidth = nav.offsetWidth;
let clientWidth = navBoxRef.value.clientWidth;
let navLeft = navOffsetLeft.value;
if (clientWidth + navLeft < clientWidth) {
navLeft = clientWidth - totalWidth;
}
if (navLeft > 0) navLeft = 0;
navOffsetLeft.value = navLeft;
nextBtnDisabled.value = navLeft == clientWidth - totalWidth;
prevBtnDisabled.value = navLeft == 0;
nav.style.transform = `translate3d(${navLeft}px,0,0)`;
resetActivePosition();
updateInkBarPosition();
updateNav();
});
};
const scroll = (direction) => {
//control left or right
const panel = navScrollRef.value;
let totalWidth = panel.offsetWidth;
let clientWidth = navBoxRef.value.clientWidth;
let navLeft = navOffsetLeft.value;
// console.log(totalWidth, clientWidth)
if (direction == "right") {
const endWidth = totalWidth - clientWidth + navLeft;
if (endWidth > clientWidth) {
navLeft -= clientWidth;
} else if (endWidth > 0) {
navLeft -= endWidth;
}
} else {
if (navLeft < -clientWidth) {
navLeft += clientWidth;
} else if (navLeft < 0) {
navLeft = 0;
}
}
nextBtnDisabled.value = navLeft == clientWidth - totalWidth;
prevBtnDisabled.value = navLeft == 0;
navOffsetLeft.value = navLeft;
panel.style.transform = `translate3d(${navLeft}px,0,0)`;
};
const tabClick = ({ disabled, key }, index) => {
if (!disabled) {
emit("input", key);
// emit("update:activeKey", key);
emit("tab-click", key);
defaultActiveKey.value = key;
currentIndex.value = index;
emit("change", key);
updateIndex(); // for 2
}
};
const updateIndex = () => {
nextTick(() => {
const nodes = getChildren(slots.default?.());
currentIndex.value = nodes
?.map((p) => p.key)
.indexOf(defaultActiveKey.value);
resetActivePosition();
updateInkBarPosition();
});
};
const updateInkBarPosition = () => {
if (!ps.card && !ps.sample) {
const nav = navRef.value.children[currentIndex.value];
if (nav) {
const inkBar = inkBarRef.value;
let offsetLeft = nav.offsetLeft;
if (ps.centered) {
// offsetLeft = (navBoxRef.value.offsetWidth - offsetLeft) ;
}
inkBar.style.width = `${nav.offsetWidth}px`;
inkBar.style.transform = `translate3d(${offsetLeft}px, 0px, 0px)`;
}
}
};
const updateNav = () => {
nextTick(() => {
// update inkBar position
// set panel has scroll arrow
const navBox = navBoxRef.value;
if (!navBox) return;
scrollable.value = navBox.scrollWidth > navBox.clientWidth;
});
};
const renderNodes = () => {
const nodes = getChildren(slots.default?.());
const panels = nodes?.map((item) => {
return cloneVNode(item, {
props: { activeKey: defaultActiveKey.value },
on: { resetNavPosition: () => resetNavPosition() },
});
});
const navNodes = nodes?.map((panel, index) => {
const key = panel.key;
let { icon, title, closable, disabled } =
panel?.componentOptions?.propsData || {}; // for 2
// let { icon, title, closable, disabled } = panel.props; // for 3
disabled = disabled !== undefined && disabled != false;
closable = closable !== undefined;
const prop = {
class: [
"k-tabs-tab",
{
["k-tabs-tab-active"]: key === defaultActiveKey.value,
["k-tabs-tab-disabled"]: disabled,
},
],
on: {
click: () => tabClick({ disabled, key }, index),
},
};
return (
<div {...prop}>
{icon ? <Icon type={icon} /> : null}
{title}
{closable && ps.card ? (
<Icon
type={Close}
class="k-tabs-close"
strokeWidth={45}
onClick={(e) => closeTab(key, e)}
/>
) : null}
</div>
);
});
return { panels, navNodes };
};
return () => {
const { card, animated, centered, sample } = ps;
const classes = [
"k-tabs",
{
["k-tabs-animated"]: animated && !card && !sample,
["k-tabs-card"]: card && !sample,
["k-tabs-sample"]: sample && !card,
["k-tabs-centered"]: centered,
},
];
let scrollStyle = {},
paneStyle = {};
if (animated && !card && !sample) {
paneStyle.marginLeft = `-${100 * currentIndex.value}%`;
}
const navCls = [
"k-tabs-nav-container",
{ ["k-tabs-nav-container-scroll"]: scrollable.value },
];
const { panels, navNodes } = renderNodes();
return (
<div class={classes}>
<div class="k-tabs-bar">
<div class={navCls}>
{scrollable.value ? (
<span
class={[
"k-tabs-tab-btn-prev",
{ "k-tabs-tab-btn-prev-disabled": prevBtnDisabled.value },
]}
onClick={() => scroll("left")}
>
<Icon type={ChevronBack} />
</span>
) : null}
<div class="k-tabs-nav-wrap" ref={navBoxRef}>
<div class="k-tabs-nav" style={scrollStyle} ref={navScrollRef}>
{!card && !sample ? (
<div class="k-tabs-ink-bar" ref={inkBarRef} />
) : null}
<div class="k-tabs-nav-inner" ref={navRef}>
{navNodes}
</div>
</div>
</div>
{scrollable.value ? (
<span
class={[
"k-tabs-tab-btn-next",
{ "k-tabs-tab-btn-next-disabled": nextBtnDisabled.value },
]}
onClick={() => scroll("right")}
>
<Icon type={ChevronForward} />
</span>
) : null}
</div>
{slots.extra ? (
<div class="k-tabs-extra">{slots.extra()}</div>
) : null}
</div>
<div class="k-tabs-content" style={paneStyle}>
{panels}
</div>
</div>
);
};
},
});
export default withInstall(Tabs);