feat(theme): add doc-hero support for custom home (#462)
BIN
docs/.vuepress/public/images/custom-banner.jpg
Normal file
|
After Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 5.0 MiB |
BIN
docs/.vuepress/public/images/custom-content.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 52 KiB |
BIN
docs/.vuepress/public/images/custom-doc-hero.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/.vuepress/public/images/custom-features.jpg
Normal file
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 278 KiB |
BIN
docs/.vuepress/public/images/custom-hero.jpg
Normal file
|
After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 774 KiB |
BIN
docs/.vuepress/public/images/custom-image-text.jpg
Normal file
|
After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 218 KiB |
BIN
docs/.vuepress/public/images/custom-profile.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 96 KiB |
BIN
docs/.vuepress/public/images/custom-text-image.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 195 KiB |
@ -32,13 +32,13 @@ outline: 2
|
||||
|
||||
```md
|
||||
::: demo-wrapper img no-padding
|
||||

|
||||

|
||||
:::
|
||||
```
|
||||
|
||||
**输出:**
|
||||
::: demo-wrapper img no-padding
|
||||

|
||||

|
||||
:::
|
||||
|
||||
包含 markdown 语法:
|
||||
|
||||
@ -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" />
|
||||
:::
|
||||
|
||||
## 自定义区域类型
|
||||
|
||||
@ -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,
|
||||
|
||||
334
theme/src/client/components/Home/VPHomeDocHero.vue
Normal 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>
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||