chore: update uikit -> ui-kit

This commit is contained in:
vben
2024-06-23 20:03:41 +08:00
parent 89586ef2c4
commit d4f61c283f
351 changed files with 341 additions and 391 deletions

View File

@@ -0,0 +1,5 @@
export { default as LayoutContent } from './layout-content.vue';
export { default as LayoutFooter } from './layout-footer.vue';
export { default as LayoutHeader } from './layout-header.vue';
export { default as LayoutSidebar } from './layout-sidebar.vue';
export { default as LayoutTabbar } from './layout-tabbar.vue';

View File

@@ -0,0 +1,103 @@
<script setup lang="ts">
import type { ContentCompactType } from '@vben-core/typings';
import type { CSSProperties } from 'vue';
import { computed, onMounted, ref, watch } from 'vue';
import { useCssVar, useDebounceFn, useWindowSize } from '@vueuse/core';
interface Props {
/**
* 内容区域定宽
* @default 'wide'
*/
contentCompact?: ContentCompactType;
/**
* 定宽布局宽度
* @default 1200
*/
contentCompactWidth?: number;
/**
* padding
* @default 16
*/
padding?: number;
/**
* paddingBottom
* @default 16
*/
paddingBottom?: number;
/**
* paddingLeft
* @default 16
*/
paddingLeft?: number;
/**
* paddingRight
* @default 16
*/
paddingRight?: number;
/**
* paddingTop
* @default 16
*/
paddingTop?: number;
}
const props = withDefaults(defineProps<Props>(), {
contentCompact: 'wide',
contentCompactWidth: 1200,
padding: 16,
paddingBottom: 16,
paddingLeft: 16,
paddingRight: 16,
paddingTop: 16,
});
const domElement = ref<HTMLDivElement | null>();
const { height, width } = useWindowSize();
const contentClientHeight = useCssVar('--vben-content-client-height');
const debouncedCalcHeight = useDebounceFn(() => {
contentClientHeight.value = `${domElement.value?.clientHeight ?? window.innerHeight}px`;
}, 200);
const style = computed((): CSSProperties => {
const {
contentCompact,
padding,
paddingBottom,
paddingLeft,
paddingRight,
paddingTop,
} = props;
const compactStyle: CSSProperties =
contentCompact === 'compact'
? { margin: '0 auto', width: `${props.contentCompactWidth}px` }
: {};
return {
...compactStyle,
flex: 1,
padding: `${padding}px`,
paddingBottom: `${paddingBottom}px`,
paddingLeft: `${paddingLeft}px`,
paddingRight: `${paddingRight}px`,
paddingTop: `${paddingTop}px`,
};
});
watch([height, width], () => {
debouncedCalcHeight();
});
onMounted(() => {
debouncedCalcHeight();
});
</script>
<template>
<main ref="domElement" :style="style">
<slot></slot>
</main>
</template>

View File

@@ -0,0 +1,63 @@
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import { computed } from 'vue';
interface Props {
/**
* 背景颜色
*/
backgroundColor?: string;
/**
* 是否固定在顶部
* @default true
*/
fixed?: boolean;
/**
* 高度
* @default 32
*/
height?: number;
/**
* 是否显示
* @default true
*/
show?: boolean;
/**
* 高度
* @default 100%
*/
width?: string;
/**
* zIndex
* @default 0
*/
zIndex?: number;
}
const props = withDefaults(defineProps<Props>(), {
backgroundColor: 'hsl(var(--background))',
fixed: true,
height: 32,
show: true,
width: '100%',
zIndex: 0,
});
const style = computed((): CSSProperties => {
const { backgroundColor, fixed, height, show, width, zIndex } = props;
return {
backgroundColor,
height: `${height}px`,
marginBottom: show ? '0' : `-${height}px`,
position: fixed ? 'fixed' : 'static',
width,
zIndex,
};
});
</script>
<template>
<footer :style="style" class="bottom-0 w-full transition-all duration-200">
<slot></slot>
</footer>
</template>

View File

@@ -0,0 +1,123 @@
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import { computed, useSlots } from 'vue';
import { IcRoundMenu } from '@vben-core/iconify';
import { VbenIconButton } from '@vben-core/shadcn-ui';
interface Props {
/**
* 背景颜色
*/
backgroundColor?: string;
/**
* 横屏
* @default false
*/
fullWidth?: boolean;
/**
* 高度
* @default 60
*/
height?: number;
/**
* 是否混合导航
* @default false
*/
isMixedNav?: boolean;
/**
* 是否移动端
* @default false
*/
isMobile?: boolean;
/**
* 是否显示
* @default true
*/
show?: boolean;
/**
* 是否显示关闭菜单按钮
* @default true
*/
showToggleBtn?: boolean;
/**
* 侧边菜单宽度
* @default 0
*/
sidebarWidth?: number;
/**
* 宽度
* @default 100%
*/
width?: string;
/**
* zIndex
* @default 0
*/
zIndex?: number;
}
const props = withDefaults(defineProps<Props>(), {
backgroundColor: 'hsl(var(--background))',
// fixed: true,
height: 60,
isMixedNav: false,
show: true,
showToggleBtn: false,
sidebarWidth: 0,
width: '100%',
zIndex: 0,
});
const emit = defineEmits<{ openMenu: []; toggleSidebar: [] }>();
const slots = useSlots();
const style = computed((): CSSProperties => {
const { backgroundColor, fullWidth, height, show } = props;
const right = !show || !fullWidth ? undefined : 0;
return {
// ...(props.isMixedNav ? { left: 0, position: `fixed` } : {}),
backgroundColor,
height: `${height}px`,
marginTop: show ? 0 : `-${height}px`,
right,
};
});
const logoStyle = computed((): CSSProperties => {
return {
minWidth: `${props.isMobile ? 40 : props.sidebarWidth}px`,
};
});
function handleToggleMenu() {
if (props.isMobile) {
emit('openMenu');
} else {
emit('toggleSidebar');
}
}
</script>
<template>
<header
:style="style"
class="border-border top-0 flex w-full flex-[0_0_auto] items-center border-b transition-[margin-top] duration-200"
>
<div v-if="slots.logo" :style="logoStyle">
<slot name="logo"></slot>
</div>
<VbenIconButton
v-if="showToggleBtn || isMobile"
class="my-0 ml-2 mr-1 rounded"
@click="handleToggleMenu"
>
<IcRoundMenu class="size-5" />
</VbenIconButton>
<slot></slot>
</header>
</template>

View File

@@ -0,0 +1,298 @@
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import { computed, shallowRef, useSlots, watchEffect } from 'vue';
import { VbenScrollbar } from '@vben-core/shadcn-ui';
import { SidebarCollapseButton, SidebarFixedButton } from './widgets';
interface Props {
/**
* 背景颜色
*/
backgroundColor: string;
/**
* 折叠区域高度
* @default 32
*/
collapseHeight?: number;
/**
* 折叠宽度
* @default 48
*/
collapseWidth?: number;
/**
* 隐藏的dom是否可见
* @default true
*/
domVisible?: boolean;
/**
* 扩展区域背景颜色
*/
extraBackgroundColor: string;
/**
* 扩展区域宽度
* @default 180
*/
extraWidth?: number;
/**
* 固定扩展区域
* @default false
*/
fixedExtra?: boolean;
/**
* 头部高度
*/
headerHeight: number;
/**
* 是否侧边混合模式
* @default false
*/
isSidebarMixed?: boolean;
/**
* 混合菜单宽度
* @default 80
*/
mixedWidth?: number;
/**
* 顶部padding
* @default 60
*/
paddingTop?: number;
/**
* 是否显示
* @default true
*/
show?: boolean;
/**
* 显示折叠按钮
* @default false
*/
showCollapseButton?: boolean;
/**
* 主题
*/
theme?: string;
/**
* 宽度
* @default 180
*/
width?: number;
/**
* zIndex
* @default 0
*/
zIndex?: number;
}
const props = withDefaults(defineProps<Props>(), {
collapseHeight: 42,
collapseWidth: 48,
domVisible: true,
extraWidth: 180,
fixedExtra: false,
isSideMixed: false,
mixedWidth: 80,
paddingTop: 60,
show: true,
showCollapseButton: true,
theme: 'dark',
width: 180,
zIndex: 0,
});
const emit = defineEmits<{ leave: [] }>();
const collapse = defineModel<boolean>('collapse');
const extraCollapse = defineModel<boolean>('extraCollapse');
const expandOnHovering = defineModel<boolean>('expandOnHovering');
const expandOnHover = defineModel<boolean>('expandOnHover');
const extraVisible = defineModel<boolean>('extraVisible');
const slots = useSlots();
const asideRef = shallowRef<HTMLDivElement | null>();
const hiddenSideStyle = computed((): CSSProperties => {
return calcMenuWidthStyle(true);
});
const style = computed((): CSSProperties => {
const { isSideMixed, paddingTop, zIndex } = props;
return {
...calcMenuWidthStyle(false),
paddingTop: `${paddingTop}px`,
zIndex,
...(isSideMixed && extraVisible.value ? { transition: 'none' } : {}),
};
});
const extraStyle = computed((): CSSProperties => {
const { extraBackgroundColor, extraWidth, show, width, zIndex } = props;
return {
backgroundColor: extraBackgroundColor,
left: `${width}px`,
width: extraVisible.value && show ? `${extraWidth}px` : 0,
zIndex,
};
});
const extraTitleStyle = computed((): CSSProperties => {
const { headerHeight } = props;
return {
height: `${headerHeight - 1}px`,
};
});
const contentWidthStyle = computed((): CSSProperties => {
const { collapseWidth, fixedExtra, isSideMixed, mixedWidth } = props;
if (isSideMixed && fixedExtra) {
return { width: `${collapse.value ? collapseWidth : mixedWidth}px` };
}
return {};
});
const contentStyle = computed((): CSSProperties => {
const { collapseHeight, headerHeight } = props;
return {
height: `calc(100% - ${headerHeight + collapseHeight}px)`,
paddingTop: '8px',
...contentWidthStyle.value,
};
});
const headerStyle = computed((): CSSProperties => {
const { headerHeight, isSideMixed } = props;
return {
...(isSideMixed ? { display: 'flex', justifyContent: 'center' } : {}),
height: `${headerHeight}px`,
...contentWidthStyle.value,
};
});
const extraContentStyle = computed((): CSSProperties => {
const { collapseHeight, headerHeight } = props;
return {
color: 'red',
height: `calc(100% - ${headerHeight + collapseHeight}px)`,
};
});
const collapseStyle = computed((): CSSProperties => {
const { collapseHeight } = props;
return {
height: `${collapseHeight}px`,
};
});
watchEffect(() => {
extraVisible.value = props.fixedExtra ? true : extraVisible.value;
});
function calcMenuWidthStyle(isHiddenDom: boolean): CSSProperties {
const { backgroundColor, extraWidth, fixedExtra, isSideMixed, show, width } =
props;
let widthValue = `${width + (isSideMixed && fixedExtra && extraVisible.value ? extraWidth : 0)}px`;
const { collapseWidth } = props;
if (isHiddenDom && expandOnHovering.value && !expandOnHover.value) {
widthValue = `${collapseWidth}px`;
}
return {
...(widthValue === '0px' ? { overflow: 'hidden' } : {}),
backgroundColor,
flex: `0 0 ${widthValue}`,
marginLeft: show ? 0 : `-${widthValue}`,
maxWidth: widthValue,
minWidth: widthValue,
width: widthValue,
};
}
function handleMouseenter() {
// 未开启和未折叠状态不生效
if (expandOnHover.value) {
return;
}
if (!expandOnHovering.value) {
collapse.value = false;
}
expandOnHovering.value = true;
}
function handleMouseleave() {
emit('leave');
if (expandOnHover.value) {
return;
}
expandOnHovering.value = false;
collapse.value = true;
extraVisible.value = false;
}
</script>
<template>
<div
v-if="domVisible"
:style="hiddenSideStyle"
class="h-full transition-all duration-200"
></div>
<aside
:style="style"
class="fixed left-0 top-0 h-full transition-all duration-200"
@mouseenter="handleMouseenter"
@mouseleave="handleMouseleave"
>
<SidebarFixedButton
v-if="!collapse && !isSidebarMixed"
v-model:expand-on-hover="expandOnHover"
:theme="theme"
/>
<div v-if="slots.logo" :style="headerStyle">
<slot name="logo"></slot>
</div>
<VbenScrollbar :style="contentStyle">
<slot></slot>
</VbenScrollbar>
<div :style="collapseStyle"></div>
<SidebarCollapseButton
v-if="showCollapseButton && !isSidebarMixed"
v-model:collapsed="collapse"
:theme="theme"
/>
<div
v-if="isSidebarMixed"
ref="asideRef"
:style="extraStyle"
class="fixed top-0 h-full overflow-hidden transition-all duration-200"
>
<SidebarCollapseButton
v-if="isSidebarMixed && expandOnHover"
v-model:collapsed="extraCollapse"
:theme="theme"
/>
<SidebarFixedButton
v-if="!extraCollapse"
v-model:expand-on-hover="expandOnHover"
:theme="theme"
/>
<div v-if="!extraCollapse" :style="extraTitleStyle">
<slot name="extra-title"></slot>
</div>
<VbenScrollbar :style="extraContentStyle" class="py-4">
<slot name="extra"></slot>
</VbenScrollbar>
</div>
</aside>
</template>

View File

@@ -0,0 +1,46 @@
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import { computed } from 'vue';
interface Props {
/**
* 背景颜色
*/
backgroundColor?: string;
/**
* 高度
* @default 30
*/
height?: number;
}
const props = withDefaults(defineProps<Props>(), {
backgroundColor: 'hsl(var(--background))',
fixed: true,
height: 30,
});
const hiddenStyle = computed((): CSSProperties => {
const { height } = props;
return {
height: `${height}px`,
};
});
const style = computed((): CSSProperties => {
const { backgroundColor } = props;
return {
...hiddenStyle.value,
backgroundColor,
};
});
</script>
<template>
<section :style="style" class="border-border flex w-full border-b">
<slot></slot>
<div class="flex items-center">
<slot name="toolbar"></slot>
</div>
</section>
</template>

View File

@@ -0,0 +1,2 @@
export { default as SidebarCollapseButton } from './sidebar-collapse-button.vue';
export { default as SidebarFixedButton } from './sidebar-fixed-button.vue';

View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
import { MdiMenuClose, MdiMenuOpen } from '@vben-core/iconify';
interface Props {
theme: string;
}
withDefaults(defineProps<Props>(), {});
const collapsed = defineModel<boolean>('collapsed');
function handleCollapsed() {
collapsed.value = !collapsed.value;
}
</script>
<template>
<div
:data-theme="theme"
class="flex-center hover:text-foreground text-foreground/60 hover:bg-accent-hover bg-accent absolute bottom-2 left-3 z-10 cursor-pointer rounded-sm p-1 transition-all duration-300 data-[theme=dark]:bg-[hsl(var(--dark-accent))] data-[theme=dark]:text-[hsl(var(--dark-foreground)/60%)] data-[theme=dark]:hover:bg-[hsl(var(--dark-accent-hover))] data-[theme=dark]:hover:text-[hsl(var(--dark-foreground))]"
@click.stop="handleCollapsed"
>
<MdiMenuClose v-if="collapsed" />
<MdiMenuOpen v-else />
</div>
</template>

View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
import { MdiPin, MdiPinOff } from '@vben-core/iconify';
interface Props {
theme: string;
}
withDefaults(defineProps<Props>(), {});
const expandOnHover = defineModel<boolean>('expandOnHover');
function toggleFixed() {
expandOnHover.value = !expandOnHover.value;
}
</script>
<template>
<div
:data-theme="theme"
class="flex-center hover:text-foreground text-foreground/60 hover:bg-accent-hover bg-accent absolute bottom-2 right-3 z-10 cursor-pointer rounded-sm p-1 transition-all duration-300 data-[theme=dark]:bg-[hsl(var(--dark-accent))] data-[theme=dark]:text-[hsl(var(--dark-foreground)/60%)] data-[theme=dark]:hover:bg-[hsl(var(--dark-accent-hover))] data-[theme=dark]:hover:text-[hsl(var(--dark-foreground))]"
@click="toggleFixed"
>
<MdiPinOff v-if="!expandOnHover" />
<MdiPin v-else />
</div>
</template>