Skip to content

Adding live examples with code preview #2348

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

Open
wants to merge 25 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d20bd03
wip(docs): add ExamplePreview
posva Jul 12, 2018
698685c
feat: add the url bar to live demo component
posva Jul 13, 2018
119ead1
docs: add content preview
posva Aug 6, 2018
3cec75c
docs: add initial-view prop to ExamplePreview
posva Aug 6, 2018
51c56ba
docs: make code explorable
posva Aug 7, 2018
83e689e
docs: refactor ExamplePreview
posva Aug 7, 2018
667f5ea
docs: support i18n
posva Aug 7, 2018
3899cfe
docs: remove duplicated demo code in example
posva Aug 7, 2018
e305ab2
docs: support opening in CodeSandbox
posva Aug 9, 2018
718387a
docs: refactor ExamplePreview components
posva Aug 9, 2018
1e26fb1
docs: refactor code, add dynamic routes example
posva Aug 9, 2018
891edad
docs: add titles to buttons and update CodeSandbox button
posva Aug 9, 2018
5c583fe
docs: fix build
posva Aug 9, 2018
56dcd5f
chore(deps): update vuepress
posva Aug 10, 2018
759b527
docs: adapt sideEffects in package.json
posva Aug 12, 2018
c692610
Merge branch 'dev' into feat/docs-live-examples
posva Aug 12, 2018
c7f9837
docs: handle errors in lazy loading
posva Aug 12, 2018
938ed0f
docs: fix url sync in ExamplePreview
posva Aug 13, 2018
0d869d1
docs: add example for nested-routes
posva Aug 13, 2018
9ebafcd
docs: add named-routes example
posva Aug 13, 2018
ba825f5
docs: fix params object in dynamic routes
posva Aug 13, 2018
d035ec6
docs: refactor style for mobile
posva Aug 13, 2018
47a57d1
docs: remove redundant class [skip ci]
posva Aug 13, 2018
168b1eb
docs: refactor icons, fix click outline, note about the preview
posva Aug 14, 2018
14ad208
docs: add note about SFC
posva Aug 14, 2018
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
203 changes: 203 additions & 0 deletions docs/.vuepress/components/ExamplePreview.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
<template>
<div class="demo" :class="containerClasses">
<Promised :promise="examplePromise">
<span></span>
<span slot="catch" slot-scope="error"></span>
<ExamplePreviewBar slot-scope="then" :router="router" :view-code.sync="viewCode" :files="files" :current-file.sync="currentFile" :codesandbox-params="codesandboxParams" />
</Promised>
<Promised :promise="examplePromise">
<div class="example">Loading example...</div>
<div slot-scope="then">
<template v-if="viewCode">
<!-- <ExamplePreviewFilesTabs :files="files" :current-file.sync="currentFile" :view-code.sync="viewCode" /> -->
<ExamplePreviewExplorer v-if="currentFile" :file="currentFile" />
</template>
<template v-else>
<div class="example">
<component :is="page" />
</div>
</template>
</div>
<div class="example error" slot="catch" slot-scope="error">
<p>There was an error loading the example</p>

<button class="action-button" @click="loadPage">Retry</button>
</div>
</Promised>
</div>
</template>

<script>
import Router from 'vue-router'
import Promised from 'vue-promised'
import ExamplePreviewBar from '../example-preview/ExamplePreviewBar'
import 'focus-visible'
import ExamplePreviewExplorer from '../example-preview/ExamplePreviewExplorer'
import { getCodesandboxParameters, removeScriptSection } from '../example-preview/utils'

export default {
props: {
name: String,
initialView: {
type: String,
default: 'demo'
}
},

data() {
return {
viewCode: this.initialView === 'code',
router: null,
page: null,
files: [],
currentFile: null,
codesandboxParams: null,

examplePromise: null
}
},

methods: {
async loadPage () {
if (!this.name) return
this.examplePromise = (this.pagePath ? import(`@docs/${this.pagePath}/examples/${this.name}/index.js`) : import(`@docs/examples/${this.name}/index.js`))
const Page = await this.examplePromise
if (!Page || !Page.App) return
let { App, files, codesandbox } = Page

// Do not create a new Router if the example has been loaded before
// otherwise, visiting a new page and coming back will break the navigation bar
if (App._exampleLoaded) {
this.router = App.router
} else {
// create the router again but force the mode to abstract
this.router = App.router = new Router({
...App.router.options,
mode: 'abstract'
})
App._exampleLoaded = true
}

this.router.push('/')
this.page = App
// reset file
this.currentFile = null
this.codesandboxParams = null

this.files = []
// files is usually an object, transform if it's not
if (Array.isArray(files)) {
files = files.reduce((filesDict, name) => ({...filesDict, [name]: name}), {})
}

// load the content of all the files
Object.keys(files).forEach(name => {
const handleImport = ({ default: content }) => {
// remove the script part that contains the router
if (name === 'App.vue') {
content = removeScriptSection(content)
}
this.files.push({ name, content })
if (!this.currentFile) this.currentFile = this.files[this.files.length - 1]
}
if (this.pagePath) {
import(`!raw-loader!@docs/${this.pagePath}/examples/${this.name}/${files[name]}`).then(handleImport)
} else {
import(`!raw-loader!@docs/examples/${this.name}/${files[name]}`).then(handleImport)
}
})

if (!codesandbox || !codesandbox.length) return
const allFiles = codesandbox.map(filename => {
const transformFile = ({ default: content }) => {
// remove the script part from App.vue because it has the router
if (filename === 'App.vue') {
content = removeScriptSection(content)
}
return {
[filename]: { content }
}
}
if (this.pagePath) {
return import(`!raw-loader!@docs/${this.pagePath}/examples/${this.name}/${filename}`).then(transformFile)
} else {
return import(`!raw-loader!@docs/examples/${this.name}/${filename}`).then(transformFile)
}
})

// add the entry point (common for all examples) main.js
if (codesandbox.indexOf('main.js') < 0) {
allFiles.push(import(`!raw-loader!@docs/examples/common/main.js`).then(({ default: content })=> ({
'main.js': { content }
})))
}

this.codesandboxParams = await Promise.all(allFiles).then(getCodesandboxParameters)
}
},

computed: {
containerClasses() {
return { explorer: this.viewCode }
},
// this seems to be necessary to correctly code split
// it allows import path to have the slashes in them
pagePath () {
return this.$localePath.replace(/^\//, '').replace(/\/$/, '')
}
},

watch: {
name: {
handler: 'loadPage',
immediate: true
}
},

components: { ExamplePreviewBar, ExamplePreviewExplorer, Promised }
}
</script>

<style scoped lang="stylus">
@import '~@default-theme/styles/config.styl';

.demo {
border: 1px solid #ddd;
border-radius: 4px;

& .example {
padding: 1rem 1.5rem;
overflow: hidden;

&.error {
color: #ff2828;
}
}
}

@media (max-width: $MQMobileNarrow) {
.demo {
margin-left: -1.5rem;
margin-right: -1.5rem;
border-radius: 0;
}
}

.action-button {
display: inline-block;
font-size: 1.2rem;
color: #fff;
background-color: #3eaf7c;
padding: 0.8rem 1.6rem;
border-radius: 4px;
transition: background-color 0.1s ease;
box-sizing: border-box;
border-bottom: 1px solid #389d70;

&:hover {
background-color: #4abf8a;
cursor: pointer;
}
}
</style>

45 changes: 45 additions & 0 deletions docs/.vuepress/components/ExamplePreviewBarButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<template>
<Promised :promise="svgPromise">
<button class="reset-button button" slot="pending">.</button>
<button class="reset-button button" slot-scope="svg" slot="then" v-html="svg"></button>
<button class="reset-button" slot="catch">?</button>
</Promised>
</template>

<script>
import Promised from 'vue-promised'

export default {
props: {
icon: String
},

computed: {
svgPromise () {
return import(`!raw-loader!@docs/.vuepress/example-preview/icons/${this.icon}.svg`).then(f => f.default)
}
},

components: { Promised }
}
</script>

<style scoped lang="stylus">
.button {
color: rgb(135, 135, 135);
font-size: 1.5rem;
line-height: 0.5;
vertical-align: middle;
text-align: center;
margin: 0px 0.1rem;

&:not([disabled]):hover {
background-color: rgb(226, 226, 226);
cursor: pointer;
}

&[disabled] {
color: rgb(192, 192, 192);
}
}
</style>
9 changes: 9 additions & 0 deletions docs/.vuepress/config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
const { join } = require('path')

module.exports = {
configureWebpack: {
resolve: {
alias: {
'@docs': join(__dirname, '../')
}
}
},
locales: {
'/': {
lang: 'en-US',
Expand Down
Loading