forked from unovue/reka-ui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: enhance components preview with support for multiple framework (u…
…novue#484) * refactor: implement dynamic code snippet, prepare for more css framework * feat: implement folder based import for code snippets * docs: persist selected css framework * chore: fix frozen pnpm * docs: update accordion css styling * docs: move component around, update alert-dialog * chore: test component loader * docs: add css framework for all components * docs: improve previwer, stackblitz * fix: dynamic vars import limitation * fix: codeeditor can't import correct files * docs: update css for new components
- Loading branch information
Showing
163 changed files
with
5,360 additions
and
700 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<script setup lang="ts"> | ||
import { defineAsyncComponent } from 'vue' | ||
import Spinner from './Spinner.vue' | ||
const props = defineProps<{ | ||
name: string | ||
}>() | ||
const Component = defineAsyncComponent({ | ||
loadingComponent: Spinner, | ||
loader: () => import(`../../components/demo/${props.name}/tailwind/index.vue`), | ||
timeout: 5000, | ||
suspensible: false, | ||
}) | ||
</script> | ||
|
||
<template> | ||
<Component :is="Component" /> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<script setup lang="ts"> | ||
import HeroContainer from './NewHeroContainer.vue' | ||
import HeroCodeGroup from './NewHeroCodeGroup.vue' | ||
import { computed } from 'vue' | ||
import { useStorage } from '@vueuse/core' | ||
const props = defineProps<{ | ||
name: string | ||
files?: string | ||
}>() | ||
const cssFramework = useStorage<'css' | 'tailwind' | 'pinceau' >('cssFramework', 'tailwind') | ||
const parsedFiles = computed(() => JSON.parse(decodeURIComponent(props.files ?? ''))[cssFramework.value]) | ||
</script> | ||
|
||
<template> | ||
<HeroContainer :folder="name" :files="parsedFiles" :css-framework="cssFramework"> | ||
<slot /> | ||
|
||
<template #codeSlot> | ||
<HeroCodeGroup v-model="cssFramework"> | ||
<slot name="tailwind" /> | ||
<slot name="css" /> | ||
</HeroCodeGroup> | ||
</template> | ||
</HeroContainer> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
<script setup lang="ts"> | ||
import { type VNode, capitalize, computed, ref, useSlots, watch } from 'vue' | ||
import { SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectPortal, SelectRoot, SelectTrigger, SelectValue, SelectViewport, TabsContent, TabsList, TabsRoot, TabsTrigger } from 'radix-vue' | ||
import { Icon } from '@iconify/vue' | ||
import { useVModel } from '@vueuse/core' | ||
defineOptions({ | ||
inheritAttrs: false, | ||
}) | ||
const props = defineProps<{ | ||
modelValue: 'css' | 'tailwind' | 'pinceau' | ||
}>() | ||
const emits = defineEmits<{ | ||
'update:modelValue': [payload: 'css' | 'tailwind' | 'pinceau'] | ||
}>() | ||
const cssFramework = useVModel(props, 'modelValue', emits) | ||
const slots = useSlots() | ||
const slotsFramework = computed(() => slots.default?.().map(slot => slot.props?.key?.toString()?.replace('_', '')) ?? []) | ||
const cssFrameworkOptions = computed(() => [ | ||
{ label: 'TailwindCSS', value: 'tailwind' }, | ||
{ label: 'CSS', value: 'css' }, | ||
{ label: 'Pinceau', value: 'pinceau' }, | ||
].filter(i => slotsFramework.value.includes(i.value))) | ||
const tabs = computed( | ||
() => { | ||
const currentFramework = slots.default?.().find(slot => slot.props?.key?.toString().includes(cssFramework.value)) | ||
const childSlots = (currentFramework?.children as VNode[]).sort((a, b) => a?.props?.title.localeCompare(b?.props?.title)) | ||
return childSlots?.map((slot, index) => { | ||
return { | ||
label: slot.props?.title || `${index}`, | ||
component: slot, | ||
} | ||
}) || [] | ||
}, | ||
) | ||
const open = ref(false) | ||
const codeScrollWrapper = ref<HTMLElement | undefined>() | ||
const buttonRef = ref<HTMLElement | undefined>() | ||
const currentTab = ref('index.vue') | ||
watch(open, () => { | ||
if (!open.value) { | ||
codeScrollWrapper.value!.scrollTo({ | ||
top: 0, | ||
}) | ||
} | ||
}) | ||
</script> | ||
|
||
<template> | ||
<TabsRoot | ||
v-model="currentTab" | ||
class="bg-[var(--vp-code-block-bg)] border border-neutral-700/40 rounded-b-lg overflow-hidden" | ||
@update:model-value="open = true" | ||
> | ||
<div class="bg-[var(--vp-code-block-bg)] border-b-2 border-[#272727] flex pr-2"> | ||
<div class="flex justify-between items-center w-full text-[13px]"> | ||
<TabsList class="flex"> | ||
<TabsTrigger | ||
v-for="(tab, index) in tabs" | ||
:key="index" | ||
:value="tab.label" | ||
tabindex="-1" | ||
class="text-white/70 py-2.5 px-4 border-box data-[state=active]:shadow-[0_1px_0_#10b981] data-[state=active]:font-medium data-[state=active]:text-white" | ||
> | ||
{{ tab.label }} | ||
</TabsTrigger> | ||
</TabsList> | ||
<div> | ||
<SelectRoot v-model="cssFramework" @update:model-value="currentTab = 'index.vue'"> | ||
<SelectTrigger class="flex items-center justify-between bg-stone-800 rounded-sm w-[115px] text-xs py-1 pl-2 pr-1"> | ||
<SelectValue /> | ||
<Icon icon="radix-icons:chevron-down" class="h-3.5 w-3.5" /> | ||
</SelectTrigger> | ||
|
||
<SelectPortal> | ||
<SelectContent class="border border-stone-700 min-w-[115px] bg-stone-800 rounded shadow-[0px_10px_38px_-10px_rgba(22,_23,_24,_0.35),_0px_10px_20px_-15px_rgba(22,_23,_24,_0.2)] will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade z-[100]"> | ||
<SelectViewport class="p-[5px]"> | ||
<SelectItem | ||
v-for="framework in cssFrameworkOptions" | ||
:key="framework.label" | ||
class="text-xs leading-none text-grass11 rounded-[3px] flex items-center h-[25px] pl-[25px] relative select-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-green9 data-[highlighted]:text-green1" | ||
:value="framework.value" | ||
> | ||
<SelectItemIndicator class="absolute left-0 w-[25px] inline-flex items-center justify-center"> | ||
<Icon icon="radix-icons:check" /> | ||
</SelectItemIndicator> | ||
|
||
<SelectItemText> | ||
{{ capitalize(framework.label ?? '') }} | ||
</SelectItemText> | ||
</SelectItem> | ||
</SelectViewport> | ||
</SelectContent> | ||
</SelectPortal> | ||
</SelectRoot> | ||
</div> | ||
</div> | ||
</div> | ||
<div | ||
ref="codeScrollWrapper" | ||
:key="cssFramework" | ||
class="pb-10 block h-full" | ||
:class="`${open ? 'overflow-scroll max-h-[80vh]' : 'overflow-hidden max-h-[150px]'}`" | ||
> | ||
<TabsContent v-for="tab in tabs" :key="tab.label" :value="tab.label" as-child> | ||
<div class="relative -mt-5 text-base"> | ||
<component :is="tab.component" class="border-0" /> | ||
</div> | ||
</TabsContent> | ||
<div | ||
class="bg-gradient-to-t from-[#161618FF] to-[#16161800] bottom-[1px] left-[1px] right-[1px] h-20 flex items-center justify-center absolute rounded-b-lg" | ||
> | ||
<button | ||
ref="buttonRef" | ||
class="mt-4 bg-neutral-800 hover:bg-neutral-700 px-3 py-1 rounded border-neutral-700 border" | ||
@click="open = !open" | ||
> | ||
{{ open ? "Collapse code" : "Expand code" }} | ||
</button> | ||
</div> | ||
</div> | ||
</TabsRoot> | ||
</template> | ||
|
||
<style scoped> | ||
:deep(*) { | ||
color: white; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<script setup lang="ts"> | ||
import CodeSandbox from '../../components/CodeSandbox.vue' | ||
import Stackblitz from '../../components/Stackblitz.vue' | ||
withDefaults( | ||
defineProps<{ | ||
overflow?: boolean | ||
folder?: string | ||
files?: string[] | ||
cssFramework?: string | ||
}>(), | ||
{ folder: '', files: () => [] }, | ||
) | ||
</script> | ||
|
||
<template> | ||
<div class="relative text-[15px] text-black"> | ||
<div | ||
class="vp-raw bg-gradient-to-br p-4 rounded-t-lg from-teal9 to-green9 w-full relative items-center justify-center flex" | ||
:class="{ 'overflow-x-auto': overflow }" | ||
> | ||
<div class="w-full max-w-[700px] flex items-center py-12 sm:py-[100px] custom-justify-center z-10"> | ||
<slot /> | ||
|
||
<CodeSandbox v-if="folder" :key="cssFramework" class="hidden sm:block absolute bottom-4 right-4" :name="folder" :files="files" /> | ||
<Stackblitz v-if="folder" :key="cssFramework" class="hidden sm:block absolute bottom-4 right-12" :name="folder" :files="files" /> | ||
</div> | ||
</div> | ||
<slot name="codeSlot" /> | ||
</div> | ||
</template> | ||
|
||
<style scoped> | ||
:deep(input) { | ||
background-color: white; | ||
} | ||
:deep(li) { | ||
margin-top: 0 !important; | ||
} | ||
:deep(button:focus), | ||
:deep(button:focus-visible) { | ||
outline: 0; | ||
} | ||
:deep(h3) { | ||
margin: 0px !important; | ||
font-weight: unset !important; | ||
} | ||
:deep(pre) { | ||
z-index: 0 !important; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<script setup lang="ts"> | ||
import { Icon } from '@iconify/vue' | ||
</script> | ||
|
||
<template> | ||
<Icon icon="lucide:loader-2" class="animate-spin" /> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { dirname, resolve } from 'node:path' | ||
import { readdirSync } from 'node:fs' | ||
import type { MarkdownEnv, MarkdownRenderer } from 'vitepress' | ||
|
||
export const rawPathRegexp | ||
= /^(.+?(?:(?:\.([a-z0-9]+))?))(?:(#[\w-]+))?(?: ?(?:{(\d+(?:[,-]\d+)*)? ?(\S+)?}))? ?(?:\[(.+)\])?$/ | ||
|
||
function rawPathToToken(rawPath: string) { | ||
const [ | ||
filepath = '', | ||
extension = '', | ||
region = '', | ||
lines = '', | ||
lang = '', | ||
rawTitle = '', | ||
] = (rawPathRegexp.exec(rawPath) || []).slice(1) | ||
|
||
const title = rawTitle || filepath.split('/').pop() || '' | ||
|
||
return { filepath, extension, region, lines, lang, title } | ||
} | ||
|
||
export default function (md: MarkdownRenderer) { | ||
md.core.ruler.after('inline', 'component-preview', (state) => { | ||
// Define the regular expression to match the desired pattern | ||
const regex = /<ComponentPreview name="([^"]+)" \/>/g | ||
|
||
// Iterate through the Markdown content and replace the pattern | ||
state.src = state.src.replace(regex, (match, componentName) => { | ||
const importComponent = new state.Token('html_block', '', 0) | ||
const pathName = `../../components/demo/${componentName}` | ||
|
||
importComponent.content = `<script setup>\nimport Demo from '${pathName}/tailwind/index.vue'\n</script>\n` | ||
state.tokens.splice(0, 0, importComponent) | ||
|
||
const index = state.tokens.findIndex(i => i.content.match(regex)) | ||
|
||
const { realPath, path: _path } = state.env as MarkdownEnv | ||
|
||
const childFiles = readdirSync(resolve(dirname(realPath ?? _path), pathName), { withFileTypes: false, recursive: true }) | ||
const groupedFiles = childFiles.reduce((prev, curr) => { | ||
if (typeof curr !== 'string') | ||
return prev | ||
if (!curr.includes('/')) { | ||
prev[curr] = [] | ||
} | ||
else { | ||
const folder = curr.split('/')[0] | ||
prev[folder].push(curr) | ||
} | ||
return prev | ||
}, {} as { [key: string]: string[] }) | ||
|
||
state.tokens[index].content = `<ComponentPreview name="${componentName}" files="${encodeURIComponent(JSON.stringify(groupedFiles))}" ><Demo />` | ||
const tokenArray: Array<typeof importComponent> = [] | ||
|
||
Object.entries(groupedFiles).forEach(([key, value]) => { | ||
const templateStart = new state.Token('html_inline', '', 0) | ||
templateStart.content = `<template #${key}>` | ||
tokenArray.push(templateStart) | ||
|
||
value.forEach((file) => { | ||
const { filepath, extension, lines, lang, title } = rawPathToToken(`${pathName}/${file}`) | ||
const resolvedPath = resolve(dirname(realPath ?? _path), filepath) | ||
|
||
// Add code tokens for each line | ||
const token = new state.Token('fence', 'code', 0) | ||
token.info = `${lang || extension}${lines ? `{${lines}}` : ''}${ | ||
title ? `[${title}]` : '' | ||
}` | ||
|
||
token.content = `<<< ${filepath}` | ||
// @ts-expect-error token.src is for snippets plugin to handle importing snippet | ||
token.src = [resolvedPath] | ||
tokenArray.push(token) | ||
}) | ||
|
||
const templateEnd = new state.Token('html_inline', '', 0) | ||
templateEnd.content = '</template>' | ||
tokenArray.push(templateEnd) | ||
}) | ||
|
||
const endTag = new state.Token('html_inline', '', 0) | ||
endTag.content = '</ComponentPreview>' | ||
tokenArray.push(endTag) | ||
|
||
state.tokens.splice(index + 1, 0, ...tokenArray) | ||
|
||
// Return an empty string to replace the original pattern | ||
return '' | ||
}) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.