Skip to content
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
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