feat(theme): add doc-hero support for custom home (#462)

This commit is contained in:
pengzhanbo 2025-02-16 15:02:26 +08:00 committed by GitHub
parent 8dd860e570
commit 6442ffa25a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 435 additions and 12 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 774 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

View File

@ -32,13 +32,13 @@ outline: 2
```md
::: demo-wrapper img no-padding
![hero](/images/custom-hero.png)
![hero](/images/custom-hero.jpg)
:::
```
**输出:**
::: demo-wrapper img no-padding
![hero](/images/custom-hero.png)
![hero](/images/custom-hero.jpg)
:::
包含 markdown 语法:

View File

@ -135,7 +135,7 @@ config:
**效果:**
:::demo-wrapper img no-padding
<img src="/images/custom-banner.png" alt="" />
<img src="/images/custom-banner.jpg" alt="" />
:::
### hero
@ -225,7 +225,7 @@ config:
**效果:**
:::demo-wrapper img no-padding
<img src="/images/custom-hero.png" alt="Theme Plume" />
<img src="/images/custom-hero.jpg" alt="Theme Plume" />
:::
`background` 配置为 `tint-plate` 时,还可以额外配置 `tintPlate` 调整 背景色调,范围为 `0 ~ 255`
@ -262,13 +262,89 @@ config:
/* 默认设置,可以在 `index.css` 中覆盖 */
:root {
/* home hero name 背景色,通过背景色裁剪的方式定义文本颜色,
因此,可以设置渐变背景的方式使文本根据表现力 */
因此,可以设置渐变背景的方式使文本更具表现力 */
--vp-bg-home-hero-name: linear-gradient(315deg, var(--vp-c-purple-1) 15%, var(--vp-c-brand-2) 65%, var(--vp-c-brand-2) 100%);
--vp-c-home-hero-tagline: var(--vp-c-text-2);
--vp-c-home-hero-text: var(--vp-c-text-3);
}
```
### doc-hero
- 类型: `PlumeThemeHomeDocHero`
适用于 文档 类型站点,放置于 首位。
```ts
interface PlumeThemeHomeDocHero {
type: 'doc-hero'
hero: {
name: string
tagline?: string
text?: string
image?: string
| { src: string, alt?: string }
| { dark: string, light: string, alt?: string }
actions?: {
theme?: 'brand' | 'alt' | 'sponsor'
text: string
link?: string
icon?: string // 文本左侧图标
suffixIcon?: string // 文本右侧图标
target?: '_blank' | '_self' | string
rel?: string
}
}
}
```
**示例:**
```md
---
home: true
config:
-
type: doc-hero
hero:
name: Theme Plume
text: VuePress Next Theme
tagline: 一个简约易用的,功能丰富的 vuepress 文档&博客 主题
image: /plume.png
actions:
-
theme: brand
text: 快速开始 →
link: /guide/intro/
-
theme: alt
text: Github
link: https://github.com/pengzhanbo/vuepress-theme-plume
---
```
**效果:**
:::demo-wrapper img no-padding
<img src="/images/custom-doc-hero.jpg" alt="Theme Plume" />
:::
主题还支持自定义 `name`, `tagline` `text` 的颜色,以及 `image` 的背景色。
通过 `CSS Vars` 进行配置。
```css
/* 默认设置,可以在 `index.css` 中覆盖 */
:root {
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: linear-gradient(120deg, var(--vp-c-purple-1) 30%, var(--vp-c-brand-2));
--vp-home-hero-tagline: var(--vp-c-text-2);
--vp-home-hero-text: var(--vp-c-text-1);
--vp-home-hero-image-background-image: linear-gradient(-45deg, var(--vp-c-brand-soft) 50%, var(--vp-c-brand-2) 50%);
--vp-home-hero-image-filter: blur(44px);
}
```
### features
- 类型: `PlumeThemeHomeFeatures`
@ -363,7 +439,7 @@ config:
**效果:**
:::demo-wrapper img no-padding
<img src="/images/custom-features.png" alt="" />
<img src="/images/custom-features.jpg" alt="" />
:::
### text-image | image-text
@ -436,11 +512,11 @@ config:
**效果:**
:::demo-wrapper img no-padding
<img src="/images/custom-image-text.png" alt="image-text" />
<img src="/images/custom-image-text.jpg" alt="image-text" />
:::
:::demo-wrapper img no-padding
<img src="/images/custom-text-image.png" alt="text-image" />
<img src="/images/custom-text-image.jpg" alt="text-image" />
:::
### blog
@ -485,7 +561,7 @@ config:
**效果:**
:::demo-wrapper img no-padding
<img src="/images/custom-profile.png" alt="profile" />
<img src="/images/custom-profile.jpg" alt="profile" />
:::
### custom
@ -531,7 +607,7 @@ yarn add vuepress@next vuepress-theme-plume
**效果:**
:::demo-wrapper img no-padding
<img src="/images/custom-content.png" alt="content" />
<img src="/images/custom-content.jpg" alt="content" />
:::
## 自定义区域类型

View File

@ -3,6 +3,7 @@ import type { Component } from 'vue'
import VPBlog from '@theme/Blog/VPBlog.vue'
import VPHomeBanner from '@theme/Home/VPHomeBanner.vue'
import VPHomeCustom from '@theme/Home/VPHomeCustom.vue'
import VPHomeDocHero from '@theme/Home/VPHomeDocHero.vue'
import VPHomeFeatures from '@theme/Home/VPHomeFeatures.vue'
import VPHomeHero from '@theme/Home/VPHomeHero.vue'
import VPHomeProfile from '@theme/Home/VPHomeProfile.vue'
@ -31,6 +32,7 @@ function VPHomeBlog() {
const components: Record<string, Component<any, any, any>> = {
'banner': VPHomeBanner,
'hero': VPHomeHero,
'doc-hero': VPHomeDocHero,
'features': VPHomeFeatures,
'text-image': VPHomeTextImage,
'image-text': VPHomeTextImage,

View File

@ -0,0 +1,334 @@
<script setup lang="ts">
import type { PlumeThemeHomeDocHero } from '../../../shared/index.js'
import VPButton from '@theme/VPButton.vue'
import VPImage from '@theme/VPImage.vue'
defineProps<PlumeThemeHomeDocHero>()
</script>
<template>
<div class="vp-home-doc-hero" :class="{ 'has-image': hero.image }">
<div class="container">
<div class="main">
<h1 class="heading">
<span v-if="hero.name" class="name clip" v-html="hero.name" />
<span v-if="hero.text" class="text" v-html="hero.text" />
</h1>
<p v-if="hero.tagline" class="tagline" v-html="hero.tagline" />
<div v-if="hero.actions.length" class="actions">
<div class="action">
<VPButton
v-for="action in hero.actions"
:key="action.link"
tag="a"
size="medium"
:theme="action.theme"
:text="action.text"
:href="action.link"
:target="action.target"
:rel="action.rel"
:icon="action.icon"
:suffix-icon="action.suffixIcon"
/>
</div>
</div>
</div>
<div v-if="hero.image" class="image">
<div class="image-container">
<div class="image-bg" />
<slot name="home-doc-hero-image">
<VPImage v-if="hero.image" class="image-src" :image="hero.image" />
</slot>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.vp-home-doc-hero {
padding: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px) 24px 48px;
margin-top: calc((var(--vp-nav-height) + var(--vp-layout-top-height, 0px)) * -1);
}
@media (min-width: 640px) {
.vp-home-doc-hero {
padding: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 48px 64px;
}
}
@media (min-width: 960px) {
.vp-home-doc-hero {
padding: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 64px 64px;
}
}
.container {
display: flex;
flex-direction: column;
max-width: 1152px;
margin: 0 auto;
}
@media (min-width: 960px) {
.container {
flex-direction: row;
}
}
.main {
position: relative;
z-index: 10;
flex-grow: 1;
flex-shrink: 0;
order: 2;
}
.vp-home-doc-hero.has-image .container {
text-align: center;
}
@media (min-width: 960px) {
.vp-home-doc-hero.has-image .container {
text-align: left;
}
}
@media (min-width: 960px) {
.main {
order: 1;
width: calc((100% / 3) * 2);
}
.vp-home-doc-hero.has-image .main {
max-width: 592px;
}
}
.heading {
display: flex;
flex-direction: column;
}
.name,
.text {
width: fit-content;
max-width: 392px;
font-size: 32px;
font-weight: 700;
line-height: 40px;
letter-spacing: -0.4px;
white-space: pre-wrap;
}
.name {
color: var(--vp-home-hero-name-color);
}
.text {
color: var(--vp-home-hero-text, var(--vp-c-text-1));
}
.vp-home-doc-hero.has-image .name,
.vp-home-doc-hero.has-image .text {
margin: 0 auto;
}
.clip {
background: var(--vp-home-hero-name-background);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: var(--vp-home-hero-name-color);
}
@media (min-width: 640px) {
.name,
.text {
max-width: 576px;
font-size: 48px;
line-height: 56px;
}
}
@media (min-width: 960px) {
.name,
.text {
font-size: 56px;
line-height: 64px;
}
.vp-home-doc-hero.has-image .name,
.vp-home-doc-hero.has-image .text {
margin: 0;
}
}
.tagline {
max-width: 392px;
padding-top: 8px;
font-size: 18px;
font-weight: 500;
line-height: 28px;
color: var(--vp-home-hero-tagline, var(--vp-c-text-2));
white-space: pre-wrap;
}
.vp-home-doc-hero.has-image .tagline {
margin: 0 auto;
}
@media (min-width: 640px) {
.tagline {
max-width: 576px;
padding-top: 12px;
font-size: 20px;
line-height: 32px;
}
}
@media (min-width: 960px) {
.tagline {
font-size: 24px;
line-height: 36px;
}
.vp-home-doc-hero.has-image .tagline {
margin: 0;
}
}
.actions {
display: flex;
flex-wrap: wrap;
padding-top: 24px;
margin: -6px;
}
.vp-home-doc-hero.has-image .actions {
justify-content: center;
}
@media (min-width: 640px) {
.actions {
padding-top: 32px;
}
}
@media (min-width: 960px) {
.vp-home-doc-hero.has-image .actions {
justify-content: flex-start;
}
}
.action {
flex-shrink: 0;
padding: 6px;
}
.image {
order: 1;
margin: -76px -24px -48px;
}
@media (min-width: 640px) {
.image {
margin: -108px -24px -48px;
}
}
@media (min-width: 960px) {
.image {
flex-grow: 1;
order: 2;
min-height: 100%;
margin: 0;
}
}
.image-container {
position: relative;
width: 320px;
height: 320px;
margin: 0 auto;
}
@media (min-width: 640px) {
.image-container {
width: 392px;
height: 392px;
}
}
@media (min-width: 960px) {
.image-container {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
/* rtl:ignore */
transform: translate(-32px, -32px);
}
}
.image-bg {
position: absolute;
top: 50%;
/* rtl:ignore */
left: 50%;
width: 192px;
height: 192px;
background-image: var(--vp-home-hero-image-background-image);
filter: var(--vp-home-hero-image-filter);
border-radius: 50%;
/* rtl:ignore */
transform: translate(-50%, -50%);
}
@media (min-width: 640px) {
.image-bg {
width: 256px;
height: 256px;
}
}
@media (min-width: 960px) {
.image-bg {
width: 320px;
height: 320px;
}
}
:deep(.image-src) {
position: absolute;
top: 50%;
/* rtl:ignore */
left: 50%;
max-width: 192px;
max-height: 192px;
/* rtl:ignore */
transform: translate(-50%, -50%);
}
@media (min-width: 640px) {
:deep(.image-src) {
max-width: 256px;
max-height: 256px;
}
}
@media (min-width: 960px) {
:deep(.image-src) {
max-width: 320px;
max-height: 320px;
}
}
</style>

View File

@ -172,8 +172,10 @@ useHomeHeroTintPlate(
.action {
display: flex;
gap: 24px;
flex-wrap: wrap;
gap: 16px 24px;
align-items: center;
justify-content: center;
}
.action :deep(.vp-button) {

View File

@ -16,6 +16,10 @@ export interface PlumeThemeHero {
actions: PlumeThemeHeroAction[]
}
export interface PlumeThemeDocHero extends PlumeThemeHero {
image?: ThemeImage
}
export interface PlumeThemeHeroAction {
theme?: 'brand' | 'alt'
text: string
@ -27,7 +31,7 @@ export interface PlumeThemeHeroAction {
}
export interface PlumeHomeConfigBase {
type: 'banner' | 'hero' | 'text-image' | 'image-text' | 'features' | 'profile' | 'custom'
type: 'banner' | 'hero' | 'doc-hero' | 'text-image' | 'image-text' | 'features' | 'profile' | 'custom'
full?: boolean
backgroundImage?: string | { light: string, dark: string }
backgroundAttachment?: 'fixed' | 'local'
@ -60,6 +64,11 @@ export interface PlumeThemeHomeHero extends PlumeHomeConfigBase {
filter?: string
}
export interface PlumeThemeHomeDocHero extends PlumeHomeConfigBase {
type: 'doc-hero'
hero: PlumeThemeDocHero
}
export interface PlumeThemeHomeTextImage extends PlumeHomeConfigBase {
type: 'text-image' | 'image-text'
image: ThemeImage