Skip to content

Commit ad9e80a

Browse files
kermanxantfu
andauthored
feat: slide layers (#1634)
Co-authored-by: Anthony Fu <github@antfu.me>
1 parent 8f49c19 commit ad9e80a

File tree

9 files changed

+103
-104
lines changed

9 files changed

+103
-104
lines changed

docs/custom/global-layers.md

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
# Global Layers
22

3-
> Available since v0.17
4-
53
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.
64

75
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.
86

9-
Layers relationship:
7+
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.
8+
9+
::: tip
10+
When exporting, the `--per-slide` option should be used to ensure the global layers are applied to each slide correctly.
11+
:::
12+
13+
## Layers relationship
14+
15+
At z-axis, from top to bottom:
1016

11-
- Global Top (`global-top.vue`)
12-
- Slides
13-
- Global Bottom (`global-bottom.vue`)
1417
- NavControls
1518
- Customized Navigation Controls (`custom-nav-controls.vue`)
16-
17-
Note that this uses the terms "top" and "bottom" in the context of depth, not the screen position.
19+
- Global Top (`global-top.vue`) - single instance
20+
- Slide Top (`slide-top.vue`) - instance per slide
21+
- Slide Content
22+
- Slide Bottom (`slide-bottom.vue`) - instance per slide
23+
- Global Bottom (`global-bottom.vue`) - single instance
1824

1925
## Example
2026

@@ -30,7 +36,7 @@ The text `Your Name` will appear on all your slides.
3036
```html
3137
<!-- custom-nav-controls -->
3238
<template>
33-
<button class="icon-btn" title="Next" @click="$slidev.nav.next">
39+
<button class="icon-btn" title="Next" @click="$nav.next">
3440
<carbon:arrow-right />
3541
</button>
3642
</template>
@@ -44,7 +50,7 @@ To enable it conditionally, you can apply it with the [Vue Global Context](/cust
4450
<!-- hide the footer from Page 4 -->
4551
<template>
4652
<footer
47-
v-if="$slidev.nav.currentPage !== 4"
53+
v-if="$nav.currentPage !== 4"
4854
class="absolute bottom-0 left-0 right-0 p-2"
4955
>
5056
Your Name
@@ -56,7 +62,7 @@ To enable it conditionally, you can apply it with the [Vue Global Context](/cust
5662
<!-- hide the footer from "cover" layout -->
5763
<template>
5864
<footer
59-
v-if="$slidev.nav.currentLayout !== 'cover'"
65+
v-if="$nav.currentLayout !== 'cover'"
6066
class="absolute bottom-0 left-0 right-0 p-2"
6167
>
6268
Your Name
@@ -68,10 +74,10 @@ To enable it conditionally, you can apply it with the [Vue Global Context](/cust
6874
<!-- an example footer for pages -->
6975
<template>
7076
<footer
71-
v-if="$slidev.nav.currentLayout !== 'cover'"
77+
v-if="$nav.currentLayout !== 'cover'"
7278
class="absolute bottom-0 left-0 right-0 p-2"
7379
>
74-
{{ $slidev.nav.currentPage }} / {{ $slidev.nav.total }}
80+
{{ $nav.currentPage }} / {{ $nav.total }}
7581
</footer>
7682
</template>
7783
```
@@ -80,7 +86,7 @@ To enable it conditionally, you can apply it with the [Vue Global Context](/cust
8086
<!-- custom-nav-controls -->
8187
<!-- hide the button in Presenter model -->
8288
<template>
83-
<button v-if="!$slidev.nav.isPresenter" class="icon-btn" title="Next" @click="$slidev.nav.next">
89+
<button v-if="!$nav.isPresenter" class="icon-btn" title="Next" @click="$nav.next">
8490
<carbon:arrow-right />
8591
</button>
8692
</template>

packages/client/internals/PrintSlideClick.vue

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import { configs, slideHeight, slideWidth } from '../env'
66
import { getSlideClass } from '../utils'
77
import type { SlidevContextNav } from '../composables/useNav'
88
import SlideWrapper from './SlideWrapper.vue'
9-
10-
import GlobalTop from '#slidev/global-components/top'
11-
import GlobalBottom from '#slidev/global-components/bottom'
9+
import { GlobalBottom, GlobalTop } from '#slidev/global-layers'
1210
1311
const { nav } = defineProps<{
1412
nav: SlidevContextNav

packages/client/internals/SlideWrapper.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { injectionClicksContext, injectionCurrentPage, injectionRenderContext, i
77
import { getSlideClass } from '../utils'
88
import { configs } from '../env'
99
import SlideLoading from './SlideLoading.vue'
10+
import { SlideBottom, SlideTop } from '#slidev/global-layers'
1011
1112
const props = defineProps({
1213
clicksContext: {
@@ -67,7 +68,13 @@ const SlideComponent = computed(() => props.route && defineAsyncComponent({
6768
:class="getSlideClass(route, ['slide', 'presenter'].includes(props.renderContext) ? '' : 'disable-view-transition')"
6869
:style="style"
6970
>
71+
<div v-if="SlideBottom" class="absolute inset-0">
72+
<SlideBottom />
73+
</div>
7074
<SlideComponent />
75+
<div v-if="SlideTop" class="absolute inset-0">
76+
<SlideTop />
77+
</div>
7178
</div>
7279
</template>
7380

packages/client/internals/SlidesShow.vue

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ import { activeDragElement } from '../state'
99
import { CLICKS_MAX } from '../constants'
1010
import SlideWrapper from './SlideWrapper.vue'
1111
import DragControl from './DragControl.vue'
12-
13-
import GlobalTop from '#slidev/global-components/top'
14-
import GlobalBottom from '#slidev/global-components/bottom'
12+
import { GlobalBottom, GlobalTop } from '#slidev/global-layers'
1513
1614
defineProps<{
1715
renderContext: 'slide' | 'presenter'

packages/slidev/node/virtual/global-components.ts

Lines changed: 0 additions & 72 deletions
This file was deleted.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { existsSync } from 'node:fs'
2+
import { join } from 'node:path'
3+
import { toAtFS } from '../resolver'
4+
import type { VirtualModuleTemplate } from './types'
5+
6+
export const templateGlobalLayers: VirtualModuleTemplate = {
7+
id: `/@slidev/global-layers`,
8+
getContent({ roots }) {
9+
const imports: string[] = []
10+
11+
let n = 0
12+
function getComponent(names: string[]) {
13+
const components = roots
14+
.flatMap(root => names.map(name => join(root, name)))
15+
.filter(i => existsSync(i))
16+
17+
imports.push(components.map((path, i) => `import __n${n}_${i} from '${toAtFS(path)}'`).join('\n'))
18+
const render = components.map((_, i) => `h(__n${n}_${i})`).join(',')
19+
20+
n++
21+
22+
return `{ render: () => [${render}] }`
23+
}
24+
25+
const globalTop = getComponent(['global.vue', 'global-top.vue', 'GlobalTop.vue'])
26+
const globalBottom = getComponent(['global-bottom.vue', 'GlobalBottom.vue'])
27+
const slideTop = getComponent(['slide-top.vue', 'SlideTop.vue'])
28+
const slideBottom = getComponent(['slide-bottom.vue', 'SlideBottom.vue'])
29+
30+
return [
31+
imports.join('\n'),
32+
`import { h } from 'vue'`,
33+
`export const GlobalTop = ${globalTop}`,
34+
`export const GlobalBottom = ${globalBottom}`,
35+
`export const SlideTop = ${slideTop}`,
36+
`export const SlideBottom = ${slideBottom}`,
37+
].join('\n')
38+
},
39+
}

packages/slidev/node/virtual/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { templateConfigs } from './configs'
22
import { templateLegacyRoutes, templateLegacyTitles } from './deprecated'
3-
import { templateGlobalBottom, templateGlobalTop, templateNavControls } from './global-components'
3+
import { templateGlobalLayers } from './global-layers'
4+
import { templateNavControls } from './nav-controls'
45
import { templateLayouts } from './layouts'
56
import { templateMonacoRunDeps } from './monaco-deps'
67
import { templateMonacoTypes } from './monaco-types'
@@ -16,8 +17,7 @@ export const templates = [
1617
templateMonacoRunDeps,
1718
templateConfigs,
1819
templateStyle,
19-
templateGlobalBottom,
20-
templateGlobalTop,
20+
templateGlobalLayers,
2121
templateNavControls,
2222
templateSlides,
2323
templateLayouts,
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { existsSync } from 'node:fs'
2+
import { join } from 'node:path'
3+
import { toAtFS } from '../resolver'
4+
import type { VirtualModuleTemplate } from './types'
5+
6+
export const templateNavControls: VirtualModuleTemplate = {
7+
id: '/@slidev/custom-nav-controls',
8+
getContent({ roots }) {
9+
const components = roots
10+
.flatMap((root) => {
11+
return [
12+
join(root, 'custom-nav-controls.vue'),
13+
join(root, 'CustomNavControls.vue'),
14+
]
15+
})
16+
.filter(i => existsSync(i))
17+
18+
const imports = components.map((i, idx) => `import __n${idx} from '${toAtFS(i)}'`).join('\n')
19+
const render = components.map((i, idx) => `h(__n${idx})`).join(',')
20+
21+
return `${imports}
22+
import { h } from 'vue'
23+
export default {
24+
render: () => [${render}],
25+
}`
26+
},
27+
}

packages/types/client.d.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,14 @@ declare module '#slidev/configs' {
88
export default configs
99
}
1010

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

14-
const component: ComponentOptions
15-
export default component
16-
}
14+
export const GlobalTop: ComponentOptions
15+
export const GlobalBottom: ComponentOptions
1716

18-
declare module '#slidev/global-components/bottom' {
19-
import type { ComponentOptions } from 'vue'
20-
21-
const component: ComponentOptions
22-
export default component
17+
export const SlideTop: ComponentOptions
18+
export const SlideBottom: ComponentOptions
2319
}
2420

2521
declare module '#slidev/slides' {

0 commit comments

Comments
 (0)