Skip to content

feat: slide layers #1634

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions docs/custom/global-layers.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
# Global Layers

> Available since v0.17

Global layers allow you to have custom components that **persist** across slides. This could be useful for having footers, cross-slide animations, global effects, etc.

Slidev provides three layers for this usage, create `global-top.vue`, `global-bottom.vue` or `custom-nav-controls.vue` under your project root and it will pick up automatically.

Layers relationship:
There are also layers for **each** slide: `layouts/slide-top.vue` and `layouts/slide-bottom.vue`. The usage is similar to the global layers, but they are applied to every slide, so there may be more than one instance of them.

::: tip
When exporting, the `--per-slide` option should be used to ensure the global layers are applied to each slide correctly.
:::

## Layers relationship

At z-axis, from top to bottom:

- Global Top (`global-top.vue`)
- Slides
- Global Bottom (`global-bottom.vue`)
- NavControls
- Customized Navigation Controls (`custom-nav-controls.vue`)

Note that this uses the terms "top" and "bottom" in the context of depth, not the screen position.
- Global Top (`global-top.vue`) - single instance
- Slide Top (`slide-top.vue`) - instance per slide
- Slide Content
- Slide Bottom (`slide-bottom.vue`) - instance per slide
- Global Bottom (`global-bottom.vue`) - single instance

## Example

Expand All @@ -30,7 +36,7 @@ The text `Your Name` will appear on all your slides.
```html
<!-- custom-nav-controls -->
<template>
<button class="icon-btn" title="Next" @click="$slidev.nav.next">
<button class="icon-btn" title="Next" @click="$nav.next">
<carbon:arrow-right />
</button>
</template>
Expand All @@ -44,7 +50,7 @@ To enable it conditionally, you can apply it with the [Vue Global Context](/cust
<!-- hide the footer from Page 4 -->
<template>
<footer
v-if="$slidev.nav.currentPage !== 4"
v-if="$nav.currentPage !== 4"
class="absolute bottom-0 left-0 right-0 p-2"
>
Your Name
Expand All @@ -56,7 +62,7 @@ To enable it conditionally, you can apply it with the [Vue Global Context](/cust
<!-- hide the footer from "cover" layout -->
<template>
<footer
v-if="$slidev.nav.currentLayout !== 'cover'"
v-if="$nav.currentLayout !== 'cover'"
class="absolute bottom-0 left-0 right-0 p-2"
>
Your Name
Expand All @@ -68,10 +74,10 @@ To enable it conditionally, you can apply it with the [Vue Global Context](/cust
<!-- an example footer for pages -->
<template>
<footer
v-if="$slidev.nav.currentLayout !== 'cover'"
v-if="$nav.currentLayout !== 'cover'"
class="absolute bottom-0 left-0 right-0 p-2"
>
{{ $slidev.nav.currentPage }} / {{ $slidev.nav.total }}
{{ $nav.currentPage }} / {{ $nav.total }}
</footer>
</template>
```
Expand All @@ -80,7 +86,7 @@ To enable it conditionally, you can apply it with the [Vue Global Context](/cust
<!-- custom-nav-controls -->
<!-- hide the button in Presenter model -->
<template>
<button v-if="!$slidev.nav.isPresenter" class="icon-btn" title="Next" @click="$slidev.nav.next">
<button v-if="!$nav.isPresenter" class="icon-btn" title="Next" @click="$nav.next">
<carbon:arrow-right />
</button>
</template>
Expand Down
4 changes: 1 addition & 3 deletions packages/client/internals/PrintSlideClick.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import { configs, slideHeight, slideWidth } from '../env'
import { getSlideClass } from '../utils'
import type { SlidevContextNav } from '../composables/useNav'
import SlideWrapper from './SlideWrapper.vue'

import GlobalTop from '#slidev/global-components/top'
import GlobalBottom from '#slidev/global-components/bottom'
import { GlobalBottom, GlobalTop } from '#slidev/global-layers'

const { nav } = defineProps<{
nav: SlidevContextNav
Expand Down
7 changes: 7 additions & 0 deletions packages/client/internals/SlideWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { injectionClicksContext, injectionCurrentPage, injectionRenderContext, i
import { getSlideClass } from '../utils'
import { configs } from '../env'
import SlideLoading from './SlideLoading.vue'
import { SlideBottom, SlideTop } from '#slidev/global-layers'

const props = defineProps({
clicksContext: {
Expand Down Expand Up @@ -67,7 +68,13 @@ const SlideComponent = computed(() => props.route && defineAsyncComponent({
:class="getSlideClass(route, ['slide', 'presenter'].includes(props.renderContext) ? '' : 'disable-view-transition')"
:style="style"
>
<div v-if="SlideBottom" class="absolute inset-0">
<SlideBottom />
</div>
<SlideComponent />
<div v-if="SlideTop" class="absolute inset-0">
<SlideTop />
</div>
</div>
</template>

Expand Down
4 changes: 1 addition & 3 deletions packages/client/internals/SlidesShow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ import { activeDragElement } from '../state'
import { CLICKS_MAX } from '../constants'
import SlideWrapper from './SlideWrapper.vue'
import DragControl from './DragControl.vue'

import GlobalTop from '#slidev/global-components/top'
import GlobalBottom from '#slidev/global-components/bottom'
import { GlobalBottom, GlobalTop } from '#slidev/global-layers'

defineProps<{
renderContext: 'slide' | 'presenter'
Expand Down
72 changes: 0 additions & 72 deletions packages/slidev/node/virtual/global-components.ts

This file was deleted.

39 changes: 39 additions & 0 deletions packages/slidev/node/virtual/global-layers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { existsSync } from 'node:fs'
import { join } from 'node:path'
import { toAtFS } from '../resolver'
import type { VirtualModuleTemplate } from './types'

export const templateGlobalLayers: VirtualModuleTemplate = {
id: `/@slidev/global-layers`,
getContent({ roots }) {
const imports: string[] = []

let n = 0
function getComponent(names: string[]) {
const components = roots
.flatMap(root => names.map(name => join(root, name)))
.filter(i => existsSync(i))

imports.push(components.map((path, i) => `import __n${n}_${i} from '${toAtFS(path)}'`).join('\n'))
const render = components.map((_, i) => `h(__n${n}_${i})`).join(',')

n++

return `{ render: () => [${render}] }`
}

const globalTop = getComponent(['global.vue', 'global-top.vue', 'GlobalTop.vue'])
const globalBottom = getComponent(['global-bottom.vue', 'GlobalBottom.vue'])
const slideTop = getComponent(['slide-top.vue', 'SlideTop.vue'])
const slideBottom = getComponent(['slide-bottom.vue', 'SlideBottom.vue'])

return [
imports.join('\n'),
`import { h } from 'vue'`,
`export const GlobalTop = ${globalTop}`,
`export const GlobalBottom = ${globalBottom}`,
`export const SlideTop = ${slideTop}`,
`export const SlideBottom = ${slideBottom}`,
].join('\n')
},
}
6 changes: 3 additions & 3 deletions packages/slidev/node/virtual/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { templateConfigs } from './configs'
import { templateLegacyRoutes, templateLegacyTitles } from './deprecated'
import { templateGlobalBottom, templateGlobalTop, templateNavControls } from './global-components'
import { templateGlobalLayers } from './global-layers'
import { templateNavControls } from './nav-controls'
import { templateLayouts } from './layouts'
import { templateMonacoRunDeps } from './monaco-deps'
import { templateMonacoTypes } from './monaco-types'
Expand All @@ -16,8 +17,7 @@ export const templates = [
templateMonacoRunDeps,
templateConfigs,
templateStyle,
templateGlobalBottom,
templateGlobalTop,
templateGlobalLayers,
templateNavControls,
templateSlides,
templateLayouts,
Expand Down
27 changes: 27 additions & 0 deletions packages/slidev/node/virtual/nav-controls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { existsSync } from 'node:fs'
import { join } from 'node:path'
import { toAtFS } from '../resolver'
import type { VirtualModuleTemplate } from './types'

export const templateNavControls: VirtualModuleTemplate = {
id: '/@slidev/custom-nav-controls',
getContent({ roots }) {
const components = roots
.flatMap((root) => {
return [
join(root, 'custom-nav-controls.vue'),
join(root, 'CustomNavControls.vue'),
]
})
.filter(i => existsSync(i))

const imports = components.map((i, idx) => `import __n${idx} from '${toAtFS(i)}'`).join('\n')
const render = components.map((i, idx) => `h(__n${idx})`).join(',')

return `${imports}
import { h } from 'vue'
export default {
render: () => [${render}],
}`
},
}
14 changes: 5 additions & 9 deletions packages/types/client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,14 @@ declare module '#slidev/configs' {
export default configs
}

declare module '#slidev/global-components/top' {
declare module '#slidev/global-layers' {
import type { ComponentOptions } from 'vue'

const component: ComponentOptions
export default component
}
export const GlobalTop: ComponentOptions
export const GlobalBottom: ComponentOptions

declare module '#slidev/global-components/bottom' {
import type { ComponentOptions } from 'vue'

const component: ComponentOptions
export default component
export const SlideTop: ComponentOptions
export const SlideBottom: ComponentOptions
}

declare module '#slidev/slides' {
Expand Down
Loading