feat: add `resizable` and `ColPage` component (#5188)
* feat: add component resizable * feat: component `ColPage` with demomaster
parent
1853ba1d60
commit
acd87b2250
@ -0,0 +1,48 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, type HTMLAttributes } from 'vue';
|
||||||
|
|
||||||
|
import { GripVertical } from '@vben-core/icons';
|
||||||
|
import { cn } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
SplitterResizeHandle,
|
||||||
|
type SplitterResizeHandleEmits,
|
||||||
|
type SplitterResizeHandleProps,
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from 'radix-vue';
|
||||||
|
|
||||||
|
const props = defineProps<
|
||||||
|
{
|
||||||
|
class?: HTMLAttributes['class'];
|
||||||
|
withHandle?: boolean;
|
||||||
|
} & SplitterResizeHandleProps
|
||||||
|
>();
|
||||||
|
const emits = defineEmits<SplitterResizeHandleEmits>();
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props;
|
||||||
|
return delegated;
|
||||||
|
});
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SplitterResizeHandle
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-offset-1 [&[data-orientation=vertical]>div]:rotate-90 [&[data-orientation=vertical]]:h-px [&[data-orientation=vertical]]:w-full [&[data-orientation=vertical]]:after:left-0 [&[data-orientation=vertical]]:after:h-1 [&[data-orientation=vertical]]:after:w-full [&[data-orientation=vertical]]:after:-translate-y-1/2 [&[data-orientation=vertical]]:after:translate-x-0',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template v-if="props.withHandle">
|
||||||
|
<div
|
||||||
|
class="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-sm border"
|
||||||
|
>
|
||||||
|
<GripVertical class="h-2.5 w-2.5" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</SplitterResizeHandle>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, type HTMLAttributes } from 'vue';
|
||||||
|
|
||||||
|
import { cn } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
SplitterGroup,
|
||||||
|
type SplitterGroupEmits,
|
||||||
|
type SplitterGroupProps,
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from 'radix-vue';
|
||||||
|
|
||||||
|
const props = defineProps<
|
||||||
|
{ class?: HTMLAttributes['class'] } & SplitterGroupProps
|
||||||
|
>();
|
||||||
|
const emits = defineEmits<SplitterGroupEmits>();
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props;
|
||||||
|
return delegated;
|
||||||
|
});
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SplitterGroup
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'flex h-full w-full data-[panel-group-direction=vertical]:flex-col',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</SplitterGroup>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
export { default as ResizableHandle } from './ResizableHandle.vue';
|
||||||
|
export { default as ResizablePanelGroup } from './ResizablePanelGroup.vue';
|
||||||
|
export { SplitterPanel as ResizablePanel } from 'radix-vue';
|
||||||
@ -0,0 +1,107 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { ColPageProps } from './types';
|
||||||
|
|
||||||
|
import { computed, ref, useSlots } from 'vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ResizableHandle,
|
||||||
|
ResizablePanel,
|
||||||
|
ResizablePanelGroup,
|
||||||
|
} from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
|
import Page from '../page/page.vue';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'ColPage',
|
||||||
|
inheritAttrs: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<ColPageProps>(), {
|
||||||
|
leftWidth: 30,
|
||||||
|
rightWidth: 70,
|
||||||
|
resizable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { leftWidth: _, ...delegated } = props;
|
||||||
|
return delegated;
|
||||||
|
});
|
||||||
|
|
||||||
|
const slots = useSlots();
|
||||||
|
|
||||||
|
const delegatedSlots = computed(() => {
|
||||||
|
const resultSlots: string[] = [];
|
||||||
|
|
||||||
|
for (const key of Object.keys(slots)) {
|
||||||
|
if (!['default', 'left'].includes(key)) {
|
||||||
|
resultSlots.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultSlots;
|
||||||
|
});
|
||||||
|
|
||||||
|
const leftPanelRef = ref<InstanceType<typeof ResizablePanel>>();
|
||||||
|
|
||||||
|
function expandLeft() {
|
||||||
|
leftPanelRef.value?.expand();
|
||||||
|
}
|
||||||
|
|
||||||
|
function collapseLeft() {
|
||||||
|
leftPanelRef.value?.collapse();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
expandLeft,
|
||||||
|
collapseLeft,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page v-bind="delegatedProps">
|
||||||
|
<!-- 继承默认的slot -->
|
||||||
|
<template
|
||||||
|
v-for="slotName in delegatedSlots"
|
||||||
|
:key="slotName"
|
||||||
|
#[slotName]="slotProps"
|
||||||
|
>
|
||||||
|
<slot :name="slotName" v-bind="slotProps"></slot>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<ResizablePanelGroup class="w-full" direction="horizontal">
|
||||||
|
<ResizablePanel
|
||||||
|
ref="leftPanelRef"
|
||||||
|
:collapsed-size="leftCollapsedWidth"
|
||||||
|
:collapsible="leftCollapsible"
|
||||||
|
:default-size="leftWidth"
|
||||||
|
:max-size="leftMaxWidth"
|
||||||
|
:min-size="leftMinWidth"
|
||||||
|
>
|
||||||
|
<template #default="slotProps">
|
||||||
|
<slot
|
||||||
|
name="left"
|
||||||
|
v-bind="{
|
||||||
|
...slotProps,
|
||||||
|
expand: expandLeft,
|
||||||
|
collapse: collapseLeft,
|
||||||
|
}"
|
||||||
|
></slot>
|
||||||
|
</template>
|
||||||
|
</ResizablePanel>
|
||||||
|
<ResizableHandle
|
||||||
|
v-if="resizable"
|
||||||
|
:style="{ backgroundColor: splitLine ? undefined : 'transparent' }"
|
||||||
|
:with-handle="splitHandle"
|
||||||
|
/>
|
||||||
|
<ResizablePanel
|
||||||
|
:collapsed-size="rightCollapsedWidth"
|
||||||
|
:collapsible="rightCollapsible"
|
||||||
|
:default-size="rightWidth"
|
||||||
|
:max-size="rightMaxWidth"
|
||||||
|
:min-size="rightMinWidth"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<slot></slot>
|
||||||
|
</template>
|
||||||
|
</ResizablePanel>
|
||||||
|
</ResizablePanelGroup>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export { default as ColPage } from './col-page.vue';
|
||||||
|
export * from './types';
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import type { PageProps } from '../page/types';
|
||||||
|
|
||||||
|
export interface ColPageProps extends PageProps {
|
||||||
|
/**
|
||||||
|
* 左侧宽度
|
||||||
|
* @default 30
|
||||||
|
*/
|
||||||
|
leftWidth?: number;
|
||||||
|
leftMinWidth?: number;
|
||||||
|
leftMaxWidth?: number;
|
||||||
|
leftCollapsedWidth?: number;
|
||||||
|
leftCollapsible?: boolean;
|
||||||
|
/**
|
||||||
|
* 右侧宽度
|
||||||
|
* @default 70
|
||||||
|
*/
|
||||||
|
rightWidth?: number;
|
||||||
|
rightMinWidth?: number;
|
||||||
|
rightCollapsedWidth?: number;
|
||||||
|
rightMaxWidth?: number;
|
||||||
|
rightCollapsible?: boolean;
|
||||||
|
|
||||||
|
resizable?: boolean;
|
||||||
|
splitLine?: boolean;
|
||||||
|
splitHandle?: boolean;
|
||||||
|
}
|
||||||
@ -1 +1,2 @@
|
|||||||
export { default as Page } from './page.vue';
|
export { default as Page } from './page.vue';
|
||||||
|
export * from './types';
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
export interface PageProps {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
contentClass?: string;
|
||||||
|
/**
|
||||||
|
* 根据content可见高度自适应
|
||||||
|
*/
|
||||||
|
autoContentHeight?: boolean;
|
||||||
|
headerClass?: string;
|
||||||
|
footerClass?: string;
|
||||||
|
}
|
||||||
Loading…
Reference in new issue