mirror of
https://github.com/element-plus/element-plus.git
synced 2025-02-17 11:49:41 +08:00
feat(components): [page-header] enhancement (#9215)
* feat(components): [page-header] enhancement * Add new slot `extra` for page header. * Add addtional documentations for page header. * chore: update doc * feat: add default slot * chore: add a11y support for back button * chore: update examples * chore: enhancements * chore: example enhancement * chore: restore previous actions for back button * chore: address PR comments Co-authored-by: JeremyWuuuuu <15975785+JeremyWuuuuu@users.noreply.github.com>
This commit is contained in:
parent
a1834a4151
commit
9230af7976
@ -7,8 +7,18 @@ lang: en-US
|
||||
|
||||
If path of the page is simple, it is recommended to use PageHeader instead of the Breadcrumb.
|
||||
|
||||
## Complete example
|
||||
|
||||
:::demo
|
||||
|
||||
page-header/complete
|
||||
|
||||
:::
|
||||
|
||||
## Basic usage
|
||||
|
||||
Standard page header, for simply scenarios.
|
||||
|
||||
:::demo
|
||||
|
||||
page-header/basic
|
||||
@ -17,12 +27,77 @@ page-header/basic
|
||||
|
||||
## Custom icon
|
||||
|
||||
The default icon might not meet your satisfaction, you can customize the icon by setting `icon` attribute
|
||||
like the example.
|
||||
|
||||
:::demo
|
||||
|
||||
page-header/custom-icon
|
||||
|
||||
:::
|
||||
|
||||
## No icon
|
||||
|
||||
Sometimes the page is just full of elements, and you might not want the icon to show up on the page,
|
||||
you can set the `icon` attribute to `null` to get rid of it.
|
||||
|
||||
:::demo
|
||||
|
||||
page-header/no-icon
|
||||
|
||||
:::
|
||||
|
||||
## Breadcrumbs
|
||||
|
||||
Page header allows you to add breadcrumbs for giving route information to the users by `breadcrumb` slot.
|
||||
|
||||
:::demo
|
||||
|
||||
page-header/breadcrumb
|
||||
|
||||
:::
|
||||
|
||||
## Additional operation section
|
||||
|
||||
The header can be as complicated as needed, you may add additional sections to the header, to allow rich
|
||||
interactions.
|
||||
|
||||
:::demo
|
||||
|
||||
page-header/additional-sections
|
||||
|
||||
:::
|
||||
|
||||
## Main content
|
||||
|
||||
Sometimes we want the head to show with some co-responding content, we can utilize the `default` slot for doing so.
|
||||
|
||||
:::demo
|
||||
|
||||
page-header/main-content
|
||||
|
||||
:::
|
||||
|
||||
## Anatomy
|
||||
|
||||
The component is consisted of these parts
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-page-header>
|
||||
<!-- Line 1 -->
|
||||
<template #breadcrumb />
|
||||
<!-- Line 2 -->
|
||||
<template #icon />
|
||||
<template #title />
|
||||
<template #content />
|
||||
<template #extra />
|
||||
<!-- Lines after 2 -->
|
||||
<template #default />
|
||||
</el-page-header>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Attributes
|
||||
|
||||
| Attribute | Description | Type | Accepted Values | Default |
|
||||
@ -39,8 +114,11 @@ page-header/custom-icon
|
||||
|
||||
## Slots
|
||||
|
||||
| Name | Description |
|
||||
| ------- | ------------- |
|
||||
| icon | custom icon |
|
||||
| title | title content |
|
||||
| content | content |
|
||||
| Name | Description |
|
||||
| ---------- | ------------------ |
|
||||
| icon | custom icon |
|
||||
| title | title content |
|
||||
| content | content |
|
||||
| extra | extra |
|
||||
| breadcrumb | breadcrumb content |
|
||||
| default | main content |
|
||||
|
24
docs/examples/page-header/additional-sections.vue
Normal file
24
docs/examples/page-header/additional-sections.vue
Normal file
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<el-page-header :icon="null">
|
||||
<template #content>
|
||||
<div class="flex items-center">
|
||||
<el-avatar
|
||||
:size="32"
|
||||
class="mr-3"
|
||||
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
|
||||
/>
|
||||
<span class="text-large font-600 mr-3"> Title </span>
|
||||
<span class="text-sm mr-2" style="color: var(--el-text-color-regular)">
|
||||
Sub title
|
||||
</span>
|
||||
<el-tag>Default</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #extra>
|
||||
<div class="flex items-center">
|
||||
<el-button>Print</el-button>
|
||||
<el-button type="primary" class="ml-2">Edit</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-page-header>
|
||||
</template>
|
@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<el-page-header content="detail" @back="goBack" />
|
||||
<el-page-header @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600 mr-3"> Title </span>
|
||||
</template>
|
||||
</el-page-header>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
const goBack = () => {
|
||||
|
18
docs/examples/page-header/breadcrumb.vue
Normal file
18
docs/examples/page-header/breadcrumb.vue
Normal file
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<el-page-header>
|
||||
<template #breadcrumb>
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item :to="{ path: './page-header.html' }">
|
||||
homepage
|
||||
</el-breadcrumb-item>
|
||||
<el-breadcrumb-item
|
||||
><a href="./page-header.html">route 1</a></el-breadcrumb-item
|
||||
>
|
||||
<el-breadcrumb-item>route 2</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
<template #content>
|
||||
<span class="text-large font-600 mr-3"> Title </span>
|
||||
</template>
|
||||
</el-page-header>
|
||||
</template>
|
69
docs/examples/page-header/complete.vue
Normal file
69
docs/examples/page-header/complete.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div aria-label="A complete example of page header">
|
||||
<el-page-header @back="onBack">
|
||||
<template #breadcrumb>
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item :to="{ path: './page-header.html' }">
|
||||
homepage
|
||||
</el-breadcrumb-item>
|
||||
<el-breadcrumb-item
|
||||
><a href="./page-header.html">route 1</a></el-breadcrumb-item
|
||||
>
|
||||
<el-breadcrumb-item>route 2</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="flex items-center">
|
||||
<el-avatar
|
||||
class="mr-3"
|
||||
:size="32"
|
||||
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
|
||||
/>
|
||||
<span class="text-large font-600 mr-3"> Title </span>
|
||||
<span
|
||||
class="text-sm mr-2"
|
||||
style="color: var(--el-text-color-regular)"
|
||||
>
|
||||
Sub title
|
||||
</span>
|
||||
<el-tag>Default</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #extra>
|
||||
<div class="flex items-center">
|
||||
<el-button>Print</el-button>
|
||||
<el-button type="primary" class="ml-2">Edit</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-descriptions :column="3" size="small" class="mt-4">
|
||||
<el-descriptions-item label="Username"
|
||||
>kooriookami</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item label="Telephone"
|
||||
>18100000000</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item label="Place">Suzhou</el-descriptions-item>
|
||||
<el-descriptions-item label="Remarks">
|
||||
<el-tag size="small">School</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="Address"
|
||||
>No.1188, Wuzhong Avenue, Wuzhong District, Suzhou, Jiangsu Province
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<p class="mt-4 text-sm">
|
||||
Element Plus team uses <b>weekly</b> release strategy under normal
|
||||
circumstance, but critical bug fixes would require hotfix so the actual
|
||||
release number <b>could be</b> more than 1 per week.
|
||||
</p>
|
||||
</el-page-header>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElNotification as notify } from 'element-plus'
|
||||
|
||||
const onBack = () => {
|
||||
notify('Back')
|
||||
}
|
||||
</script>
|
@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<el-page-header :icon="ArrowLeft" content="detail" />
|
||||
<el-page-header :icon="ArrowLeft">
|
||||
<template #content>
|
||||
<span class="text-large font-600 mr-3"> Title </span>
|
||||
</template>
|
||||
</el-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
11
docs/examples/page-header/main-content.vue
Normal file
11
docs/examples/page-header/main-content.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<el-page-header>
|
||||
<template #content>
|
||||
<span class="text-large font-600 mr-3"> Title </span>
|
||||
</template>
|
||||
<div class="mt-4 text-sm font-bold">
|
||||
Your additional content can be added with default slot, You may put as
|
||||
many content as you want here.
|
||||
</div>
|
||||
</el-page-header>
|
||||
</template>
|
7
docs/examples/page-header/no-icon.vue
Normal file
7
docs/examples/page-header/no-icon.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<el-page-header :icon="null">
|
||||
<template #content>
|
||||
<span class="text-large font-600 mr-3"> Title </span>
|
||||
</template>
|
||||
</el-page-header>
|
||||
</template>
|
@ -29,15 +29,53 @@ describe('PageHeader.vue', () => {
|
||||
expect(wrapper.find('.el-page-header__icon').text()).toEqual(AXIOM)
|
||||
})
|
||||
|
||||
test('slot content', () => {
|
||||
const wrapper = mount(() => (
|
||||
<PageHeader
|
||||
v-slots={{
|
||||
content: () => AXIOM,
|
||||
}}
|
||||
/>
|
||||
))
|
||||
expect(wrapper.find('.el-page-header__content').text()).toEqual(AXIOM)
|
||||
describe('slots', () => {
|
||||
test('content', () => {
|
||||
const wrapper = mount(() => (
|
||||
<PageHeader
|
||||
v-slots={{
|
||||
content: () => AXIOM,
|
||||
}}
|
||||
/>
|
||||
))
|
||||
expect(wrapper.find('.el-page-header__content').text()).toEqual(AXIOM)
|
||||
})
|
||||
|
||||
test('breadcrumb', () => {
|
||||
const wrapper = mount(() => (
|
||||
<PageHeader
|
||||
v-slots={{
|
||||
breadcrumb: () => AXIOM,
|
||||
}}
|
||||
/>
|
||||
))
|
||||
expect(wrapper.find('.el-page-header__breadcrumb').exists()).toBe(true)
|
||||
expect(wrapper.classes()).toContain('el-page-header--has-breadcrumb')
|
||||
})
|
||||
|
||||
test('extra', () => {
|
||||
const wrapper = mount(() => (
|
||||
<PageHeader
|
||||
v-slots={{
|
||||
extra: () => AXIOM,
|
||||
}}
|
||||
/>
|
||||
))
|
||||
expect(wrapper.find('.el-page-header__extra').exists()).toBe(true)
|
||||
expect(wrapper.classes()).toContain('el-page-header--has-extra')
|
||||
})
|
||||
|
||||
test('default', () => {
|
||||
const wrapper = mount(() => (
|
||||
<PageHeader
|
||||
v-slots={{
|
||||
default: () => AXIOM,
|
||||
}}
|
||||
/>
|
||||
))
|
||||
expect(wrapper.find('.el-page-header__main').exists()).toBe(true)
|
||||
expect(wrapper.classes()).toContain('is-contentful')
|
||||
})
|
||||
})
|
||||
|
||||
test('prop title', () => {
|
||||
|
@ -1,24 +1,51 @@
|
||||
<template>
|
||||
<div :class="ns.b()">
|
||||
<div :class="ns.e('left')" @click="handleClick">
|
||||
<div v-if="icon || $slots.icon" :class="ns.e('icon')">
|
||||
<slot name="icon">
|
||||
<el-icon v-if="icon">
|
||||
<component :is="icon" />
|
||||
</el-icon>
|
||||
</slot>
|
||||
<div :class="kls">
|
||||
<div :class="ns.e('breadcrumb')">
|
||||
<slot name="breadcrumb" />
|
||||
</div>
|
||||
<div :class="ns.e('header')">
|
||||
<div :class="ns.e('left')">
|
||||
<div
|
||||
:class="ns.e('back')"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="handleClick"
|
||||
>
|
||||
<div
|
||||
v-if="icon || $slots.icon"
|
||||
:aria-label="title || t('el.pageHeader.title')"
|
||||
:class="ns.e('icon')"
|
||||
>
|
||||
<slot name="icon">
|
||||
<el-icon v-if="icon">
|
||||
<component :is="icon" />
|
||||
</el-icon>
|
||||
</slot>
|
||||
</div>
|
||||
<div :class="ns.e('title')">
|
||||
<slot name="title">{{ title || t('el.pageHeader.title') }}</slot>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider direction="vertical" />
|
||||
<div :class="ns.e('content')">
|
||||
<slot name="content">{{ content }}</slot>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="ns.e('title')">
|
||||
<slot name="title">{{ title || t('el.pageHeader.title') }}</slot>
|
||||
|
||||
<div v-if="$slots.extra" :class="ns.e('extra')">
|
||||
<slot name="extra" />
|
||||
</div>
|
||||
</div>
|
||||
<div :class="ns.e('content')">
|
||||
<slot name="content">{{ content }}</slot>
|
||||
|
||||
<div v-if="$slots.default" :class="ns.e('main')">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, useSlots } from 'vue'
|
||||
import { ElIcon } from '@element-plus/components/icon'
|
||||
import { ElDivider } from '@element-plus/components/divider'
|
||||
|
||||
import { useLocale, useNamespace } from '@element-plus/hooks'
|
||||
import { pageHeaderEmits, pageHeaderProps } from './page-header'
|
||||
@ -26,11 +53,23 @@ import { pageHeaderEmits, pageHeaderProps } from './page-header'
|
||||
defineOptions({
|
||||
name: 'ElPageHeader',
|
||||
})
|
||||
|
||||
defineProps(pageHeaderProps)
|
||||
const emit = defineEmits(pageHeaderEmits)
|
||||
const slots = useSlots()
|
||||
|
||||
const { t } = useLocale()
|
||||
const ns = useNamespace('page-header')
|
||||
const kls = computed(() => {
|
||||
return [
|
||||
ns.b(),
|
||||
{
|
||||
[ns.m('has-breadcrumb')]: !!slots.breadcrumb,
|
||||
[ns.m('has-extra')]: !!slots.extra,
|
||||
[ns.is('contentful')]: !!slots.default,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
function handleClick() {
|
||||
emit('back')
|
||||
|
@ -2,40 +2,50 @@
|
||||
@use 'common/var' as *;
|
||||
|
||||
@include b(page-header) {
|
||||
display: flex;
|
||||
line-height: 24px;
|
||||
|
||||
@include e(left) {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
margin-right: 40px;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
right: -20px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background-color: getCssVar('border-color');
|
||||
@include when(contentful) {
|
||||
@include e('main') {
|
||||
border-top: 1px solid getCssVar('border-color', 'light');
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@include e(icon) {
|
||||
font-size: 18px;
|
||||
margin-right: 6px;
|
||||
@include e(header) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
line-height: 24px;
|
||||
|
||||
@include e(left) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 40px;
|
||||
position: relative;
|
||||
|
||||
.#{$namespace}-icon {
|
||||
font-size: inherit;
|
||||
@include e('back') {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
@include e(title) {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
.#{$namespace}-divider--vertical {
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
@include e(icon) {
|
||||
font-size: 16px;
|
||||
margin-right: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.#{$namespace}-icon {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
@include e(title) {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,4 +53,8 @@
|
||||
font-size: 18px;
|
||||
color: getCssVar('text-color', 'primary');
|
||||
}
|
||||
|
||||
@include e(breadcrumb) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user