diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 99175da7..e2aa7800 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -6,19 +6,11 @@ export default defineConfig({ description: 'Vite & Vue powered static site generator.', themeConfig: { - nav: [ - { text: 'Guide', link: '/guide/what-is-vitepress' }, - { text: 'Configs', link: '/config/app-configs' }, - { - text: 'Release Notes', - link: 'https://github.com/vuejs/vitepress/releases' - } - ], + nav: nav(), sidebar: { - '/guide/': getGuideSidebar(), - '/config/': getConfigSidebar(), - // '/': getGuideSidebar() + '/guide/': sidebarGuide(), + '/config/': sidebarConfig() }, editLink: { @@ -32,6 +24,11 @@ export default defineConfig({ { icon: 'github', link: 'https://github.com/vuejs/vitepress' } ], + footer: { + message: 'Released under the MIT License.', + copyright: 'Copyright © 2019-present Evan You' + }, + algolia: { appId: '8J64VVRP8K', apiKey: 'a18e2f4cc5665f6602c5631fd868adfd', @@ -40,16 +37,40 @@ export default defineConfig({ } }) -function getGuideSidebar() { +function nav() { + return [ + { text: 'Guide', link: '/guide/what-is-vitepress' }, + { text: 'Configs', link: '/config/introduction' }, + { + text: 'Release Notes', + link: 'https://github.com/vuejs/vitepress/releases' + } + ] +} + +function sidebarGuide() { return [ { text: 'Introduction', items: [{ text: 'What is VitePress?', link: '/guide/what-is-vitepress' }] + }, + { + text: 'Migrations', + items: [ + { + text: 'Migration from VuePress', + link: '/guide/migration-from-vuepress' + }, + { + text: 'Migration from VitePress 0.x', + link: '/guide/migration-from-vitepress-0' + } + ] } ] } -function getConfigSidebar() { +function sidebarConfig() { return [ { text: 'Config', diff --git a/docs/config/app-configs.md b/docs/config/app-configs.md index d3092f20..2b85ce90 100644 --- a/docs/config/app-configs.md +++ b/docs/config/app-configs.md @@ -71,7 +71,7 @@ export default { - Type: `boolean` - Default: `true` -Whether to enable "Dark Mode" or not. If the option is set to `true`, it adds `.dark` class to the `` tag. +Whether to enable "Dark Mode" or not. If the option is set to `true`, it adds `.dark` class to the `` tag depending on the users preference. It also injects inline script that tries to read users settings from local storage by `vitepress-theme-appearance` key and restores users preferred color mode. diff --git a/docs/config/frontmatter-configs.md b/docs/config/frontmatter-configs.md index 1419f9e0..a8834a67 100644 --- a/docs/config/frontmatter-configs.md +++ b/docs/config/frontmatter-configs.md @@ -72,18 +72,80 @@ hero: name: VuePress text: Vite & Vue powered static site generator. tagline: Lorem ipsum... + actions: + - theme: brand + text: Get Started + link: /guide/what-is-vitepress + - theme: alt + text: View on GitHub + link: https://github.com/vuejs/vitepress --- ``` ```ts interface Hero { - // The string shown top of `text`. Best used for product name. - name: string + // The string shown top of `text`. Comes with brand color + // and expected to be short, such as product name. + name?: string - // The main text for the hero section. This will be defined as `h1`. + // The main text for the hero section. This will be defined + // as `h1` tag. text: string // Tagline displayed below `text`. - tagline: string + tagline?: string + + // Action buttons to display in home hero section. + actions?: HeroAction[] +} + +interface HeroAction { + // Color theme of the button. Defaults to `brand`. + theme?: 'brand' | 'alt' + + // Label of the button. + text: string + + // Destination link of the button. + link: string +} +``` + +## features + +- Type: `Feature[]` + +This option only take effect when `layout` is set to `home`. + +It defines items to display in features section. + +```yaml +--- +layout: home + +features: + - icon: ⚡️ + title: Vite, The DX that can't be beat + details: Lorem ipsum... + - icon: 🖖 + title: Power of Vue meets Markdown + details: Lorem ipsum... + - icon: 🛠️ + title: Simple and minimal, always + details: Lorem ipsum... +--- +``` + +```ts +interface Feature { + // Show icon on each feature box. Currently, only emojis + // are supported. + icon?: string + + // Title of the feature. + title: string + + // Details of the feature. + details: string } ``` diff --git a/docs/config/theme-configs.md b/docs/config/theme-configs.md index 3bd5693f..e30843f8 100644 --- a/docs/config/theme-configs.md +++ b/docs/config/theme-configs.md @@ -1,3 +1,58 @@ # 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 +} +``` diff --git a/docs/guide/migration-from-vitepress-0.md b/docs/guide/migration-from-vitepress-0.md new file mode 100644 index 00000000..038a31e7 --- /dev/null +++ b/docs/guide/migration-from-vitepress-0.md @@ -0,0 +1,3 @@ +# Migration from VitePress 0.x + +Coming soon... diff --git a/docs/guide/migration-from-vuepress.md b/docs/guide/migration-from-vuepress.md new file mode 100644 index 00000000..75faf43a --- /dev/null +++ b/docs/guide/migration-from-vuepress.md @@ -0,0 +1,3 @@ +# Migration from VuePress + +Coming soon... diff --git a/docs/index.md b/docs/index.md index e8122241..3a95aca1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,7 +2,24 @@ layout: home hero: - name: VuePress + name: VitePress text: Vite & Vue powered static site generator. - tagline: Simple, minimal, yet powerful as lightning. Meet the modern SSG framework you've always wanted. + tagline: Simple, minimal, yet strikingly powerful. Meet the modern SSG framework you've always wanted. + actions: + - theme: brand + text: Get Started + link: /guide/what-is-vitepress + - theme: alt + text: View on GitHub + link: https://github.com/vuejs/vitepress + +features: + - title: Vite, The DX that can't be beat + details: Feel the speed of Vite. Instant server start and lightning fast HMR that stays fast regardless of the app size. + - title: Power of Vue meets Markdown + details: Enjoy the outstanding feature set of Vue Component in markdown, and develop custom themes with Vue. + - title: Simple and minimal, always + details: The project structure that helps you focus on writing, yet fully customizable for any website development. + - title: Fully static, but dynammic + details: Go wild with the true SSG + SPA architecture. Static on the page load, engage users with 100% interactive from there. --- diff --git a/src/client/theme-default/Layout.vue b/src/client/theme-default/Layout.vue index a890b801..321b3b88 100644 --- a/src/client/theme-default/Layout.vue +++ b/src/client/theme-default/Layout.vue @@ -7,6 +7,7 @@ import VPNav from './components/VPNav.vue' import VPLocalNav from './components/VPLocalNav.vue' import VPSidebar from './components/VPSidebar.vue' import VPContent from './components/VPContent.vue' +import VPFooter from './components/VPFooter.vue' const { isOpen: isSidebarOpen, @@ -26,6 +27,22 @@ provide('close-sidebar', closeSidebar) - + + + + + + + + + + + diff --git a/src/client/theme-default/components/VPBackdrop.vue b/src/client/theme-default/components/VPBackdrop.vue index b19239d5..1aa5d0b1 100644 --- a/src/client/theme-default/components/VPBackdrop.vue +++ b/src/client/theme-default/components/VPBackdrop.vue @@ -17,7 +17,7 @@ defineProps<{ right: 0; bottom: 0; left: 0; - z-index: var(--vp-z-backdrop); + z-index: var(--vp-z-index-backdrop); background: rgba(0, 0, 0, .6); transition: opacity 0.5s; } diff --git a/src/client/theme-default/components/VPBox.vue b/src/client/theme-default/components/VPBox.vue new file mode 100644 index 00000000..d1a4b4ae --- /dev/null +++ b/src/client/theme-default/components/VPBox.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/src/client/theme-default/components/VPButton.vue b/src/client/theme-default/components/VPButton.vue new file mode 100644 index 00000000..17d3aecd --- /dev/null +++ b/src/client/theme-default/components/VPButton.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/src/client/theme-default/components/VPContent.vue b/src/client/theme-default/components/VPContent.vue index f09be8bf..9a93b63c 100644 --- a/src/client/theme-default/components/VPContent.vue +++ b/src/client/theme-default/components/VPContent.vue @@ -15,21 +15,38 @@ const { hasSidebar } = useSidebar()
+ - + + + + + + + +
diff --git a/src/client/theme-default/components/VPHero.vue b/src/client/theme-default/components/VPHero.vue new file mode 100644 index 00000000..89258050 --- /dev/null +++ b/src/client/theme-default/components/VPHero.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/src/client/theme-default/components/VPHome.vue b/src/client/theme-default/components/VPHome.vue index da120e7c..12e85c39 100644 --- a/src/client/theme-default/components/VPHome.vue +++ b/src/client/theme-default/components/VPHome.vue @@ -1,9 +1,33 @@ + + diff --git a/src/client/theme-default/components/VPHomeFeatures.vue b/src/client/theme-default/components/VPHomeFeatures.vue new file mode 100644 index 00000000..b0248cf4 --- /dev/null +++ b/src/client/theme-default/components/VPHomeFeatures.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/src/client/theme-default/components/VPHomeHero.vue b/src/client/theme-default/components/VPHomeHero.vue index 0899ade0..1e98618b 100644 --- a/src/client/theme-default/components/VPHomeHero.vue +++ b/src/client/theme-default/components/VPHomeHero.vue @@ -1,106 +1,17 @@ - - diff --git a/src/client/theme-default/components/VPHomeSponsors.vue b/src/client/theme-default/components/VPHomeSponsors.vue new file mode 100644 index 00000000..86173ee4 --- /dev/null +++ b/src/client/theme-default/components/VPHomeSponsors.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavBarSearch.vue b/src/client/theme-default/components/VPNavBarSearch.vue index 3359fc49..227dfba4 100644 --- a/src/client/theme-default/components/VPNavBarSearch.vue +++ b/src/client/theme-default/components/VPNavBarSearch.vue @@ -3,9 +3,9 @@ import '@docsearch/css' import { useData } from 'vitepress' import { defineAsyncComponent, ref, onMounted, onUnmounted } from 'vue' -const VPAlgoliaSearchBox = defineAsyncComponent( - () => import('./VPAlgoliaSearchBox.vue') -) +const VPAlgoliaSearchBox = defineAsyncComponent(() => { + return import('./VPAlgoliaSearchBox.vue') +}) const { theme } = useData() diff --git a/src/client/theme-default/components/VPSponsors.vue b/src/client/theme-default/components/VPSponsors.vue new file mode 100644 index 00000000..fe90add9 --- /dev/null +++ b/src/client/theme-default/components/VPSponsors.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/client/theme-default/components/VPSponsorsGrid.vue b/src/client/theme-default/components/VPSponsorsGrid.vue new file mode 100644 index 00000000..68790653 --- /dev/null +++ b/src/client/theme-default/components/VPSponsorsGrid.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/client/theme-default/components/icons/VPIconHeart.vue b/src/client/theme-default/components/icons/VPIconHeart.vue new file mode 100644 index 00000000..d408828f --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconHeart.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/composables/outline.ts b/src/client/theme-default/composables/outline.ts index f1ce455a..460e5ca8 100644 --- a/src/client/theme-default/composables/outline.ts +++ b/src/client/theme-default/composables/outline.ts @@ -1,6 +1,7 @@ import { Ref, onMounted, onUpdated, onUnmounted } from 'vue' import { Header } from 'vitepress' import { useMediaQuery } from '@vueuse/core' +import { throttleAndDebounce } from '../support/utils' interface HeaderWithChildren extends Header { children?: Header[] @@ -162,24 +163,3 @@ function isAnchorActive( return [false, null] } - -function throttleAndDebounce(fn: () => void, delay: number): () => void { - let timeout: number - let called = false - - return () => { - if (timeout) { - clearTimeout(timeout) - } - - if (!called) { - fn() - called = true - setTimeout(() => { - called = false - }, delay) - } else { - timeout = setTimeout(fn, delay) - } - } -} diff --git a/src/client/theme-default/composables/sponsor-grid.ts b/src/client/theme-default/composables/sponsor-grid.ts new file mode 100644 index 00000000..cd356c74 --- /dev/null +++ b/src/client/theme-default/composables/sponsor-grid.ts @@ -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 + 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!) + } +} diff --git a/src/client/theme-default/index.ts b/src/client/theme-default/index.ts index c3d43d7a..ca744086 100644 --- a/src/client/theme-default/index.ts +++ b/src/client/theme-default/index.ts @@ -3,6 +3,7 @@ import './styles/vars.css' import './styles/base.css' import './styles/utils.css' import './styles/vp-doc.css' +import './styles/vp-sponsor.css' import { Theme } from 'vitepress' import Layout from './Layout.vue' @@ -10,6 +11,10 @@ import NotFound from './NotFound.vue' export { DefaultTheme } from './config' +export { default as VPHomeHero } from './components/VPHomeHero.vue' +export { default as VPHomeFeatures } from './components/VPHomeFeatures.vue' +export { default as VPHomeSponsors } from './components/VPHomeSponsors.vue' + const theme: Theme = { Layout, NotFound diff --git a/src/client/theme-default/styles/vars.css b/src/client/theme-default/styles/vars.css index fe6e2a5d..0ec0581d 100644 --- a/src/client/theme-default/styles/vars.css +++ b/src/client/theme-default/styles/vars.css @@ -52,6 +52,9 @@ --vp-c-indigo: #213547; --vp-c-indigo-soft: #476582; --vp-c-indigo-light: #aac8e4; + --vp-c-indigo-lighter: #c9def1; + --vp-c-indigo-dark: #1d2f3f; + --vp-c-indigo-darker: #14212e; --vp-c-blue: #3b8eed; --vp-c-blue-light: #549ced; @@ -109,7 +112,11 @@ --vp-c-brand: var(--vp-c-green); --vp-c-brand-light: var(--vp-c-green-light); + --vp-c-brand-lighter: var(--vp-c-green-lighter); --vp-c-brand-dark: var(--vp-c-green-dark); + --vp-c-brand-darker: var(--vp-c-green-darker); + + --vp-c-sponsor: #fd1d7c; } .dark { @@ -168,8 +175,9 @@ :root { --vp-z-index-local-nav: 10; --vp-z-index-nav: 20; - --vp-z-backdrop: 30; + --vp-z-index-backdrop: 30; --vp-z-index-sidebar: 40; + --vp-z-index-footer: 50; } /** @@ -221,13 +229,71 @@ } /** - * Component: Home + * Component: Button * -------------------------------------------------------------------------- */ :root { - + --vp-button-brand-border: var(--vp-c-brand-light); + --vp-button-brand-text: var(--vp-c-text-dark-1); + --vp-button-brand-bg: var(--vp-c-brand); + --vp-button-brand-hover-border: var(--vp-c-brand-light); + --vp-button-brand-hover-text: var(--vp-c-text-dark-1); + --vp-button-brand-hover-bg: var(--vp-c-brand-light); + --vp-button-brand-active-border: var(--vp-c-brand-light); + --vp-button-brand-active-text: var(--vp-c-text-dark-1); + --vp-button-brand-active-bg: var(--vp-button-brand-bg); + + --vp-button-alt-border: var(--vp-c-gray-light-3); + --vp-button-alt-text: var(--vp-c-text-light-1); + --vp-button-alt-bg: var(--vp-c-gray-light-5); + --vp-button-alt-hover-border: var(--vp-c-gray-light-3); + --vp-button-alt-hover-text: var(--vp-c-text-light-1); + --vp-button-alt-hover-bg: var(--vp-c-gray-light-4); + --vp-button-alt-active-border: var(--vp-c-gray-light-3); + --vp-button-alt-active-text: var(--vp-c-text-light-1); + --vp-button-alt-active-bg: var(--vp-c-gray-light-3); + + --vp-button-sponsor-border: var(--vp-c-gray-light-3); + --vp-button-sponsor-text: var(--vp-c-text-light-2); + --vp-button-sponsor-bg: transparent; + --vp-button-sponsor-hover-border: var(--vp-c-sponsor); + --vp-button-sponsor-hover-text: var(--vp-c-sponsor); + --vp-button-sponsor-hover-bg: transparent; + --vp-button-sponsor-active-border: var(--vp-c-sponsor); + --vp-button-sponsor-active-text: var(--vp-c-sponsor); + --vp-button-sponsor-active-bg: transparent; } .dark { + --vp-button-brand-border: var(--vp-c-brand-lighter); + --vp-button-brand-text: var(--vp-c-text-light-1); + --vp-button-brand-bg: var(--vp-c-brand-light); + --vp-button-brand-hover-border: var(--vp-c-brand-lighter); + --vp-button-brand-hover-text: var(--vp-c-text-light-1); + --vp-button-brand-hover-bg: var(--vp-c-brand-lighter); + --vp-button-brand-active-border: var(--vp-c-brand-lighter); + --vp-button-brand-active-text: var(--vp-c-text-light-1); + --vp-button-brand-active-bg: var(--vp-button-brand-bg); + + --vp-button-alt-border: var(--vp-c-gray-dark-2); + --vp-button-alt-text: var(--vp-c-text-dark-1); + --vp-button-alt-bg: var(--vp-c-bg-mute); + --vp-button-alt-hover-border: var(--vp-c-gray-dark-2); + --vp-button-alt-hover-text: var(--vp-c-text-dark-1); + --vp-button-alt-hover-bg: var(--vp-c-gray-dark-3); + --vp-button-alt-active-border: var(--vp-c-gray-dark-2); + --vp-button-alt-active-text: var(--vp-c-text-dark-1); + --vp-button-alt-active-bg: var(--vp-button-alt-bg); + + --vp-button-sponsor-border: var(--vp-c-gray-dark-1); + --vp-button-sponsor-text: var(--vp-c-text-dark-2); +} +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: var(--vp-c-brand); + --vp-home-hero-name-background: transparent; } diff --git a/src/client/theme-default/styles/vp-doc.css b/src/client/theme-default/styles/vp-doc.css index 44f79bf5..e664afbc 100644 --- a/src/client/theme-default/styles/vp-doc.css +++ b/src/client/theme-default/styles/vp-doc.css @@ -221,7 +221,7 @@ @media (min-width: 640px) { .vp-doc div[class*='language-'] { border-radius: 8px; - margin: 20px 0; + margin: 24px 0; } } diff --git a/src/client/theme-default/styles/vp-sponsor.css b/src/client/theme-default/styles/vp-sponsor.css new file mode 100644 index 00000000..877e0216 --- /dev/null +++ b/src/client/theme-default/styles/vp-sponsor.css @@ -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); +} diff --git a/src/client/theme-default/support/utils.ts b/src/client/theme-default/support/utils.ts index bc99d224..be2a39c8 100644 --- a/src/client/theme-default/support/utils.ts +++ b/src/client/theme-default/support/utils.ts @@ -12,6 +12,27 @@ export function isExternal(path: string): boolean { return OUTBOUND_RE.test(path) } +export function throttleAndDebounce(fn: () => void, delay: number): () => void { + let timeout: number + let called = false + + return () => { + if (timeout) { + clearTimeout(timeout) + } + + if (!called) { + fn() + called = true + setTimeout(() => { + called = false + }, delay) + } else { + timeout = setTimeout(fn, delay) + } + } +} + export function isActive( currentPath: string, matchPath?: string, diff --git a/types/default-theme.d.ts b/types/default-theme.d.ts index 2cfd0ea7..ef6d7a9e 100644 --- a/types/default-theme.d.ts +++ b/types/default-theme.d.ts @@ -29,6 +29,11 @@ export namespace DefaultTheme { */ socialLinks?: SocialLink[] + /** + * The footer configuration. + */ + footer?: Footer + /** * Adds locale menu to the nav. This option should be used when you have * your translated sites outside of the project. @@ -133,6 +138,13 @@ export namespace DefaultTheme { | 'twitter' | 'youtube' + // footer -------------------------------------------------------------------- + + export interface Footer { + message?: string + copyright?: string + } + // locales ------------------------------------------------------------------- export interface LocaleLinks {