Skip to content

Commit

Permalink
feat: SandBox Component (#518)
Browse files Browse the repository at this point in the history
* feat: SandBox Component

* chore: minor fix

* feat: init Tabs component; refactor CodeGroup

* update Sandbox UI

Co-authored-by: Sergey Bedritsky <sergey.bedritsky@gmail.com>
  • Loading branch information
farnabaz and bdrtsky authored Jul 1, 2021
1 parent 9623495 commit 24a4534
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 138 deletions.
12 changes: 6 additions & 6 deletions docs/content/4.theme/2.components.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,23 +183,23 @@ Cross-reference other files within your documentation (such as example code you

:props{of="components/atoms/InjectContent"}

## `code-sandbox`
## `sandbox`

[:icon-git-hub{class="inline -mt-1 w-6"} Source](https://github.com/nuxtlabs/docus/tree/main/src/defaultTheme/components/atoms/CodeSandbox.vue)
[:icon-git-hub{class="inline -mt-1 w-6"} Source](https://github.com/nuxtlabs/docus/tree/main/src/defaultTheme/components/atoms/Sandbox.vue)

Embed CodeSandbox easily in your documentation with great performances, using the [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) to load when visible in the viewport.
Embed CodeSandbox/StackBlitz easily in your documentation with great performances, using the [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) to load when visible in the viewport.

::code-group
::code-block{label="Preview" active preview}
:code-sandbox{src="https://codesandbox.io/embed/nuxt-content-l164h?hidenavigation=1&theme=dark"}
:sandbox{src="https://codesandbox.io/embed/nuxt-content-l164h?hidenavigation=1&theme=dark"}
::

```md [Code]
:code-sandbox{src="https://codesandbox.io/embed/nuxt-content-l164h?hidenavigation=1&theme=dark"}
:sandbox{src="https://codesandbox.io/embed/nuxt-content-l164h?hidenavigation=1&theme=dark"}
```
::

:props{of="components/atoms/CodeSandbox"}
:props{of="components/atoms/Sandbox"}

## `tweet`

Expand Down
51 changes: 5 additions & 46 deletions src/defaultTheme/components/atoms/CodeGroup.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,6 @@
<template>
<div class="code-group" :class="[activeTabIndex == 0 && 'first-tab']">
<div class="relative z-0 px-2 text-white rounded-t-lg d-code-group-header-bg">
<button
v-for="({ label }, i) in tabs"
ref="tabs"
:key="`${counter}${label}`"
class="relative px-3 py-1.5 xs:py-3 my-1.5 xs:my-0 text-sm font-mono font-medium tracking-tight"
:class="[activeTabIndex === i ? 'active text-gray-800 dark:text-white' : 'd-prose-code-filename-text']"
@click="updateTabs(i)"
>
{{ label }}
</button>
<span ref="highlight-underline" class="absolute -z-1 highlight-underline h-full xs:py-1.5">
<span class="flex w-full h-full d-code-group-tab rounded-md"></span>
</span>
</div>
<TabsHeader ref="tabs-header" :active-tab-index="activeTabIndex" :tabs="tabs" @update="activeTabIndex = $event" />
<slot />
</div>
</template>
Expand All @@ -36,7 +22,7 @@ export default defineComponent({
computed: {
tabs() {
// eslint-disable-next-line no-unused-expressions
this.counter
// this.counter
return this.calculateTabs()
}
},
Expand All @@ -54,39 +40,22 @@ export default defineComponent({
updated() {
const newTabs = this.calculateTabs()
if (JSON.stringify(newTabs) !== JSON.stringify(this.tabs)) {
this.counter += 1
// this.counter += 1
this.$nextTick(() => {
this.updateActiveTab()
this.updateHighlighteUnderlinePosition()
// TODO: refactor tabs completely
this.$refs['tabs-header'].updateHighlightUnderlinePosition()
})
}
},
created() {
this.updateActiveTab()
},
mounted() {
this.updateHighlighteUnderlinePosition()
},
methods: {
updateActiveTab() {
const index = this.tabs.findIndex(tab => tab.active)
this.activeTabIndex = index < 0 ? 0 : index
},
updateTabs(i) {
this.activeTabIndex = i
this.updateHighlighteUnderlinePosition()
},
updateHighlighteUnderlinePosition() {
const activeTab = this.$refs.tabs[this.activeTabIndex]
if (!activeTab) {
return
}
const highlightUnderline = this.$refs['highlight-underline']
highlightUnderline.style.left = `${activeTab.offsetLeft}px`
highlightUnderline.style.top = `${activeTab.offsetTop}px`
highlightUnderline.style.width = `${activeTab.clientWidth}px`
highlightUnderline.style.height = `${activeTab.clientHeight}px`
},
calculateTabs() {
const components = this.$slots.default
.flatMap(slot => (slot.data?.attrs?.class?.includes('prose') ? slot.children : slot))
Expand Down Expand Up @@ -140,10 +109,6 @@ export default defineComponent({
}
}
button {
outline: none;
}
.first-tab {
::v-deep {
.code-block:nth-child(2),
Expand All @@ -152,10 +117,4 @@ button {
}
}
}
.highlight-underline {
/* bottom: -2px; */
/* height: 2px; */
transition: left 150ms, top 150ms, width 150ms, height 150ms;
}
</style>
86 changes: 0 additions & 86 deletions src/defaultTheme/components/atoms/CodeSandbox.vue

This file was deleted.

128 changes: 128 additions & 0 deletions src/defaultTheme/components/atoms/Sandbox.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<template>
<div ref="box" class="w-full min-h-[500px] mx-auto mb-6 overflow-hidden text-3xl rounded-md sandbox">
<TabsHeader ref="tabs-header" :active-tab-index="activeTabIndex" :tabs="providersTabs" @update="updateTab">
<div slot="footer" class="absolute top-1/2 transform -translate-y-1/2 right-0 px-2">
<Link class="flex items-center text-gray-500 dark:text-gray-400" :to="url" blank>
<IconExternalLink class="h-5 w-5" />
</Link>
</div>
</TabsHeader>

<iframe
v-if="isIntersecting && url"
:src="url"
title="Sandbox editor"
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
class="w-full h-full min-h-[500px] overflow-hidden"
/>
<span v-else class="text-white flex-1">Loading Sandbox...</span>
</div>
</template>

<script lang="ts">
import { defineComponent, onBeforeUnmount, onMounted, ref, useContext } from '@nuxtjs/composition-api'
export default defineComponent({
props: {
/**
* Url to Sandbox embed
*/
src: {
type: String,
default: undefined
},
/**
* Github Repository
*/
repo: {
type: String,
default: undefined
},
/**
* Target branch
*/
branch: {
type: String,
default: undefined
},
dir: {
type: String,
default: undefined
}
},
setup(props) {
const { $docus, $colorMode } = useContext()
const repository = props.repo || $docus.settings.github?.repo
const providers = {
CodeSandBox: () =>
`https://codesandbox.io/embed/github/${repository}/tree/${props.branch}/${props.dir}?hidenavigation=1&theme=${$colorMode.value}`,
StackBlitz: () =>
`https://stackblitz.com/github/${repository}/tree/${props.branch}/${props.dir}?embed=1&hideExplorer=1&hideNavigation=1&theme=${$colorMode.value}`
}
const providersTabs = Object.keys(providers).map(p => ({ label: p }))
const box = ref()
const activeTabIndex = ref(0)
const url = ref('')
const provider = ref('')
const observer = ref(null)
const isIntersecting = ref(false)
function updateTab(i) {
activeTabIndex.value = i
changeProvider(providersTabs[i].label)
}
onMounted(() => {
provider.value = window.localStorage.getItem('docus_sandbox') || 'CodeSandBox'
url.value = props.src || providers[provider.value]()
if (!window.IntersectionObserver) {
isIntersecting.value = true
return
}
observer.value = new window.IntersectionObserver(entries => {
entries.forEach(({ intersectionRatio }) => {
if (intersectionRatio > 0) {
isIntersecting.value = true
observer.value.disconnect()
observer.value = null
}
})
})
observer.value.observe(box.value)
})
onBeforeUnmount(() => {
if (observer.value) observer.value.disconnect()
})
const changeProvider = value => {
provider.value = value
url.value = props.src || providers[provider.value]()
localStorage.setItem('docus_sandbox', value)
}
return {
isIntersecting,
box,
provider,
url,
changeProvider,
updateTab,
activeTabIndex,
providersTabs
}
}
})
</script>

<style lang="postcss" scoped>
.sandbox,
.sandbox iframe {
@apply w-full rounded-md rounded-tl-none rounded-tr-none overflow-hidden h-64;
height: 650px;
}
</style>
Loading

0 comments on commit 24a4534

Please sign in to comment.