chore: init project

This commit is contained in:
vben
2024-05-19 21:20:42 +08:00
commit 399334ac57
630 changed files with 45623 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
{
"name": "@vben-core/tabs-ui",
"version": "1.0.0",
"type": "module",
"license": "MIT",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "packages/@vben-core/uikit/tabs-ui"
},
"bugs": {
"url": "https://github.com/vbenjs/vue-vben-admin/issues"
},
"scripts": {
"build": "pnpm vite build",
"prepublishOnly": "npm run build"
},
"files": [
"dist"
],
"sideEffects": [
"**/*.css"
],
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"imports": {
"#*": "./src/*"
},
"exports": {
".": {
"development": "./src/index.ts",
"types": "./src/index.ts",
"default": "./dist/index.mjs"
}
},
"publishConfig": {
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.mjs"
}
}
},
"dependencies": {
"@vben-core/design": "workspace:*",
"@vben-core/iconify": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben-core/toolkit": "workspace:*",
"@vben-core/typings": "workspace:*",
"vue": "^3.4.27"
}
}

View File

@@ -0,0 +1 @@
export { default } from '@vben/tailwind-config/postcss';

View File

@@ -0,0 +1,3 @@
export { default as Tabs } from './tabs.vue';
export { default as TabsMore } from './tabs-more.vue';
export { default as TabsScreen } from './tabs-screen.vue';

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
import { useNamespace } from '@vben-core/toolkit';
defineOptions({
name: 'TabBackground',
});
const { b, e } = useNamespace('tab-background');
</script>
<template>
<div :class="b()">
<div :class="e('divider')"></div>
<div :class="e('content')"></div>
<svg width="10" height="10" :class="e('before')">
<path d="M 0 10 A 10 10 0 0 0 10 0 L 10 10 Z" />
</svg>
<svg width="10" height="10" :class="e('after')">
<path d="M 0 0 A 10 10 0 0 0 10 10 L 0 10 Z" />
</svg>
</div>
</template>

View File

@@ -0,0 +1,68 @@
<script setup lang="ts">
import type { IContextMenuItem } from '@vben-core/shadcn-ui';
import type { TabItem } from '@vben-core/typings';
import { IcRoundClose, MdiPin } from '@vben-core/iconify';
import { VbenContextMenu, VbenIcon } from '@vben-core/shadcn-ui';
import { useNamespace } from '@vben-core/toolkit';
import TabBackground from './tab-background.vue';
interface Props {
affixTab?: boolean;
icon?: string;
menus: (data: any) => IContextMenuItem[];
onlyOne?: boolean;
showIcon?: boolean;
tab: TabItem;
title: string;
}
defineOptions({
name: 'Tab',
});
withDefaults(defineProps<Props>(), {
icon: '',
});
const emit = defineEmits<{ close: []; unPushPin: [] }>();
const { b, e, is } = useNamespace('tab');
function handleClose() {
emit('close');
}
function handleUnPushPin() {
emit('unPushPin');
}
</script>
<template>
<div :class="[b()]">
<VbenContextMenu :menus="menus" :handler-data="tab" item-class="pr-4">
<div class="h-full">
<TabBackground />
<div :class="e('content')" :title="title">
<VbenIcon v-if="showIcon" :class="e('icon')" :icon="icon" fallback />
<span :class="[e('label'), is('hidden-icon', !icon)]">
{{ title }}
</span>
</div>
<div
v-show="!affixTab && !onlyOne"
:class="e('extra')"
@click.stop="handleClose"
>
<IcRoundClose :class="e('extra-icon')" />
</div>
<div
v-show="affixTab && !onlyOne"
:class="[e('extra'), is('pin', true)]"
@click.stop="handleUnPushPin"
>
<MdiPin :class="e('extra-icon')" />
</div>
</div>
</VbenContextMenu>
</div>
</template>

View File

@@ -0,0 +1,18 @@
<script lang="ts" setup>
import type { DropdownMenuProps } from '@vben-core/shadcn-ui';
import { IcRoundMoreVert } from '@vben-core/iconify';
import { VbenDropdownMenu } from '@vben-core/shadcn-ui';
defineProps<DropdownMenuProps>();
</script>
<template>
<VbenDropdownMenu :menus="menus">
<div
class="flex-center hover:bg-accent hover:text-foreground text-muted-foreground h-full cursor-pointer border-l px-2 text-lg font-semibold"
>
<IcRoundMoreVert />
</div>
</VbenDropdownMenu>
</template>

View File

@@ -0,0 +1,19 @@
<script lang="ts" setup>
import { IcRoundFitScreen, IcTwotoneFitScreen } from '@vben-core/iconify';
const screen = defineModel<boolean>('screen');
function toggleScreen() {
screen.value = !screen.value;
}
</script>
<template>
<div
class="flex-center hover:bg-accent hover:text-foreground text-muted-foreground h-full cursor-pointer border-l px-2 text-lg font-semibold"
@click="toggleScreen"
>
<IcTwotoneFitScreen v-if="screen" />
<IcRoundFitScreen v-else />
</div>
</template>

View File

@@ -0,0 +1,119 @@
<script setup lang="ts">
import type { IContextMenuItem } from '@vben-core/shadcn-ui';
import { useNamespace } from '@vben-core/toolkit';
import { TabItem } from '@vben-core/typings';
import { computed, nextTick, onMounted, ref, watch } from 'vue';
import Tab from './tab.vue';
interface Props {
maxWidth?: number;
menus?: (data: any) => IContextMenuItem[];
minWidth?: number;
showIcon?: boolean;
tabs?: TabItem[];
}
defineOptions({
name: 'Tabs',
});
const props = withDefaults(defineProps<Props>(), {
maxWidth: 150,
menus: () => [],
minWidth: 40,
tabs: () => [],
});
const emit = defineEmits<{ close: [string]; unPushPin: [TabItem] }>();
const gap = 7;
const active = defineModel<string>('active');
const { b, e, is } = useNamespace('tabs-ui');
const contentRef = ref();
const tabWidth = ref<number>(0);
const layout = () => {
const { maxWidth, minWidth, tabs } = props;
if (!contentRef.value) {
return Math.max(maxWidth, minWidth);
}
const contentWidth = contentRef.value.clientWidth - gap * 3;
let width = contentWidth / tabs.length;
width += gap * 2;
if (width > maxWidth) {
width = maxWidth;
}
if (width < minWidth) {
width = minWidth;
}
tabWidth.value = width;
};
const tabsView = computed(() => {
return props.tabs.map((tab) => {
return {
...tab,
affixTab: !!tab.meta?.affixTab,
icon: tab.meta.icon as string,
key: tab.fullPath || tab.path,
title: (tab.meta?.title || tab.name) as string,
};
});
});
watch(
() => props.tabs,
() => {
nextTick(() => {
layout();
});
},
);
onMounted(() => {
layout();
});
function handleClose(key: string) {
emit('close', key);
}
function handleUnPushPin(tab: TabItem) {
emit('unPushPin', tab);
}
</script>
<template>
<div :class="b()">
<div ref="contentRef" :class="e('content')">
<TransitionGroup name="slide-down">
<Tab
v-for="(tab, i) in tabsView"
:key="tab.key"
:menus="menus"
:tab="tab"
:icon="tab.icon"
:title="tab.title"
:show-icon="showIcon"
:affix-tab="tab.affixTab"
:only-one="tabsView.length <= 1"
:class="[e('tab'), is('active', tab.key === active)]"
:style="{
width: `${tabWidth}px`,
left: `${(tabWidth - gap * 2) * i}px`,
}"
@click="active = tab.key"
@close="() => handleClose(tab.key)"
@un-push-pin="() => handleUnPushPin(tab)"
/>
</TransitionGroup>
</div>
</div>
</template>
<style lang="scss">
@import '../styles/tabs.scss';
</style>

View File

@@ -0,0 +1,2 @@
export { Tabs as TabsView, TabsMore, TabsScreen } from './components';
export type { IContextMenuItem } from '@vben-core/shadcn-ui';

View File

@@ -0,0 +1,229 @@
@import '@vben-core/design/global';
@include b('tabs-ui') {
--tabs-background: hsl(var(--color-background));
--tabs-gap: 8px;
--tabs-divider: hsl(var(--color-border));
--tabs-hover: hsl(var(--color-heavy));
--tabs-active-background: hsl(var(--color-primary) / 15%);
--tabs-active: hsl(var(--color-primary));
position: relative;
width: 100%;
height: 100%;
padding-top: 2px;
background-color: var(--tabs-background);
@include e('content') {
position: relative;
height: 100%;
overflow: hidden;
}
}
@include b('tab') {
position: absolute;
box-sizing: border-box;
display: flex;
align-items: center;
height: 100%;
color: hsl(var(--color-muted-foreground));
cursor: pointer;
user-select: none;
// mask-image: linear-gradient(to right, #000 calc(100% - 0px), transparent);
@include is('active') {
z-index: 1;
color: var(--tabs-active);
.#{$namespace}-tab__extra:not(.is-pin) {
background-color: var(--tabs-active-background);
opacity: 1;
}
.#{$namespace}-tab-background__divider {
display: none;
}
.#{$namespace}-tab-background__content {
background-color: var(--tabs-active-background);
}
// .#{$namespace}-tab-background__before,
// .#{$namespace}-tab-background__after {
// fill: var(--tabs-active-background);
// }
}
@include e('content') {
position: absolute;
right: 0;
left: 0;
z-index: 1;
box-sizing: border-box;
display: flex;
align-items: center;
height: 100%;
padding-right: 10px;
margin: 0 calc(var(--tabs-gap) * 2);
overflow: hidden;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
@include e('extra') {
position: absolute;
top: 50%;
right: calc(var(--tabs-gap) * 1.5);
z-index: 1;
width: 14px;
height: 14px;
border-radius: 50%;
opacity: 0;
// transition: all 0.15s ease;
transform: translateY(-50%);
&:hover {
// background-color: hsl(var(--color-accent));
}
}
@include e('extra-icon') {
flex-shrink: 0;
width: 100%;
height: 100%;
font-size: 12px;
border-radius: 50%;
// transition: all 0.15s ease;
&:hover {
color: hsl(var(--color-foreground));
transform: scale(1.05);
}
}
@include e('icon') {
display: flex;
align-items: center;
height: 16px;
margin-left: 3%;
overflow: hidden;
img {
height: 100%;
}
}
@include e('label') {
position: relative;
box-sizing: border-box;
flex: 1;
margin-right: px;
margin-left: 5%;
overflow: hidden;
font-size: 14px;
text-align: center;
white-space: nowrap;
}
@include is('hidden-icon') {
margin-left: 0;
}
&:hover {
.#{$namespace}-tab__extra.is-pin {
opacity: 1;
}
}
&:not(.is-active):hover {
z-index: 10;
.#{$namespace}-tab__extra {
opacity: 1;
}
.#{$namespace}-tab-background__divider {
display: none;
}
.#{$namespace}-tab-background__content {
background-color: var(--tabs-hover);
}
.#{$namespace}-tab-background__before,
.#{$namespace}-tab-background__after {
fill: var(--tabs-hover);
}
}
&:last-of-type {
.#{$namespace}-tab-background__divider::after {
display: none;
}
}
&:first-of-type {
.#{$namespace}-tab-background__divider::before {
display: none;
}
}
}
@include b('tab-background') {
position: absolute;
box-sizing: border-box;
width: 100%;
height: 100%;
padding: 0 calc(var(--tabs-gap) - 1px);
@include e('divider') {
position: absolute;
left: 0;
width: calc(100% - 14px);
height: 100%;
margin: 0 7px;
&::before {
position: absolute;
top: 20%;
right: 100%;
width: 1px;
height: 60%;
content: '';
background-color: var(--tabs-divider);
}
// &::after {
// position: absolute;
// top: 20%;
// left: calc(100% - 1px);
// width: 1px;
// height: 60%;
// content: '';
// background-color: var(--tabs-divider);
// }
}
@include e('content') {
height: 100%;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
transition: background 0.15s ease;
}
@include e('before') {
position: absolute;
bottom: -1px;
left: -1px;
fill: transparent;
}
@include e('after') {
position: absolute;
right: -1px;
bottom: -1px;
fill: transparent;
}
}

View File

@@ -0,0 +1 @@
export { default } from '@vben/tailwind-config';

View File

@@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/web.json",
"include": ["src"]
}

View File

@@ -0,0 +1,3 @@
import { defineConfig } from '@vben/vite-config';
export default defineConfig();