mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
feat(components): [drawer] add resizable prop (#21608)
* feat(components): [drawer] add `resizable` prop * style(theme-chalk): [drawer] update based on review feeddback * chore: adjust style & example * docs: update example * docs: remove default direction --------- Co-authored-by: btea <2356281422@qq.com>
This commit is contained in:
@@ -53,6 +53,16 @@ drawer/customization-header
|
||||
|
||||
:::
|
||||
|
||||
## Resizable Drawer ^(2.11.0)
|
||||
|
||||
Try to drag the edge part.
|
||||
|
||||
:::demo Set `resizable` to `true` to resize.
|
||||
|
||||
drawer/resizable-drawer
|
||||
|
||||
:::
|
||||
|
||||
## Nested Drawer
|
||||
|
||||
You can also have multiple layer of `Drawer` just like `Dialog`.
|
||||
@@ -93,6 +103,7 @@ Drawer provides an API called `destroyOnClose`, which is a flag variable that in
|
||||
| destroy-on-close | Indicates whether children should be destroyed after Drawer closed | ^[boolean] | false |
|
||||
| modal | Should show shadowing layer | ^[boolean] | true |
|
||||
| direction | Drawer's opening direction | ^[enum]`'rtl' \| 'ltr' \| 'ttb' \| 'btt'` | rtl |
|
||||
| resizable ^(2.11.0) | enable resizable feature for Drawer | ^[boolean] | false |
|
||||
| show-close | Should show close button at the top right of Drawer | ^[boolean] | true |
|
||||
| size | Drawer's size, if Drawer is horizontal mode, it effects the width property, otherwise it effects the height property, when size is `number` type, it describes the size by unit of pixels; when size is `string` type, it should be used with `x%` notation, other wise it will be interpreted to pixel unit | ^[number] / ^[string] | 30% |
|
||||
| title | Drawer's title, can also be set by named slot, detailed descriptions can be found in the slot form | ^[string] | — |
|
||||
|
||||
31
docs/examples/drawer/resizable-drawer.vue
Normal file
31
docs/examples/drawer/resizable-drawer.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<el-segmented
|
||||
v-model="direction"
|
||||
:options="options"
|
||||
size="large"
|
||||
@change="visible = true"
|
||||
/>
|
||||
|
||||
<el-drawer v-model="visible" :direction="direction" resizable>
|
||||
<template #header="{ titleId, titleClass }">
|
||||
<h4 :id="titleId" :class="titleClass">This is a custom header!</h4>
|
||||
</template>
|
||||
This is drawer content.
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
import type { DrawerProps } from 'element-plus'
|
||||
|
||||
const visible = ref(false)
|
||||
const direction = ref<DrawerProps['direction']>()
|
||||
|
||||
const options = [
|
||||
{ label: 'Top', value: 'ttb' },
|
||||
{ label: 'Right', value: 'rtl' },
|
||||
{ label: 'Bottom', value: 'btt' },
|
||||
{ label: 'Left', value: 'ltr' },
|
||||
]
|
||||
</script>
|
||||
@@ -406,15 +406,15 @@ describe('Drawer', () => {
|
||||
)
|
||||
|
||||
test('should effect height when drawer is vertical', async () => {
|
||||
const drawerEl = renderer('50%', true).find('.el-drawer')
|
||||
.element as HTMLDivElement
|
||||
expect(drawerEl.style.width).toEqual('50%')
|
||||
const wrapper = renderer('50%', true)
|
||||
const drawerEl = wrapper.findAllComponents({ name: 'ElSplitterPanel' })[0]
|
||||
expect(drawerEl.vm.size).toContain('50%')
|
||||
})
|
||||
|
||||
test('should effect width when drawer is horizontal', async () => {
|
||||
const drawerEl = renderer('50%', false).find('.el-drawer')
|
||||
.element as HTMLDivElement
|
||||
expect(drawerEl.style.height).toEqual('50%')
|
||||
const wrapper = renderer('50%', false)
|
||||
const drawerEl = wrapper.findAllComponents({ name: 'ElSplitterPanel' })[0]
|
||||
expect(drawerEl.vm.size).toContain('50%')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ export const drawerProps = buildProps({
|
||||
default: 'rtl',
|
||||
values: ['ltr', 'rtl', 'ttb', 'btt'],
|
||||
},
|
||||
resizable: Boolean,
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: '30%',
|
||||
|
||||
@@ -26,63 +26,85 @@
|
||||
@focusout-prevented="onFocusoutPrevented"
|
||||
@release-requested="onCloseRequested"
|
||||
>
|
||||
<div
|
||||
ref="drawerRef"
|
||||
aria-modal="true"
|
||||
:aria-label="title || undefined"
|
||||
:aria-labelledby="!title ? titleId : undefined"
|
||||
:aria-describedby="bodyId"
|
||||
v-bind="$attrs"
|
||||
:class="[ns.b(), direction, visible && 'open']"
|
||||
:style="
|
||||
isHorizontal ? 'width: ' + drawerSize : 'height: ' + drawerSize
|
||||
"
|
||||
role="dialog"
|
||||
@click.stop
|
||||
<el-splitter
|
||||
:class="ns.b('splitter')"
|
||||
:layout="isHorizontal ? 'horizontal' : 'vertical'"
|
||||
>
|
||||
<span ref="focusStartRef" :class="ns.e('sr-focus')" tabindex="-1" />
|
||||
<header v-if="withHeader" :class="[ns.e('header'), headerClass]">
|
||||
<slot
|
||||
v-if="!$slots.title"
|
||||
name="header"
|
||||
:close="handleClose"
|
||||
:title-id="titleId"
|
||||
:title-class="ns.e('title')"
|
||||
<el-splitter-panel
|
||||
v-if="['rtl', 'btt'].includes(direction)"
|
||||
@click="onModalClick"
|
||||
/>
|
||||
<el-splitter-panel :resizable="resizable" :size="drawerSize">
|
||||
<div
|
||||
ref="drawerRef"
|
||||
aria-modal="true"
|
||||
:aria-label="title || undefined"
|
||||
:aria-labelledby="!title ? titleId : undefined"
|
||||
:aria-describedby="bodyId"
|
||||
v-bind="$attrs"
|
||||
:class="[ns.b(), direction, visible && 'open']"
|
||||
role="dialog"
|
||||
@click.stop
|
||||
>
|
||||
<span
|
||||
v-if="!$slots.title"
|
||||
:id="titleId"
|
||||
role="heading"
|
||||
:aria-level="headerAriaLevel"
|
||||
:class="ns.e('title')"
|
||||
ref="focusStartRef"
|
||||
:class="ns.e('sr-focus')"
|
||||
tabindex="-1"
|
||||
/>
|
||||
<header
|
||||
v-if="withHeader"
|
||||
:class="[ns.e('header'), headerClass]"
|
||||
>
|
||||
{{ title }}
|
||||
</span>
|
||||
</slot>
|
||||
<slot v-else name="title">
|
||||
<!-- DEPRECATED SLOT -->
|
||||
</slot>
|
||||
<button
|
||||
v-if="showClose"
|
||||
:aria-label="t('el.drawer.close')"
|
||||
:class="ns.e('close-btn')"
|
||||
type="button"
|
||||
@click="handleClose"
|
||||
>
|
||||
<el-icon :class="ns.e('close')">
|
||||
<close />
|
||||
</el-icon>
|
||||
</button>
|
||||
</header>
|
||||
<template v-if="rendered">
|
||||
<div :id="bodyId" :class="[ns.e('body'), bodyClass]">
|
||||
<slot />
|
||||
<slot
|
||||
v-if="!$slots.title"
|
||||
name="header"
|
||||
:close="handleClose"
|
||||
:title-id="titleId"
|
||||
:title-class="ns.e('title')"
|
||||
>
|
||||
<span
|
||||
v-if="!$slots.title"
|
||||
:id="titleId"
|
||||
role="heading"
|
||||
:aria-level="headerAriaLevel"
|
||||
:class="ns.e('title')"
|
||||
>
|
||||
{{ title }}
|
||||
</span>
|
||||
</slot>
|
||||
<slot v-else name="title">
|
||||
<!-- DEPRECATED SLOT -->
|
||||
</slot>
|
||||
<button
|
||||
v-if="showClose"
|
||||
:aria-label="t('el.drawer.close')"
|
||||
:class="ns.e('close-btn')"
|
||||
type="button"
|
||||
@click="handleClose"
|
||||
>
|
||||
<el-icon :class="ns.e('close')">
|
||||
<close />
|
||||
</el-icon>
|
||||
</button>
|
||||
</header>
|
||||
<template v-if="rendered">
|
||||
<div :id="bodyId" :class="[ns.e('body'), bodyClass]">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
v-if="$slots.footer"
|
||||
:class="[ns.e('footer'), footerClass]"
|
||||
>
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="$slots.footer" :class="[ns.e('footer'), footerClass]">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</div>
|
||||
</el-splitter-panel>
|
||||
<el-splitter-panel
|
||||
v-if="['ltr', 'ttb'].includes(direction)"
|
||||
@click="onModalClick"
|
||||
/>
|
||||
</el-splitter>
|
||||
</el-focus-trap>
|
||||
</el-overlay>
|
||||
</transition>
|
||||
@@ -95,11 +117,12 @@ import { Close } from '@element-plus/icons-vue'
|
||||
import { ElOverlay } from '@element-plus/components/overlay'
|
||||
import ElFocusTrap from '@element-plus/components/focus-trap'
|
||||
import ElTeleport from '@element-plus/components/teleport'
|
||||
import ElSplitter, { ElSplitterPanel } from '@element-plus/components/splitter'
|
||||
import { useDialog } from '@element-plus/components/dialog'
|
||||
import { addUnit } from '@element-plus/utils'
|
||||
import ElIcon from '@element-plus/components/icon'
|
||||
import { useDeprecated, useLocale, useNamespace } from '@element-plus/hooks'
|
||||
import { drawerEmits, drawerProps } from './drawer'
|
||||
import { addUnit } from '@element-plus/utils'
|
||||
|
||||
defineOptions({
|
||||
name: 'ElDrawer',
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import '@element-plus/components/base/style/css'
|
||||
import '@element-plus/theme-chalk/el-drawer.css'
|
||||
import '@element-plus/components/overlay/style/css'
|
||||
import '@element-plus/components/splitter/style/css'
|
||||
import '@element-plus/components/splitter-panel/style/css'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import '@element-plus/components/base/style'
|
||||
import '@element-plus/theme-chalk/src/drawer.scss'
|
||||
import '@element-plus/components/overlay/style'
|
||||
import '@element-plus/components/splitter/style'
|
||||
import '@element-plus/components/splitter-panel/style'
|
||||
|
||||
@@ -9,7 +9,8 @@ $directions: rtl, ltr, ttb, btt;
|
||||
}
|
||||
|
||||
@include b(drawer) {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: getCssVar('drawer', 'bg-color');
|
||||
display: flex;
|
||||
@@ -78,35 +79,29 @@ $directions: rtl, ltr, ttb, btt;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.ltr,
|
||||
&.rtl {
|
||||
height: 100%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.#{$namespace}-drawer-splitter {
|
||||
$bar: #{$namespace}-splitter-bar;
|
||||
|
||||
&.ttb,
|
||||
&.btt {
|
||||
width: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
> .#{$bar} {
|
||||
.#{$bar}__disable {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.ltr {
|
||||
left: 0;
|
||||
}
|
||||
.#{$bar}__dragger {
|
||||
&-horizontal::before {
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
&.rtl {
|
||||
right: 0;
|
||||
}
|
||||
&-vertical::before {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
&.ttb {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&.btt {
|
||||
bottom: 0;
|
||||
&:not(&-active, &:hover)::before {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +109,10 @@ $directions: rtl, ltr, ttb, btt;
|
||||
&-enter-active,
|
||||
&-leave-active {
|
||||
transition: all getCssVar('transition-duration');
|
||||
|
||||
.#{$namespace}-splitter-bar__dragger {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-enter-from,
|
||||
|
||||
Reference in New Issue
Block a user