mirror of https://github.com/vuejs/vitepress
parent
905b4a0142
commit
6f037e2688
@ -1,3 +1,58 @@
|
|||||||
# Theme Configs
|
# Theme Configs
|
||||||
|
|
||||||
Coming soon...
|
Theme configs let you customize your theme. You can define theme configs by adding `themeConfig` key to the config file.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
lang: 'en-US',
|
||||||
|
title: 'VitePress',
|
||||||
|
description: 'Vite & Vue powered static site generator.',
|
||||||
|
|
||||||
|
// Theme related configurations.
|
||||||
|
themeConfig: {
|
||||||
|
logo: '/logo.svg',
|
||||||
|
nav: [...],
|
||||||
|
sidebar: { ... }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here it describes the settings for the VitePress default theme. If you're using a custom theme created by others, these settings may not have any effect, or might behave differently.
|
||||||
|
|
||||||
|
## logo
|
||||||
|
|
||||||
|
- Type: `string`
|
||||||
|
|
||||||
|
Logo file to display in nav bar, right before the site title.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
logo: '/logo.svg'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## footer
|
||||||
|
|
||||||
|
- Type: `Footer`
|
||||||
|
|
||||||
|
Footer configuration. You can add a message and copyright. The footer will displayed only when the page doesn't contain sidebar due to design reason.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
footer: {
|
||||||
|
message: 'Released under the MIT License.',
|
||||||
|
copyright: 'Copyright © 2019-present Evan You'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface Footer {
|
||||||
|
message?: string
|
||||||
|
copyright?: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
# Migration from VitePress 0.x
|
||||||
|
|
||||||
|
Coming soon...
|
@ -0,0 +1,3 @@
|
|||||||
|
# Migration from VuePress
|
||||||
|
|
||||||
|
Coming soon...
|
@ -0,0 +1,59 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
icon?: string
|
||||||
|
title: string
|
||||||
|
details: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<article class="VPBox">
|
||||||
|
<div v-if="icon" class="icon">{{ icon }}</div>
|
||||||
|
<h1 class="title">{{ title }}</h1>
|
||||||
|
<p class="details">{{ details }}</p>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPBox {
|
||||||
|
border: 1px solid var(--vp-c-divider-light);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--vp-c-bg-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .VPBox {
|
||||||
|
background-color: var(--vp-c-bg-mute);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: var(--vp-c-gray-light-4);
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .icon {
|
||||||
|
background-color: var(--vp-c-bg-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
padding-top: 8px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,123 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
tag?: string
|
||||||
|
size?: 'medium' | 'big'
|
||||||
|
theme?: 'brand' | 'alt' | 'sponsor'
|
||||||
|
text: string
|
||||||
|
href?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const classes = computed(() => [
|
||||||
|
props.size ?? 'medium',
|
||||||
|
props.theme ?? 'brand'
|
||||||
|
])
|
||||||
|
|
||||||
|
const isExternal = computed(() => props.href && /^[a-z]+:/i.test(props.href))
|
||||||
|
|
||||||
|
const component = computed(() => {
|
||||||
|
if (props.tag) {
|
||||||
|
return props.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.href ? 'a' : 'button'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="component"
|
||||||
|
class="VPButton"
|
||||||
|
:class="classes"
|
||||||
|
:href="href"
|
||||||
|
:target="isExternal ? '_blank' : undefined"
|
||||||
|
:rel="isExternal ? 'noopener noreferrer' : undefined"
|
||||||
|
>
|
||||||
|
{{ text }}
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPButton {
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton:active {
|
||||||
|
transition: color 0.1s, border-color 0.1s, background-color 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.medium {
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 0 20px;
|
||||||
|
line-height: 38px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.big {
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 0 24px;
|
||||||
|
line-height: 46px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.brand {
|
||||||
|
border-color: var(--vp-button-brand-border);
|
||||||
|
color: var(--vp-button-brand-text);
|
||||||
|
background-color: var(--vp-button-brand-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.brand:hover {
|
||||||
|
border-color: var(--vp-button-brand-hover-border);
|
||||||
|
color: var(--vp-button-brand-hover-text);
|
||||||
|
background-color: var(--vp-button-brand-hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.brand:active {
|
||||||
|
border-color: var(--vp-button-brand-active-border);
|
||||||
|
color: var(--vp-button-brand-active-text);
|
||||||
|
background-color: var(--vp-button-brand-active-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.alt {
|
||||||
|
border-color: var(--vp-button-alt-border);
|
||||||
|
color: var(--vp-button-alt-text);
|
||||||
|
background-color: var(--vp-button-alt-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.alt:hover {
|
||||||
|
border-color: var(--vp-button-alt-hover-border);
|
||||||
|
color: var(--vp-button-alt-hover-text);
|
||||||
|
background-color: var(--vp-button-alt-hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.alt:active {
|
||||||
|
border-color: var(--vp-button-alt-active-border);
|
||||||
|
color: var(--vp-button-alt-active-text);
|
||||||
|
background-color: var(--vp-button-alt-active-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.sponsor {
|
||||||
|
border-color: var(--vp-button-sponsor-border);
|
||||||
|
color: var(--vp-button-sponsor-text);
|
||||||
|
background-color: var(--vp-button-sponsor-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.sponsor:hover {
|
||||||
|
border-color: var(--vp-button-sponsor-hover-border);
|
||||||
|
color: var(--vp-button-sponsor-hover-text);
|
||||||
|
background-color: var(--vp-button-sponsor-hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.sponsor:active {
|
||||||
|
border-color: var(--vp-button-sponsor-active-border);
|
||||||
|
color: var(--vp-button-sponsor-active-text);
|
||||||
|
background-color: var(--vp-button-sponsor-active-bg);
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,53 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useData } from 'vitepress'
|
||||||
|
import { useSidebar } from '../composables/sidebar'
|
||||||
|
|
||||||
|
const { theme } = useData()
|
||||||
|
const { hasSidebar } = useSidebar()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<footer v-if="theme.footer" class="VPFooter" :class="{ 'has-sidebar': hasSidebar }">
|
||||||
|
<div class="container">
|
||||||
|
<p class="message">{{ theme.footer.message }}</p>
|
||||||
|
<p class="copyright">{{ theme.footer.copyright }}</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPFooter {
|
||||||
|
position: relative;
|
||||||
|
z-index: var(--vp-z-index-footer);
|
||||||
|
border-top: 1px solid var(--vp-c-divider-light);
|
||||||
|
padding: 32px 24px;
|
||||||
|
background-color: var(--vp-c-bg-content);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPFooter.has-sidebar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.VPFooter {
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: var(--vp-layout-max-width);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message,
|
||||||
|
.copyright {
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message { order: 2; }
|
||||||
|
.copyright { order: 1; }
|
||||||
|
</style>
|
@ -0,0 +1,144 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import VPButton from './VPButton.vue'
|
||||||
|
|
||||||
|
interface HeroAction {
|
||||||
|
theme?: 'brand' | 'alt'
|
||||||
|
text: string
|
||||||
|
link: string
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
name?: string
|
||||||
|
text: string
|
||||||
|
tagline?: string
|
||||||
|
actions?: HeroAction[]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="VPHero">
|
||||||
|
<div class="container">
|
||||||
|
<p v-if="name" class="name"><span class="clip">{{ name }}</span></p>
|
||||||
|
<h1 v-if="text" class="text">{{ text }}</h1>
|
||||||
|
<p v-if="tagline" class="tagline">{{ tagline }}</p>
|
||||||
|
|
||||||
|
<div v-if="actions" class="actions">
|
||||||
|
<div v-for="action in actions" :key="action.link" class="action">
|
||||||
|
<VPButton
|
||||||
|
tag="a"
|
||||||
|
size="big"
|
||||||
|
:theme="action.theme"
|
||||||
|
:text="action.text"
|
||||||
|
:href="action.link"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPHero {
|
||||||
|
margin-top: calc(var(--vp-nav-height) * -1);
|
||||||
|
padding: calc(var(--vp-nav-height) + 48px) 24px 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.VPHero {
|
||||||
|
padding: calc(var(--vp-nav-height) + 80px) 48px 64px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 960px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name,
|
||||||
|
.text {
|
||||||
|
line-height: 40px;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
padding-bottom: 4px;
|
||||||
|
color: var(--vp-home-hero-name-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clip {
|
||||||
|
background: var(--vp-home-hero-name-background);
|
||||||
|
-webkit-text-fill-color: var(--vp-home-hero-name-color);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.name,
|
||||||
|
.text {
|
||||||
|
max-width: 640px;
|
||||||
|
line-height: 56px;
|
||||||
|
font-size: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
padding-bottom: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.name,
|
||||||
|
.text {
|
||||||
|
max-width: 720px;
|
||||||
|
line-height: 64px;
|
||||||
|
font-size: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
padding-bottom: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tagline {
|
||||||
|
padding-top: 16px;
|
||||||
|
line-height: 28px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.tagline {
|
||||||
|
padding-top: 24px;
|
||||||
|
max-width: 540px;
|
||||||
|
line-height: 32px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.tagline {
|
||||||
|
padding-top: 24px;
|
||||||
|
max-width: 640px;
|
||||||
|
line-height: 36px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: -8px;
|
||||||
|
padding-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.actions {
|
||||||
|
padding-top: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,9 +1,33 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import VPHomeHero from './VPHomeHero.vue'
|
import VPHomeHero from './VPHomeHero.vue'
|
||||||
|
import VPHomeFeatures from './VPHomeFeatures.vue'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="VPHome">
|
<div class="VPHome">
|
||||||
|
<slot name="home-hero-before" />
|
||||||
<VPHomeHero />
|
<VPHomeHero />
|
||||||
|
<slot name="home-hero-after" />
|
||||||
|
|
||||||
|
<slot name="home-features-before" />
|
||||||
|
<VPHomeFeatures />
|
||||||
|
<slot name="home-features-after" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPHome {
|
||||||
|
padding-bottom: 96px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPHome :deep(.VPHomeSponsors) {
|
||||||
|
margin-top: 112px;
|
||||||
|
margin-bottom: -128px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.VPHome {
|
||||||
|
padding-bottom: 128px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -0,0 +1,96 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useData } from 'vitepress'
|
||||||
|
import VPBox from './VPBox.vue'
|
||||||
|
|
||||||
|
const { frontmatter: fm } = useData()
|
||||||
|
|
||||||
|
const grid = computed(() => {
|
||||||
|
const length = fm.value.features?.length
|
||||||
|
|
||||||
|
if (!length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length === 2) {
|
||||||
|
return 'grid-2'
|
||||||
|
} else if (length === 3) {
|
||||||
|
return 'grid-3'
|
||||||
|
} else if (length % 3 === 0) {
|
||||||
|
return 'grid-6'
|
||||||
|
} else if (length % 2 === 0) {
|
||||||
|
return 'grid-4'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="fm.features" class="VPHomeFeatures">
|
||||||
|
<div class="container">
|
||||||
|
<div class="items">
|
||||||
|
<div v-for="feature in fm.features" :key="feature.title" class="item" :class="[grid]">
|
||||||
|
<VPBox
|
||||||
|
:icon="feature.icon"
|
||||||
|
:title="feature.title"
|
||||||
|
:details="feature.details"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPHomeFeatures {
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.VPHomeFeatures {
|
||||||
|
padding: 0 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 960px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
padding: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.item.grid-2,
|
||||||
|
.item.grid-4,
|
||||||
|
.item.grid-6 {
|
||||||
|
width: calc(100% / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.item.grid-2,
|
||||||
|
.item.grid-4 {
|
||||||
|
width: calc(100% / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.grid-3,
|
||||||
|
.item.grid-6 {
|
||||||
|
width: calc(100% / 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.item.grid-4 {
|
||||||
|
width: calc(100% / 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
@ -0,0 +1,93 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import VPIconHeart from './icons/VPIconHeart.vue'
|
||||||
|
import VPButton from './VPButton.vue'
|
||||||
|
import VPSponsors from './VPSponsors.vue'
|
||||||
|
|
||||||
|
interface Sponsors {
|
||||||
|
tier: string
|
||||||
|
size?: 'medium' | 'big'
|
||||||
|
items: Sponsor[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Sponsor {
|
||||||
|
name: string
|
||||||
|
img: string
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
message?: string
|
||||||
|
actionText?: string
|
||||||
|
actionLink?: string
|
||||||
|
data: Sponsors[]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="VPHomeSponsors">
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<div class="love"><VPIconHeart class="icon" /></div>
|
||||||
|
<h2 v-if="message" class="message">{{ message }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sponsors">
|
||||||
|
<VPSponsors :data="data" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="actionLink" class="action">
|
||||||
|
<VPButton
|
||||||
|
theme="sponsor"
|
||||||
|
:text="actionText ?? 'Become a sponsor'"
|
||||||
|
:href="actionLink"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPHomeSponsors {
|
||||||
|
border-top: 1px solid var(--vp-c-divider-light);
|
||||||
|
padding: 88px 24px 96px;
|
||||||
|
background-color: var(--vp-c-bg-content);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 960px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.love {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
color: var(--vp-c-text-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin: 0 auto;
|
||||||
|
padding-top: 10px;
|
||||||
|
max-width: 320px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sponsors {
|
||||||
|
padding-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
padding-top: 40px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,77 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import VPSponsorsGrid from './VPSponsorsGrid.vue'
|
||||||
|
|
||||||
|
interface Sponsors {
|
||||||
|
tier?: string
|
||||||
|
size?: 'small' | 'medium' | 'big'
|
||||||
|
items: Sponsor[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Sponsor {
|
||||||
|
name: string
|
||||||
|
img: string
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
tier?: string
|
||||||
|
size?: 'small' | 'medium' | 'big'
|
||||||
|
data: Sposors[] | Sponsor[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const sponsors = computed(() => {
|
||||||
|
const isSponsors = props.data.some((s: any) => s.items)
|
||||||
|
|
||||||
|
if (isSponsors) {
|
||||||
|
return props.data
|
||||||
|
}
|
||||||
|
|
||||||
|
return [{
|
||||||
|
tier: props.tier,
|
||||||
|
size: props.size,
|
||||||
|
items: props.data
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="VPSponsors">
|
||||||
|
<section v-for="(sponsor, index) in sponsors" :key="index" class="section">
|
||||||
|
<h3 v-if="sponsor.tier" class="tier">{{ sponsor.tier }}</h3>
|
||||||
|
<VPSponsorsGrid :size="sponsor.size" :data="sponsor.items" />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPSponsors {
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section + .section {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tier {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
padding: 13px 0 11px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
line-height: 24px;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
background-color: var(--vp-c-white-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .tier {
|
||||||
|
background-color: var(--vp-c-black-mute);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPSponsorsGrid + .tier {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,32 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useSponsorsGrid } from '../composables/sponsor-grid'
|
||||||
|
|
||||||
|
interface Sponsor {
|
||||||
|
name: string
|
||||||
|
img: string
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
size?: 'small' | 'medium' | 'big'
|
||||||
|
data: Sponsor[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const el = ref(null)
|
||||||
|
|
||||||
|
useSponsorsGrid({ el, size: props.size })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="VPSponsorsGrid vp-sponsor-grid" :class="[props.size ?? 'medium']" ref="el">
|
||||||
|
<div v-for="sponsor in data" :key="sponsor.tier" class="vp-sponsor-grid-item">
|
||||||
|
<a class="vp-sponsor-grid-link" :href="sponsor.url" target="_blank" rel="sponsored noopener">
|
||||||
|
<article class="vp-sponsor-grid-box">
|
||||||
|
<h4 class="visually-hidden">{{ sponsor.name }}</h4>
|
||||||
|
<img class="vp-sponsor-grid-image" :src="sponsor.img" :alt="sponsor.name" />
|
||||||
|
</article>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path d="M12,22.2c-0.3,0-0.5-0.1-0.7-0.3l-8.8-8.8c-2.5-2.5-2.5-6.7,0-9.2c2.5-2.5,6.7-2.5,9.2,0L12,4.3l0.4-0.4c0,0,0,0,0,0C13.6,2.7,15.2,2,16.9,2c0,0,0,0,0,0c1.7,0,3.4,0.7,4.6,1.9l0,0c1.2,1.2,1.9,2.9,1.9,4.6c0,1.7-0.7,3.4-1.9,4.6l-8.8,8.8C12.5,22.1,12.3,22.2,12,22.2zM7,4C5.9,4,4.7,4.4,3.9,5.3c-1.8,1.8-1.8,4.6,0,6.4l8.1,8.1l8.1-8.1c0.9-0.9,1.3-2,1.3-3.2c0-1.2-0.5-2.3-1.3-3.2l0,0C19.3,4.5,18.2,4,17,4c0,0,0,0,0,0c-1.2,0-2.3,0.5-3.2,1.3c0,0,0,0,0,0l-1.1,1.1c-0.4,0.4-1,0.4-1.4,0l-1.1-1.1C9.4,4.4,8.2,4,7,4z" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
@ -0,0 +1,130 @@
|
|||||||
|
import { Ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
import { throttleAndDebounce } from '../support/utils'
|
||||||
|
|
||||||
|
export interface GridSetting {
|
||||||
|
[size: string]: [number, number][]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GridSize = 'small' | 'medium' | 'big'
|
||||||
|
|
||||||
|
export interface UseSponsorsGridOprions {
|
||||||
|
el: Ref<HTMLElement | null>
|
||||||
|
size: GridSize
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines grid configuration for each sponsor size in touple.
|
||||||
|
*
|
||||||
|
* [Screen widh, Column size]
|
||||||
|
*
|
||||||
|
* It sets grid size on matching screen size. For example, `[768, 5]` will set
|
||||||
|
* 5 columns when screen size is bigger or equal to 768px.
|
||||||
|
*
|
||||||
|
* Column will set only when item size is bigger than the column size. For
|
||||||
|
* example, even we want 5 columns, if we only have 1 sponsor yet, we would
|
||||||
|
* like to show it in 1 column.
|
||||||
|
*/
|
||||||
|
const GridSettings: GridSetting = {
|
||||||
|
small: [
|
||||||
|
[920, 6],
|
||||||
|
[768, 5],
|
||||||
|
[640, 4],
|
||||||
|
[480, 3],
|
||||||
|
[0, 2]
|
||||||
|
],
|
||||||
|
medium: [
|
||||||
|
[960, 5],
|
||||||
|
[832, 4],
|
||||||
|
[640, 3],
|
||||||
|
[480, 2]
|
||||||
|
],
|
||||||
|
big: [
|
||||||
|
[832, 3],
|
||||||
|
[640, 2]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSponsorsGrid(options: UseSponsorsGridOprions) {
|
||||||
|
const onResize = throttleAndDebounce(manage, 100)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
manage()
|
||||||
|
window.addEventListener('resize', onResize)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', onResize)
|
||||||
|
})
|
||||||
|
|
||||||
|
function manage() {
|
||||||
|
adjustSlots(options.el.value!, options.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustSlots(el: HTMLElement, size: GridSize) {
|
||||||
|
const tsize = el.children.length
|
||||||
|
const asize = el.querySelectorAll('.vp-sponsor-grid-item:not(.empty)').length
|
||||||
|
|
||||||
|
const grid = setGrid(el, size, asize)
|
||||||
|
|
||||||
|
manageSlots(el, grid, tsize, asize)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setGrid(el: HTMLElement, size: GridSize, items: number) {
|
||||||
|
const settings = GridSettings[size]
|
||||||
|
const screen = window.innerWidth
|
||||||
|
|
||||||
|
let grid = 1
|
||||||
|
|
||||||
|
settings.some(([breakpoint, value]) => {
|
||||||
|
if (screen >= breakpoint) {
|
||||||
|
grid = items < value ? items : value
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
setGridData(el, grid)
|
||||||
|
|
||||||
|
return grid
|
||||||
|
}
|
||||||
|
|
||||||
|
function setGridData(el: HTMLElement, value: number) {
|
||||||
|
el.dataset.vpGrid = String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function manageSlots(
|
||||||
|
el: HTMLElement,
|
||||||
|
grid: number,
|
||||||
|
tsize: number,
|
||||||
|
asize: number
|
||||||
|
) {
|
||||||
|
const diff = tsize - asize
|
||||||
|
const rem = asize % grid
|
||||||
|
const drem = rem === 0 ? rem : grid - rem
|
||||||
|
|
||||||
|
neutralizeSlots(el, drem - diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
function neutralizeSlots(el: HTMLElement, count: number) {
|
||||||
|
if (count === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count > 0 ? addSlots(el, count) : removeSlots(el, count * -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSlots(el: HTMLElement, count: number) {
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const slot = document.createElement('div')
|
||||||
|
|
||||||
|
slot.classList.add('vp-sponsor-grid-item', 'empty')
|
||||||
|
|
||||||
|
el.append(slot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSlots(el: HTMLElement, count: number) {
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
el.removeChild(el.lastElementChild!)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
.vp-sponsor-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-sponsor-grid.small .vp-sponsor-grid-link { height: 96px; }
|
||||||
|
.vp-sponsor-grid.small .vp-sponsor-grid-image { max-width: 96px; max-height: 24px }
|
||||||
|
|
||||||
|
.vp-sponsor-grid.medium .vp-sponsor-grid-link { height: 112px; }
|
||||||
|
.vp-sponsor-grid.medium .vp-sponsor-grid-image { max-width: 120px; max-height: 36px }
|
||||||
|
|
||||||
|
.vp-sponsor-grid.big .vp-sponsor-grid-link { height: 184px; }
|
||||||
|
.vp-sponsor-grid.big .vp-sponsor-grid-image { max-width: 192px; max-height: 56px }
|
||||||
|
|
||||||
|
.vp-sponsor-grid[data-vp-grid="2"] .vp-sponsor-grid-item {
|
||||||
|
width: calc((100% - 4px) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-sponsor-grid[data-vp-grid="3"] .vp-sponsor-grid-item {
|
||||||
|
width: calc((100% - 4px * 2) / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-sponsor-grid[data-vp-grid="4"] .vp-sponsor-grid-item {
|
||||||
|
width: calc((100% - 4px * 3) / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-sponsor-grid[data-vp-grid="5"] .vp-sponsor-grid-item {
|
||||||
|
width: calc((100% - 4px * 4) / 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-sponsor-grid[data-vp-grid="6"] .vp-sponsor-grid-item {
|
||||||
|
width: calc((100% - 4px * 5) / 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-sponsor-grid-item {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--vp-c-white-soft);
|
||||||
|
transition: background-color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-sponsor-grid-item:hover {
|
||||||
|
background-color: var(--vp-c-white-mute);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-sponsor-grid-item:hover .vp-sponsor-grid-image {
|
||||||
|
filter: grayscale(0) invert(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-sponsor-grid-item.empty:hover {
|
||||||
|
background-color: var(--vp-c-white-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .vp-sponsor-grid-item {
|
||||||
|
background-color: var(--vp-c-black-mute);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .vp-sponsor-grid-item:hover {
|
||||||
|
background-color: var(--vp-c-white-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .vp-sponsor-grid-item.empty:hover {
|
||||||
|
background-color: var(--vp-c-black-mute);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-sponsor-grid-link {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-sponsor-grid-box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-sponsor-grid-image {
|
||||||
|
max-width: 100%;
|
||||||
|
filter: grayscale(1);
|
||||||
|
transition: filter 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .vp-sponsor-grid-image {
|
||||||
|
filter: grayscale(1) invert(1);
|
||||||
|
}
|
Loading…
Reference in new issue